diff --git a/Doc/library/functions.rst b/Doc/library/functions.rst --- a/Doc/library/functions.rst +++ b/Doc/library/functions.rst @@ -780,7 +780,7 @@ :meth:`__index__` method that returns an integer. -.. function:: open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True) +.. function:: open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, dirfd=-1) Open *file* and return a corresponding stream. If the file cannot be opened, an :exc:`IOError` is raised. @@ -887,6 +887,11 @@ closed. If a filename is given *closefd* has no effect and must be ``True`` (the default). + If an open file descriptor to a directory is given as *dirfd*, *name* will be + opened relative to that directory. This requires that the operating system + supports the :c:func:`openat` system call (otherwise it raises + :exc:`NotImplementedError`). + The type of file object returned by the :func:`open` function depends on the mode. When :func:`open` is used to open a file in a text mode (``'w'``, ``'r'``, ``'wt'``, ``'rt'``, etc.), it returns a subclass of diff --git a/Doc/library/io.rst b/Doc/library/io.rst --- a/Doc/library/io.rst +++ b/Doc/library/io.rst @@ -462,7 +462,7 @@ Raw File I/O ^^^^^^^^^^^^ -.. class:: FileIO(name, mode='r', closefd=True) +.. class:: FileIO(name, mode='r', closefd=True, dirfd=-1) :class:`FileIO` represents an OS-level file containing bytes data. It implements the :class:`RawIOBase` interface (and therefore the @@ -483,6 +483,11 @@ The :meth:`read` (when called with a positive argument), :meth:`readinto` and :meth:`write` methods on this class will only make one system call. + If an open file descriptor to a directory is given as *dirfd*, *name* will be + opened relative to that directory. This requires that the operating system + supports the :c:func:`openat` system call (otherwise it raises + :exc:`NotImplementedError`). + In addition to the attributes and methods from :class:`IOBase` and :class:`RawIOBase`, :class:`FileIO` provides the following data attributes and methods: diff --git a/Lib/_pyio.py b/Lib/_pyio.py --- a/Lib/_pyio.py +++ b/Lib/_pyio.py @@ -36,7 +36,7 @@ def open(file, mode="r", buffering=-1, encoding=None, errors=None, - newline=None, closefd=True): + newline=None, closefd=True, dirfd=-1): r"""Open file and return a stream. Raise IOError upon failure. @@ -131,6 +131,10 @@ be kept open when the file is closed. This does not work when a file name is given and must be True in that case. + If dirfd is specified, the file is opened relative to the given file + descriptor opened at a directory. This requires underlying support from + the operating system. + open() returns a file object whose type depends on the mode, and through which the standard file operations such as reading and writing are performed. When open() is used to open a file in a text mode ('w', @@ -185,7 +189,7 @@ (writing and "w" or "") + (appending and "a" or "") + (updating and "+" or ""), - closefd) + closefd, dirfd) line_buffering = False if buffering == 1 or buffering < 0 and raw.isatty(): buffering = -1 diff --git a/Lib/test/test_io.py b/Lib/test/test_io.py --- a/Lib/test/test_io.py +++ b/Lib/test/test_io.py @@ -553,6 +553,15 @@ file = self.open(f.fileno(), "r", closefd=False) self.assertEqual(file.buffer.raw.closefd, False) + @unittest.skipUnless(hasattr(os, 'openat'), "test needs os.openat()") + def test_dirfd(self): + with self.open(support.TESTFN, "w") as f: + f.write("egg\n") + dfd = os.open(os.getcwd(), os.O_RDONLY) + self.addCleanup(os.close, dfd) + with self.open(support.TESTFN, "r", dirfd=dfd) as f: + self.assertEqual(f.read(), "egg\n") + def test_garbage_collection(self): # FileIO objects are collected, and collecting them flushes # all data to disk. diff --git a/Modules/_io/_iomodule.c b/Modules/_io/_iomodule.c --- a/Modules/_io/_iomodule.c +++ b/Modules/_io/_iomodule.c @@ -293,10 +293,10 @@ { char *kwlist[] = {"file", "mode", "buffering", "encoding", "errors", "newline", - "closefd", NULL}; + "closefd", "dirfd", NULL}; PyObject *file; char *mode = "r"; - int buffering = -1, closefd = 1; + int buffering = -1, closefd = 1, dirfd = -1; char *encoding = NULL, *errors = NULL, *newline = NULL; unsigned i; @@ -308,10 +308,10 @@ PyObject *raw, *modeobj = NULL, *buffer = NULL, *wrapper = NULL; - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|sizzzi:open", kwlist, + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|sizzzii:open", kwlist, &file, &mode, &buffering, &encoding, &errors, &newline, - &closefd)) { + &closefd, &dirfd)) { return NULL; } @@ -411,7 +411,7 @@ /* Create the Raw file stream */ raw = PyObject_CallFunction((PyObject *)&PyFileIO_Type, - "Osi", file, rawmode, closefd); + "Osii", file, rawmode, closefd, dirfd); if (raw == NULL) return NULL; diff --git a/Modules/_io/fileio.c b/Modules/_io/fileio.c --- a/Modules/_io/fileio.c +++ b/Modules/_io/fileio.c @@ -217,7 +217,7 @@ fileio_init(PyObject *oself, PyObject *args, PyObject *kwds) { fileio *self = (fileio *) oself; - static char *kwlist[] = {"file", "mode", "closefd", NULL}; + static char *kwlist[] = {"file", "mode", "closefd", "dirfd", NULL}; const char *name = NULL; PyObject *nameobj, *stringobj = NULL; char *mode = "r"; @@ -230,6 +230,7 @@ int flags = 0; int fd = -1; int closefd = 1; + int dirfd = -1; assert(PyFileIO_Check(oself)); if (self->fd >= 0) { @@ -238,10 +239,18 @@ return -1; } - if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|si:fileio", - kwlist, &nameobj, &mode, &closefd)) + if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|sii:fileio", + kwlist, &nameobj, &mode, &closefd, &dirfd)) return -1; +#ifndef HAVE_OPENAT + if (dirfd != -1) { + PyErr_SetString(PyExc_NotImplementedError, + "using dirfd requires openat()"); + return -1; + } +#endif + if (PyFloat_Check(nameobj)) { PyErr_SetString(PyExc_TypeError, "integer argument expected, got float"); @@ -373,7 +382,16 @@ self->fd = _wopen(widename, flags, 0666); else #endif - self->fd = open(name, flags, 0666); +#ifdef HAVE_OPENAT + { + if (dirfd == -1) + self->fd = open(name, flags, 0666); + else + self->fd = openat(dirfd, name, flags, 0666); + } +#else + self->fd = open(name, flags, 0666); +#endif Py_END_ALLOW_THREADS if (self->fd < 0) { #ifdef MS_WINDOWS @@ -1021,13 +1039,15 @@ PyDoc_STRVAR(fileio_doc, -"file(name: str[, mode: str]) -> file IO object\n" +"file(name: str[, mode: str][, dirfd: int]) -> file IO object\n" "\n" "Open a file. The mode can be 'r', 'w' or 'a' for reading (default),\n" "writing or appending. The file will be created if it doesn't exist\n" "when opened for writing or appending; it will be truncated when\n" "opened for writing. Add a '+' to the mode to allow simultaneous\n" -"reading and writing."); +"reading and writing. If dirfd is specified, the file is opened relative\n" +"to the given file descriptor opened at a directory. This requires\n" +"underlying support from the operating system."); PyDoc_STRVAR(read_doc, "read(size: int) -> bytes. read at most size bytes, returned as bytes.\n"