Index: Python/pythonrun.c =================================================================== --- Python/pythonrun.c (revision 58700) +++ Python/pythonrun.c (working copy) @@ -151,7 +151,7 @@ { PyInterpreterState *interp; PyThreadState *tstate; - PyObject *bimod, *sysmod; + PyObject *bimod, *sysmod, *pstderr; char *p; #if defined(HAVE_LANGINFO_H) && defined(CODESET) char *codeset; @@ -228,6 +228,13 @@ PyDict_SetItemString(interp->sysdict, "modules", interp->modules); + /* Set up a dumb stderr printer until we have enough infrastructure + for the io module in place. */ + pstderr = PyFile_NewStdPrinter(fileno(stderr)); + if (pstderr == NULL) + Py_FatalError("Py_Initialize: can't set preliminary stderr"); + PySys_SetObject("stderr", pstderr); + _PyImport_Init(); /* initialize builtin exceptions */ @@ -720,7 +727,7 @@ /* Set sys.stdin */ if (!(std = PyFile_FromFd(fileno(stdin), "", "r", -1, - NULL, "\n"))) { + NULL, "\n", 0))) { goto error; } PySys_SetObject("__stdin__", std); @@ -729,16 +736,16 @@ /* 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); Index: Python/import.c =================================================================== --- Python/import.c (revision 58700) +++ Python/import.c (working copy) @@ -2588,7 +2588,7 @@ (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); Index: Include/fileobject.h =================================================================== --- Include/fileobject.h (revision 58700) +++ Include/fileobject.h (working copy) @@ -8,7 +8,8 @@ #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 *); @@ -20,6 +21,13 @@ */ PyAPI_DATA(const char *) Py_FileSystemDefaultEncoding; +/* Internal API + + The std printer acts as a preliminary sys.stderr until the new io + infrastructure is in place. */ +PyAPI_FUNC(PyObject *) PyFile_NewStdPrinter(int); +PyAPI_DATA(PyTypeObject) PyStdPrinter_Type; + #ifdef __cplusplus } #endif Index: Objects/object.c =================================================================== --- Objects/object.c (revision 58700) +++ Objects/object.c (working copy) @@ -1595,6 +1595,9 @@ if (PyType_Ready(&PyCode_Type) < 0) Py_FatalError("Can't initialize 'code'"); + + if (PyType_Ready(&PyStdPrinter_Type) < 0) + Py_FatalError("Can't initialize StdPrinter"); } Index: Objects/fileobject.c =================================================================== --- Objects/fileobject.c (revision 58700) +++ Objects/fileobject.c (working copy) @@ -27,15 +27,15 @@ 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; @@ -325,6 +325,124 @@ return buf; } +/* **************************** std printer **************************** */ + +typedef struct { + PyObject_HEAD + int fd : 1; +} PyStdPrinter_Object; + +static PyObject * +stdprinter_new(PyTypeObject *type, PyObject *args, PyObject *kews) +{ + PyStdPrinter_Object *self; + + assert(type != NULL && type->tp_alloc != NULL); + + self = (PyStdPrinter_Object *) type->tp_alloc(type, 0); + if (self != NULL) { + self->fd = -1; + } + + return (PyObject *) self; +} + +PyObject * +PyFile_NewStdPrinter(int fd) +{ + PyStdPrinter_Object *self; + + if (fd != 1 && fd != 2) { + PyErr_BadInternalCall(); + return NULL; + } + + self = PyObject_New(PyStdPrinter_Object, + &PyStdPrinter_Type); + self->fd = fd; + return (PyObject*)self; +} + +PyObject * +stdprinter_write(PyStdPrinter_Object *self, PyObject *args) +{ + char *c; + Py_ssize_t n; + + if (self->fd < 0) { + PyErr_SetString(PyExc_ValueError, + "I/O operation on closed file"); + return NULL; + } + + if (!PyArg_ParseTuple(args, "s#", &c, &n)) { + return NULL; + } + + Py_BEGIN_ALLOW_THREADS + errno = 0; + n = write(self->fd, c, n); + Py_END_ALLOW_THREADS + + if (n < 0) { + if (errno == EAGAIN) + Py_RETURN_NONE; + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + return PyInt_FromSsize_t(n); +} + +static PyMethodDef stdprinter_methods[] = { + {"write", (PyCFunction)stdprinter_write, METH_VARARGS, ""}, + {NULL, NULL} /* sentinel */ +}; + +PyTypeObject PyStdPrinter_Type = { + PyVarObject_HEAD_INIT(&PyType_Type, 0) + "stderrprinter", /* tp_name */ + sizeof(PyStdPrinter_Object), /* tp_basicsize */ + 0, /* tp_itemsize */ + /* methods */ + 0, /* tp_dealloc */ + 0, /* tp_print */ + 0, /* tp_getattr */ + 0, /* tp_setattr */ + 0, /* tp_compare */ + 0, /* tp_repr */ + 0, /* tp_as_number */ + 0, /* tp_as_sequence */ + 0, /* tp_as_mapping */ + 0, /* tp_hash */ + 0, /* tp_call */ + 0, /* tp_str */ + PyObject_GenericGetAttr, /* tp_getattro */ + 0, /* tp_setattro */ + 0, /* tp_as_buffer */ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC,/* tp_flags */ + 0, /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + stdprinter_methods, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + PyType_GenericAlloc, /* tp_alloc */ + stdprinter_new, /* tp_new */ + PyObject_Del, /* tp_free */ +}; + + #ifdef __cplusplus } #endif Index: Misc/NEWS =================================================================== --- Misc/NEWS (revision 58700) +++ Misc/NEWS (working copy) @@ -28,6 +28,9 @@ 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 ----------------- Index: Doc/c-api/concrete.rst =================================================================== --- Doc/c-api/concrete.rst (revision 58700) +++ Doc/c-api/concrete.rst (working copy) @@ -2401,12 +2401,12 @@ :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* as well as *buffering* can be *-1* to use the defaults. Return *NULL* + on failure. .. warning:: Index: Lib/quopri.py =================================================================== --- Lib/quopri.py (revision 58700) +++ Lib/quopri.py (working copy) @@ -231,7 +231,7 @@ decode(fp, sys.stdout.buffer) else: encode(fp, sys.stdout.buffer, tabs) - if fp is not sys.stdin: + if fp is not sys.stdin.buffer: fp.close() if sts: sys.exit(sts) Index: Lib/io.py =================================================================== --- Lib/io.py (revision 58700) +++ Lib/io.py (working copy) @@ -49,7 +49,8 @@ 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,13 @@ 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. If you don't want this to happen, set closefd=False + or use os.dup() to create a duplicate file descriptor. Mode strings characters: 'r': open for reading (default) @@ -138,7 +143,8 @@ (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(): Index: Lib/test/test_io.py =================================================================== --- Lib/test/test_io.py (revision 58700) +++ Lib/test/test_io.py (working copy) @@ -259,6 +259,9 @@ 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: Index: Modules/_fileio.c =================================================================== --- Modules/_fileio.c (revision 58700) +++ Modules/_fileio.c (working copy) @@ -33,6 +33,7 @@ unsigned readable : 1; unsigned writable : 1; int seekable : 2; /* -1 means unknown */ + int closefd : 1; PyObject *weakreflist; } PyFileIOObject; @@ -59,6 +60,13 @@ static PyObject * fileio_close(PyFileIOObject *self) { + if (self->closefd == 0) { + 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ -165,10 +175,10 @@ 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 @@ if (fd >= 0) { self->fd = fd; + self->closefd = closefd; } else { + self->closefd = 1; + if (closefd == 0) { + PyErr_SetString(PyExc_ValueError, + "Cannot disable close fd with file name!"); + goto error; + } + Py_BEGIN_ALLOW_THREADS errno = 0; #ifdef MS_WINDOWS @@ -270,7 +288,7 @@ if (self->weakreflist != NULL) PyObject_ClearWeakRefs((PyObject *) self); - if (self->fd >= 0) { + if (self->fd >= 0 && self->closefd != 0) { errno = internal_close(self); if (errno < 0) { #ifdef HAVE_STRERROR