| #!/usr/bin/python -b |
| # Copyright 2012-2014 Gentoo Foundation |
| # Distributed under the terms of the GNU General Public License v2 |
| |
| """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 |
| """ |
| |
| import array |
| import os |
| import re |
| import sys |
| |
| from portage.util._argparse import ArgumentParser |
| |
| 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): |
| """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 = 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 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) |
| |
| if sys.hexversion >= 0x3000000: |
| file_in = sys.stdin.buffer.raw |
| else: |
| file_in = sys.stdin |
| if not options.paths: |
| options.paths += [x for x in file_in.read().split(b'\0') if x] |
| |
| if options.dump: |
| if sys.hexversion >= 0x3000000: |
| file_out = sys.stdout.buffer |
| else: |
| file_out = sys.stdout |
| 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:])) |