#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.
This commit is contained in:
commit
b550110f64
|
@ -375,19 +375,20 @@ def _rmtree_safe_fd(topfd, path, onerror):
|
|||
names = []
|
||||
try:
|
||||
names = os.listdir(topfd)
|
||||
except os.error:
|
||||
except OSError as err:
|
||||
err.filename = path
|
||||
onerror(os.listdir, path, sys.exc_info())
|
||||
for name in names:
|
||||
fullname = os.path.join(path, name)
|
||||
try:
|
||||
orig_st = os.stat(name, dir_fd=topfd, follow_symlinks=False)
|
||||
mode = orig_st.st_mode
|
||||
except os.error:
|
||||
except OSError:
|
||||
mode = 0
|
||||
if stat.S_ISDIR(mode):
|
||||
try:
|
||||
dirfd = os.open(name, os.O_RDONLY, dir_fd=topfd)
|
||||
except os.error:
|
||||
except OSError:
|
||||
onerror(os.open, fullname, sys.exc_info())
|
||||
else:
|
||||
try:
|
||||
|
@ -395,14 +396,23 @@ def _rmtree_safe_fd(topfd, path, onerror):
|
|||
_rmtree_safe_fd(dirfd, fullname, onerror)
|
||||
try:
|
||||
os.rmdir(name, dir_fd=topfd)
|
||||
except os.error:
|
||||
except OSError:
|
||||
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:
|
||||
os.close(dirfd)
|
||||
else:
|
||||
try:
|
||||
os.unlink(name, dir_fd=topfd)
|
||||
except os.error:
|
||||
except OSError:
|
||||
onerror(os.unlink, fullname, sys.exc_info())
|
||||
|
||||
_use_fd_functions = ({os.open, os.stat, os.unlink, os.rmdir} <=
|
||||
|
@ -444,16 +454,18 @@ def rmtree(path, ignore_errors=False, onerror=None):
|
|||
onerror(os.lstat, path, sys.exc_info())
|
||||
return
|
||||
try:
|
||||
if (stat.S_ISDIR(orig_st.st_mode) and
|
||||
os.path.samestat(orig_st, os.fstat(fd))):
|
||||
if os.path.samestat(orig_st, os.fstat(fd)):
|
||||
_rmtree_safe_fd(fd, path, onerror)
|
||||
try:
|
||||
os.rmdir(path)
|
||||
except os.error:
|
||||
onerror(os.rmdir, path, sys.exc_info())
|
||||
else:
|
||||
raise NotADirectoryError(20,
|
||||
"Not a directory: '{}'".format(path))
|
||||
try:
|
||||
# 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:
|
||||
os.close(fd)
|
||||
else:
|
||||
|
|
|
@ -126,6 +126,15 @@ class TestShutil(unittest.TestCase):
|
|||
os.symlink(dir_, link)
|
||||
self.assertRaises(OSError, shutil.rmtree, link)
|
||||
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
|
||||
def test_rmtree_works_on_symlinks(self):
|
||||
|
@ -152,7 +161,34 @@ class TestShutil(unittest.TestCase):
|
|||
def test_rmtree_errors(self):
|
||||
# filename is guaranteed not to exist
|
||||
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
|
||||
# and bug #1076467 for why we don't run this as root.
|
||||
|
|
|
@ -108,6 +108,10 @@ Core and Builtins
|
|||
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
|
||||
tkinter when the -E flag is passed to Python. Patch by Zachary Ware.
|
||||
|
||||
|
|
Loading…
Reference in New Issue