summaryrefslogtreecommitdiff
path: root/lib/spack/llnl
diff options
context:
space:
mode:
authorBen Wibking <benjamin.wibking@anu.edu.au>2023-02-20 11:28:03 -0500
committerGitHub <noreply@github.com>2023-02-20 17:28:03 +0100
commite8238fe330f3761a0537232807ce0b32d3535b3e (patch)
treeb512d4dfc3c556b59356fb98918bb82727b38d31 /lib/spack/llnl
parente8a19aa089cb3d0efc819774b178098cb8dbc188 (diff)
downloadspack-e8238fe330f3761a0537232807ce0b32d3535b3e.tar.gz
spack-e8238fe330f3761a0537232807ce0b32d3535b3e.tar.bz2
spack-e8238fe330f3761a0537232807ce0b32d3535b3e.tar.xz
spack-e8238fe330f3761a0537232807ce0b32d3535b3e.zip
Patchel shutil.copystat to avoid PermissionError on Lustre (#27247)
Diffstat (limited to 'lib/spack/llnl')
-rw-r--r--lib/spack/llnl/util/filesystem.py68
1 files changed, 68 insertions, 0 deletions
diff --git a/lib/spack/llnl/util/filesystem.py b/lib/spack/llnl/util/filesystem.py
index 4dfea7975d..99bf834eb2 100644
--- a/lib/spack/llnl/util/filesystem.py
+++ b/lib/spack/llnl/util/filesystem.py
@@ -84,6 +84,74 @@ __all__ = [
"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():
if is_windows: