closes bpo-27805: Ignore ESPIPE in initializing seek of append-mode files. (GH-17112)

This change, which follows the behavior of C stdio's fdopen and Python 2's file object, allows pipes to be opened in append mode.
This commit is contained in:
Benjamin Peterson 2019-11-12 14:51:34 -08:00 committed by GitHub
parent d593881505
commit 74fa9f723f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 33 additions and 10 deletions

View File

@ -1577,7 +1577,11 @@ def __init__(self, file, mode='r', closefd=True, opener=None):
# For consistent behaviour, we explicitly seek to the # For consistent behaviour, we explicitly seek to the
# end of file (otherwise, it might be done only on the # end of file (otherwise, it might be done only on the
# first write()). # first write()).
os.lseek(fd, 0, SEEK_END) try:
os.lseek(fd, 0, SEEK_END)
except OSError as e:
if e.errno != errno.ESPIPE:
raise
except: except:
if owned_fd is not None: if owned_fd is not None:
os.close(owned_fd) os.close(owned_fd)

View File

@ -3906,6 +3906,17 @@ def test_removed_u_mode(self):
self.open(support.TESTFN, mode) self.open(support.TESTFN, mode)
self.assertIn('invalid mode', str(cm.exception)) self.assertIn('invalid mode', str(cm.exception))
def test_open_pipe_with_append(self):
# bpo-27805: Ignore ESPIPE from lseek() in open().
r, w = os.pipe()
self.addCleanup(os.close, r)
f = self.open(w, 'a')
self.addCleanup(f.close)
# Check that the file is marked non-seekable. On Windows, however, lseek
# somehow succeeds on pipes.
if sys.platform != 'win32':
self.assertFalse(f.seekable())
def test_io_after_close(self): def test_io_after_close(self):
for kwargs in [ for kwargs in [
{"mode": "w"}, {"mode": "w"},

View File

@ -0,0 +1,2 @@
Allow opening pipes and other non-seekable files in append mode with
:func:`open`.

View File

@ -4,6 +4,7 @@
#include "Python.h" #include "Python.h"
#include "pycore_object.h" #include "pycore_object.h"
#include "structmember.h" #include "structmember.h"
#include <stdbool.h>
#ifdef HAVE_SYS_TYPES_H #ifdef HAVE_SYS_TYPES_H
#include <sys/types.h> #include <sys/types.h>
#endif #endif
@ -75,7 +76,7 @@ _Py_IDENTIFIER(name);
#define PyFileIO_Check(op) (PyObject_TypeCheck((op), &PyFileIO_Type)) #define PyFileIO_Check(op) (PyObject_TypeCheck((op), &PyFileIO_Type))
/* Forward declarations */ /* Forward declarations */
static PyObject* portable_lseek(fileio *self, PyObject *posobj, int whence); static PyObject* portable_lseek(fileio *self, PyObject *posobj, int whence, bool suppress_pipe_error);
int int
_PyFileIO_closed(PyObject *self) _PyFileIO_closed(PyObject *self)
@ -480,7 +481,7 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
/* For consistent behaviour, we explicitly seek to the /* For consistent behaviour, we explicitly seek to the
end of file (otherwise, it might be done only on the end of file (otherwise, it might be done only on the
first write()). */ first write()). */
PyObject *pos = portable_lseek(self, NULL, 2); PyObject *pos = portable_lseek(self, NULL, 2, true);
if (pos == NULL) if (pos == NULL)
goto error; goto error;
Py_DECREF(pos); Py_DECREF(pos);
@ -603,7 +604,7 @@ _io_FileIO_seekable_impl(fileio *self)
return err_closed(); return err_closed();
if (self->seekable < 0) { if (self->seekable < 0) {
/* portable_lseek() sets the seekable attribute */ /* portable_lseek() sets the seekable attribute */
PyObject *pos = portable_lseek(self, NULL, SEEK_CUR); PyObject *pos = portable_lseek(self, NULL, SEEK_CUR, false);
assert(self->seekable >= 0); assert(self->seekable >= 0);
if (pos == NULL) { if (pos == NULL) {
PyErr_Clear(); PyErr_Clear();
@ -870,7 +871,7 @@ _io_FileIO_write_impl(fileio *self, Py_buffer *b)
/* Cribbed from posix_lseek() */ /* Cribbed from posix_lseek() */
static PyObject * static PyObject *
portable_lseek(fileio *self, PyObject *posobj, int whence) portable_lseek(fileio *self, PyObject *posobj, int whence, bool suppress_pipe_error)
{ {
Py_off_t pos, res; Py_off_t pos, res;
int fd = self->fd; int fd = self->fd;
@ -921,8 +922,13 @@ portable_lseek(fileio *self, PyObject *posobj, int whence)
self->seekable = (res >= 0); self->seekable = (res >= 0);
} }
if (res < 0) if (res < 0) {
return PyErr_SetFromErrno(PyExc_OSError); if (suppress_pipe_error && errno == ESPIPE) {
res = 0;
} else {
return PyErr_SetFromErrno(PyExc_OSError);
}
}
#if defined(HAVE_LARGEFILE_SUPPORT) #if defined(HAVE_LARGEFILE_SUPPORT)
return PyLong_FromLongLong(res); return PyLong_FromLongLong(res);
@ -955,7 +961,7 @@ _io_FileIO_seek_impl(fileio *self, PyObject *pos, int whence)
if (self->fd < 0) if (self->fd < 0)
return err_closed(); return err_closed();
return portable_lseek(self, pos, whence); return portable_lseek(self, pos, whence, false);
} }
/*[clinic input] /*[clinic input]
@ -973,7 +979,7 @@ _io_FileIO_tell_impl(fileio *self)
if (self->fd < 0) if (self->fd < 0)
return err_closed(); return err_closed();
return portable_lseek(self, NULL, 1); return portable_lseek(self, NULL, 1, false);
} }
#ifdef HAVE_FTRUNCATE #ifdef HAVE_FTRUNCATE
@ -1004,7 +1010,7 @@ _io_FileIO_truncate_impl(fileio *self, PyObject *posobj)
if (posobj == Py_None) { if (posobj == Py_None) {
/* Get the current position. */ /* Get the current position. */
posobj = portable_lseek(self, NULL, 1); posobj = portable_lseek(self, NULL, 1, false);
if (posobj == NULL) if (posobj == NULL)
return NULL; return NULL;
} }