+def simple_memo(func):
+ memo = dict()
+ def _(*args):
+ try:
+ r = memo[args]
+ except KeyError:
+ r = func(*args)
+ memo[args] = r
+ return r
+ return _
+
+@simple_memo
+def name_uid(name):
+ pw = PW.getpwnam(name)
+ return pw[2]
+
+@simple_memo
+def name_gid(name):
+ gr = GR.getgrnam(name)
+ return gr[2]
+
+###--------------------------------------------------------------------------
+### Extended attributes.
+
+def listxattr(f, follow_symlinks = True): return []
+if _PYVER >= (3, 3):
+ if hasattr(OS, "listxattr"):
+ getxattr, listxattr = OS.getxattr, OS.listxattr
+else:
+ try:
+ import xattr as _XA
+ except ImportError:
+ pass
+ else:
+ if hasattr(_XA, "list"):
+ def listxattr(f, follow_symlinks = True):
+ return _XA.list(f, nofollow = not follow_symlinks)
+ def getxattr(f, a, follow_symlinks = True):
+ return _XA.get(f, a, nofollow = not follow_symlinks)
+ else:
+ def listxattr(f, follow_symlinks = True):
+ return _XA.listxattr(f, nofollow = not follow_symlinks)
+ def getxattr(f, a, follow_symlinks = True):
+ return _XA.getxattr(f, a, nofollow = not follow_symlinks)
+
+###--------------------------------------------------------------------------
+### Access control lists.
+
+HAVE_ACL_P = False
+
+ACL_ACC= 1
+ACL_DFLT = 2
+
+def getacl(f, which): return None
+try:
+ import posix1e as ACL
+except ImportError:
+ pass
+else:
+
+ ## Match a line from the standard ACL text format.
+ R_ACLENT = RX.compile(r"""^
+ \s*
+ (?: (u | user | g | group | m | mask | o | other)
+ \s* : \s*
+ (| [^:\s] | [^:\s] [^:]* [^:\s])
+ \s* : \s*
+ ([-rwx]*)
+ \s*) ?
+ (?: \# .*)? $
+ """, RX.VERBOSE)
+
+ ## Codes for the possible entry tag types. These are ordered so that we
+ ## can sort.
+ AT_OWNUID = 1
+ AT_USER = 2
+ AT_MASK = 3
+ AT_OWNGID = 4
+ AT_GROUP = 5
+ AT_OTHER = 6
+
+ ## Output tags corresponding to the codes.
+ ACL_TAGMAP = [None, "u", "u", "m", "g", "g", "o"]
+
+ HAVE_ACL_P = True
+
+ def getacl(f, which):
+
+ ## Fetch the file ACL.
+ if which == ACL_ACC: acl = ACL.ACL(file = f)
+ elif which == ACL_DFLT: acl = ACL.ACL(filedef = f)
+ else: raise ValueError("unexpected WHICH = %d" % which)
+
+ ## For maximum portability, only use the text format, which is guaranteed
+ ## to be supported if anything is. We'll have to parse this ourselves.
+ ## Honestly, an important part of what we're doing here is producing a
+ ## /canonical/ presentation of the ACL, which doesn't seem to be
+ ## something that even the less portable functions will do for us.
+ s = str(acl)
+ extp = False
+ entries = []
+
+ ## First pass: grind through the ACL entries and build a list of (TAG,
+ ## QUAL, MODE) triples, where the TAG is an `AT_...' code, the QUAL is
+ ## either `None' or a numeric ID, and the MODE is a bitmask of
+ ## permissions.
+ for line in s.split("\n"):
+ m = R_ACLENT.match(line)
+ if m is None: raise ValueError("unexpected ACL line `%s'" % line)
+ if not m.group(1): continue
+ tag, qual, perm = m.group(1), m.group(2), m.group(3)
+
+ if qual == "": qual = None
+
+ ## Convert the tag and qualifier.
+ if tag == "u" or tag == "user":
+ if qual is None: pass
+ elif qual.isdigit(): qual = int(qual, 10)
+ else: qual = name_uid(qual)
+ if qual is None: tag = AT_OWNUID
+ else: tag = AT_USER; extp = True
+ elif tag == "m" or tag == "mask":
+ if qual is not None:
+ raise ValueError("unexpected mask qualifier `%s'" % qual)
+ tag = AT_MASK; extp = True
+ elif tag == "g" or tag == "group":
+ if qual is None: pass
+ elif qual.isdigit(): qual = int(qual, 10)
+ else: qual = name_gid(qual)
+ if qual is None: tag = AT_OWNGID
+ else: tag = AT_GROUP; extp = True
+ elif tag == "o" or tag == "other":
+ if qual is not None:
+ raise ValueError("unexpected other qualifier `%s'" % qual)
+ tag = AT_OTHER
+ else:
+ raise ValueError("unexpected tag type `%s'" % tag)
+
+ ## Convert the permissions.
+ mode = 0
+ for ch in perm:
+ if ch == "r": mode |= 4
+ elif ch == "w": mode |= 2
+ elif ch == "x": mode |= 1
+ elif ch == "-": pass
+ else: raise ValueError("unexpected permission character `%s'" % ch)
+
+ ## Done.
+ entries.append((tag, qual, mode))
+
+ ## If the ACL is trivial then ignore it. An access ACL trivial if it
+ ## contains only entries which are reflected in the traditional
+ ## permission bits. A default ACL is trivial if it's empty.
+ if (which == ACL_ACC and not extp) or \
+ (which == ACL_DFLT and not entries):
+ return None
+
+ ## Sort the entries. The tag codes are arranged so that this is a useful
+ ## ordering.
+ entries.sort()
+
+ ## Produce output. This happens to be the standard short text format,
+ ## with exclusively numeric IDs.
+ out = StringIO()
+ firstp = True
+ for tag, qual, mode in entries:
+ if firstp: firstp = False
+ else: out.write(",")
+ out.write(ACL_TAGMAP[tag])
+ out.write(":")
+ if qual is not None: out.write(str(qual))
+ out.write(":")
+ if mode&4: out.write("r")
+ else: out.write("-")
+ if mode&2: out.write("w")
+ else: out.write("-")
+ if mode&1: out.write("x")
+ else: out.write("-")
+
+ return out.getvalue()
+