Skip to content

Commit c334a43

Browse files
committed
[3.7] 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.. (cherry picked from commit 74fa9f7) Co-authored-by: Benjamin Peterson <benjamin@python.org>
1 parent 42b619a commit c334a43

File tree

4 files changed

+33
-10
lines changed

4 files changed

+33
-10
lines changed

Lib/_pyio.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -1543,7 +1543,11 @@ def __init__(self, file, mode='r', closefd=True, opener=None):
15431543
# For consistent behaviour, we explicitly seek to the
15441544
# end of file (otherwise, it might be done only on the
15451545
# first write()).
1546-
os.lseek(fd, 0, SEEK_END)
1546+
try:
1547+
os.lseek(fd, 0, SEEK_END)
1548+
except OSError as e:
1549+
if e.errno != errno.ESPIPE:
1550+
raise
15471551
except:
15481552
if owned_fd is not None:
15491553
os.close(owned_fd)

Lib/test/test_io.py

+11
Original file line numberDiff line numberDiff line change
@@ -3830,6 +3830,17 @@ def test_attributes(self):
38303830
f.close()
38313831
g.close()
38323832

3833+
def test_open_pipe_with_append(self):
3834+
# bpo-27805: Ignore ESPIPE from lseek() in open().
3835+
r, w = os.pipe()
3836+
self.addCleanup(os.close, r)
3837+
f = self.open(w, 'a')
3838+
self.addCleanup(f.close)
3839+
# Check that the file is marked non-seekable. On Windows, however, lseek
3840+
# somehow succeeds on pipes.
3841+
if sys.platform != 'win32':
3842+
self.assertFalse(f.seekable())
3843+
38333844
def test_io_after_close(self):
38343845
for kwargs in [
38353846
{"mode": "w"},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Allow opening pipes and other non-seekable files in append mode with
2+
:func:`open`.

Modules/_io/fileio.c

+15-9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#define PY_SSIZE_T_CLEAN
44
#include "Python.h"
55
#include "structmember.h"
6+
#include <stdbool.h>
67
#ifdef HAVE_SYS_TYPES_H
78
#include <sys/types.h>
89
#endif
@@ -74,7 +75,7 @@ _Py_IDENTIFIER(name);
7475
#define PyFileIO_Check(op) (PyObject_TypeCheck((op), &PyFileIO_Type))
7576

7677
/* Forward declarations */
77-
static PyObject* portable_lseek(fileio *self, PyObject *posobj, int whence);
78+
static PyObject* portable_lseek(fileio *self, PyObject *posobj, int whence, bool suppress_pipe_error);
7879

7980
int
8081
_PyFileIO_closed(PyObject *self)
@@ -475,7 +476,7 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
475476
/* For consistent behaviour, we explicitly seek to the
476477
end of file (otherwise, it might be done only on the
477478
first write()). */
478-
PyObject *pos = portable_lseek(self, NULL, 2);
479+
PyObject *pos = portable_lseek(self, NULL, 2, true);
479480
if (pos == NULL)
480481
goto error;
481482
Py_DECREF(pos);
@@ -598,7 +599,7 @@ _io_FileIO_seekable_impl(fileio *self)
598599
return err_closed();
599600
if (self->seekable < 0) {
600601
/* portable_lseek() sets the seekable attribute */
601-
PyObject *pos = portable_lseek(self, NULL, SEEK_CUR);
602+
PyObject *pos = portable_lseek(self, NULL, SEEK_CUR, false);
602603
assert(self->seekable >= 0);
603604
if (pos == NULL) {
604605
PyErr_Clear();
@@ -865,7 +866,7 @@ _io_FileIO_write_impl(fileio *self, Py_buffer *b)
865866

866867
/* Cribbed from posix_lseek() */
867868
static PyObject *
868-
portable_lseek(fileio *self, PyObject *posobj, int whence)
869+
portable_lseek(fileio *self, PyObject *posobj, int whence, bool suppress_pipe_error)
869870
{
870871
Py_off_t pos, res;
871872
int fd = self->fd;
@@ -916,8 +917,13 @@ portable_lseek(fileio *self, PyObject *posobj, int whence)
916917
self->seekable = (res >= 0);
917918
}
918919

919-
if (res < 0)
920-
return PyErr_SetFromErrno(PyExc_OSError);
920+
if (res < 0) {
921+
if (suppress_pipe_error && errno == ESPIPE) {
922+
res = 0;
923+
} else {
924+
return PyErr_SetFromErrno(PyExc_OSError);
925+
}
926+
}
921927

922928
#if defined(HAVE_LARGEFILE_SUPPORT)
923929
return PyLong_FromLongLong(res);
@@ -950,7 +956,7 @@ _io_FileIO_seek_impl(fileio *self, PyObject *pos, int whence)
950956
if (self->fd < 0)
951957
return err_closed();
952958

953-
return portable_lseek(self, pos, whence);
959+
return portable_lseek(self, pos, whence, false);
954960
}
955961

956962
/*[clinic input]
@@ -968,7 +974,7 @@ _io_FileIO_tell_impl(fileio *self)
968974
if (self->fd < 0)
969975
return err_closed();
970976

971-
return portable_lseek(self, NULL, 1);
977+
return portable_lseek(self, NULL, 1, false);
972978
}
973979

974980
#ifdef HAVE_FTRUNCATE
@@ -999,7 +1005,7 @@ _io_FileIO_truncate_impl(fileio *self, PyObject *posobj)
9991005

10001006
if (posobj == Py_None || posobj == NULL) {
10011007
/* Get the current position. */
1002-
posobj = portable_lseek(self, NULL, 1);
1008+
posobj = portable_lseek(self, NULL, 1, false);
10031009
if (posobj == NULL)
10041010
return NULL;
10051011
}

0 commit comments

Comments
 (0)