diff -r 1fb9383baa5f -r ffa4f9ce9cd5 Lib/test/test_io.py --- a/Lib/test/test_io.py Fri Dec 27 02:07:49 2013 -0500 +++ b/Lib/test/test_io.py Fri Dec 27 18:27:03 2013 -0500 @@ -1730,6 +1730,30 @@ f.flush() self.assertEqual(raw.getvalue(), b'1b\n2def\n3\n') + def test_append_write(self): + # Uses a real FileIO so that append behavior is reproduced accurately + with self.FileIO(support.TESTFN, 'wb') as f: + f.write(b'test test') + + with self.FileIO(support.TESTFN, 'ab+') as raw: + with self.tp(raw) as f: + self.assertEqual(f.tell(), 9) + f.write(b'A') + f.seek(0) + self.assertEqual(f.read(), b'test testA') + f.seek(0) + self.assertEqual(f.read(1), b't') + self.assertEqual(f.write(b'B'), 1) + f.seek(0) + + # This read previously returned 'tBst testA' but that is + # incorrect if the underlying raw file is in append mode + self.assertEqual(f.read(), b'test testAB') + f.flush() + f.seek(0) + self.assertEqual(f.read(), b'test testAB') + + # You can't construct a BufferedRandom over a non-seekable stream. test_unseekable = None diff -r 1fb9383baa5f -r ffa4f9ce9cd5 Modules/_io/bufferedio.c --- a/Modules/_io/bufferedio.c Fri Dec 27 02:07:49 2013 -0500 +++ b/Modules/_io/bufferedio.c Fri Dec 27 18:27:03 2013 -0500 @@ -232,6 +232,7 @@ int detached; int readable; int writable; + int appending; char finalizing; /* True if this is a vanilla Buffered object (rather than a user derived @@ -1202,9 +1203,39 @@ } static PyObject * +_buffered_seek_unlocked(buffered *self, Py_off_t target, int whence) +{ + Py_off_t n; + PyObject *res = NULL; + + + if (self->writable) { + res = _bufferedwriter_flush_unlocked(self); + if (res == NULL) + return res; + Py_CLEAR(res); + _bufferedwriter_reset_buf(self); + } + + /* TODO: align on block boundary and read buffer if needed? */ + if (whence == 1) + target -= RAW_OFFSET(self); + n = _buffered_raw_seek(self, target, whence); + if (n == -1) + return res; + self->raw_pos = -1; + res = PyLong_FromOff_t(n); + if (res != NULL && self->readable) + _bufferedreader_reset_buf(self); + + return res; +} + + +static PyObject * buffered_seek(buffered *self, PyObject *args) { - Py_off_t target, n; + Py_off_t target; int whence = 0; PyObject *targetobj, *res = NULL; @@ -1213,6 +1244,10 @@ return NULL; } + target = PyNumber_AsOff_t(targetobj, PyExc_ValueError); + if (target == -1 && PyErr_Occurred()) + return NULL; + /* Do some error checking instead of trusting OS 'seek()' ** error detection, just in case. */ @@ -1234,10 +1269,6 @@ if (_PyIOBase_check_seekable(self->raw, Py_True) == NULL) return NULL; - target = PyNumber_AsOff_t(targetobj, PyExc_ValueError); - if (target == -1 && PyErr_Occurred()) - return NULL; - /* SEEK_SET and SEEK_CUR are special because we could seek inside the buffer. Other whence values must be managed without this optimization. Some Operating Systems can provide additional values, like @@ -1269,30 +1300,13 @@ return NULL; /* Fallback: invoke raw seek() method and clear buffer */ - if (self->writable) { - res = _bufferedwriter_flush_unlocked(self); - if (res == NULL) - goto end; - Py_CLEAR(res); - _bufferedwriter_reset_buf(self); - } + res = _buffered_seek_unlocked(self, target, whence); - /* TODO: align on block boundary and read buffer if needed? */ - if (whence == 1) - target -= RAW_OFFSET(self); - n = _buffered_raw_seek(self, target, whence); - if (n == -1) - goto end; - self->raw_pos = -1; - res = PyLong_FromOff_t(n); - if (res != NULL && self->readable) - _bufferedreader_reset_buf(self); - -end: LEAVE_BUFFERED(self) return res; } + static PyObject * buffered_truncate(buffered *self, PyObject *args) { @@ -1420,6 +1434,7 @@ self->buffer_size = buffer_size; self->readable = 1; self->writable = 0; + self->appending = 0; if (_buffered_init(self) < 0) return -1; @@ -1843,6 +1858,27 @@ self->write_end = -1; } +static void +_bufferedwriter_set_append(buffered *self) +{ + PyObject *mode; + + mode = _PyObject_GetAttrId(self->raw, &PyId_mode); + if (mode != NULL) { + /* Raw fileobj has no mode string so as far as we can know it has + normal write behavior */ + if (PyUnicode_FindChar(mode, 'a', 0, PyUnicode_GET_LENGTH(mode), 1) != -1) { + self->appending = 1; + } else { + self->appending = 0; + } + Py_DECREF(mode); + } else { + PyErr_Clear(); + self->appending = 0; + } +} + static int bufferedwriter_init(buffered *self, PyObject *args, PyObject *kwds) { @@ -1867,6 +1903,8 @@ self->readable = 0; self->writable = 1; + _bufferedwriter_set_append(self); + self->buffer_size = buffer_size; if (_buffered_init(self) < 0) return -1; @@ -2007,6 +2045,14 @@ self->pos = 0; self->raw_pos = 0; } + + if (self->appending) { + res = _buffered_seek_unlocked(self, 0, SEEK_END); + if (res == NULL) + goto error; + Py_DECREF(res); + } + avail = Py_SAFE_DOWNCAST(self->buffer_size - self->pos, Py_off_t, Py_ssize_t); if (buf.len <= avail) { memcpy(self->buffer + self->pos, buf.buf, buf.len); @@ -2522,6 +2568,8 @@ self->readable = 1; self->writable = 1; + _bufferedwriter_set_append(self); + if (_buffered_init(self) < 0) return -1; _bufferedreader_reset_buf(self);