Skip to content
Draft
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
66 changes: 64 additions & 2 deletions Lib/test/test_os/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -2778,15 +2778,77 @@ def test_fpathconf_bad_fd(self):
self.check(os.pathconf, "PC_NAME_MAX")
self.check(os.fpathconf, "PC_NAME_MAX")

@contextlib.contextmanager
def check_for_ebadf(self, errnos=(errno.EBADF,)):
with self.assertRaises(OSError) as ctx:
yield
self.assertIn(ctx.exception.errno, errnos)

@unittest.skipUnless(hasattr(os, 'pathconf'), 'test needs os.pathconf()')
@unittest.skipIf(
support.linked_to_musl(),
'musl fpathconf ignores the file descriptor and returns a constant',
)
def test_pathconf_negative_fd_uses_fd_semantics(self):
with self.assertRaises(OSError) as ctx:
if os.pathconf not in os.supports_fd:
self.skipTest('needs fpathconf()')

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

@support.subTests("fd", [-1, -5])
def test_negative_fd_ebadf(self, fd):
with self.check_for_ebadf():
os.stat(fd)
if hasattr(os, "statx"):
with self.check_for_ebadf():
os.statx(fd, mask=0)
if os.chdir in os.supports_fd:
with self.check_for_ebadf():
os.chdir(fd)
if os.chmod in os.supports_fd:
with self.check_for_ebadf():
os.chmod(fd, 0o777)
if hasattr(os, "chown") and os.chown in os.supports_fd:
with self.check_for_ebadf():
os.chown(fd, 0, 0)
if os.listdir in os.supports_fd:
with self.check_for_ebadf():
os.listdir(fd)
if os.utime in os.supports_fd:
with self.check_for_ebadf():
os.utime(fd, (0, 0))
if hasattr(os, "execve") and os.execve in os.supports_fd:
# glibc fails with EINVAL, musl fails with EBADF
with self.check_for_ebadf(errnos=(errno.EBADF, errno.EINVAL)):
os.execve(fd, [sys.executable, "-c", "pass"], os.environ)
if hasattr(os, "truncate") and os.truncate in os.supports_fd:
with self.check_for_ebadf():
os.truncate(fd, 0)
if hasattr(os, 'statvfs') and os.statvfs in os.supports_fd:
with self.check_for_ebadf():
os.statvfs(fd)
if hasattr(os, "setxattr"):
with self.check_for_ebadf():
os.getxattr(fd, b"user.test")
with self.check_for_ebadf():
os.setxattr(fd, b"user.test", b"1")
with self.check_for_ebadf():
os.removexattr(fd, b"user.test")
with self.check_for_ebadf():
os.listxattr(fd)
if os.scandir in os.supports_fd:
with self.check_for_ebadf():
os.scandir(fd)

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):
Expand Down
Original file line number Diff line number Diff 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.
67 changes: 35 additions & 32 deletions Modules/posixmodule.c
Original file line number Diff line number Diff 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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 Down Expand 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