Skip to content
Open
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
28 changes: 25 additions & 3 deletions Lib/test/test_os/test_os.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check_statx_attributes only accidentally checks that os.statx raises in this case (because os.statx is called before os.stat). I think it's sufficient and clearer to call os.statx directly here.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean: replace self.check_statx_attributes -> os.statx on the line just after the lines shown in the comment. (Sorry, not used to GitHub's review/comment interface.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed that calling statx here is clearer. I've updated the test.

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:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix :func:`os.statx` ignoring the *follow_symlinks* argument.
3 changes: 3 additions & 0 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
Loading