Commit ae9a9e9a authored by Hynek Schlawack's avatar Hynek Schlawack

#15872: Fix 3.3 regression introduced by the new fd-based shutil.rmtree

It caused rmtree to not ignore certain errors when ignore_errors was set.

Patch by Alessandro Moura and Serhiy Storchaka.
parents e53e44f3 b550110f
...@@ -381,19 +381,20 @@ def _rmtree_safe_fd(topfd, path, onerror): ...@@ -381,19 +381,20 @@ def _rmtree_safe_fd(topfd, path, onerror):
names = [] names = []
try: try:
names = os.listdir(topfd) names = os.listdir(topfd)
except os.error: except OSError as err:
err.filename = path
onerror(os.listdir, path, sys.exc_info()) onerror(os.listdir, path, sys.exc_info())
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, follow_symlinks=False) 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 OSError:
mode = 0 mode = 0
if stat.S_ISDIR(mode): if stat.S_ISDIR(mode):
try: try:
dirfd = os.open(name, os.O_RDONLY, dir_fd=topfd) dirfd = os.open(name, os.O_RDONLY, dir_fd=topfd)
except os.error: except OSError:
onerror(os.open, fullname, sys.exc_info()) onerror(os.open, fullname, sys.exc_info())
else: else:
try: try:
...@@ -401,14 +402,23 @@ def _rmtree_safe_fd(topfd, path, onerror): ...@@ -401,14 +402,23 @@ def _rmtree_safe_fd(topfd, path, onerror):
_rmtree_safe_fd(dirfd, fullname, onerror) _rmtree_safe_fd(dirfd, fullname, onerror)
try: try:
os.rmdir(name, dir_fd=topfd) os.rmdir(name, dir_fd=topfd)
except os.error: except OSError:
onerror(os.rmdir, fullname, sys.exc_info()) onerror(os.rmdir, fullname, sys.exc_info())
else:
try:
# This can only happen if someone replaces
# a directory with a symlink after the call to
# stat.S_ISDIR above.
raise OSError("Cannot call rmtree on a symbolic "
"link")
except OSError:
onerror(os.path.islink, fullname, sys.exc_info())
finally: finally:
os.close(dirfd) os.close(dirfd)
else: else:
try: try:
os.unlink(name, dir_fd=topfd) os.unlink(name, dir_fd=topfd)
except os.error: except OSError:
onerror(os.unlink, fullname, sys.exc_info()) onerror(os.unlink, fullname, sys.exc_info())
_use_fd_functions = ({os.open, os.stat, os.unlink, os.rmdir} <= _use_fd_functions = ({os.open, os.stat, os.unlink, os.rmdir} <=
...@@ -450,16 +460,18 @@ def rmtree(path, ignore_errors=False, onerror=None): ...@@ -450,16 +460,18 @@ def rmtree(path, ignore_errors=False, onerror=None):
onerror(os.lstat, path, sys.exc_info()) onerror(os.lstat, path, sys.exc_info())
return return
try: try:
if (stat.S_ISDIR(orig_st.st_mode) and if 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)
try: try:
os.rmdir(path) os.rmdir(path)
except os.error: except os.error:
onerror(os.rmdir, path, sys.exc_info()) onerror(os.rmdir, path, sys.exc_info())
else: else:
raise NotADirectoryError(20, try:
"Not a directory: '{}'".format(path)) # symlinks to directories are forbidden, see bug #1669
raise OSError("Cannot call rmtree on a symbolic link")
except OSError:
onerror(os.path.islink, path, sys.exc_info())
finally: finally:
os.close(fd) os.close(fd)
else: else:
......
...@@ -127,6 +127,15 @@ class TestShutil(unittest.TestCase): ...@@ -127,6 +127,15 @@ class TestShutil(unittest.TestCase):
os.symlink(dir_, link) os.symlink(dir_, link)
self.assertRaises(OSError, shutil.rmtree, link) self.assertRaises(OSError, shutil.rmtree, link)
self.assertTrue(os.path.exists(dir_)) self.assertTrue(os.path.exists(dir_))
self.assertTrue(os.path.lexists(link))
errors = []
def onerror(*args):
errors.append(args)
shutil.rmtree(link, onerror=onerror)
self.assertEqual(len(errors), 1)
self.assertIs(errors[0][0], os.path.islink)
self.assertEqual(errors[0][1], link)
self.assertIsInstance(errors[0][2][1], OSError)
@support.skip_unless_symlink @support.skip_unless_symlink
def test_rmtree_works_on_symlinks(self): def test_rmtree_works_on_symlinks(self):
...@@ -153,7 +162,34 @@ class TestShutil(unittest.TestCase): ...@@ -153,7 +162,34 @@ class TestShutil(unittest.TestCase):
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()
self.assertRaises(OSError, shutil.rmtree, filename) self.assertRaises(FileNotFoundError, shutil.rmtree, filename)
# test that ignore_errors option is honored
shutil.rmtree(filename, ignore_errors=True)
# existing file
tmpdir = self.mkdtemp()
write_file((tmpdir, "tstfile"), "")
filename = os.path.join(tmpdir, "tstfile")
with self.assertRaises(NotADirectoryError) as cm:
shutil.rmtree(filename)
self.assertEqual(cm.exception.filename, filename)
self.assertTrue(os.path.exists(filename))
# test that ignore_errors option is honored
shutil.rmtree(filename, ignore_errors=True)
self.assertTrue(os.path.exists(filename))
errors = []
def onerror(*args):
errors.append(args)
shutil.rmtree(filename, onerror=onerror)
self.assertEqual(len(errors), 2)
self.assertIs(errors[0][0], os.listdir)
self.assertEqual(errors[0][1], filename)
self.assertIsInstance(errors[0][2][1], NotADirectoryError)
self.assertEqual(errors[0][2][1].filename, filename)
self.assertIs(errors[1][0], os.rmdir)
self.assertEqual(errors[1][1], filename)
self.assertIsInstance(errors[1][2][1], NotADirectoryError)
self.assertEqual(errors[1][2][1].filename, filename)
# See bug #1071513 for why we don't run this on cygwin # See bug #1071513 for why we don't run this on cygwin
# and bug #1076467 for why we don't run this as root. # and bug #1076467 for why we don't run this as root.
......
...@@ -163,6 +163,10 @@ Core and Builtins ...@@ -163,6 +163,10 @@ Core and Builtins
Library Library
------- -------
- Issue #15872: Fix 3.3 regression introduced by the new fd-based shutil.rmtree
that caused it to not ignore certain errors when ignore_errors was set.
Patch by Alessandro Moura and Serhiy Storchaka.
- Issue #16248: Disable code execution from the user's home directory by - Issue #16248: Disable code execution from the user's home directory by
tkinter when the -E flag is passed to Python. Patch by Zachary Ware. tkinter when the -E flag is passed to Python. Patch by Zachary Ware.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment