mirror of https://github.com/python/cpython.git
gh-116401: Fix blocking os.fwalk() and shutil.rmtree() on opening a named pipe (GH-116421)
This commit is contained in:
parent
8332e85b2f
commit
aa7bcf284f
|
@ -475,7 +475,7 @@ def fwalk(top=".", topdown=True, onerror=None, *, follow_symlinks=False, dir_fd=
|
||||||
# lstat()/open()/fstat() trick.
|
# lstat()/open()/fstat() trick.
|
||||||
if not follow_symlinks:
|
if not follow_symlinks:
|
||||||
orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd)
|
orig_st = stat(top, follow_symlinks=False, dir_fd=dir_fd)
|
||||||
topfd = open(top, O_RDONLY, dir_fd=dir_fd)
|
topfd = open(top, O_RDONLY | O_NONBLOCK, dir_fd=dir_fd)
|
||||||
try:
|
try:
|
||||||
if (follow_symlinks or (st.S_ISDIR(orig_st.st_mode) and
|
if (follow_symlinks or (st.S_ISDIR(orig_st.st_mode) and
|
||||||
path.samestat(orig_st, stat(topfd)))):
|
path.samestat(orig_st, stat(topfd)))):
|
||||||
|
@ -524,7 +524,7 @@ def _fwalk(topfd, toppath, isbytes, topdown, onerror, follow_symlinks):
|
||||||
assert entries is not None
|
assert entries is not None
|
||||||
name, entry = name
|
name, entry = name
|
||||||
orig_st = entry.stat(follow_symlinks=False)
|
orig_st = entry.stat(follow_symlinks=False)
|
||||||
dirfd = open(name, O_RDONLY, dir_fd=topfd)
|
dirfd = open(name, O_RDONLY | O_NONBLOCK, dir_fd=topfd)
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
if onerror is not None:
|
if onerror is not None:
|
||||||
onerror(err)
|
onerror(err)
|
||||||
|
|
|
@ -681,7 +681,7 @@ def _rmtree_safe_fd(topfd, path, onexc):
|
||||||
continue
|
continue
|
||||||
if is_dir:
|
if is_dir:
|
||||||
try:
|
try:
|
||||||
dirfd = os.open(entry.name, os.O_RDONLY, dir_fd=topfd)
|
dirfd = os.open(entry.name, os.O_RDONLY | os.O_NONBLOCK, dir_fd=topfd)
|
||||||
dirfd_closed = False
|
dirfd_closed = False
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
continue
|
continue
|
||||||
|
@ -786,7 +786,7 @@ def onexc(*args):
|
||||||
onexc(os.lstat, path, err)
|
onexc(os.lstat, path, err)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
fd = os.open(path, os.O_RDONLY, dir_fd=dir_fd)
|
fd = os.open(path, os.O_RDONLY | os.O_NONBLOCK, dir_fd=dir_fd)
|
||||||
fd_closed = False
|
fd_closed = False
|
||||||
except OSError as err:
|
except OSError as err:
|
||||||
onexc(os.open, path, err)
|
onexc(os.open, path, err)
|
||||||
|
|
|
@ -344,6 +344,18 @@ def test_glob_non_directory(self):
|
||||||
eq(self.rglob('nonexistent', '*'), [])
|
eq(self.rglob('nonexistent', '*'), [])
|
||||||
eq(self.rglob('nonexistent', '**'), [])
|
eq(self.rglob('nonexistent', '**'), [])
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()')
|
||||||
|
@unittest.skipIf(sys.platform == "vxworks",
|
||||||
|
"fifo requires special path on VxWorks")
|
||||||
|
def test_glob_named_pipe(self):
|
||||||
|
path = os.path.join(self.tempdir, 'mypipe')
|
||||||
|
os.mkfifo(path)
|
||||||
|
self.assertEqual(self.rglob('mypipe'), [path])
|
||||||
|
self.assertEqual(self.rglob('mypipe*'), [path])
|
||||||
|
self.assertEqual(self.rglob('mypipe', ''), [])
|
||||||
|
self.assertEqual(self.rglob('mypipe', 'sub'), [])
|
||||||
|
self.assertEqual(self.rglob('mypipe', '*'), [])
|
||||||
|
|
||||||
def test_glob_many_open_files(self):
|
def test_glob_many_open_files(self):
|
||||||
depth = 30
|
depth = 30
|
||||||
base = os.path.join(self.tempdir, 'deep')
|
base = os.path.join(self.tempdir, 'deep')
|
||||||
|
|
|
@ -1298,6 +1298,7 @@ def test_ror_operator(self):
|
||||||
|
|
||||||
class WalkTests(unittest.TestCase):
|
class WalkTests(unittest.TestCase):
|
||||||
"""Tests for os.walk()."""
|
"""Tests for os.walk()."""
|
||||||
|
is_fwalk = False
|
||||||
|
|
||||||
# Wrapper to hide minor differences between os.walk and os.fwalk
|
# Wrapper to hide minor differences between os.walk and os.fwalk
|
||||||
# to tests both functions with the same code base
|
# to tests both functions with the same code base
|
||||||
|
@ -1332,14 +1333,14 @@ def setUp(self):
|
||||||
self.sub11_path = join(self.sub1_path, "SUB11")
|
self.sub11_path = join(self.sub1_path, "SUB11")
|
||||||
sub2_path = join(self.walk_path, "SUB2")
|
sub2_path = join(self.walk_path, "SUB2")
|
||||||
sub21_path = join(sub2_path, "SUB21")
|
sub21_path = join(sub2_path, "SUB21")
|
||||||
tmp1_path = join(self.walk_path, "tmp1")
|
self.tmp1_path = join(self.walk_path, "tmp1")
|
||||||
tmp2_path = join(self.sub1_path, "tmp2")
|
tmp2_path = join(self.sub1_path, "tmp2")
|
||||||
tmp3_path = join(sub2_path, "tmp3")
|
tmp3_path = join(sub2_path, "tmp3")
|
||||||
tmp5_path = join(sub21_path, "tmp3")
|
tmp5_path = join(sub21_path, "tmp3")
|
||||||
self.link_path = join(sub2_path, "link")
|
self.link_path = join(sub2_path, "link")
|
||||||
t2_path = join(os_helper.TESTFN, "TEST2")
|
t2_path = join(os_helper.TESTFN, "TEST2")
|
||||||
tmp4_path = join(os_helper.TESTFN, "TEST2", "tmp4")
|
tmp4_path = join(os_helper.TESTFN, "TEST2", "tmp4")
|
||||||
broken_link_path = join(sub2_path, "broken_link")
|
self.broken_link_path = join(sub2_path, "broken_link")
|
||||||
broken_link2_path = join(sub2_path, "broken_link2")
|
broken_link2_path = join(sub2_path, "broken_link2")
|
||||||
broken_link3_path = join(sub2_path, "broken_link3")
|
broken_link3_path = join(sub2_path, "broken_link3")
|
||||||
|
|
||||||
|
@ -1349,13 +1350,13 @@ def setUp(self):
|
||||||
os.makedirs(sub21_path)
|
os.makedirs(sub21_path)
|
||||||
os.makedirs(t2_path)
|
os.makedirs(t2_path)
|
||||||
|
|
||||||
for path in tmp1_path, tmp2_path, tmp3_path, tmp4_path, tmp5_path:
|
for path in self.tmp1_path, tmp2_path, tmp3_path, tmp4_path, tmp5_path:
|
||||||
with open(path, "x", encoding='utf-8') as f:
|
with open(path, "x", encoding='utf-8') as f:
|
||||||
f.write("I'm " + path + " and proud of it. Blame test_os.\n")
|
f.write("I'm " + path + " and proud of it. Blame test_os.\n")
|
||||||
|
|
||||||
if os_helper.can_symlink():
|
if os_helper.can_symlink():
|
||||||
os.symlink(os.path.abspath(t2_path), self.link_path)
|
os.symlink(os.path.abspath(t2_path), self.link_path)
|
||||||
os.symlink('broken', broken_link_path, True)
|
os.symlink('broken', self.broken_link_path, True)
|
||||||
os.symlink(join('tmp3', 'broken'), broken_link2_path, True)
|
os.symlink(join('tmp3', 'broken'), broken_link2_path, True)
|
||||||
os.symlink(join('SUB21', 'tmp5'), broken_link3_path, True)
|
os.symlink(join('SUB21', 'tmp5'), broken_link3_path, True)
|
||||||
self.sub2_tree = (sub2_path, ["SUB21", "link"],
|
self.sub2_tree = (sub2_path, ["SUB21", "link"],
|
||||||
|
@ -1451,6 +1452,11 @@ def test_walk_symlink(self):
|
||||||
else:
|
else:
|
||||||
self.fail("Didn't follow symlink with followlinks=True")
|
self.fail("Didn't follow symlink with followlinks=True")
|
||||||
|
|
||||||
|
walk_it = self.walk(self.broken_link_path, follow_symlinks=True)
|
||||||
|
if self.is_fwalk:
|
||||||
|
self.assertRaises(FileNotFoundError, next, walk_it)
|
||||||
|
self.assertRaises(StopIteration, next, walk_it)
|
||||||
|
|
||||||
def test_walk_bad_dir(self):
|
def test_walk_bad_dir(self):
|
||||||
# Walk top-down.
|
# Walk top-down.
|
||||||
errors = []
|
errors = []
|
||||||
|
@ -1472,6 +1478,73 @@ def test_walk_bad_dir(self):
|
||||||
finally:
|
finally:
|
||||||
os.rename(path1new, path1)
|
os.rename(path1new, path1)
|
||||||
|
|
||||||
|
def test_walk_bad_dir2(self):
|
||||||
|
walk_it = self.walk('nonexisting')
|
||||||
|
if self.is_fwalk:
|
||||||
|
self.assertRaises(FileNotFoundError, next, walk_it)
|
||||||
|
self.assertRaises(StopIteration, next, walk_it)
|
||||||
|
|
||||||
|
walk_it = self.walk('nonexisting', follow_symlinks=True)
|
||||||
|
if self.is_fwalk:
|
||||||
|
self.assertRaises(FileNotFoundError, next, walk_it)
|
||||||
|
self.assertRaises(StopIteration, next, walk_it)
|
||||||
|
|
||||||
|
walk_it = self.walk(self.tmp1_path)
|
||||||
|
self.assertRaises(StopIteration, next, walk_it)
|
||||||
|
|
||||||
|
walk_it = self.walk(self.tmp1_path, follow_symlinks=True)
|
||||||
|
if self.is_fwalk:
|
||||||
|
self.assertRaises(NotADirectoryError, next, walk_it)
|
||||||
|
self.assertRaises(StopIteration, next, walk_it)
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()')
|
||||||
|
@unittest.skipIf(sys.platform == "vxworks",
|
||||||
|
"fifo requires special path on VxWorks")
|
||||||
|
def test_walk_named_pipe(self):
|
||||||
|
path = os_helper.TESTFN + '-pipe'
|
||||||
|
os.mkfifo(path)
|
||||||
|
self.addCleanup(os.unlink, path)
|
||||||
|
|
||||||
|
walk_it = self.walk(path)
|
||||||
|
self.assertRaises(StopIteration, next, walk_it)
|
||||||
|
|
||||||
|
walk_it = self.walk(path, follow_symlinks=True)
|
||||||
|
if self.is_fwalk:
|
||||||
|
self.assertRaises(NotADirectoryError, next, walk_it)
|
||||||
|
self.assertRaises(StopIteration, next, walk_it)
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()')
|
||||||
|
@unittest.skipIf(sys.platform == "vxworks",
|
||||||
|
"fifo requires special path on VxWorks")
|
||||||
|
def test_walk_named_pipe2(self):
|
||||||
|
path = os_helper.TESTFN + '-dir'
|
||||||
|
os.mkdir(path)
|
||||||
|
self.addCleanup(shutil.rmtree, path)
|
||||||
|
os.mkfifo(os.path.join(path, 'mypipe'))
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
walk_it = self.walk(path, onerror=errors.append)
|
||||||
|
next(walk_it)
|
||||||
|
self.assertRaises(StopIteration, next, walk_it)
|
||||||
|
self.assertEqual(errors, [])
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
walk_it = self.walk(path, onerror=errors.append)
|
||||||
|
root, dirs, files = next(walk_it)
|
||||||
|
self.assertEqual(root, path)
|
||||||
|
self.assertEqual(dirs, [])
|
||||||
|
self.assertEqual(files, ['mypipe'])
|
||||||
|
dirs.extend(files)
|
||||||
|
files.clear()
|
||||||
|
if self.is_fwalk:
|
||||||
|
self.assertRaises(NotADirectoryError, next, walk_it)
|
||||||
|
self.assertRaises(StopIteration, next, walk_it)
|
||||||
|
if self.is_fwalk:
|
||||||
|
self.assertEqual(errors, [])
|
||||||
|
else:
|
||||||
|
self.assertEqual(len(errors), 1, errors)
|
||||||
|
self.assertIsInstance(errors[0], NotADirectoryError)
|
||||||
|
|
||||||
def test_walk_many_open_files(self):
|
def test_walk_many_open_files(self):
|
||||||
depth = 30
|
depth = 30
|
||||||
base = os.path.join(os_helper.TESTFN, 'deep')
|
base = os.path.join(os_helper.TESTFN, 'deep')
|
||||||
|
@ -1537,6 +1610,7 @@ def test_walk_above_recursion_limit(self):
|
||||||
@unittest.skipUnless(hasattr(os, 'fwalk'), "Test needs os.fwalk()")
|
@unittest.skipUnless(hasattr(os, 'fwalk'), "Test needs os.fwalk()")
|
||||||
class FwalkTests(WalkTests):
|
class FwalkTests(WalkTests):
|
||||||
"""Tests for os.fwalk()."""
|
"""Tests for os.fwalk()."""
|
||||||
|
is_fwalk = True
|
||||||
|
|
||||||
def walk(self, top, **kwargs):
|
def walk(self, top, **kwargs):
|
||||||
for root, dirs, files, root_fd in self.fwalk(top, **kwargs):
|
for root, dirs, files, root_fd in self.fwalk(top, **kwargs):
|
||||||
|
|
|
@ -667,6 +667,23 @@ def test_rmtree_on_junction(self):
|
||||||
finally:
|
finally:
|
||||||
shutil.rmtree(TESTFN, ignore_errors=True)
|
shutil.rmtree(TESTFN, ignore_errors=True)
|
||||||
|
|
||||||
|
@unittest.skipUnless(hasattr(os, "mkfifo"), 'requires os.mkfifo()')
|
||||||
|
@unittest.skipIf(sys.platform == "vxworks",
|
||||||
|
"fifo requires special path on VxWorks")
|
||||||
|
def test_rmtree_on_named_pipe(self):
|
||||||
|
os.mkfifo(TESTFN)
|
||||||
|
try:
|
||||||
|
with self.assertRaises(NotADirectoryError):
|
||||||
|
shutil.rmtree(TESTFN)
|
||||||
|
self.assertTrue(os.path.exists(TESTFN))
|
||||||
|
finally:
|
||||||
|
os.unlink(TESTFN)
|
||||||
|
|
||||||
|
os.mkdir(TESTFN)
|
||||||
|
os.mkfifo(os.path.join(TESTFN, 'mypipe'))
|
||||||
|
shutil.rmtree(TESTFN)
|
||||||
|
self.assertFalse(os.path.exists(TESTFN))
|
||||||
|
|
||||||
@unittest.skipIf(sys.platform[:6] == 'cygwin',
|
@unittest.skipIf(sys.platform[:6] == 'cygwin',
|
||||||
"This test can't be run on Cygwin (issue #1071513).")
|
"This test can't be run on Cygwin (issue #1071513).")
|
||||||
@os_helper.skip_if_dac_override
|
@os_helper.skip_if_dac_override
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
Fix blocking :func:`os.fwalk` and :func:`shutil.rmtree` on opening named
|
||||||
|
pipe.
|
Loading…
Reference in New Issue