summaryrefslogtreecommitdiffstats
path: root/bin/xattr-helper.py
diff options
context:
space:
mode:
Diffstat (limited to 'bin/xattr-helper.py')
-rwxr-xr-xbin/xattr-helper.py173
1 files changed, 173 insertions, 0 deletions
diff --git a/bin/xattr-helper.py b/bin/xattr-helper.py
new file mode 100755
index 000000000..d40217c9a
--- /dev/null
+++ b/bin/xattr-helper.py
@@ -0,0 +1,173 @@
+#!/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
+ 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)