| #!/usr/bin/python -b |
| # Copyright 2012-2018 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| doc = """Dump and restore extended attributes. |
| |
| We use formats like that used by getfattr --dump. This is meant for shell |
| helpers to save/restore. If you're looking for a python/portage API, see |
| portage.util.movefile._copyxattr instead. |
| |
| https://en.wikipedia.org/wiki/Extended_file_attributes |
| """ |
| __doc__ = doc |
| |
| |
| import argparse |
| import array |
| import os |
| import re |
| import sys |
| |
| from portage.util._xattr import xattr |
| |
| |
| _UNQUOTE_RE = re.compile(br"\\[0-7]{3}") |
| _FS_ENCODING = sys.getfilesystemencoding() |
| |
| |
| def octal_quote_byte(b): |
| return ("\\%03o" % ord(b)).encode("ascii") |
| |
| |
| def unicode_encode(s): |
| if isinstance(s, str): |
| s = s.encode(_FS_ENCODING, "surrogateescape") |
| return s |
| |
| |
| def quote(s, quote_chars): |
| """Convert all |quote_chars| in |s| to escape sequences |
| |
| This is normally used to escape any embedded quotation marks. |
| """ |
| quote_re = re.compile(b"[" + quote_chars + b"]") |
| result = [] |
| pos = 0 |
| s_len = len(s) |
| |
| while pos < s_len: |
| m = quote_re.search(s, pos=pos) |
| if m is None: |
| result.append(s[pos:]) |
| pos = s_len |
| else: |
| start = m.start() |
| result.append(s[pos:start]) |
| result.append(octal_quote_byte(s[start : start + 1])) |
| pos = start + 1 |
| |
| return b"".join(result) |
| |
| |
| def unquote(s): |
| """Process all escape sequences in |s|""" |
| result = [] |
| pos = 0 |
| s_len = len(s) |
| |
| while pos < s_len: |
| m = _UNQUOTE_RE.search(s, pos=pos) |
| if m is None: |
| result.append(s[pos:]) |
| pos = s_len |
| else: |
| start = m.start() |
| result.append(s[pos:start]) |
| pos = start + 4 |
| a = array.array("B") |
| a.append(int(s[start + 1 : pos], 8)) |
| try: |
| # Python >= 3.2 |
| result.append(a.tobytes()) |
| except AttributeError: |
| result.append(a.tostring()) |
| |
| return b"".join(result) |
| |
| |
| def dump_xattrs(pathnames, file_out): |
| """Dump the xattr data for |pathnames| to |file_out|""" |
| # NOTE: Always quote backslashes, in order to ensure that they are |
| # not interpreted as quotes when they are processed by unquote. |
| quote_chars = b"\n\r\\\\" |
| |
| for pathname in pathnames: |
| attrs = xattr.list(pathname) |
| if not attrs: |
| continue |
| |
| file_out.write(b"# file: %s\n" % quote(pathname, quote_chars)) |
| for attr in attrs: |
| attr = unicode_encode(attr) |
| value = xattr.get(pathname, attr) |
| file_out.write( |
| b'%s="%s"\n' |
| % (quote(attr, b"=" + quote_chars), quote(value, b'\0"' + quote_chars)) |
| ) |
| |
| |
| def restore_xattrs(file_in): |
| """Read |file_in| and restore xattrs content from it |
| |
| This expects textual data in the format written by dump_xattrs. |
| """ |
| pathname = None |
| for i, line in enumerate(file_in): |
| if line.startswith(b"# file: "): |
| pathname = unquote(line.rstrip(b"\n")[8:]) |
| else: |
| parts = line.split(b"=", 1) |
| if len(parts) == 2: |
| if pathname is None: |
| raise ValueError("line %d: missing pathname" % (i + 1,)) |
| attr = unquote(parts[0]) |
| # strip trailing newline and quotes |
| value = unquote(parts[1].rstrip(b"\n")[1:-1]) |
| xattr.set(pathname, attr, value) |
| elif line.strip(): |
| raise ValueError("line %d: malformed entry" % (i + 1,)) |
| |
| |
| def main(argv): |
| |
| parser = argparse.ArgumentParser(description=doc) |
| parser.add_argument("paths", nargs="*", default=[]) |
| |
| actions = parser.add_argument_group("Actions") |
| actions.add_argument( |
| "--dump", |
| action="store_true", |
| help="Dump the values of all extended " |
| "attributes associated with paths " |
| "passed as arguments or null-separated " |
| "paths read from stdin.", |
| ) |
| actions.add_argument( |
| "--restore", |
| action="store_true", |
| help="Restore extended attributes using " "a dump read from stdin.", |
| ) |
| |
| options = parser.parse_args(argv) |
| |
| file_in = sys.stdin.buffer.raw |
| |
| if options.dump: |
| if options.paths: |
| options.paths = [unicode_encode(x) for x in options.paths] |
| else: |
| options.paths = [x for x in file_in.read().split(b"\0") if x] |
| file_out = sys.stdout.buffer |
| dump_xattrs(options.paths, file_out) |
| |
| elif options.restore: |
| restore_xattrs(file_in) |
| |
| else: |
| parser.error("missing action!") |
| |
| return os.EX_OK |
| |
| |
| if __name__ == "__main__": |
| sys.exit(main(sys.argv[1:])) |