Patchel shutil.copystat to avoid PermissionError on Lustre (#27247)
This commit is contained in:
parent
e8a19aa089
commit
e8238fe330
@ -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:
|
||||||
|
Loading…
Reference in New Issue
Block a user