From 2dced8b602df10531cab6cd87da5503c06f14888 Mon Sep 17 00:00:00 2001 From: Guido van Rossum Date: Tue, 30 Oct 2007 17:27:30 +0000 Subject: [PATCH] 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.) --- Doc/c-api/concrete.rst | 6 +++--- Include/fileobject.h | 3 ++- Lib/io.py | 13 +++++++++---- Lib/quopri.py | 14 ++++++++------ Lib/test/test_io.py | 3 +++ Misc/NEWS | 3 +++ Modules/_fileio.c | 36 +++++++++++++++++++++++++++--------- Objects/fileobject.c | 6 +++--- Python/import.c | 2 +- Python/pythonrun.c | 8 ++++---- 10 files changed, 63 insertions(+), 31 deletions(-) diff --git a/Doc/c-api/concrete.rst b/Doc/c-api/concrete.rst index cdea26f1bce..1e1fa0dccf1 100644 --- a/Doc/c-api/concrete.rst +++ b/Doc/c-api/concrete.rst @@ -2401,12 +2401,12 @@ change in future releases of Python. :ctype:`PyFileObject`. -.. cfunction:: PyFile_FromFd(int fd, char *name, char *mode, int buffering, char *encoding, char *newline) +.. cfunction:: PyFile_FromFd(int fd, char *name, char *mode, int buffering, char *encoding, char *newline, int closefd) Create a new :ctype:`PyFileObject` from the file descriptor of an already opened file *fd*. The arguments *name*, *encoding* and *newline* can be - *NULL* as well as buffering can be *-1* to use the defaults. Return *NULL* on - failure. + *NULL* to use the defaults; *buffering* can be *-1* to use the default. + Return *NULL* on failure. .. warning:: diff --git a/Include/fileobject.h b/Include/fileobject.h index acb8c6ddc19..ae127da0c78 100644 --- a/Include/fileobject.h +++ b/Include/fileobject.h @@ -8,7 +8,8 @@ extern "C" { #define PY_STDIOTEXTMODE "b" -PyAPI_FUNC(PyObject *) PyFile_FromFd(int, char *, char *, int, char *, char *); +PyAPI_FUNC(PyObject *) PyFile_FromFd(int, char *, char *, int, char *, char *, + int); PyAPI_FUNC(PyObject *) PyFile_GetLine(PyObject *, int); PyAPI_FUNC(int) PyFile_WriteObject(PyObject *, PyObject *, int); PyAPI_FUNC(int) PyFile_WriteString(const char *, PyObject *); diff --git a/Lib/io.py b/Lib/io.py index c2d803ccfed..d7709c45941 100644 --- a/Lib/io.py +++ b/Lib/io.py @@ -49,7 +49,8 @@ class BlockingIOError(IOError): self.characters_written = characters_written -def open(file, mode="r", buffering=None, encoding=None, newline=None): +def open(file, mode="r", buffering=None, encoding=None, newline=None, + closefd=True): r"""Replacement for the built-in open function. Args: @@ -81,9 +82,12 @@ def open(file, mode="r", buffering=None, encoding=None, newline=None): other legal values, any `'\n'` characters written are translated to the given string. + closefd: optional argument to keep the underlying file descriptor + open when the file is closed. It must not be false when + a filename is given. + (*) If a file descriptor is given, it is closed when the returned - I/O object is closed. If you don't want this to happen, use - os.dup() to create a duplicate file descriptor. + I/O object is closed, unless closefd=False is give. Mode strings characters: 'r': open for reading (default) @@ -138,7 +142,8 @@ def open(file, mode="r", buffering=None, encoding=None, newline=None): (reading and "r" or "") + (writing and "w" or "") + (appending and "a" or "") + - (updating and "+" or "")) + (updating and "+" or ""), + closefd) if buffering is None: buffering = -1 if buffering < 0 and raw.isatty(): diff --git a/Lib/quopri.py b/Lib/quopri.py index 62c0503877c..6b3d13eca45 100755 --- a/Lib/quopri.py +++ b/Lib/quopri.py @@ -227,12 +227,14 @@ def main(): sys.stderr.write("%s: can't open (%s)\n" % (file, msg)) sts = 1 continue - if deco: - decode(fp, sys.stdout.buffer) - else: - encode(fp, sys.stdout.buffer, tabs) - if fp is not sys.stdin: - fp.close() + try: + if deco: + decode(fp, sys.stdout.buffer) + else: + encode(fp, sys.stdout.buffer, tabs) + finally: + if file != '-': + fp.close() if sts: sys.exit(sts) diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py index e826ff4b369..9d4163edc1d 100644 --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -259,6 +259,9 @@ class IOTest(unittest.TestCase): self.assertEqual(f.write(a), n) f.close() + def test_closefd(self): + self.assertRaises(ValueError, io.open, test_support.TESTFN, 'w', + closefd=False) class MemorySeekTestMixin: diff --git a/Misc/NEWS b/Misc/NEWS index 4ec628f7bcd..41b8ab12b98 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -28,6 +28,9 @@ Core and Builtins with `Py_FileSystemDefaultEncoding` and a new API method `PyUnicode_DecodeFSDefault(char*)` was added. +- io.open() and _fileio.FileIO have grown a new argument closefd. A false + value disables the closing of the file descriptor. + Extension Modules ----------------- diff --git a/Modules/_fileio.c b/Modules/_fileio.c index bc707e85968..8469bb248f2 100644 --- a/Modules/_fileio.c +++ b/Modules/_fileio.c @@ -33,6 +33,7 @@ typedef struct { unsigned readable : 1; unsigned writable : 1; int seekable : 2; /* -1 means unknown */ + int closefd : 1; PyObject *weakreflist; } PyFileIOObject; @@ -59,6 +60,13 @@ internal_close(PyFileIOObject *self) static PyObject * fileio_close(PyFileIOObject *self) { + if (!self->closefd) { + if (PyErr_WarnEx(PyExc_RuntimeWarning, + "Trying to close unclosable fd!", 3) < 0) { + return NULL; + } + Py_RETURN_NONE; + } errno = internal_close(self); if (errno < 0) { PyErr_SetFromErrno(PyExc_IOError); @@ -119,7 +127,7 @@ static int fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) { PyFileIOObject *self = (PyFileIOObject *) oself; - static char *kwlist[] = {"file", "mode", NULL}; + static char *kwlist[] = {"file", "mode", "closefd", NULL}; char *name = NULL; char *mode = "r"; char *s; @@ -130,6 +138,7 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) int rwa = 0, plus = 0, append = 0; int flags = 0; int fd = -1; + int closefd = 1; assert(PyFileIO_Check(oself)); if (self->fd >= 0) { @@ -138,8 +147,8 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) return -1; } - if (PyArg_ParseTupleAndKeywords(args, kwds, "i|s:fileio", - kwlist, &fd, &mode)) { + if (PyArg_ParseTupleAndKeywords(args, kwds, "i|si:fileio", + kwlist, &fd, &mode, &closefd)) { if (fd < 0) { PyErr_SetString(PyExc_ValueError, "Negative filedescriptor"); @@ -153,8 +162,9 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) if (GetVersion() < 0x80000000) { /* On NT, so wide API available */ PyObject *po; - if (PyArg_ParseTupleAndKeywords(args, kwds, "U|s:fileio", - kwlist, &po, &mode)) { + if (PyArg_ParseTupleAndKeywords(args, kwds, "U|si:fileio", + kwlist, &po, &mode, &closefd) + ) { widename = PyUnicode_AS_UNICODE(po); } else { /* Drop the argument parsing error as narrow @@ -162,13 +172,13 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) PyErr_Clear(); } } - if (widename == NULL) + if (widename == NULL) #endif { - if (!PyArg_ParseTupleAndKeywords(args, kwds, "et|s:fileio", + if (!PyArg_ParseTupleAndKeywords(args, kwds, "et|si:fileio", kwlist, Py_FileSystemDefaultEncoding, - &name, &mode)) + &name, &mode, &closefd)) goto error; } } @@ -237,8 +247,16 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) if (fd >= 0) { self->fd = fd; + self->closefd = closefd; } else { + self->closefd = 1; + if (!closefd) { + PyErr_SetString(PyExc_ValueError, + "Cannot use closefd=True with file name"); + goto error; + } + Py_BEGIN_ALLOW_THREADS errno = 0; #ifdef MS_WINDOWS @@ -270,7 +288,7 @@ fileio_dealloc(PyFileIOObject *self) if (self->weakreflist != NULL) PyObject_ClearWeakRefs((PyObject *) self); - if (self->fd >= 0) { + if (self->fd >= 0 && self->closefd) { errno = internal_close(self); if (errno < 0) { #ifdef HAVE_STRERROR diff --git a/Objects/fileobject.c b/Objects/fileobject.c index 9f63814ba18..4e18480b312 100644 --- a/Objects/fileobject.c +++ b/Objects/fileobject.c @@ -27,15 +27,15 @@ extern "C" { PyObject * PyFile_FromFd(int fd, char *name, char *mode, int buffering, char *encoding, - char *newline) + char *newline, int closefd) { PyObject *io, *stream, *nameobj = NULL; io = PyImport_ImportModule("io"); if (io == NULL) return NULL; - stream = PyObject_CallMethod(io, "open", "isiss", fd, mode, - buffering, encoding, newline); + stream = PyObject_CallMethod(io, "open", "isissi", fd, mode, + buffering, encoding, newline, closefd); Py_DECREF(io); if (stream == NULL) return NULL; diff --git a/Python/import.c b/Python/import.c index 24935547e6d..be456f1b50d 100644 --- a/Python/import.c +++ b/Python/import.c @@ -2588,7 +2588,7 @@ call_find_module(char *name, PyObject *path) (char*)PyUnicode_GetDefaultEncoding(); } fob = PyFile_FromFd(fd, pathname, fdp->mode, -1, - (char*)encoding, NULL); + (char*)encoding, NULL, 1); if (fob == NULL) { close(fd); PyMem_FREE(found_encoding); diff --git a/Python/pythonrun.c b/Python/pythonrun.c index 330667a219b..76da8fb7f77 100644 --- a/Python/pythonrun.c +++ b/Python/pythonrun.c @@ -720,7 +720,7 @@ initstdio(void) /* Set sys.stdin */ if (!(std = PyFile_FromFd(fileno(stdin), "", "r", -1, - NULL, "\n"))) { + NULL, "\n", 0))) { goto error; } PySys_SetObject("__stdin__", std); @@ -729,16 +729,16 @@ initstdio(void) /* Set sys.stdout */ if (!(std = PyFile_FromFd(fileno(stdout), "", "w", -1, - NULL, "\n"))) { + NULL, "\n", 0))) { goto error; } PySys_SetObject("__stdout__", std); PySys_SetObject("stdout", std); Py_DECREF(std); - /* Set sys.stderr */ + /* Set sys.stderr, replaces the preliminary stderr */ if (!(std = PyFile_FromFd(fileno(stderr), "", "w", -1, - NULL, "\n"))) { + NULL, "\n", 0))) { goto error; } PySys_SetObject("__stderr__", std);