| #!/usr/bin/python |
| # Copyright 2012 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| import array |
| import optparse |
| import os |
| import re |
| import sys |
| |
| if hasattr(os, "getxattr"): |
| |
| class xattr(object): |
| get = os.getxattr |
| set = os.setxattr |
| list = os.listxattr |
| |
| else: |
| import xattr |
| |
| _unquote_re = re.compile(br'\\[0-7]{3}') |
| _fs_encoding = sys.getfilesystemencoding() |
| |
| if sys.hexversion < 0x3000000: |
| |
| def octal_quote_byte(b): |
| return b'\\%03o' % ord(b) |
| |
| def unicode_encode(s): |
| if isinstance(s, unicode): |
| s = s.encode(_fs_encoding) |
| return s |
| else: |
| |
| def octal_quote_byte(b): |
| return ('\\%03o' % ord(b)).encode('ascii') |
| |
| def unicode_encode(s): |
| if isinstance(s, str): |
| s = s.encode(_fs_encoding) |
| return s |
| |
| def quote(s, quote_chars): |
| |
| 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): |
| |
| 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(file_in, file_out): |
| |
| for pathname in file_in.read().split(b'\0'): |
| if not pathname: |
| continue |
| |
| attrs = xattr.list(pathname) |
| if not attrs: |
| continue |
| |
| # NOTE: Always quote backslashes, in order to ensure that they are |
| # not interpreted as quotes when they are processed by unquote. |
| file_out.write(b'# file: ' + quote(pathname, b'\n\r\\\\') + b'\n') |
| for attr in attrs: |
| attr = unicode_encode(attr) |
| file_out.write(quote(attr, b'=\n\r\\\\') + b'="' + |
| quote(xattr.get(pathname, attr), b'\0\n\r"\\\\') + b'"\n') |
| |
| def restore_xattrs(file_in): |
| |
| 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 AssertionError('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 AssertionError("line %d: malformed entry" % (i + 1,)) |
| |
| def main(argv): |
| |
| description = "Dump and restore extended attributes," \ |
| " using format like that used by getfattr --dump." |
| usage = "usage: %s [--dump | --restore]\n" % \ |
| os.path.basename(argv[0]) |
| |
| parser = optparse.OptionParser(description=description, usage=usage) |
| |
| actions = optparse.OptionGroup(parser, 'Actions') |
| actions.add_option("--dump", |
| action="store_true", |
| help="Dump the values of all extended " |
| "attributes associated with null-separated" |
| " paths read from stdin.") |
| actions.add_option("--restore", |
| action="store_true", |
| help="Restore extended attributes using" |
| " a dump read from stdin.") |
| parser.add_option_group(actions) |
| |
| options, args = parser.parse_args(argv[1:]) |
| |
| if len(args) != 0: |
| parser.error("expected zero arguments, " |
| "got %s" % len(args)) |
| |
| if sys.hexversion >= 0x3000000: |
| file_in = sys.stdin.buffer.raw |
| else: |
| file_in = sys.stdin |
| |
| if options.dump: |
| |
| if sys.hexversion >= 0x3000000: |
| file_out = sys.stdout.buffer |
| else: |
| file_out = sys.stdout |
| |
| dump_xattrs(file_in, file_out) |
| |
| elif options.restore: |
| |
| restore_xattrs(file_in) |
| |
| else: |
| parser.error("available actions: --dump, --restore") |
| |
| return os.EX_OK |
| |
| if __name__ == "__main__": |
| rval = main(sys.argv[:]) |
| sys.exit(rval) |