diff --git a/Lib/test/test_os/test_os.py b/Lib/test/test_os/test_os.py index 81b3043eb7e75bc..79aa9e96dc91a6b 100644 --- a/Lib/test/test_os/test_os.py +++ b/Lib/test/test_os/test_os.py @@ -752,13 +752,13 @@ def test_stat_result_pickle(self): unpickled = pickle.loads(p) self.assertEqual(result, unpickled) - def check_statx_attributes(self, filename): + def check_statx_attributes(self, filename, follow_symlinks=True): maximal_mask = 0 for name in dir(os): if name.startswith('STATX_'): maximal_mask |= getattr(os, name) - result = os.statx(filename, maximal_mask) - stat_result = os.stat(filename) + result = os.statx(filename, maximal_mask, follow_symlinks=follow_symlinks) + stat_result = os.stat(filename, follow_symlinks=follow_symlinks) time_attributes = ('stx_atime', 'stx_btime', 'stx_ctime', 'stx_mtime') # gh-83714: stx_btime can be None on tmpfs even if STATX_BTIME mask @@ -873,6 +873,28 @@ def getmask(name): def test_statx_attributes(self): self.check_statx_attributes(self.fname) + @unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()') + @os_helper.skip_unless_symlink + def test_statx_attributes_symlink(self): + link = self.fname + '-link' + os.symlink(os.path.basename(self.fname), link) + self.addCleanup(os_helper.unlink, link) + + with self.subTest(broken=False, follow_symlinks=True): + self.check_statx_attributes(link, follow_symlinks=True) + + with self.subTest(broken=False, follow_symlinks=False): + self.check_statx_attributes(link, follow_symlinks=False) + + # break the symlink + os.unlink(self.fname) + with self.subTest(broken=True, follow_symlinks=True): + with self.assertRaises(FileNotFoundError): + os.statx(link, os.STATX_BASIC_STATS, follow_symlinks=True) + + with self.subTest(broken=True, follow_symlinks=False): + self.check_statx_attributes(link, follow_symlinks=False) + @unittest.skipUnless(hasattr(os, 'statx'), 'test needs os.statx()') def test_statx_attributes_bytes(self): try: diff --git a/Misc/NEWS.d/next/Library/2026-10-08-21-02-38.gh-issue-158998.DZYJI7.rst b/Misc/NEWS.d/next/Library/2026-10-08-21-02-38.gh-issue-158998.DZYJI7.rst new file mode 100644 index 000000000000000..65ce9201fb01352 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-08-21-02-38.gh-issue-158998.DZYJI7.rst @@ -0,0 +1 @@ +Fix :func:`os.statx` ignoring the *follow_symlinks* argument. diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index 028270e7b841fda..7879599491faee0 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -3669,6 +3669,9 @@ os_statx_impl(PyObject *module, path_t *path, unsigned int mask, int flags, "use dir_fd kwarg instead of AT_EMPTY_PATH flag"); return NULL; } + if (!follow_symlinks) { + flags |= AT_SYMLINK_NOFOLLOW; + } /* Future bits may refer to members beyond the current size of struct statx, so we need to mask them off to prevent memory corruption. */