Commit 905a2ffe authored by Antoine Pitrou's avatar Antoine Pitrou

Merged revisions 77890 via svnmerge from

svn+ssh://pythondev@svn.python.org/python/trunk

........
  r77890 | antoine.pitrou | 2010-01-31 23:26:04 +0100 (dim., 31 janv. 2010) | 7 lines

  - Issue #6939: Fix file I/O objects in the `io` module to keep the original
    file position when calling `truncate()`.  It would previously change the
    file position to the given argument, which goes against the tradition of
    ftruncate() and other truncation APIs.  Patch by Pascal Chambon.
........
parent 9b661e6e
...@@ -856,7 +856,7 @@ class BytesIO(BufferedIOBase): ...@@ -856,7 +856,7 @@ class BytesIO(BufferedIOBase):
elif pos < 0: elif pos < 0:
raise ValueError("negative truncate position %r" % (pos,)) raise ValueError("negative truncate position %r" % (pos,))
del self._buffer[pos:] del self._buffer[pos:]
return self.seek(pos) return pos
def readable(self): def readable(self):
return True return True
...@@ -1215,8 +1215,7 @@ class BufferedRandom(BufferedWriter, BufferedReader): ...@@ -1215,8 +1215,7 @@ class BufferedRandom(BufferedWriter, BufferedReader):
if pos is None: if pos is None:
pos = self.tell() pos = self.tell()
# Use seek to flush the read buffer. # Use seek to flush the read buffer.
self.seek(pos) return BufferedWriter.truncate(self, pos)
return BufferedWriter.truncate(self)
def read(self, n=None): def read(self, n=None):
if n is None: if n is None:
...@@ -1717,8 +1716,7 @@ class TextIOWrapper(TextIOBase): ...@@ -1717,8 +1716,7 @@ class TextIOWrapper(TextIOBase):
self.flush() self.flush()
if pos is None: if pos is None:
pos = self.tell() pos = self.tell()
self.seek(pos) return self.buffer.truncate(pos)
return self.buffer.truncate()
def detach(self): def detach(self):
if self.buffer is None: if self.buffer is None:
......
...@@ -328,6 +328,17 @@ class OtherFileTests(unittest.TestCase): ...@@ -328,6 +328,17 @@ class OtherFileTests(unittest.TestCase):
f.close() f.close()
self.fail("no error for invalid mode: %s" % bad_mode) self.fail("no error for invalid mode: %s" % bad_mode)
def testTruncate(self):
f = _FileIO(TESTFN, 'w')
f.write(bytes(bytearray(range(10))))
self.assertEqual(f.tell(), 10)
f.truncate(5)
self.assertEqual(f.tell(), 10)
self.assertEqual(f.seek(0, os.SEEK_END), 5)
f.truncate(15)
self.assertEqual(f.tell(), 5)
self.assertEqual(f.seek(0, os.SEEK_END), 15)
def testTruncateOnWindows(self): def testTruncateOnWindows(self):
def bug801631(): def bug801631():
# SF bug <http://www.python.org/sf/801631> # SF bug <http://www.python.org/sf/801631>
......
...@@ -228,6 +228,11 @@ class IOTest(unittest.TestCase): ...@@ -228,6 +228,11 @@ class IOTest(unittest.TestCase):
support.unlink(support.TESTFN) support.unlink(support.TESTFN)
def write_ops(self, f): def write_ops(self, f):
self.assertEqual(f.write(b"blah."), 5)
f.truncate(0)
self.assertEqual(f.tell(), 5)
f.seek(0)
self.assertEqual(f.write(b"blah."), 5) self.assertEqual(f.write(b"blah."), 5)
self.assertEqual(f.seek(0), 0) self.assertEqual(f.seek(0), 0)
self.assertEqual(f.write(b"Hello."), 6) self.assertEqual(f.write(b"Hello."), 6)
...@@ -239,8 +244,9 @@ class IOTest(unittest.TestCase): ...@@ -239,8 +244,9 @@ class IOTest(unittest.TestCase):
self.assertEqual(f.write(b"h"), 1) self.assertEqual(f.write(b"h"), 1)
self.assertEqual(f.seek(-1, 2), 13) self.assertEqual(f.seek(-1, 2), 13)
self.assertEqual(f.tell(), 13) self.assertEqual(f.tell(), 13)
self.assertEqual(f.truncate(12), 12) self.assertEqual(f.truncate(12), 12)
self.assertEqual(f.tell(), 12) self.assertEqual(f.tell(), 13)
self.assertRaises(TypeError, f.seek, 0.0) self.assertRaises(TypeError, f.seek, 0.0)
def read_ops(self, f, buffered=False): def read_ops(self, f, buffered=False):
...@@ -285,7 +291,7 @@ class IOTest(unittest.TestCase): ...@@ -285,7 +291,7 @@ class IOTest(unittest.TestCase):
self.assertEqual(f.tell(), self.LARGE + 2) self.assertEqual(f.tell(), self.LARGE + 2)
self.assertEqual(f.seek(0, 2), self.LARGE + 2) self.assertEqual(f.seek(0, 2), self.LARGE + 2)
self.assertEqual(f.truncate(self.LARGE + 1), self.LARGE + 1) self.assertEqual(f.truncate(self.LARGE + 1), self.LARGE + 1)
self.assertEqual(f.tell(), self.LARGE + 1) self.assertEqual(f.tell(), self.LARGE + 2)
self.assertEqual(f.seek(0, 2), self.LARGE + 1) self.assertEqual(f.seek(0, 2), self.LARGE + 1)
self.assertEqual(f.seek(-1, 2), self.LARGE) self.assertEqual(f.seek(-1, 2), self.LARGE)
self.assertEqual(f.read(2), b"x") self.assertEqual(f.read(2), b"x")
...@@ -980,7 +986,7 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests): ...@@ -980,7 +986,7 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests):
bufio = self.tp(raw, 8) bufio = self.tp(raw, 8)
bufio.write(b"abcdef") bufio.write(b"abcdef")
self.assertEqual(bufio.truncate(3), 3) self.assertEqual(bufio.truncate(3), 3)
self.assertEqual(bufio.tell(), 3) self.assertEqual(bufio.tell(), 6)
with self.open(support.TESTFN, "rb", buffering=0) as f: with self.open(support.TESTFN, "rb", buffering=0) as f:
self.assertEqual(f.read(), b"abc") self.assertEqual(f.read(), b"abc")
...@@ -1366,6 +1372,14 @@ class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest): ...@@ -1366,6 +1372,14 @@ class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest):
self.assertEqual(s, self.assertEqual(s,
b"A" + b"B" * overwrite_size + b"A" * (9 - overwrite_size)) b"A" + b"B" * overwrite_size + b"A" * (9 - overwrite_size))
def test_truncate_after_read_or_write(self):
raw = self.BytesIO(b"A" * 10)
bufio = self.tp(raw, 100)
self.assertEqual(bufio.read(2), b"AA") # the read buffer gets filled
self.assertEqual(bufio.truncate(), 2)
self.assertEqual(bufio.write(b"BB"), 2) # the write buffer increases
self.assertEqual(bufio.truncate(), 4)
def test_misbehaved_io(self): def test_misbehaved_io(self):
BufferedReaderTest.test_misbehaved_io(self) BufferedReaderTest.test_misbehaved_io(self)
BufferedWriterTest.test_misbehaved_io(self) BufferedWriterTest.test_misbehaved_io(self)
......
...@@ -122,14 +122,14 @@ class LargeFileTest(unittest.TestCase): ...@@ -122,14 +122,14 @@ class LargeFileTest(unittest.TestCase):
newsize -= 1 newsize -= 1
f.seek(42) f.seek(42)
f.truncate(newsize) f.truncate(newsize)
self.assertEqual(f.tell(), newsize) # else wasn't truncated self.assertEqual(f.tell(), 42)
f.seek(0, 2) f.seek(0, 2)
self.assertEqual(f.tell(), newsize) self.assertEqual(f.tell(), newsize)
# XXX truncate(larger than true size) is ill-defined # XXX truncate(larger than true size) is ill-defined
# across platform; cut it waaaaay back # across platform; cut it waaaaay back
f.seek(0) f.seek(0)
f.truncate(1) f.truncate(1)
self.assertEqual(f.tell(), 1) # else pointer moved self.assertEqual(f.tell(), 0) # else pointer moved
f.seek(0) f.seek(0)
self.assertEqual(len(f.read()), 1) # else wasn't truncated self.assertEqual(len(f.read()), 1) # else wasn't truncated
......
...@@ -73,7 +73,7 @@ class MemoryTestMixin: ...@@ -73,7 +73,7 @@ class MemoryTestMixin:
self.assertEqual(f.seek(0), 0) self.assertEqual(f.seek(0), 0)
self.assertEqual(f.write(t("h")), 1) self.assertEqual(f.write(t("h")), 1)
self.assertEqual(f.truncate(12), 12) self.assertEqual(f.truncate(12), 12)
self.assertEqual(f.tell(), 12) self.assertEqual(f.tell(), 1)
def test_write(self): def test_write(self):
buf = self.buftype("hello world\n") buf = self.buftype("hello world\n")
...@@ -121,7 +121,8 @@ class MemoryTestMixin: ...@@ -121,7 +121,8 @@ class MemoryTestMixin:
self.assertEqual(memio.getvalue(), buf[:6]) self.assertEqual(memio.getvalue(), buf[:6])
self.assertEqual(memio.truncate(4), 4) self.assertEqual(memio.truncate(4), 4)
self.assertEqual(memio.getvalue(), buf[:4]) self.assertEqual(memio.getvalue(), buf[:4])
self.assertEqual(memio.tell(), 4) self.assertEqual(memio.tell(), 6)
memio.seek(0, 2)
memio.write(buf) memio.write(buf)
self.assertEqual(memio.getvalue(), buf[:4] + buf) self.assertEqual(memio.getvalue(), buf[:4] + buf)
pos = memio.tell() pos = memio.tell()
......
...@@ -123,6 +123,7 @@ Donn Cave ...@@ -123,6 +123,7 @@ Donn Cave
Charles Cazabon Charles Cazabon
Per Cederqvist Per Cederqvist
Octavian Cerna Octavian Cerna
Pascal Chambon
Hye-Shik Chang Hye-Shik Chang
Jeffrey Chang Jeffrey Chang
Mitch Chapman Mitch Chapman
......
...@@ -239,6 +239,11 @@ C-API ...@@ -239,6 +239,11 @@ C-API
Library Library
------- -------
- Issue #6939: Fix file I/O objects in the `io` module to keep the original
file position when calling `truncate()`. It would previously change the
file position to the given argument, which goes against the tradition of
ftruncate() and other truncation APIs. Patch by Pascal Chambon.
- Issue #7610: Reworked implementation of the internal - Issue #7610: Reworked implementation of the internal
:class:`zipfile.ZipExtFile` class used to represent files stored inside :class:`zipfile.ZipExtFile` class used to represent files stored inside
an archive. The new implementation is significantly faster and can an archive. The new implementation is significantly faster and can
......
...@@ -412,7 +412,7 @@ PyDoc_STRVAR(truncate_doc, ...@@ -412,7 +412,7 @@ PyDoc_STRVAR(truncate_doc,
"truncate([size]) -> int. Truncate the file to at most size bytes.\n" "truncate([size]) -> int. Truncate the file to at most size bytes.\n"
"\n" "\n"
"Size defaults to the current file position, as returned by tell().\n" "Size defaults to the current file position, as returned by tell().\n"
"Returns the new size. Imply an absolute seek to the position size."); "The current file position is unchanged. Returns the new size.\n");
static PyObject * static PyObject *
bytesio_truncate(bytesio *self, PyObject *args) bytesio_truncate(bytesio *self, PyObject *args)
...@@ -451,7 +451,6 @@ bytesio_truncate(bytesio *self, PyObject *args) ...@@ -451,7 +451,6 @@ bytesio_truncate(bytesio *self, PyObject *args)
if (resize_buffer(self, size) < 0) if (resize_buffer(self, size) < 0)
return NULL; return NULL;
} }
self->pos = size;
return PyLong_FromSsize_t(size); return PyLong_FromSsize_t(size);
} }
......
...@@ -758,8 +758,10 @@ fileio_tell(fileio *self, PyObject *args) ...@@ -758,8 +758,10 @@ fileio_tell(fileio *self, PyObject *args)
static PyObject * static PyObject *
fileio_truncate(fileio *self, PyObject *args) fileio_truncate(fileio *self, PyObject *args)
{ {
PyObject *posobj = NULL; PyObject *posobj = NULL; /* the new size wanted by the user */
#ifndef MS_WINDOWS
Py_off_t pos; Py_off_t pos;
#endif
int ret; int ret;
int fd; int fd;
...@@ -774,58 +776,86 @@ fileio_truncate(fileio *self, PyObject *args) ...@@ -774,58 +776,86 @@ fileio_truncate(fileio *self, PyObject *args)
if (posobj == Py_None || posobj == NULL) { if (posobj == Py_None || posobj == NULL) {
/* Get the current position. */ /* Get the current position. */
posobj = portable_lseek(fd, NULL, 1); posobj = portable_lseek(fd, NULL, 1);
if (posobj == NULL) if (posobj == NULL)
return NULL; return NULL;
} }
else { else {
/* Move to the position to be truncated. */ Py_INCREF(posobj);
posobj = portable_lseek(fd, posobj, 0); }
}
if (posobj == NULL)
return NULL;
#if defined(HAVE_LARGEFILE_SUPPORT)
pos = PyLong_AsLongLong(posobj);
#else
pos = PyLong_AsLong(posobj);
#endif
if (pos == -1 && PyErr_Occurred())
return NULL;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
/* MS _chsize doesn't work if newsize doesn't fit in 32 bits, /* MS _chsize doesn't work if newsize doesn't fit in 32 bits,
so don't even try using it. */ so don't even try using it. */
{ {
PyObject *oldposobj, *tempposobj;
HANDLE hFile; HANDLE hFile;
/* we save the file pointer position */
oldposobj = portable_lseek(fd, NULL, 1);
if (oldposobj == NULL) {
Py_DECREF(posobj);
return NULL;
}
/* we then move to the truncation position */
tempposobj = portable_lseek(fd, posobj, 0);
if (tempposobj == NULL) {
Py_DECREF(oldposobj);
Py_DECREF(posobj);
return NULL;
}
Py_DECREF(tempposobj);
/* Truncate. Note that this may grow the file! */ /* Truncate. Note that this may grow the file! */
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
errno = 0; errno = 0;
hFile = (HANDLE)_get_osfhandle(fd); hFile = (HANDLE)_get_osfhandle(fd);
ret = hFile == (HANDLE)-1; ret = hFile == (HANDLE)-1; /* testing for INVALID_HANDLE value */
if (ret == 0) { if (ret == 0) {
ret = SetEndOfFile(hFile) == 0; ret = SetEndOfFile(hFile) == 0;
if (ret) if (ret)
errno = EACCES; errno = EACCES;
} }
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
/* we restore the file pointer position in any case */
tempposobj = portable_lseek(fd, oldposobj, 0);
Py_DECREF(oldposobj);
if (tempposobj == NULL) {
Py_DECREF(posobj);
return NULL;
}
Py_DECREF(tempposobj);
} }
#else #else
#if defined(HAVE_LARGEFILE_SUPPORT)
pos = PyLong_AsLongLong(posobj);
#else
pos = PyLong_AsLong(posobj);
#endif
if (PyErr_Occurred()){
Py_DECREF(posobj);
return NULL;
}
Py_BEGIN_ALLOW_THREADS Py_BEGIN_ALLOW_THREADS
errno = 0; errno = 0;
ret = ftruncate(fd, pos); ret = ftruncate(fd, pos);
Py_END_ALLOW_THREADS Py_END_ALLOW_THREADS
#endif /* !MS_WINDOWS */ #endif /* !MS_WINDOWS */
if (ret != 0) { if (ret != 0) {
Py_DECREF(posobj);
PyErr_SetFromErrno(PyExc_IOError); PyErr_SetFromErrno(PyExc_IOError);
return NULL; return NULL;
} }
return posobj; return posobj;
} }
#endif #endif /* HAVE_FTRUNCATE */
static char * static char *
mode_string(fileio *self) mode_string(fileio *self)
......
...@@ -102,8 +102,8 @@ iobase_tell(PyObject *self, PyObject *args) ...@@ -102,8 +102,8 @@ iobase_tell(PyObject *self, PyObject *args)
PyDoc_STRVAR(iobase_truncate_doc, PyDoc_STRVAR(iobase_truncate_doc,
"Truncate file to size bytes.\n" "Truncate file to size bytes.\n"
"\n" "\n"
"Size defaults to the current IO position as reported by tell(). Return\n" "File pointer is left unchanged. Size defaults to the current IO\n"
"the new size."); "position as reported by tell(). Returns the new size.");
static PyObject * static PyObject *
iobase_truncate(PyObject *self, PyObject *args) iobase_truncate(PyObject *self, PyObject *args)
......
...@@ -350,7 +350,7 @@ PyDoc_STRVAR(stringio_truncate_doc, ...@@ -350,7 +350,7 @@ PyDoc_STRVAR(stringio_truncate_doc,
"Truncate size to pos.\n" "Truncate size to pos.\n"
"\n" "\n"
"The pos argument defaults to the current file position, as\n" "The pos argument defaults to the current file position, as\n"
"returned by tell(). Imply an absolute seek to pos.\n" "returned by tell(). The current file position is unchanged.\n"
"Returns the new absolute position.\n"); "Returns the new absolute position.\n");
static PyObject * static PyObject *
...@@ -390,7 +390,6 @@ stringio_truncate(stringio *self, PyObject *args) ...@@ -390,7 +390,6 @@ stringio_truncate(stringio *self, PyObject *args)
return NULL; return NULL;
self->string_size = size; self->string_size = size;
} }
self->pos = size;
return PyLong_FromSsize_t(size); return PyLong_FromSsize_t(size);
} }
......
...@@ -2318,15 +2318,7 @@ textiowrapper_truncate(textio *self, PyObject *args) ...@@ -2318,15 +2318,7 @@ textiowrapper_truncate(textio *self, PyObject *args)
return NULL; return NULL;
Py_DECREF(res); Py_DECREF(res);
if (pos != Py_None) { return PyObject_CallMethodObjArgs(self->buffer, _PyIO_str_truncate, pos, NULL);
res = PyObject_CallMethodObjArgs((PyObject *) self,
_PyIO_str_seek, pos, NULL);
if (res == NULL)
return NULL;
Py_DECREF(res);
}
return PyObject_CallMethodObjArgs(self->buffer, _PyIO_str_truncate, NULL);
} }
static PyObject * static PyObject *
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment