https://github.com/python/cpython/commit/b84e0b7ce05f71de1dd017c899ba3a3d81fbdc0a
commit: b84e0b7ce05f71de1dd017c899ba3a3d81fbdc0a
branch: main
author: Jonathan J. Helmus <[email protected]>
committer: StanFromIreland <[email protected]>
date: 2026-10-31T09:40:30Z
summary:

gh-158998: Fix `os.statx()` ignoring `follow_symlinks=False` (#158999)

files:
A Misc/NEWS.d/next/Library/2026-10-08-21-02-38.gh-issue-158998.DZYJI7.rst
M Lib/test/test_os/test_os.py
M Modules/posixmodule.c

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 503b237ab704c01..b78bc1683538312 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -3673,6 +3673,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. */

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to