#4489: Don't follow ever symlinks in rmtree
Also added several regression tests.
This commit is contained in:
parent
591c1cca32
commit
a75cd1ce73
|
@ -380,7 +380,7 @@ def _rmtree_safe_fd(topfd, path, onerror):
|
||||||
for name in names:
|
for name in names:
|
||||||
fullname = os.path.join(path, name)
|
fullname = os.path.join(path, name)
|
||||||
try:
|
try:
|
||||||
orig_st = os.stat(name, dir_fd=topfd)
|
orig_st = os.stat(name, dir_fd=topfd, follow_symlinks=False)
|
||||||
mode = orig_st.st_mode
|
mode = orig_st.st_mode
|
||||||
except os.error:
|
except os.error:
|
||||||
mode = 0
|
mode = 0
|
||||||
|
@ -445,7 +445,7 @@ def rmtree(path, ignore_errors=False, onerror=None):
|
||||||
if (stat.S_ISDIR(orig_st.st_mode) and
|
if (stat.S_ISDIR(orig_st.st_mode) and
|
||||||
os.path.samestat(orig_st, os.fstat(fd))):
|
os.path.samestat(orig_st, os.fstat(fd))):
|
||||||
_rmtree_safe_fd(fd, path, onerror)
|
_rmtree_safe_fd(fd, path, onerror)
|
||||||
elif (stat.S_ISREG(orig_st.st_mode)):
|
else:
|
||||||
raise NotADirectoryError(20,
|
raise NotADirectoryError(20,
|
||||||
"Not a directory: '{}'".format(path))
|
"Not a directory: '{}'".format(path))
|
||||||
finally:
|
finally:
|
||||||
|
|
|
@ -117,6 +117,38 @@ class TestShutil(unittest.TestCase):
|
||||||
self.assertIsInstance(victim, bytes)
|
self.assertIsInstance(victim, bytes)
|
||||||
shutil.rmtree(victim)
|
shutil.rmtree(victim)
|
||||||
|
|
||||||
|
@support.skip_unless_symlink
|
||||||
|
def test_rmtree_fails_on_symlink(self):
|
||||||
|
tmp = self.mkdtemp()
|
||||||
|
dir_ = os.path.join(tmp, 'dir')
|
||||||
|
os.mkdir(dir_)
|
||||||
|
link = os.path.join(tmp, 'link')
|
||||||
|
os.symlink(dir_, link)
|
||||||
|
self.assertRaises(OSError, shutil.rmtree, link)
|
||||||
|
self.assertTrue(os.path.exists(dir_))
|
||||||
|
|
||||||
|
@support.skip_unless_symlink
|
||||||
|
def test_rmtree_works_on_symlinks(self):
|
||||||
|
tmp = self.mkdtemp()
|
||||||
|
dir1 = os.path.join(tmp, 'dir1')
|
||||||
|
dir2 = os.path.join(dir1, 'dir2')
|
||||||
|
dir3 = os.path.join(tmp, 'dir3')
|
||||||
|
for d in dir1, dir2, dir3:
|
||||||
|
os.mkdir(d)
|
||||||
|
file1 = os.path.join(tmp, 'file1')
|
||||||
|
write_file(file1, 'foo')
|
||||||
|
link1 = os.path.join(dir1, 'link1')
|
||||||
|
os.symlink(dir2, link1)
|
||||||
|
link2 = os.path.join(dir1, 'link2')
|
||||||
|
os.symlink(dir3, link2)
|
||||||
|
link3 = os.path.join(dir1, 'link3')
|
||||||
|
os.symlink(file1, link3)
|
||||||
|
# make sure symlinks are removed but not followed
|
||||||
|
shutil.rmtree(dir1)
|
||||||
|
self.assertFalse(os.path.exists(dir1))
|
||||||
|
self.assertTrue(os.path.exists(dir3))
|
||||||
|
self.assertTrue(os.path.exists(file1))
|
||||||
|
|
||||||
def test_rmtree_errors(self):
|
def test_rmtree_errors(self):
|
||||||
# filename is guaranteed not to exist
|
# filename is guaranteed not to exist
|
||||||
filename = tempfile.mktemp()
|
filename = tempfile.mktemp()
|
||||||
|
@ -184,7 +216,7 @@ class TestShutil(unittest.TestCase):
|
||||||
def test_rmtree_does_not_choke_on_failing_lstat(self):
|
def test_rmtree_does_not_choke_on_failing_lstat(self):
|
||||||
try:
|
try:
|
||||||
orig_lstat = os.lstat
|
orig_lstat = os.lstat
|
||||||
def raiser(fn):
|
def raiser(fn, *args, **kwargs):
|
||||||
if fn != TESTFN:
|
if fn != TESTFN:
|
||||||
raise OSError()
|
raise OSError()
|
||||||
else:
|
else:
|
||||||
|
|
Loading…
Reference in New Issue