Skip to content

Commit 2dced8b

Browse files
committed
Patch 1329 (partial) by Christian Heimes.
Add a closefd flag to open() which can be set to False to prevent closing the file descriptor when close() is called or when the object is destroyed. Useful to ensure that sys.std{in,out,err} keep their file descriptors open when Python is uninitialized. (This was always a feature in 2.x, it just wasn't implemented in 3.0 yet.)
1 parent 2673a57 commit 2dced8b

File tree

10 files changed

+63
-31
lines changed

10 files changed

+63
-31
lines changed

Doc/c-api/concrete.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2401,12 +2401,12 @@ change in future releases of Python.
24012401
:ctype:`PyFileObject`.
24022402

24032403

2404-
.. cfunction:: PyFile_FromFd(int fd, char *name, char *mode, int buffering, char *encoding, char *newline)
2404+
.. cfunction:: PyFile_FromFd(int fd, char *name, char *mode, int buffering, char *encoding, char *newline, int closefd)
24052405

24062406
Create a new :ctype:`PyFileObject` from the file descriptor of an already
24072407
opened file *fd*. The arguments *name*, *encoding* and *newline* can be
2408-
*NULL* as well as buffering can be *-1* to use the defaults. Return *NULL* on
2409-
failure.
2408+
*NULL* to use the defaults; *buffering* can be *-1* to use the default.
2409+
Return *NULL* on failure.
24102410

24112411
.. warning::
24122412

Include/fileobject.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ extern "C" {
88

99
#define PY_STDIOTEXTMODE "b"
1010

11-
PyAPI_FUNC(PyObject *) PyFile_FromFd(int, char *, char *, int, char *, char *);
11+
PyAPI_FUNC(PyObject *) PyFile_FromFd(int, char *, char *, int, char *, char *,
12+
int);
1213
PyAPI_FUNC(PyObject *) PyFile_GetLine(PyObject *, int);
1314
PyAPI_FUNC(int) PyFile_WriteObject(PyObject *, PyObject *, int);
1415
PyAPI_FUNC(int) PyFile_WriteString(const char *, PyObject *);

Lib/io.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ def __init__(self, errno, strerror, characters_written=0):
4949
self.characters_written = characters_written
5050

5151

52-
def open(file, mode="r", buffering=None, encoding=None, newline=None):
52+
def open(file, mode="r", buffering=None, encoding=None, newline=None,
53+
closefd=True):
5354
r"""Replacement for the built-in open function.
5455
5556
Args:
@@ -81,9 +82,12 @@ def open(file, mode="r", buffering=None, encoding=None, newline=None):
8182
other legal values, any `'\n'` characters written are
8283
translated to the given string.
8384
85+
closefd: optional argument to keep the underlying file descriptor
86+
open when the file is closed. It must not be false when
87+
a filename is given.
88+
8489
(*) If a file descriptor is given, it is closed when the returned
85-
I/O object is closed. If you don't want this to happen, use
86-
os.dup() to create a duplicate file descriptor.
90+
I/O object is closed, unless closefd=False is give.
8791
8892
Mode strings characters:
8993
'r': open for reading (default)
@@ -138,7 +142,8 @@ def open(file, mode="r", buffering=None, encoding=None, newline=None):
138142
(reading and "r" or "") +
139143
(writing and "w" or "") +
140144
(appending and "a" or "") +
141-
(updating and "+" or ""))
145+
(updating and "+" or ""),
146+
closefd)
142147
if buffering is None:
143148
buffering = -1
144149
if buffering < 0 and raw.isatty():

Lib/quopri.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -227,12 +227,14 @@ def main():
227227
sys.stderr.write("%s: can't open (%s)\n" % (file, msg))
228228
sts = 1
229229
continue
230-
if deco:
231-
decode(fp, sys.stdout.buffer)
232-
else:
233-
encode(fp, sys.stdout.buffer, tabs)
234-
if fp is not sys.stdin:
235-
fp.close()
230+
try:
231+
if deco:
232+
decode(fp, sys.stdout.buffer)
233+
else:
234+
encode(fp, sys.stdout.buffer, tabs)
235+
finally:
236+
if file != '-':
237+
fp.close()
236238
if sts:
237239
sys.exit(sts)
238240

Lib/test/test_io.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,9 @@ def test_array_writes(self):
259259
self.assertEqual(f.write(a), n)
260260
f.close()
261261

262+
def test_closefd(self):
263+
self.assertRaises(ValueError, io.open, test_support.TESTFN, 'w',
264+
closefd=False)
262265

263266
class MemorySeekTestMixin:
264267

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ Core and Builtins
2828
with `Py_FileSystemDefaultEncoding` and a new API method
2929
`PyUnicode_DecodeFSDefault(char*)` was added.
3030

31+
- io.open() and _fileio.FileIO have grown a new argument closefd. A false
32+
value disables the closing of the file descriptor.
33+
3134
Extension Modules
3235
-----------------
3336

Modules/_fileio.c

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ typedef struct {
3333
unsigned readable : 1;
3434
unsigned writable : 1;
3535
int seekable : 2; /* -1 means unknown */
36+
int closefd : 1;
3637
PyObject *weakreflist;
3738
} PyFileIOObject;
3839

@@ -59,6 +60,13 @@ internal_close(PyFileIOObject *self)
5960
static PyObject *
6061
fileio_close(PyFileIOObject *self)
6162
{
63+
if (!self->closefd) {
64+
if (PyErr_WarnEx(PyExc_RuntimeWarning,
65+
"Trying to close unclosable fd!", 3) < 0) {
66+
return NULL;
67+
}
68+
Py_RETURN_NONE;
69+
}
6270
errno = internal_close(self);
6371
if (errno < 0) {
6472
PyErr_SetFromErrno(PyExc_IOError);
@@ -119,7 +127,7 @@ static int
119127
fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
120128
{
121129
PyFileIOObject *self = (PyFileIOObject *) oself;
122-
static char *kwlist[] = {"file", "mode", NULL};
130+
static char *kwlist[] = {"file", "mode", "closefd", NULL};
123131
char *name = NULL;
124132
char *mode = "r";
125133
char *s;
@@ -130,6 +138,7 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
130138
int rwa = 0, plus = 0, append = 0;
131139
int flags = 0;
132140
int fd = -1;
141+
int closefd = 1;
133142

134143
assert(PyFileIO_Check(oself));
135144
if (self->fd >= 0) {
@@ -138,8 +147,8 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
138147
return -1;
139148
}
140149

141-
if (PyArg_ParseTupleAndKeywords(args, kwds, "i|s:fileio",
142-
kwlist, &fd, &mode)) {
150+
if (PyArg_ParseTupleAndKeywords(args, kwds, "i|si:fileio",
151+
kwlist, &fd, &mode, &closefd)) {
143152
if (fd < 0) {
144153
PyErr_SetString(PyExc_ValueError,
145154
"Negative filedescriptor");
@@ -153,22 +162,23 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
153162
if (GetVersion() < 0x80000000) {
154163
/* On NT, so wide API available */
155164
PyObject *po;
156-
if (PyArg_ParseTupleAndKeywords(args, kwds, "U|s:fileio",
157-
kwlist, &po, &mode)) {
165+
if (PyArg_ParseTupleAndKeywords(args, kwds, "U|si:fileio",
166+
kwlist, &po, &mode, &closefd)
167+
) {
158168
widename = PyUnicode_AS_UNICODE(po);
159169
} else {
160170
/* Drop the argument parsing error as narrow
161171
strings are also valid. */
162172
PyErr_Clear();
163173
}
164174
}
165-
if (widename == NULL)
175+
if (widename == NULL)
166176
#endif
167177
{
168-
if (!PyArg_ParseTupleAndKeywords(args, kwds, "et|s:fileio",
178+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "et|si:fileio",
169179
kwlist,
170180
Py_FileSystemDefaultEncoding,
171-
&name, &mode))
181+
&name, &mode, &closefd))
172182
goto error;
173183
}
174184
}
@@ -237,8 +247,16 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds)
237247

238248
if (fd >= 0) {
239249
self->fd = fd;
250+
self->closefd = closefd;
240251
}
241252
else {
253+
self->closefd = 1;
254+
if (!closefd) {
255+
PyErr_SetString(PyExc_ValueError,
256+
"Cannot use closefd=True with file name");
257+
goto error;
258+
}
259+
242260
Py_BEGIN_ALLOW_THREADS
243261
errno = 0;
244262
#ifdef MS_WINDOWS
@@ -270,7 +288,7 @@ fileio_dealloc(PyFileIOObject *self)
270288
if (self->weakreflist != NULL)
271289
PyObject_ClearWeakRefs((PyObject *) self);
272290

273-
if (self->fd >= 0) {
291+
if (self->fd >= 0 && self->closefd) {
274292
errno = internal_close(self);
275293
if (errno < 0) {
276294
#ifdef HAVE_STRERROR

Objects/fileobject.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,15 @@ extern "C" {
2727

2828
PyObject *
2929
PyFile_FromFd(int fd, char *name, char *mode, int buffering, char *encoding,
30-
char *newline)
30+
char *newline, int closefd)
3131
{
3232
PyObject *io, *stream, *nameobj = NULL;
3333

3434
io = PyImport_ImportModule("io");
3535
if (io == NULL)
3636
return NULL;
37-
stream = PyObject_CallMethod(io, "open", "isiss", fd, mode,
38-
buffering, encoding, newline);
37+
stream = PyObject_CallMethod(io, "open", "isissi", fd, mode,
38+
buffering, encoding, newline, closefd);
3939
Py_DECREF(io);
4040
if (stream == NULL)
4141
return NULL;

Python/import.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2588,7 +2588,7 @@ call_find_module(char *name, PyObject *path)
25882588
(char*)PyUnicode_GetDefaultEncoding();
25892589
}
25902590
fob = PyFile_FromFd(fd, pathname, fdp->mode, -1,
2591-
(char*)encoding, NULL);
2591+
(char*)encoding, NULL, 1);
25922592
if (fob == NULL) {
25932593
close(fd);
25942594
PyMem_FREE(found_encoding);

Python/pythonrun.c

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -720,7 +720,7 @@ initstdio(void)
720720

721721
/* Set sys.stdin */
722722
if (!(std = PyFile_FromFd(fileno(stdin), "<stdin>", "r", -1,
723-
NULL, "\n"))) {
723+
NULL, "\n", 0))) {
724724
goto error;
725725
}
726726
PySys_SetObject("__stdin__", std);
@@ -729,16 +729,16 @@ initstdio(void)
729729

730730
/* Set sys.stdout */
731731
if (!(std = PyFile_FromFd(fileno(stdout), "<stdout>", "w", -1,
732-
NULL, "\n"))) {
732+
NULL, "\n", 0))) {
733733
goto error;
734734
}
735735
PySys_SetObject("__stdout__", std);
736736
PySys_SetObject("stdout", std);
737737
Py_DECREF(std);
738738

739-
/* Set sys.stderr */
739+
/* Set sys.stderr, replaces the preliminary stderr */
740740
if (!(std = PyFile_FromFd(fileno(stderr), "<stderr>", "w", -1,
741-
NULL, "\n"))) {
741+
NULL, "\n", 0))) {
742742
goto error;
743743
}
744744
PySys_SetObject("__stderr__", std);

0 commit comments

Comments
 (0)