diff --git a/lib/build_cache.py b/lib/build_cache.py index 1e0a8b17a..73f354f4e 100644 --- a/lib/build_cache.py +++ b/lib/build_cache.py @@ -276,8 +276,13 @@ def __init__(self, image_root, path): self.large_name = None self.xattrs = dict() if ch.save_xattrs: - for xattr in os.listxattr(self.path_abs, follow_symlinks=False): - self.xattrs[xattr] = os.getxattr(self.path_abs, xattr, follow_symlinks=False) + for xattr in ch.ossafe(os.listxattr, + "can’t list xattrs: %s" % self.path_abs, + self.path_abs, follow_symlinks=False): + self.xattrs[xattr] = \ + ch.ossafe(os.getxattr, ("can’t get xattr: %s: %s" + % (self.path_abs, xattr)), + self.path_abs, xattr, follow_symlinks=False) def __getstate__(self): return { a:v for (a,v) in self.__dict__.items() @@ -481,10 +486,8 @@ def git_restore(self, quick): ch.ossafe(os.mkfifo, "can’t make FIFO: %s" % self.path, self.path_abs) elif (self.path.git_incompatible_p): self.path_abs.git_escaped.rename_(self.path_abs) - if ch.save_xattrs: - for (xattr, val) in self.xattrs.items(): - ch.ossafe(os.setxattr, "unable to restore xattr: %s" % xattr, - self.path_abs, xattr, val, follow_symlinks=False) + for (xattr, val) in self.xattrs.items(): + self.path_abs.setxattr(xattr, val, follow_symlinks=False) # Recurse children. if (len(self.children) > 0): for child in self.children.values(): diff --git a/lib/filesystem.py b/lib/filesystem.py index d809dca65..5c4b2385d 100644 --- a/lib/filesystem.py +++ b/lib/filesystem.py @@ -111,6 +111,31 @@ def git_incompatible_p(self): "Return True if I can’t be stored in Git because of my name." return self.name.startswith(".git") + @property + def is_root(self): + return (str(self) == "/") + + @property + def mountpoint(self): + """Return the mount point of the filesystem containing me (or, if + symlink, the file pointed to).""" + # https://stackoverflow.com/a/4453715 + try: + pc = self.resolve(strict=True) + except RuntimeError: + ch.FATAL("not found, can’t resolve: %s" % self) + # Unclear whether ismount() deals correctly with the root directory, so + # do the stat(2) stuff ourself. + dev_child = pc.stat().st_dev + while (not pc.is_root): + dev_parent = pc.parent.stat().st_dev + if (dev_child != dev_parent): + return pc + pc = pc.parent + # Got all the way up to root without finding a transition, so we’re on + # the root filesystem. + return Path("/") + @classmethod def gzip_set(cls): """Set gzip class attribute on first call to file_gzip(). @@ -383,6 +408,22 @@ def rmtree(self): else: assert False, "unimplemented" + def setxattr(self, name, value, follow_symlinks=True): + if (ch.save_xattrs): + try: + os.setxattr(self, name, value, follow_symlinks) + except OSError as x: + if (x.errno == errno.ENOTSUP): # no OSError subclass + ch.WARNING("xattrs not supported on %s, setting --no-xattr" + % (self.mountpoint)) + ch.save_xattrs = False + else: + ch.FATAL("can’t set xattr: %s: %s: %s" + % (self, name, x.strerror)) + if (not ch.save_xattrs): # not “else” because could have change in “if” + ch.DEBUG("xattrs disabled, ignoring: %s: %s" % (self, name)) + return + def stat_(self, links): """An error-checking version of stat(). Note that we cannot simply change the definition of stat() to be ossafe, as the exists() method