Skip to content

Commit 7571f11

Browse files
committed
Make os.walk() and os.fwalk() raise NotADirectoryError for non-directory top
1 parent 57d4446 commit 7571f11

2 files changed

Lines changed: 33 additions & 2 deletions

File tree

Lib/os.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,14 @@ def walk(top, topdown=True, onerror=None, followlinks=False):
374374
"""
375375
sys.audit("os.walk", top, topdown, onerror, followlinks)
376376

377+
try:
378+
top_stat = stat(fspath(top))
379+
except OSError:
380+
pass # non-existing: fall through, scandir will handle it via onerror
381+
else:
382+
if not path.isdir(fspath(top)) and not path.islink(fspath(top)):
383+
raise NotADirectoryError(20, "Not a directory", top)
384+
377385
stack = [fspath(top)]
378386
islink, join = path.islink, path.join
379387
while stack:
@@ -540,11 +548,14 @@ def _fwalk(stack, isbytes, topdown, onerror, follow_symlinks):
540548
stack.append((_fwalk_close, topfd))
541549
if not follow_symlinks:
542550
if isroot and not st.S_ISDIR(orig_st.st_mode):
543-
return
551+
raise NotADirectoryError(20, "Not a directory", toppath)
544552
if not path.samestat(orig_st, stat(topfd)):
545553
return
546554

547-
scandir_it = scandir(topfd)
555+
try:
556+
scandir_it = scandir(topfd)
557+
except NotADirectoryError:
558+
raise NotADirectoryError(20, "Not a directory", toppath)
548559
dirs = []
549560
nondirs = []
550561
entries = None if topdown or follow_symlinks else []

Lib/test/test_os/test_os.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1981,6 +1981,13 @@ def test_walk_above_recursion_limit(self):
19811981
self.assertEqual(sorted(dirs), ["SUB1", "SUB2", "d"])
19821982
self.assertEqual(all, expected)
19831983

1984+
def test_walk_on_file_raises_not_a_directory(self):
1985+
# gh-101420: os.walk() was silently returning [] when top is a
1986+
# regular file instead of raising NotADirectoryError.
1987+
with tempfile.NamedTemporaryFile() as f:
1988+
with self.assertRaises(NotADirectoryError):
1989+
list(os.walk(f.name))
1990+
19841991

19851992
@unittest.skipUnless(hasattr(os, 'fwalk'), "Test needs os.fwalk()")
19861993
class FwalkTests(WalkTests):
@@ -2038,6 +2045,19 @@ def test_yields_correct_dir_fd(self):
20382045
# check that listdir() returns consistent information
20392046
self.assertEqual(set(os.listdir(rootfd)), set(dirs) | set(files))
20402047

2048+
def test_fwalk_on_file_raises_not_a_directory(self):
2049+
with tempfile.NamedTemporaryFile() as f:
2050+
with self.assertRaises(NotADirectoryError):
2051+
list(os.fwalk(f.name, follow_symlinks=False))
2052+
2053+
# follow_symlinks=True: raised but with fd int as filename, must now have path
2054+
with self.assertRaises(NotADirectoryError) as ctx:
2055+
list(os.fwalk(f.name, follow_symlinks=True))
2056+
self.assertEqual(
2057+
ctx.exception.filename, f.name,
2058+
"filename should be the path string, not a raw fd integer"
2059+
)
2060+
20412061
@unittest.skipIf(
20422062
support.is_android, "dup return value is unpredictable on Android"
20432063
)

0 commit comments

Comments
 (0)