1 |
Rather than each module implementing its own shim around the various |
2 |
methods for accessing extended attributes, start a dedicated module |
3 |
that exports a consistent API. |
4 |
--- |
5 |
bin/xattr-helper.py | 11 +-- |
6 |
pym/portage/util/_xattr.py | 189 +++++++++++++++++++++++++++++++++++++++++++ |
7 |
pym/portage/util/movefile.py | 99 ++++++----------------- |
8 |
3 files changed, 213 insertions(+), 86 deletions(-) |
9 |
create mode 100644 pym/portage/util/_xattr.py |
10 |
|
11 |
diff --git a/bin/xattr-helper.py b/bin/xattr-helper.py |
12 |
index 6d99521..69b83f7 100755 |
13 |
--- a/bin/xattr-helper.py |
14 |
+++ b/bin/xattr-helper.py |
15 |
@@ -17,16 +17,7 @@ import re |
16 |
import sys |
17 |
|
18 |
from portage.util._argparse import ArgumentParser |
19 |
- |
20 |
-if hasattr(os, "getxattr"): |
21 |
- |
22 |
- class xattr(object): |
23 |
- get = os.getxattr |
24 |
- set = os.setxattr |
25 |
- list = os.listxattr |
26 |
- |
27 |
-else: |
28 |
- import xattr |
29 |
+from portage.util._xattr import xattr |
30 |
|
31 |
|
32 |
_UNQUOTE_RE = re.compile(br'\\[0-7]{3}') |
33 |
diff --git a/pym/portage/util/_xattr.py b/pym/portage/util/_xattr.py |
34 |
new file mode 100644 |
35 |
index 0000000..0e594f9 |
36 |
--- /dev/null |
37 |
+++ b/pym/portage/util/_xattr.py |
38 |
@@ -0,0 +1,189 @@ |
39 |
+# Copyright 2010-2013 Gentoo Foundation |
40 |
+# Distributed under the terms of the GNU General Public License v2 |
41 |
+ |
42 |
+"""Portability shim for xattr support |
43 |
+ |
44 |
+Exported API is the xattr object with get/get_all/set/remove/list operations. |
45 |
+ |
46 |
+See the standard xattr module for more documentation. |
47 |
+""" |
48 |
+ |
49 |
+import contextlib |
50 |
+import os |
51 |
+import subprocess |
52 |
+ |
53 |
+from portage.exception import OperationNotSupported |
54 |
+ |
55 |
+ |
56 |
+class _XattrGetAll(object): |
57 |
+ """Implement get_all() using list()/get() if there is no easy bulk method""" |
58 |
+ |
59 |
+ @classmethod |
60 |
+ def get_all(cls, item, nofollow=False, namespace=None): |
61 |
+ return [(name, cls.get(item, name, nofollow=nofollow, namespace=namespace)) |
62 |
+ for name in cls.list(item, nofollow=nofollow, namespace=namespace)] |
63 |
+ |
64 |
+ |
65 |
+class _XattrSystemCommands(_XattrGetAll): |
66 |
+ """Implement things with getfattr/setfattr""" |
67 |
+ |
68 |
+ @staticmethod |
69 |
+ def _parse_output(output): |
70 |
+ for line in proc.stdout.readlines(): |
71 |
+ if line.startswith('#'): |
72 |
+ continue |
73 |
+ line = line.rstrip() |
74 |
+ if not line: |
75 |
+ continue |
76 |
+ # The line will have the format: |
77 |
+ # user.name0="value0" |
78 |
+ yield line.split('=', 1) |
79 |
+ |
80 |
+ @classmethod |
81 |
+ def get(item, name, nofollow=False, namespace=None): |
82 |
+ if namespace: |
83 |
+ name = '%s.%s' % (namespace, name) |
84 |
+ cmd = ['getfattr', '--absolute-names', '-n', name, item] |
85 |
+ if nofollow: |
86 |
+ cmd += ['-h'] |
87 |
+ proc = subprocess.call(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
88 |
+ proc.wait() |
89 |
+ |
90 |
+ value = None |
91 |
+ for _, value in cls._parse_output(proc.stdout): |
92 |
+ break |
93 |
+ |
94 |
+ proc.stdout.close() |
95 |
+ return value |
96 |
+ |
97 |
+ @staticmethod |
98 |
+ def set(item, name, value, flags=0, namespace=None): |
99 |
+ if namespace: |
100 |
+ name = '%s.%s' % (namespace, name) |
101 |
+ cmd = ['setfattr', '-n', name, '-v', value, item] |
102 |
+ subprocess.call(cmd) |
103 |
+ |
104 |
+ @staticmethod |
105 |
+ def remove(item, name, nofollow=False, namespace=None): |
106 |
+ if namespace: |
107 |
+ name = '%s.%s' % (namespace, name) |
108 |
+ cmd = ['setfattr', '-x', name, item] |
109 |
+ subprocess.call(cmd) |
110 |
+ |
111 |
+ @classmethod |
112 |
+ def list(cls, item, nofollow=False, namespace=None, _names_only=True): |
113 |
+ cmd = ['getfattr', '-d', '--absolute-names', item] |
114 |
+ if nofollow: |
115 |
+ cmd += ['-h'] |
116 |
+ cmd += ['-m', ('^%s[.]' % namespace) if namespace else ''] |
117 |
+ proc = subprocess.call(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
118 |
+ proc.wait() |
119 |
+ |
120 |
+ ret = [] |
121 |
+ if namespace: |
122 |
+ namespace = '%s.' % namespace |
123 |
+ for name, value in cls._parse_output(proc.stdout): |
124 |
+ if namespace: |
125 |
+ if name.startswith(namespace): |
126 |
+ name = name[len(namespace):] |
127 |
+ else: |
128 |
+ continue |
129 |
+ if _names_only: |
130 |
+ ret.append(name) |
131 |
+ else: |
132 |
+ ret.append((name, value)) |
133 |
+ |
134 |
+ proc.stdout.close() |
135 |
+ return ret |
136 |
+ |
137 |
+ @classmethod |
138 |
+ def get_all(cls, item, nofollow=False, namespace=None): |
139 |
+ cls.list(item, nofollow=nofollow, namespace=namespace, _names_only=False) |
140 |
+ |
141 |
+ |
142 |
+class _XattrStub(_XattrGetAll): |
143 |
+ """Fake object since system doesn't support xattrs""" |
144 |
+ |
145 |
+ @staticmethod |
146 |
+ def _raise(): |
147 |
+ e = OSError('stub') |
148 |
+ e.errno = OperationNotSupported.errno |
149 |
+ raise e |
150 |
+ |
151 |
+ @staticmethod |
152 |
+ def get(item, name, nofollow=False, namespace=None): |
153 |
+ raise OperationNotSupported('stub') |
154 |
+ |
155 |
+ @staticmethod |
156 |
+ def set(item, name, value, flags=0, namespace=None): |
157 |
+ raise OperationNotSupported('stub') |
158 |
+ |
159 |
+ @staticmethod |
160 |
+ def remove(item, name, nofollow=False, namespace=None): |
161 |
+ raise OperationNotSupported('stub') |
162 |
+ |
163 |
+ @staticmethod |
164 |
+ def list(item, nofollow=False, namespace=None): |
165 |
+ raise OperationNotSupported('stub') |
166 |
+ |
167 |
+ |
168 |
+if hasattr(os, 'getxattr'): |
169 |
+ # Easy as pie -- active python supports it. |
170 |
+ class xattr(_XattrGetAll): |
171 |
+ """Python >=3.3 and GNU/Linux""" |
172 |
+ get = os.getxattr |
173 |
+ set = os.setxattr |
174 |
+ remove = os.removexattr |
175 |
+ list = os.listxattr |
176 |
+ |
177 |
+else: |
178 |
+ try: |
179 |
+ # Maybe we have the xattr module. |
180 |
+ import xattr |
181 |
+ |
182 |
+ except ImportError: |
183 |
+ try: |
184 |
+ # Maybe we have the attr package. |
185 |
+ with open(os.devnull, 'wb') as f: |
186 |
+ subprocess.call(['getfattr', '--version'], stdout=f) |
187 |
+ subprocess.call(['setfattr', '--version'], stdout=f) |
188 |
+ xattr = _XattrSystemCommands |
189 |
+ |
190 |
+ except OSError: |
191 |
+ # Stub it out completely. |
192 |
+ xattr = _XattrStub |
193 |
+ |
194 |
+ |
195 |
+@××××××××××.contextmanager |
196 |
+def preserve_xattrs(path, nofollow=False, namespace=None): |
197 |
+ """Context manager to save/restore extended attributes on |path| |
198 |
+ |
199 |
+ If you want to rewrite a file (possibly replacing it with a new one), but |
200 |
+ want to preserve the extended attributes, this will do the trick. |
201 |
+ |
202 |
+ # First read all the extended attributes. |
203 |
+ with save_xattrs('/some/file'): |
204 |
+ ... rewrite the file ... |
205 |
+ # Now the extended attributes are restored as needed. |
206 |
+ """ |
207 |
+ kwargs = { |
208 |
+ 'nofollow': nofollow, |
209 |
+ 'namespace': namespace, |
210 |
+ } |
211 |
+ old_attrs = dict(xattr.get_all(path, **kwargs)) |
212 |
+ try: |
213 |
+ yield |
214 |
+ finally: |
215 |
+ new_attrs = dict(xattrs.get_all(path, **kwargs)) |
216 |
+ for name, value in new_attrs.iteritems(): |
217 |
+ if name not in old_attrs: |
218 |
+ # Clear out new ones. |
219 |
+ xattr.remove(path, name, **kwargs) |
220 |
+ elif new_attrs[name] != old: |
221 |
+ # Update changed ones. |
222 |
+ xattr.set(path, name, value, **kwargs) |
223 |
+ |
224 |
+ for name, value in old_attrs.iteritems(): |
225 |
+ if name not in new_attr: |
226 |
+ # Re-add missing ones. |
227 |
+ xattr.set(path, name, value, **kwargs) |
228 |
diff --git a/pym/portage/util/movefile.py b/pym/portage/util/movefile.py |
229 |
index 4f158cd..553374a 100644 |
230 |
--- a/pym/portage/util/movefile.py |
231 |
+++ b/pym/portage/util/movefile.py |
232 |
@@ -23,6 +23,7 @@ from portage.exception import OperationNotSupported |
233 |
from portage.localization import _ |
234 |
from portage.process import spawn |
235 |
from portage.util import writemsg |
236 |
+from portage.util._xattr import xattr |
237 |
|
238 |
def _apply_stat(src_stat, dest): |
239 |
_os.chown(dest, src_stat.st_uid, src_stat.st_gid) |
240 |
@@ -68,86 +69,32 @@ class _xattr_excluder(object): |
241 |
|
242 |
return False |
243 |
|
244 |
-if hasattr(_os, "getxattr"): |
245 |
- # Python >=3.3 and GNU/Linux |
246 |
- def _copyxattr(src, dest, exclude=None): |
247 |
- |
248 |
- try: |
249 |
- attrs = _os.listxattr(src) |
250 |
- except OSError as e: |
251 |
- if e.errno != OperationNotSupported.errno: |
252 |
- raise |
253 |
- attrs = () |
254 |
- if attrs: |
255 |
- if exclude is not None and isinstance(attrs[0], bytes): |
256 |
- exclude = exclude.encode(_encodings['fs']) |
257 |
- exclude = _get_xattr_excluder(exclude) |
258 |
- |
259 |
- for attr in attrs: |
260 |
- if exclude(attr): |
261 |
- continue |
262 |
- try: |
263 |
- _os.setxattr(dest, attr, _os.getxattr(src, attr)) |
264 |
- raise_exception = False |
265 |
- except OSError: |
266 |
- raise_exception = True |
267 |
- if raise_exception: |
268 |
- raise OperationNotSupported(_("Filesystem containing file '%s' " |
269 |
- "does not support extended attribute '%s'") % |
270 |
- (_unicode_decode(dest), _unicode_decode(attr))) |
271 |
-else: |
272 |
+def _copyxattr(src, dest, exclude=None): |
273 |
+ """Copy the extended attributes from |src| to |dest|""" |
274 |
try: |
275 |
- import xattr |
276 |
- except ImportError: |
277 |
- xattr = None |
278 |
- if xattr is not None: |
279 |
- def _copyxattr(src, dest, exclude=None): |
280 |
- |
281 |
- try: |
282 |
- attrs = xattr.list(src) |
283 |
- except IOError as e: |
284 |
- if e.errno != OperationNotSupported.errno: |
285 |
- raise |
286 |
- attrs = () |
287 |
+ attrs = xattr.list(src) |
288 |
+ except (OSError, IOError) as e: |
289 |
+ if e.errno != OperationNotSupported.errno: |
290 |
+ raise |
291 |
+ attrs = () |
292 |
|
293 |
- if attrs: |
294 |
- if exclude is not None and isinstance(attrs[0], bytes): |
295 |
- exclude = exclude.encode(_encodings['fs']) |
296 |
- exclude = _get_xattr_excluder(exclude) |
297 |
+ if attrs: |
298 |
+ if exclude is not None and isinstance(attrs[0], bytes): |
299 |
+ exclude = exclude.encode(_encodings['fs']) |
300 |
+ exclude = _get_xattr_excluder(exclude) |
301 |
|
302 |
- for attr in attrs: |
303 |
- if exclude(attr): |
304 |
- continue |
305 |
- try: |
306 |
- xattr.set(dest, attr, xattr.get(src, attr)) |
307 |
- raise_exception = False |
308 |
- except IOError: |
309 |
- raise_exception = True |
310 |
- if raise_exception: |
311 |
- raise OperationNotSupported(_("Filesystem containing file '%s' " |
312 |
- "does not support extended attribute '%s'") % |
313 |
- (_unicode_decode(dest), _unicode_decode(attr))) |
314 |
- else: |
315 |
+ for attr in attrs: |
316 |
+ if exclude(attr): |
317 |
+ continue |
318 |
try: |
319 |
- with open(os.devnull, 'wb') as f: |
320 |
- subprocess.call(["getfattr", "--version"], stdout=f) |
321 |
- subprocess.call(["setfattr", "--version"], stdout=f) |
322 |
- except OSError: |
323 |
- def _copyxattr(src, dest, exclude=None): |
324 |
- # TODO: implement exclude |
325 |
- getfattr_process = subprocess.Popen(["getfattr", "-d", "--absolute-names", src], stdout=subprocess.PIPE) |
326 |
- getfattr_process.wait() |
327 |
- extended_attributes = getfattr_process.stdout.readlines() |
328 |
- getfattr_process.stdout.close() |
329 |
- if extended_attributes: |
330 |
- extended_attributes[0] = b"# file: " + _unicode_encode(dest) + b"\n" |
331 |
- setfattr_process = subprocess.Popen(["setfattr", "--restore=-"], stdin=subprocess.PIPE, stderr=subprocess.PIPE) |
332 |
- setfattr_process.communicate(input=b"".join(extended_attributes)) |
333 |
- if setfattr_process.returncode != 0: |
334 |
- raise OperationNotSupported("Filesystem containing file '%s' does not support extended attributes" % dest) |
335 |
- else: |
336 |
- def _copyxattr(src, dest, exclude=None): |
337 |
- pass |
338 |
+ xattr.set(dest, attr, xattr.get(src, attr)) |
339 |
+ raise_exception = False |
340 |
+ except (OSError, IOError) as e: |
341 |
+ raise_exception = True |
342 |
+ if raise_exception: |
343 |
+ raise OperationNotSupported(_("Filesystem containing file '%s' " |
344 |
+ "does not support extended attribute '%s'") % |
345 |
+ (_unicode_decode(dest), _unicode_decode(attr))) |
346 |
|
347 |
def movefile(src, dest, newmtime=None, sstat=None, mysettings=None, |
348 |
hardlink_candidates=None, encoding=_encodings['fs']): |
349 |
-- |
350 |
1.8.3.2 |