- 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.
This commit is contained in:
Antoine Pitrou 2010-01-31 22:26:04 +00:00
parent e70c72c06b
commit f3fa074703
12 changed files with 97 additions and 47 deletions

View File

@ -867,7 +867,7 @@ class BytesIO(BufferedIOBase):
elif pos < 0:
raise ValueError("negative truncate position %r" % (pos,))
del self._buffer[pos:]
return self.seek(pos)
return pos
def readable(self):
return True
@ -1226,8 +1226,7 @@ class BufferedRandom(BufferedWriter, BufferedReader):
if pos is None:
pos = self.tell()
# Use seek to flush the read buffer.
self.seek(pos)
return BufferedWriter.truncate(self)
return BufferedWriter.truncate(self, pos)
def read(self, n=None):
if n is None:
@ -1727,8 +1726,7 @@ class TextIOWrapper(TextIOBase):
self.flush()
if pos is None:
pos = self.tell()
self.seek(pos)
return self.buffer.truncate()
return self.buffer.truncate(pos)
def detach(self):
if self.buffer is None:

View File

@ -329,6 +329,17 @@ class OtherFileTests(unittest.TestCase):
f.close()
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 bug801631():
# SF bug <http://www.python.org/sf/801631>

View File

@ -233,6 +233,11 @@ class IOTest(unittest.TestCase):
support.unlink(support.TESTFN)
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.seek(0), 0)
self.assertEqual(f.write(b"Hello."), 6)
@ -244,8 +249,9 @@ class IOTest(unittest.TestCase):
self.assertEqual(f.write(b"h"), 1)
self.assertEqual(f.seek(-1, 2), 13)
self.assertEqual(f.tell(), 13)
self.assertEqual(f.truncate(12), 12)
self.assertEqual(f.tell(), 12)
self.assertEqual(f.tell(), 13)
self.assertRaises(TypeError, f.seek, 0.0)
def read_ops(self, f, buffered=False):
@ -290,7 +296,7 @@ class IOTest(unittest.TestCase):
self.assertEqual(f.tell(), 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.tell(), self.LARGE + 1)
self.assertEqual(f.tell(), self.LARGE + 2)
self.assertEqual(f.seek(0, 2), self.LARGE + 1)
self.assertEqual(f.seek(-1, 2), self.LARGE)
self.assertEqual(f.read(2), b"x")
@ -988,7 +994,7 @@ class BufferedWriterTest(unittest.TestCase, CommonBufferedTests):
bufio = self.tp(raw, 8)
bufio.write(b"abcdef")
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:
self.assertEqual(f.read(), b"abc")
@ -1374,6 +1380,14 @@ class BufferedRandomTest(BufferedReaderTest, BufferedWriterTest):
self.assertEqual(s,
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):
BufferedReaderTest.test_misbehaved_io(self)
BufferedWriterTest.test_misbehaved_io(self)

View File

@ -125,7 +125,7 @@ class LargeFileTest(unittest.TestCase):
f.seek(42)
f.truncate(newsize)
if self.new_io:
self.assertEqual(f.tell(), newsize) # else wasn't truncated
self.assertEqual(f.tell(), 42)
f.seek(0, 2)
self.assertEqual(f.tell(), newsize)
# XXX truncate(larger than true size) is ill-defined
@ -133,7 +133,7 @@ class LargeFileTest(unittest.TestCase):
f.seek(0)
f.truncate(1)
if self.new_io:
self.assertEqual(f.tell(), 1) # else pointer moved
self.assertEqual(f.tell(), 0) # else pointer moved
f.seek(0)
self.assertEqual(len(f.read()), 1) # else wasn't truncated

View File

@ -76,7 +76,7 @@ class MemoryTestMixin:
self.assertEqual(f.seek(0), 0)
self.assertEqual(f.write(t("h")), 1)
self.assertEqual(f.truncate(12), 12)
self.assertEqual(f.tell(), 12)
self.assertEqual(f.tell(), 1)
def test_write(self):
buf = self.buftype("hello world\n")
@ -127,7 +127,8 @@ class MemoryTestMixin:
# truncate() accepts long objects
self.assertEqual(memio.truncate(4L), 4)
self.assertEqual(memio.getvalue(), buf[:4])
self.assertEqual(memio.tell(), 4)
self.assertEqual(memio.tell(), 6)
memio.seek(0, 2)
memio.write(buf)
self.assertEqual(memio.getvalue(), buf[:4] + buf)
pos = memio.tell()

View File

@ -124,6 +124,7 @@ Donn Cave
Charles Cazabon
Per Cederqvist
Octavian Cerna
Pascal Chambon
Hye-Shik Chang
Jeffrey Chang
Mitch Chapman

View File

@ -50,6 +50,11 @@ Core and Builtins
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.
- UserDict is now a new-style class.
- Issue #7610: Reworked implementation of the internal

View File

@ -414,7 +414,7 @@ PyDoc_STRVAR(truncate_doc,
"truncate([size]) -> int. Truncate the file to at most size bytes.\n"
"\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 *
bytesio_truncate(bytesio *self, PyObject *args)
@ -453,7 +453,6 @@ bytesio_truncate(bytesio *self, PyObject *args)
if (resize_buffer(self, size) < 0)
return NULL;
}
self->pos = size;
return PyLong_FromSsize_t(size);
}

View File

@ -758,8 +758,10 @@ fileio_tell(fileio *self, PyObject *args)
static PyObject *
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;
#endif
int ret;
int fd;
@ -774,58 +776,86 @@ fileio_truncate(fileio *self, PyObject *args)
if (posobj == Py_None || posobj == NULL) {
/* Get the current position. */
posobj = portable_lseek(fd, NULL, 1);
if (posobj == NULL)
posobj = portable_lseek(fd, NULL, 1);
if (posobj == NULL)
return NULL;
}
else {
/* Move to the position to be truncated. */
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;
}
else {
Py_INCREF(posobj);
}
#ifdef MS_WINDOWS
/* MS _chsize doesn't work if newsize doesn't fit in 32 bits,
so don't even try using it. */
{
PyObject *oldposobj, *tempposobj;
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! */
Py_BEGIN_ALLOW_THREADS
errno = 0;
hFile = (HANDLE)_get_osfhandle(fd);
ret = hFile == (HANDLE)-1;
ret = hFile == (HANDLE)-1; /* testing for INVALID_HANDLE value */
if (ret == 0) {
ret = SetEndOfFile(hFile) == 0;
if (ret)
errno = EACCES;
}
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
#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
errno = 0;
ret = ftruncate(fd, pos);
Py_END_ALLOW_THREADS
#endif /* !MS_WINDOWS */
if (ret != 0) {
Py_DECREF(posobj);
PyErr_SetFromErrno(PyExc_IOError);
return NULL;
}
return posobj;
}
#endif
#endif /* HAVE_FTRUNCATE */
static char *
mode_string(fileio *self)

View File

@ -102,8 +102,8 @@ iobase_tell(PyObject *self, PyObject *args)
PyDoc_STRVAR(iobase_truncate_doc,
"Truncate file to size bytes.\n"
"\n"
"Size defaults to the current IO position as reported by tell(). Return\n"
"the new size.");
"File pointer is left unchanged. Size defaults to the current IO\n"
"position as reported by tell(). Returns the new size.");
static PyObject *
iobase_truncate(PyObject *self, PyObject *args)

View File

@ -350,7 +350,7 @@ PyDoc_STRVAR(stringio_truncate_doc,
"Truncate size to pos.\n"
"\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");
static PyObject *
@ -390,7 +390,6 @@ stringio_truncate(stringio *self, PyObject *args)
return NULL;
self->string_size = size;
}
self->pos = size;
return PyLong_FromSsize_t(size);
}

View File

@ -2313,15 +2313,7 @@ textiowrapper_truncate(textio *self, PyObject *args)
return NULL;
Py_DECREF(res);
if (pos != Py_None) {
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);
return PyObject_CallMethodObjArgs(self->buffer, _PyIO_str_truncate, pos, NULL);
}
static PyObject *