#4489: Don't follow ever symlinks in rmtree

Also added several regression tests.
This commit is contained in:
Hynek Schlawack 2012-06-28 12:07:29 +02:00
parent 591c1cca32
commit a75cd1ce73
2 changed files with 35 additions and 3 deletions

View File

@ -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:

View File

@ -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: