Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Doc/library/os.rst
Original file line numberDiff line numberDiff line change
Expand Up@@ -2409,6 +2409,10 @@ features:
.. versionchanged:: 3.6
Accepts a :term:`path-like object`.

.. versionchanged:: next
``os.listdir(-1)`` now fails with ``OSError(errno.EBADF)`` rather than
listing the current directory.


.. function:: listdrives()

Expand DownExpand Up@@ -2939,6 +2943,10 @@ features:
.. versionchanged:: 3.7
Added support for :ref:`file descriptors <path_fd>` on Unix.

.. versionchanged:: next
``os.scandir(-1)`` now fails with ``OSError(errno.EBADF)`` rather than
listing the current directory.


.. class:: DirEntry

Expand DownExpand Up@@ -4574,6 +4582,10 @@ These functions are all available on Linux only.
.. versionchanged:: 3.6
Accepts a :term:`path-like object`.

.. versionchanged:: next
``os.listxattr(-1)`` now fails with ``OSError(errno.EBADF)`` rather than
listing extended attributes of the current directory.


.. function:: removexattr(path, attribute, *, follow_symlinks=True)

Expand Down
51 changes: 51 additions & 0 deletions Lib/test/test_os/test_os.py
Original file line numberDiff line numberDiff line change
Expand Up@@ -2784,10 +2784,61 @@ def test_fpathconf_bad_fd(self):
'musl fpathconf ignores the file descriptor and returns a constant',
)
def test_pathconf_negative_fd_uses_fd_semantics(self):
if os.pathconf not in os.supports_fd:
self.skipTest('needs fpathconf()')

with self.assertRaises(OSError) as ctx:
os.pathconf(-1, 1)
self.assertEqual(ctx.exception.errno, errno.EBADF)

@support.subTests("fd", [-1, -5])
def test_negative_fd_ebadf(self, fd):
Comment thread
picnixz marked this conversation as resolved.
tests = [(os.stat, fd)]
if hasattr(os, "statx"):
tests.append((os.statx, fd, 0))
if os.chdir in os.supports_fd:
tests.append((os.chdir, fd))
if os.chmod in os.supports_fd:
tests.append((os.chmod, fd, 0o777))
if hasattr(os, "chown") and os.chown in os.supports_fd:
tests.append((os.chown, fd, 0, 0))
if os.listdir in os.supports_fd:
tests.append((os.listdir, fd))
if os.utime in os.supports_fd:
tests.append((os.utime, fd, (0, 0)))
if hasattr(os, "truncate") and os.truncate in os.supports_fd:
tests.append((os.truncate, fd, 0))
if hasattr(os, 'statvfs') and os.statvfs in os.supports_fd:
tests.append((os.statvfs, fd))
if hasattr(os, "setxattr"):
tests.append((os.getxattr, fd, b"user.test"))
tests.append((os.setxattr, fd, b"user.test", b"1"))
tests.append((os.removexattr, fd, b"user.test"))
tests.append((os.listxattr, fd))
if os.scandir in os.supports_fd:
tests.append((os.scandir, fd))

for func, *args in tests:
with self.subTest(func=func, args=args):
with self.assertRaises(OSError) as ctx:
func(*args)
self.assertEqual(ctx.exception.errno, errno.EBADF)

if hasattr(os, "execve") and os.execve in os.supports_fd:
# glibc fails with EINVAL, musl fails with EBADF
with self.assertRaises(OSError) as ctx:
os.execve(fd, [sys.executable, "-c", "pass"], os.environ)
self.assertIn(ctx.exception.errno, (errno.EBADF, errno.EINVAL))

if support.MS_WINDOWS:
import nt
self.assertFalse(nt._path_exists(fd))
self.assertFalse(nt._path_lexists(fd))
self.assertFalse(nt._path_isdir(fd))
self.assertFalse(nt._path_isfile(fd))
self.assertFalse(nt._path_islink(fd))
self.assertFalse(nt._path_isjunction(fd))

@unittest.skipUnless(hasattr(os, 'ftruncate'), 'test needs os.ftruncate()')
def test_ftruncate(self):
self.check(os.truncate, 0)
Expand Down
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
``os.listdir(-1)`` and ``os.scandir(-1)`` now fail with
``OSError(errno.EBADF)`` rather than listing the current directory.
``os.listxattr(-1)`` now fails with ``OSError(errno.EBADF)`` rather than
listing extended attributes of the current directory. Patch by Victor
Stinner.
Comment thread
vstinner marked this conversation as resolved.
67 changes: 35 additions & 32 deletions Modules/posixmodule.c
Original file line numberDiff line numberDiff line change
Expand Up@@ -1638,10 +1638,10 @@ dir_fd_and_fd_invalid(const char *function_name, int dir_fd, int fd)
}

static int
fd_and_follow_symlinks_invalid(const char *function_name, int fd,
fd_and_follow_symlinks_invalid(const char *function_name, int is_fd,
int follow_symlinks)
{
if ((fd >= 0) && (!follow_symlinks)) {
if (is_fd && (!follow_symlinks)) {
PyErr_Format(PyExc_ValueError,
"%s: cannot use fd and follow_symlinks together",
function_name);
Expand DownExpand Up@@ -2880,12 +2880,13 @@ posix_do_stat(PyObject *module, const char *function_name, path_t *path,

if (path_and_dir_fd_invalid("stat", path, dir_fd) ||
dir_fd_and_fd_invalid("stat", dir_fd, path->fd) ||
fd_and_follow_symlinks_invalid("stat", path->fd, follow_symlinks))
fd_and_follow_symlinks_invalid("stat", path->is_fd, follow_symlinks))
return NULL;

Py_BEGIN_ALLOW_THREADS
if (path->fd != -1)
if (path->is_fd) {
result = FSTAT(path->fd, &st);
}
#ifdef MS_WINDOWS
else if (follow_symlinks)
result = win32_stat(path->wide, &st);
Expand DownExpand Up@@ -3647,7 +3648,7 @@ os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int flags,
{
if (path_and_dir_fd_invalid("statx", path, dir_fd) ||
dir_fd_and_fd_invalid("statx", dir_fd, path->fd) ||
fd_and_follow_symlinks_invalid("statx", path->fd, follow_symlinks)) {
fd_and_follow_symlinks_invalid("statx", path->is_fd, follow_symlinks)) {
return NULL;
}

Expand DownExpand Up@@ -3677,7 +3678,7 @@ os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int flags,

int result;
Py_BEGIN_ALLOW_THREADS
if (path->fd != -1) {
if (path->is_fd) {
result = statx(path->fd, "", flags | AT_EMPTY_PATH, mask, &v->stx);
}
else {
Expand DownExpand Up@@ -3934,7 +3935,7 @@ os_chdir_impl(PyObject *module, path_t *path)
result = !win32_wchdir(path->wide);
#else
#ifdef HAVE_FCHDIR
if (path->fd != -1)
if (path->is_fd)
result = fchdir(path->fd);
else
#endif
Expand DownExpand Up@@ -4090,7 +4091,7 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd,
#ifdef MS_WINDOWS
result = 0;
Py_BEGIN_ALLOW_THREADS
if (path->fd != -1) {
if (path->is_fd) {
result = win32_fchmod(path->fd, mode);
}
else if (follow_symlinks) {
Expand All@@ -4113,8 +4114,9 @@ os_chmod_impl(PyObject *module, path_t *path, int mode, int dir_fd,
#else /* MS_WINDOWS */
Py_BEGIN_ALLOW_THREADS
#ifdef HAVE_FCHMOD
if (path->fd != -1)
if (path->is_fd) {
result = fchmod(path->fd, mode);
}
else
#endif /* HAVE_CHMOD */
#ifdef HAVE_LCHMOD
Expand DownExpand Up@@ -4511,7 +4513,7 @@ os_chown_impl(PyObject *module, path_t *path, uid_t uid, gid_t gid,
return NULL;
#endif
if (dir_fd_and_fd_invalid("chown", dir_fd, path->fd) ||
fd_and_follow_symlinks_invalid("chown", path->fd, follow_symlinks))
fd_and_follow_symlinks_invalid("chown", path->is_fd, follow_symlinks))
return NULL;

if (PySys_Audit("os.chown", "OIIi", path->object, uid, gid,
Expand All@@ -4521,7 +4523,7 @@ os_chown_impl(PyObject *module, path_t *path, uid_t uid, gid_t gid,

Py_BEGIN_ALLOW_THREADS
#ifdef HAVE_FCHOWN
if (path->fd != -1)
if (path->is_fd)
result = fchown(path->fd, uid, gid);
else
#endif
Expand DownExpand Up@@ -4999,7 +5001,7 @@ _posix_listdir(path_t *path, PyObject *list)

errno = 0;
#ifdef HAVE_FDOPENDIR
if (path->fd != -1) {
if (path->is_fd) {
if (HAVE_FDOPENDIR_RUNTIME) {
/* closedir() closes the FD, so we duplicate it */
fd = _Py_dup(path->fd);
Expand DownExpand Up@@ -5898,7 +5900,7 @@ _testFileExists(path_t *path, BOOL followLinks)
}

Py_BEGIN_ALLOW_THREADS
if (path->fd != -1) {
if (path->is_fd) {
HANDLE hfile = _Py_get_osfhandle_noraise(path->fd);
if (hfile != INVALID_HANDLE_VALUE) {
if (GetFileType(hfile) != FILE_TYPE_UNKNOWN || !GetLastError()) {
Expand All@@ -5924,7 +5926,7 @@ _testFileType(path_t *path, int testedType)
}

Py_BEGIN_ALLOW_THREADS
if (path->fd != -1) {
if (path->is_fd) {
HANDLE hfile = _Py_get_osfhandle_noraise(path->fd);
if (hfile != INVALID_HANDLE_VALUE) {
result = _testFileTypeByHandle(hfile, testedType, TRUE);
Expand DownExpand Up@@ -7141,7 +7143,7 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns,

if (path_and_dir_fd_invalid("utime", path, dir_fd) ||
dir_fd_and_fd_invalid("utime", dir_fd, path->fd) ||
fd_and_follow_symlinks_invalid("utime", path->fd, follow_symlinks))
fd_and_follow_symlinks_invalid("utime", path->is_fd, follow_symlinks))
return NULL;

#if !defined(HAVE_UTIMENSAT)
Expand DownExpand Up@@ -7200,7 +7202,7 @@ os_utime_impl(PyObject *module, path_t *path, PyObject *times, PyObject *ns,
#endif

#if defined(HAVE_FUTIMES) || defined(HAVE_FUTIMENS)
if (path->fd != -1)
if (path->is_fd)
result = utime_fd(&utime, path->fd);
else
#endif
Expand DownExpand Up@@ -7569,7 +7571,7 @@ os_execve_impl(PyObject *module, path_t *path, PyObject *argv, PyObject *env)

_Py_BEGIN_SUPPRESS_IPH
#ifdef HAVE_FEXECVE
if (path->fd > -1)
if (path->is_fd)
fexecve(path->fd, argvlist, envlist);
else
#endif
Expand DownExpand Up@@ -13355,7 +13357,7 @@ os_truncate_impl(PyObject *module, path_t *path, Py_off_t length)
int fd;
#endif

if (path->fd != -1)
if (path->is_fd)
return os_ftruncate_impl(module, path->fd, length);

if (PySys_Audit("os.truncate", "On", path->object, length) < 0) {
Expand DownExpand Up@@ -14052,7 +14054,7 @@ os_statvfs_impl(PyObject *module, path_t *path)
struct statfs st;

Py_BEGIN_ALLOW_THREADS
if (path->fd != -1) {
if (path->is_fd) {
result = fstatfs(path->fd, &st);
}
else
Expand All@@ -14070,7 +14072,7 @@ os_statvfs_impl(PyObject *module, path_t *path)

Py_BEGIN_ALLOW_THREADS
#ifdef HAVE_FSTATVFS
if (path->fd != -1) {
if (path->is_fd) {
result = fstatvfs(path->fd, &st);
}
else
Expand DownExpand Up@@ -15410,7 +15412,7 @@ os_getxattr_impl(PyObject *module, path_t *path, path_t *attribute,
int follow_symlinks)
/*[clinic end generated code: output=5f2f44200a43cff2 input=025789491708f7eb]*/
{
if (fd_and_follow_symlinks_invalid("getxattr", path->fd, follow_symlinks))
if (fd_and_follow_symlinks_invalid("getxattr", path->is_fd, follow_symlinks))
return NULL;

if (PySys_Audit("os.getxattr", "OO", path->object, attribute->object) < 0) {
Expand All@@ -15432,7 +15434,7 @@ os_getxattr_impl(PyObject *module, path_t *path, path_t *attribute,
void *ptr = PyBytesWriter_GetData(writer);

Py_BEGIN_ALLOW_THREADS;
if (path->fd >= 0)
if (path->is_fd)
result = fgetxattr(path->fd, attribute->narrow, ptr, buffer_size);
else if (follow_symlinks)
result = getxattr(path->narrow, attribute->narrow, ptr, buffer_size);
Expand DownExpand Up@@ -15481,7 +15483,7 @@ os_setxattr_impl(PyObject *module, path_t *path, path_t *attribute,
{
ssize_t result;

if (fd_and_follow_symlinks_invalid("setxattr", path->fd, follow_symlinks))
if (fd_and_follow_symlinks_invalid("setxattr", path->is_fd, follow_symlinks))
return NULL;

if (PySys_Audit("os.setxattr", "OOy#i", path->object, attribute->object,
Expand All@@ -15490,7 +15492,7 @@ os_setxattr_impl(PyObject *module, path_t *path, path_t *attribute,
}

Py_BEGIN_ALLOW_THREADS;
if (path->fd > -1)
if (path->is_fd)
result = fsetxattr(path->fd, attribute->narrow,
value->buf, value->len, flags);
else if (follow_symlinks)
Expand DownExpand Up@@ -15534,15 +15536,15 @@ os_removexattr_impl(PyObject *module, path_t *path, path_t *attribute,
{
ssize_t result;

if (fd_and_follow_symlinks_invalid("removexattr", path->fd, follow_symlinks))
if (fd_and_follow_symlinks_invalid("removexattr", path->is_fd, follow_symlinks))
return NULL;

if (PySys_Audit("os.removexattr", "OO", path->object, attribute->object) < 0) {
return NULL;
}

Py_BEGIN_ALLOW_THREADS;
if (path->fd > -1)
if (path->is_fd)
result = fremovexattr(path->fd, attribute->narrow);
else if (follow_symlinks)
result = removexattr(path->narrow, attribute->narrow);
Expand DownExpand Up@@ -15584,7 +15586,7 @@ os_listxattr_impl(PyObject *module, path_t *path, int follow_symlinks)
const char *name;
char *buffer = NULL;

if (fd_and_follow_symlinks_invalid("listxattr", path->fd, follow_symlinks))
if (fd_and_follow_symlinks_invalid("listxattr", path->is_fd, follow_symlinks))
goto exit;

if (PySys_Audit("os.listxattr", "(O)",
Expand All@@ -15611,7 +15613,7 @@ os_listxattr_impl(PyObject *module, path_t *path, int follow_symlinks)
}

Py_BEGIN_ALLOW_THREADS;
if (path->fd > -1)
if (path->is_fd)
length = flistxattr(path->fd, buffer, buffer_size);
else if (follow_symlinks)
length = listxattr(name, buffer, buffer_size);
Expand DownExpand Up@@ -16664,7 +16666,7 @@ DirEntry_from_posix_info(PyObject *module, path_t *path, const char *name,
entry->stat = NULL;
entry->lstat = NULL;

if (path->fd != -1) {
if (path->is_fd) {
entry->dir_fd = path->fd;
joined_path = NULL;
}
Expand All@@ -16689,7 +16691,7 @@ DirEntry_from_posix_info(PyObject *module, path_t *path, const char *name,
if (!entry->name)
goto error;

if (path->fd != -1) {
if (path->is_fd) {
entry->path = Py_NewRef(entry->name);
}
else if (!entry->path)
Expand DownExpand Up@@ -16813,8 +16815,9 @@ ScandirIterator_closedir(ScandirIterator *iterator)
iterator->dirp = NULL;
Py_BEGIN_ALLOW_THREADS
#ifdef HAVE_FDOPENDIR
if (iterator->path.fd != -1)
if (iterator->path.is_fd) {
rewinddir(dirp);
}
#endif
closedir(dirp);
Py_END_ALLOW_THREADS
Expand DownExpand Up@@ -17034,7 +17037,7 @@ os_scandir_impl(PyObject *module, path_t *path)
#else /* POSIX */
errno = 0;
#ifdef HAVE_FDOPENDIR
if (iterator->path.fd != -1) {
if (iterator->path.is_fd) {
if (HAVE_FDOPENDIR_RUNTIME) {
/* closedir() closes the FD, so we duplicate it */
fd = _Py_dup(iterator->path.fd);
Expand Down
Loading