2 # Copyright 2012-2013 Gentoo Foundation
3 # Distributed under the terms of the GNU General Public License v2
5 """Dump and restore extended attributes.
7 We use formats like that used by getfattr --dump. This is meant for shell
8 helpers to save/restore. If you're looking for a python/portage API, see
9 portage.util.movefile._copyxattr instead.
11 https://en.wikipedia.org/wiki/Extended_file_attributes
19 from portage.util._argparse import ArgumentParser
21 if hasattr(os, "getxattr"):
32 _UNQUOTE_RE = re.compile(br'\\[0-7]{3}')
33 _FS_ENCODING = sys.getfilesystemencoding()
36 if sys.hexversion < 0x3000000:
38 def octal_quote_byte(b):
39 return b'\\%03o' % ord(b)
41 def unicode_encode(s):
42 if isinstance(s, unicode):
43 s = s.encode(_FS_ENCODING)
47 def octal_quote_byte(b):
48 return ('\\%03o' % ord(b)).encode('ascii')
50 def unicode_encode(s):
51 if isinstance(s, str):
52 s = s.encode(_FS_ENCODING)
56 def quote(s, quote_chars):
57 """Convert all |quote_chars| in |s| to escape sequences
59 This is normally used to escape any embedded quotation marks.
61 quote_re = re.compile(b'[' + quote_chars + b']')
67 m = quote_re.search(s, pos=pos)
69 result.append(s[pos:])
73 result.append(s[pos:start])
74 result.append(octal_quote_byte(s[start:start+1]))
77 return b''.join(result)
81 """Process all escape sequences in |s|"""
87 m = _UNQUOTE_RE.search(s, pos=pos)
89 result.append(s[pos:])
93 result.append(s[pos:start])
96 a.append(int(s[start + 1:pos], 8))
99 result.append(a.tobytes())
100 except AttributeError:
101 result.append(a.tostring())
103 return b''.join(result)
106 def dump_xattrs(pathnames, file_out):
107 """Dump the xattr data for |pathnames| to |file_out|"""
108 # NOTE: Always quote backslashes, in order to ensure that they are
109 # not interpreted as quotes when they are processed by unquote.
110 quote_chars = b'\n\r\\\\'
112 for pathname in pathnames:
113 attrs = xattr.list(pathname)
117 file_out.write(b'# file: %s\n' % quote(pathname, quote_chars))
119 attr = unicode_encode(attr)
120 value = xattr.get(pathname, attr)
121 file_out.write(b'%s="%s"\n' % (
122 quote(attr, b'=' + quote_chars),
123 quote(value, b'\0"' + quote_chars)))
126 def restore_xattrs(file_in):
127 """Read |file_in| and restore xattrs content from it
129 This expects textual data in the format written by dump_xattrs.
132 for i, line in enumerate(file_in):
133 if line.startswith(b'# file: '):
134 pathname = unquote(line.rstrip(b'\n')[8:])
136 parts = line.split(b'=', 1)
139 raise ValueError('line %d: missing pathname' % (i + 1,))
140 attr = unquote(parts[0])
141 # strip trailing newline and quotes
142 value = unquote(parts[1].rstrip(b'\n')[1:-1])
143 xattr.set(pathname, attr, value)
145 raise ValueError('line %d: malformed entry' % (i + 1,))
150 parser = ArgumentParser(description=__doc__)
151 parser.add_argument('paths', nargs='*', default=[])
153 actions = parser.add_argument_group('Actions')
154 actions.add_argument('--dump',
156 help='Dump the values of all extended '
157 'attributes associated with null-separated'
158 ' paths read from stdin.')
159 actions.add_argument('--restore',
161 help='Restore extended attributes using'
162 ' a dump read from stdin.')
164 options = parser.parse_args(argv)
166 if sys.hexversion >= 0x3000000:
167 file_in = sys.stdin.buffer.raw
170 if not options.paths:
171 options.paths += [x for x in file_in.read().split(b'\0') if x]
174 if sys.hexversion >= 0x3000000:
175 file_out = sys.stdout.buffer
177 file_out = sys.stdout
178 dump_xattrs(options.paths, file_out)
180 elif options.restore:
181 restore_xattrs(file_in)
184 parser.error('missing action!')
189 if __name__ == '__main__':
190 sys.exit(main(sys.argv[1:]))