Patchel shutil.copystat to avoid PermissionError on Lustre (#27247)
This commit is contained in:
		@@ -84,6 +84,74 @@
 | 
				
			|||||||
    "visit_directory_tree",
 | 
					    "visit_directory_tree",
 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if sys.version_info < (3, 7, 4):
 | 
				
			||||||
 | 
					    # monkeypatch shutil.copystat to fix PermissionError when copying read-only
 | 
				
			||||||
 | 
					    # files on Lustre when using Python < 3.7.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def copystat(src, dst, follow_symlinks=True):
 | 
				
			||||||
 | 
					        """Copy file metadata
 | 
				
			||||||
 | 
					        Copy the permission bits, last access time, last modification time, and
 | 
				
			||||||
 | 
					        flags from `src` to `dst`. On Linux, copystat() also copies the "extended
 | 
				
			||||||
 | 
					        attributes" where possible. The file contents, owner, and group are
 | 
				
			||||||
 | 
					        unaffected. `src` and `dst` are path names given as strings.
 | 
				
			||||||
 | 
					        If the optional flag `follow_symlinks` is not set, symlinks aren't
 | 
				
			||||||
 | 
					        followed if and only if both `src` and `dst` are symlinks.
 | 
				
			||||||
 | 
					        """
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        def _nop(args, ns=None, follow_symlinks=None):
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # follow symlinks (aka don't not follow symlinks)
 | 
				
			||||||
 | 
					        follow = follow_symlinks or not (os.path.islink(src) and os.path.islink(dst))
 | 
				
			||||||
 | 
					        if follow:
 | 
				
			||||||
 | 
					            # use the real function if it exists
 | 
				
			||||||
 | 
					            def lookup(name):
 | 
				
			||||||
 | 
					                return getattr(os, name, _nop)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            # use the real function only if it exists
 | 
				
			||||||
 | 
					            # *and* it supports follow_symlinks
 | 
				
			||||||
 | 
					            def lookup(name):
 | 
				
			||||||
 | 
					                fn = getattr(os, name, _nop)
 | 
				
			||||||
 | 
					                if sys.version_info >= (3, 3):
 | 
				
			||||||
 | 
					                    if fn in os.supports_follow_symlinks:  # novermin
 | 
				
			||||||
 | 
					                        return fn
 | 
				
			||||||
 | 
					                return _nop
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        st = lookup("stat")(src, follow_symlinks=follow)
 | 
				
			||||||
 | 
					        mode = stat.S_IMODE(st.st_mode)
 | 
				
			||||||
 | 
					        lookup("utime")(dst, ns=(st.st_atime_ns, st.st_mtime_ns), follow_symlinks=follow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # We must copy extended attributes before the file is (potentially)
 | 
				
			||||||
 | 
					        # chmod()'ed read-only, otherwise setxattr() will error with -EACCES.
 | 
				
			||||||
 | 
					        shutil._copyxattr(src, dst, follow_symlinks=follow)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        try:
 | 
				
			||||||
 | 
					            lookup("chmod")(dst, mode, follow_symlinks=follow)
 | 
				
			||||||
 | 
					        except NotImplementedError:
 | 
				
			||||||
 | 
					            # if we got a NotImplementedError, it's because
 | 
				
			||||||
 | 
					            #   * follow_symlinks=False,
 | 
				
			||||||
 | 
					            #   * lchown() is unavailable, and
 | 
				
			||||||
 | 
					            #   * either
 | 
				
			||||||
 | 
					            #       * fchownat() is unavailable or
 | 
				
			||||||
 | 
					            #       * fchownat() doesn't implement AT_SYMLINK_NOFOLLOW.
 | 
				
			||||||
 | 
					            #         (it returned ENOSUP.)
 | 
				
			||||||
 | 
					            # therefore we're out of options--we simply cannot chown the
 | 
				
			||||||
 | 
					            # symlink.  give up, suppress the error.
 | 
				
			||||||
 | 
					            # (which is what shutil always did in this circumstance.)
 | 
				
			||||||
 | 
					            pass
 | 
				
			||||||
 | 
					        if hasattr(st, "st_flags"):
 | 
				
			||||||
 | 
					            try:
 | 
				
			||||||
 | 
					                lookup("chflags")(dst, st.st_flags, follow_symlinks=follow)
 | 
				
			||||||
 | 
					            except OSError as why:
 | 
				
			||||||
 | 
					                for err in "EOPNOTSUPP", "ENOTSUP":
 | 
				
			||||||
 | 
					                    if hasattr(errno, err) and why.errno == getattr(errno, err):
 | 
				
			||||||
 | 
					                        break
 | 
				
			||||||
 | 
					                else:
 | 
				
			||||||
 | 
					                    raise
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    shutil.copystat = copystat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def getuid():
 | 
					def getuid():
 | 
				
			||||||
    if is_windows:
 | 
					    if is_windows:
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user