Skip to content

Commit 74fa9f7

Browse files
authored
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.
1 parent d593881 commit 74fa9f7

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
@@ -1577,7 +1577,11 @@ def __init__(self, file, mode='r', closefd=True, opener=None):
15771577
# For consistent behaviour, we explicitly seek to the
15781578
# end of file (otherwise, it might be done only on the
15791579
# first write()).
1580-
os.lseek(fd, 0, SEEK_END)
1580+
try:
1581+
os.lseek(fd, 0, SEEK_END)
1582+
except OSError as e:
1583+
if e.errno != errno.ESPIPE:
1584+
raise
15811585
except:
15821586
if owned_fd is not None:
15831587
os.close(owned_fd)

Lib/test/test_io.py

+11
Original file line numberDiff line numberDiff line change
@@ -3906,6 +3906,17 @@ def test_removed_u_mode(self):
39063906
self.open(support.TESTFN, mode)
39073907
self.assertIn('invalid mode', str(cm.exception))
39083908

3909+
def test_open_pipe_with_append(self):
3910+
# bpo-27805: Ignore ESPIPE from lseek() in open().
3911+
r, w = os.pipe()
3912+
self.addCleanup(os.close, r)
3913+
f = self.open(w, 'a')
3914+
self.addCleanup(f.close)
3915+
# Check that the file is marked non-seekable. On Windows, however, lseek
3916+
# somehow succeeds on pipes.
3917+
if sys.platform != 'win32':
3918+
self.assertFalse(f.seekable())
3919+
39093920
def test_io_after_close(self):
39103921
for kwargs in [
39113922
{"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
@@ -4,6 +4,7 @@
44
#include "Python.h"
55
#include "pycore_object.h"
66
#include "structmember.h"
7+
#include <stdbool.h>
78
#ifdef HAVE_SYS_TYPES_H
89
#include <sys/types.h>
910
#endif
@@ -75,7 +76,7 @@ _Py_IDENTIFIER(name);
7576
#define PyFileIO_Check(op) (PyObject_TypeCheck((op), &PyFileIO_Type))
7677

7778
/* Forward declarations */
78-
static PyObject* portable_lseek(fileio *self, PyObject *posobj, int whence);
79+
static PyObject* portable_lseek(fileio *self, PyObject *posobj, int whence, bool suppress_pipe_error);
7980

8081
int
8182
_PyFileIO_closed(PyObject *self)
@@ -480,7 +481,7 @@ _io_FileIO___init___impl(fileio *self, PyObject *nameobj, const char *mode,
480481
/* For consistent behaviour, we explicitly seek to the
481482
end of file (otherwise, it might be done only on the
482483
first write()). */
483-
PyObject *pos = portable_lseek(self, NULL, 2);
484+
PyObject *pos = portable_lseek(self, NULL, 2, true);
484485
if (pos == NULL)
485486
goto error;
486487
Py_DECREF(pos);
@@ -603,7 +604,7 @@ _io_FileIO_seekable_impl(fileio *self)
603604
return err_closed();
604605
if (self->seekable < 0) {
605606
/* portable_lseek() sets the seekable attribute */
606-
PyObject *pos = portable_lseek(self, NULL, SEEK_CUR);
607+
PyObject *pos = portable_lseek(self, NULL, SEEK_CUR, false);
607608
assert(self->seekable >= 0);
608609
if (pos == NULL) {
609610
PyErr_Clear();
@@ -870,7 +871,7 @@ _io_FileIO_write_impl(fileio *self, Py_buffer *b)
870871

871872
/* Cribbed from posix_lseek() */
872873
static PyObject *
873-
portable_lseek(fileio *self, PyObject *posobj, int whence)
874+
portable_lseek(fileio *self, PyObject *posobj, int whence, bool suppress_pipe_error)
874875
{
875876
Py_off_t pos, res;
876877
int fd = self->fd;
@@ -921,8 +922,13 @@ portable_lseek(fileio *self, PyObject *posobj, int whence)
921922
self->seekable = (res >= 0);
922923
}
923924

924-
if (res < 0)
925-
return PyErr_SetFromErrno(PyExc_OSError);
925+
if (res < 0) {
926+
if (suppress_pipe_error && errno == ESPIPE) {
927+
res = 0;
928+
} else {
929+
return PyErr_SetFromErrno(PyExc_OSError);
930+
}
931+
}
926932

927933
#if defined(HAVE_LARGEFILE_SUPPORT)
928934
return PyLong_FromLongLong(res);
@@ -955,7 +961,7 @@ _io_FileIO_seek_impl(fileio *self, PyObject *pos, int whence)
955961
if (self->fd < 0)
956962
return err_closed();
957963

958-
return portable_lseek(self, pos, whence);
964+
return portable_lseek(self, pos, whence, false);
959965
}
960966

961967
/*[clinic input]
@@ -973,7 +979,7 @@ _io_FileIO_tell_impl(fileio *self)
973979
if (self->fd < 0)
974980
return err_closed();
975981

976-
return portable_lseek(self, NULL, 1);
982+
return portable_lseek(self, NULL, 1, false);
977983
}
978984

979985
#ifdef HAVE_FTRUNCATE
@@ -1004,7 +1010,7 @@ _io_FileIO_truncate_impl(fileio *self, PyObject *posobj)
10041010

10051011
if (posobj == Py_None) {
10061012
/* Get the current position. */
1007-
posobj = portable_lseek(self, NULL, 1);
1013+
posobj = portable_lseek(self, NULL, 1, false);
10081014
if (posobj == NULL)
10091015
return NULL;
10101016
}

0 commit comments

Comments
 (0)