diff -r fee4bc043d73 Doc/library/functions.rst --- a/Doc/library/functions.rst Fri Jan 04 12:40:35 2013 +0200 +++ b/Doc/library/functions.rst Fri Jan 04 15:23:33 2013 +0100 @@ -852,6 +852,7 @@ are always available. They are listed h ``'w'`` open for writing, truncating the file first ``'x'`` open for exclusive creation, failing if the file already exists ``'a'`` open for writing, appending to the end of the file if it exists + ``'e'`` enable the close-on-exec flag ``'b'`` binary mode ``'t'`` text mode (default) ``'+'`` open a disk file for updating (reading and writing) @@ -989,6 +990,9 @@ are always available. They are listed h :exc:`FileExistsError` is now raised if the file opened in exclusive creation mode (``'x'``) already exists. + .. versionchanged:: 3.4 + ``'e'`` mode flag was added. + .. XXX works for bytes too, but should it? .. function:: ord(c) diff -r fee4bc043d73 Lib/_pyio.py --- a/Lib/_pyio.py Fri Jan 04 12:40:35 2013 +0200 +++ b/Lib/_pyio.py Fri Jan 04 15:23:33 2013 +0100 @@ -59,6 +59,7 @@ def open(file, mode="r", buffering=-1, e 'w' open for writing, truncating the file first 'x' create a new file and open it for writing 'a' open for writing, appending to the end of the file if it exists + 'e' enable the close-on-exec flag 'b' binary mode 't' text mode (default) '+' open a disk file for updating (reading and writing) @@ -163,6 +164,7 @@ def open(file, mode="r", buffering=-1, e if modes - set("axrwb+tU") or len(mode) > len(modes): raise ValueError("invalid mode: %r" % mode) creating = "x" in modes + cloexec = "e" in modes reading = "r" in modes writing = "w" in modes appending = "a" in modes @@ -190,6 +192,7 @@ def open(file, mode="r", buffering=-1, e (reading and "r" or "") + (writing and "w" or "") + (appending and "a" or "") + + (cloexec and "e" or "") + (updating and "+" or ""), closefd, opener=opener) line_buffering = False diff -r fee4bc043d73 Lib/test/test_builtin.py --- a/Lib/test/test_builtin.py Fri Jan 04 12:40:35 2013 +0200 +++ b/Lib/test/test_builtin.py Fri Jan 04 15:23:33 2013 +0100 @@ -20,6 +20,10 @@ try: import pty, signal except ImportError: pty = signal = None +try: + import fcntl +except ImportError: + fcntl = None class Squares: @@ -943,29 +947,24 @@ class BuiltinTest(unittest.TestCase): def write_testfile(self): # NB the first 4 lines are also used to test input, below fp = open(TESTFN, 'w') - try: + self.addCleanup(unlink, TESTFN) + with fp: fp.write('1+1\n') fp.write('The quick brown fox jumps over the lazy dog') fp.write('.\n') fp.write('Dear John\n') fp.write('XXX'*100) fp.write('YYY'*100) - finally: - fp.close() def test_open(self): self.write_testfile() - fp = open(TESTFN, 'r') - try: + with open(TESTFN, 'r') as fp: self.assertEqual(fp.readline(4), '1+1\n') self.assertEqual(fp.readline(), 'The quick brown fox jumps over the lazy dog.\n') self.assertEqual(fp.readline(4), 'Dear') self.assertEqual(fp.readline(100), ' John\n') self.assertEqual(fp.read(300), 'XXX'*100) self.assertEqual(fp.read(1000), 'YYY'*100) - finally: - fp.close() - unlink(TESTFN) def test_open_default_encoding(self): old_environ = dict(os.environ) @@ -979,16 +978,26 @@ class BuiltinTest(unittest.TestCase): self.write_testfile() current_locale_encoding = locale.getpreferredencoding(False) - fp = open(TESTFN, 'w') - try: + with open(TESTFN, 'w') as fp: self.assertEqual(fp.encoding, current_locale_encoding) - finally: - fp.close() - unlink(TESTFN) finally: os.environ.clear() os.environ.update(old_environ) + # FIXME: use subprocess to run this test on Windows + @unittest.skipUnless(fcntl, 'need fcntl module') + def test_open_cloexec(self): + self.write_testfile() + for mode in ('re', 'rbe'): + with open(TESTFN, mode) as fp: + fd = fp.fileno() + flags = fcntl.fcntl(fd, fcntl.F_GETFD, 0) + self.assertEqual(flags & fcntl.FD_CLOEXEC, flags & fcntl.FD_CLOEXEC) + + def fake_opener(path, flags): + return 0 + self.assertRaises(ValueError, open, TESTFN, 're', opener=fake_opener) + def test_ord(self): self.assertEqual(ord(' '), 32) self.assertEqual(ord('A'), 65) diff -r fee4bc043d73 Modules/_io/_iomodule.c --- a/Modules/_io/_iomodule.c Fri Jan 04 12:40:35 2013 +0200 +++ b/Modules/_io/_iomodule.c Fri Jan 04 15:23:33 2013 +0100 @@ -123,6 +123,7 @@ PyDoc_STRVAR(open_doc, "'w' open for writing, truncating the file first\n" "'x' create a new file and open it for writing\n" "'a' open for writing, appending to the end of the file if it exists\n" +"'e' enable the close-on-exec flag\n" "'b' binary mode\n" "'t' text mode (default)\n" "'+' open a disk file for updating (reading and writing)\n" @@ -226,7 +227,8 @@ io_open(PyObject *self, PyObject *args, char *encoding = NULL, *errors = NULL, *newline = NULL; unsigned i; - int creating = 0, reading = 0, writing = 0, appending = 0, updating = 0; + int creating = 0, cloexec = 0; + int reading = 0, writing = 0, appending = 0, updating = 0; int text = 0, binary = 0, universal = 0; char rawmode[6], *m; @@ -282,6 +284,9 @@ io_open(PyObject *self, PyObject *args, universal = 1; reading = 1; break; + case 'e': + cloexec = 1; + break; default: goto invalid_mode; } @@ -300,6 +305,7 @@ io_open(PyObject *self, PyObject *args, if (reading) *(m++) = 'r'; if (writing) *(m++) = 'w'; if (appending) *(m++) = 'a'; + if (cloexec) *(m++) = 'e'; if (updating) *(m++) = '+'; *m = '\0'; diff -r fee4bc043d73 Modules/_io/fileio.c --- a/Modules/_io/fileio.c Fri Jan 04 12:40:35 2013 +0200 +++ b/Modules/_io/fileio.c Fri Jan 04 15:23:33 2013 +0100 @@ -221,6 +221,10 @@ fileio_init(PyObject *oself, PyObject *a int fd = -1; int closefd = 1; int fd_is_own = 0; + int cloexec = 0; +#if defined(O_CLOEXEC) && defined(HAVE_FCNTL_H) + int cloexec_works = -1; +#endif assert(PyFileIO_Check(oself)); if (self->fd >= 0) { @@ -291,6 +295,14 @@ fileio_init(PyObject *oself, PyObject *a self->writable = 1; flags |= O_EXCL | O_CREAT; break; + case 'e': + cloexec = 1; +#ifdef MS_WINDOWS + flags |= O_NOINHERIT; +#elif defined(O_CLOEXEC) && defined(HAVE_FCNTL_H) + flags |= O_CLOEXEC; +#endif + break; case 'r': if (rwa) goto bad_mode; @@ -360,6 +372,15 @@ fileio_init(PyObject *oself, PyObject *a goto error; } +#if !defined(MS_WINDOWS) && !defined(HAVE_FCNTL_H) + if (cloexec) { + PyErr_SetString(PyExc_NotImplementedError, + "close-on-exec ('e' mode) is not supported " + "by your platform"); + goto error; + } +#endif + errno = 0; if (opener == Py_None) { Py_BEGIN_ALLOW_THREADS @@ -369,10 +390,35 @@ fileio_init(PyObject *oself, PyObject *a else #endif self->fd = open(name, flags, 0666); + if (cloexec) { + int fd_flags; +#if !defined(MS_WINDOWS) && defined(O_CLOEXEC) && defined(HAVE_FCNTL_H) + if (cloexec_works != 1) { + fd_flags = fcntl(fd, F_GETFD); + if (cloexec_works == -1) + cloexec_works = (fd_flags & FD_CLOEXEC); + if (!cloexec_works) { + fd_flags |= FD_CLOEXEC; + fd_flags = fcntl(fd, F_SETFD, fd_flags); + } + } +#elif !defined(MS_WINDOWS) && defined(HAVE_FCNTL_H) + fd_flags = fcntl(fd, F_GETFD); + fd_flags |= FD_CLOEXEC; + fd_flags = fcntl(fd, F_SETFD, fd_flags); +#endif + } Py_END_ALLOW_THREADS } else { - PyObject *fdobj = PyObject_CallFunction( - opener, "Oi", nameobj, flags); + PyObject *fdobj; + + if (cloexec) { + PyErr_SetString(PyExc_ValueError, + "'e' mode cannot be used with an opener"); + goto error; + } + + fdobj = PyObject_CallFunction(opener, "Oi", nameobj, flags); if (fdobj == NULL) goto error; if (!PyLong_Check(fdobj)) {