[Python-checkins] cpython (merge 3.2 -> 3.3): #15872: Fix 3.3 regression introduced by the new fd-based shutil.rmtree

hynek.schlawack python-checkins at python.org
Mon Dec 10 09:18:35 CET 2012


http://hg.python.org/cpython/rev/fc394216c724
changeset:   80800:fc394216c724
branch:      3.3
parent:      80797:5182cc18b7b4
parent:      80799:c9b9f786ec25
user:        Hynek Schlawack <hs at ox.cx>
date:        Mon Dec 10 09:11:25 2012 +0100
summary:
  #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.

files:
  Lib/shutil.py           |  30 ++++++++++++++++------
  Lib/test/test_shutil.py |  38 ++++++++++++++++++++++++++++-
  Misc/NEWS               |   4 +++
  3 files changed, 62 insertions(+), 10 deletions(-)


diff --git a/Lib/shutil.py b/Lib/shutil.py
--- a/Lib/shutil.py
+++ b/Lib/shutil.py
@@ -375,19 +375,20 @@
     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 @@
                         _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 @@
             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:
diff --git a/Lib/test/test_shutil.py b/Lib/test/test_shutil.py
--- a/Lib/test/test_shutil.py
+++ b/Lib/test/test_shutil.py
@@ -126,6 +126,15 @@
         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 @@
     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.
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -108,6 +108,10 @@
 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.
 

-- 
Repository URL: http://hg.python.org/cpython


More information about the Python-checkins mailing list