1 |
2013-10-21 05:07 Mike Frysinger napisał(a): |
2 |
> Rather than each module implementing its own shim around the various |
3 |
> methods for accessing extended attributes, start a dedicated module |
4 |
> that exports a consistent API. |
5 |
> --- |
6 |
> v2 |
7 |
> - passes unittests w/python 2.6 2.7 3.2 3.3 |
8 |
|
9 |
But portage.util._xattr._XattrSystemCommands does not work :) . |
10 |
|
11 |
>>> import portage.util._xattr |
12 |
>>> portage.util._xattr._XattrSystemCommands.list("/tmp") |
13 |
... |
14 |
|
15 |
See below. |
16 |
|
17 |
> bin/xattr-helper.py | 11 +- |
18 |
> pym/portage/tests/util/test_xattr.py | 178 ++++++++++++++++++++++++++++++ |
19 |
> pym/portage/util/_xattr.py | 205 +++++++++++++++++++++++++++++++++++ |
20 |
> pym/portage/util/movefile.py | 100 ++++------------- |
21 |
> 4 files changed, 407 insertions(+), 87 deletions(-) |
22 |
> create mode 100644 pym/portage/tests/util/test_xattr.py |
23 |
> create mode 100644 pym/portage/util/_xattr.py |
24 |
> |
25 |
> diff --git a/bin/xattr-helper.py b/bin/xattr-helper.py |
26 |
> index 6d99521..69b83f7 100755 |
27 |
> --- a/bin/xattr-helper.py |
28 |
> +++ b/bin/xattr-helper.py |
29 |
> @@ -17,16 +17,7 @@ import re |
30 |
> import sys |
31 |
> |
32 |
> from portage.util._argparse import ArgumentParser |
33 |
> - |
34 |
> -if hasattr(os, "getxattr"): |
35 |
> - |
36 |
> - class xattr(object): |
37 |
> - get = os.getxattr |
38 |
> - set = os.setxattr |
39 |
> - list = os.listxattr |
40 |
> - |
41 |
> -else: |
42 |
> - import xattr |
43 |
> +from portage.util._xattr import xattr |
44 |
> |
45 |
> |
46 |
> _UNQUOTE_RE = re.compile(br'\\[0-7]{3}') |
47 |
> diff --git a/pym/portage/tests/util/test_xattr.py b/pym/portage/tests/util/test_xattr.py |
48 |
> new file mode 100644 |
49 |
> index 0000000..e1f6ee8 |
50 |
> --- /dev/null |
51 |
> +++ b/pym/portage/tests/util/test_xattr.py |
52 |
> @@ -0,0 +1,178 @@ |
53 |
> +# Copyright 2010-2013 Gentoo Foundation |
54 |
> +# Distributed under the terms of the GNU General Public License v2 |
55 |
> + |
56 |
> +"""Tests for the portage.util._xattr module""" |
57 |
> + |
58 |
> +from __future__ import print_function |
59 |
> + |
60 |
> +try: |
61 |
> + # Try python-3.3 module first. |
62 |
> + from unittest import mock |
63 |
> +except ImportError: |
64 |
> + try: |
65 |
> + # Try standalone module. |
66 |
> + import mock |
67 |
> + except ImportError: |
68 |
> + mock = None |
69 |
> + |
70 |
> +import subprocess |
71 |
> + |
72 |
> +from portage.tests import TestCase |
73 |
> +from portage.util._xattr import (xattr as _xattr, _XattrSystemCommands, |
74 |
> + _XattrStub) |
75 |
> + |
76 |
> + |
77 |
> +orig_popen = subprocess.Popen |
78 |
> +def MockSubprocessPopen(stdin): |
79 |
> + """Helper to mock (closely) a subprocess.Popen call |
80 |
> + |
81 |
> + The module has minor tweaks in behavior when it comes to encoding and |
82 |
> + python versions, so use a real subprocess.Popen call to fake out the |
83 |
> + runtime behavior. This way we don't have to also implement different |
84 |
> + encodings as that gets ugly real fast. |
85 |
> + """ |
86 |
> + proc = orig_popen(['cat'], stdout=subprocess.PIPE, stdin=subprocess.PIPE) |
87 |
> + try: |
88 |
> + proc.stdin.write(bytes(stdin)) |
89 |
> + except TypeError: |
90 |
> + proc.stdin.write(bytes(stdin, 'ascii')) |
91 |
|
92 |
It can fail with UnicodeEncodeError. |
93 |
Use proc.stdin.write(portage._unicode_encode(stdin), portage._encodings['stdio']) instead of above 4 lines. |
94 |
|
95 |
> + return proc |
96 |
> + |
97 |
> + |
98 |
> +class SystemCommandsTest(TestCase): |
99 |
> + """Test _XattrSystemCommands""" |
100 |
> + |
101 |
> + OUTPUT = '\n'.join([ |
102 |
> + '# file: /bin/ping', |
103 |
> + 'security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=', |
104 |
> + 'user.foo="asdf"', |
105 |
> + '', |
106 |
> + ]) |
107 |
> + |
108 |
> + def _setUp(self): |
109 |
> + if mock is None: |
110 |
> + self.skipTest('need mock for testing') |
111 |
> + |
112 |
> + return _XattrSystemCommands |
113 |
> + |
114 |
> + def _testGetBasic(self): |
115 |
> + """Verify the get() behavior""" |
116 |
> + xattr = self._setUp() |
117 |
> + with mock.patch.object(subprocess, 'Popen') as call_mock: |
118 |
> + # Verify basic behavior, and namespace arg works as expected. |
119 |
> + xattr.get('/some/file', 'user.foo') |
120 |
> + xattr.get('/some/file', 'foo', namespace='user') |
121 |
> + self.assertEqual(call_mock.call_args_list[0], call_mock.call_args_list[1]) |
122 |
> + |
123 |
> + # Verify nofollow behavior. |
124 |
> + call_mock.reset() |
125 |
> + xattr.get('/some/file', 'user.foo', nofollow=True) |
126 |
> + self.assertIn('-h', call_mock.call_args[0][0]) |
127 |
> + |
128 |
> + def testGetParsing(self): |
129 |
> + """Verify get() parses output sanely""" |
130 |
> + xattr = self._setUp() |
131 |
> + with mock.patch.object(subprocess, 'Popen') as call_mock: |
132 |
> + # Verify output parsing. |
133 |
> + call_mock.return_value = MockSubprocessPopen('\n'.join([ |
134 |
> + '# file: /some/file', |
135 |
> + 'user.foo="asdf"', |
136 |
> + '', |
137 |
> + ])) |
138 |
> + call_mock.reset() |
139 |
> + self.assertEqual(xattr.get('/some/file', 'user.foo'), b'"asdf"') |
140 |
> + |
141 |
> + def testGetAllBasic(self): |
142 |
> + """Verify the get_all() behavior""" |
143 |
> + xattr = self._setUp() |
144 |
> + with mock.patch.object(subprocess, 'Popen') as call_mock: |
145 |
> + # Verify basic behavior. |
146 |
> + xattr.get_all('/some/file') |
147 |
> + |
148 |
> + # Verify nofollow behavior. |
149 |
> + call_mock.reset() |
150 |
> + xattr.get_all('/some/file', nofollow=True) |
151 |
> + self.assertIn('-h', call_mock.call_args[0][0]) |
152 |
> + |
153 |
> + def testGetAllParsing(self): |
154 |
> + """Verify get_all() parses output sanely""" |
155 |
> + xattr = self._setUp() |
156 |
> + with mock.patch.object(subprocess, 'Popen') as call_mock: |
157 |
> + # Verify output parsing. |
158 |
> + call_mock.return_value = MockSubprocessPopen(self.OUTPUT) |
159 |
> + exp = [ |
160 |
> + (b'security.capability', b'0sAQAAAgAgAAAAAAAAAAAAAAAAAAA='), |
161 |
> + (b'user.foo', b'"asdf"'), |
162 |
> + ] |
163 |
> + self.assertEqual(exp, xattr.get_all('/some/file')) |
164 |
> + |
165 |
> + def testSetBasic(self): |
166 |
> + """Verify the set() behavior""" |
167 |
> + xattr = self._setUp() |
168 |
> + with mock.patch.object(subprocess, 'Popen') as call_mock: |
169 |
> + # Verify basic behavior, and namespace arg works as expected. |
170 |
> + xattr.set('/some/file', 'user.foo', 'bar') |
171 |
> + xattr.set('/some/file', 'foo', 'bar', namespace='user') |
172 |
> + self.assertEqual(call_mock.call_args_list[0], call_mock.call_args_list[1]) |
173 |
> + |
174 |
> + def testListBasic(self): |
175 |
> + """Verify the list() behavior""" |
176 |
> + xattr = self._setUp() |
177 |
> + with mock.patch.object(subprocess, 'Popen') as call_mock: |
178 |
> + # Verify basic behavior. |
179 |
> + xattr.list('/some/file') |
180 |
> + |
181 |
> + # Verify nofollow behavior. |
182 |
> + call_mock.reset() |
183 |
> + xattr.list('/some/file', nofollow=True) |
184 |
> + self.assertIn('-h', call_mock.call_args[0][0]) |
185 |
> + |
186 |
> + def testListParsing(self): |
187 |
> + """Verify list() parses output sanely""" |
188 |
> + xattr = self._setUp() |
189 |
> + with mock.patch.object(subprocess, 'Popen') as call_mock: |
190 |
> + # Verify output parsing. |
191 |
> + call_mock.return_value = MockSubprocessPopen(self.OUTPUT) |
192 |
> + exp = [b'security.capability', b'user.foo'] |
193 |
> + self.assertEqual(exp, xattr.list('/some/file')) |
194 |
> + |
195 |
> + def testRemoveBasic(self): |
196 |
> + """Verify the remove() behavior""" |
197 |
> + xattr = self._setUp() |
198 |
> + with mock.patch.object(subprocess, 'Popen') as call_mock: |
199 |
> + # Verify basic behavior, and namespace arg works as expected. |
200 |
> + xattr.remove('/some/file', 'user.foo') |
201 |
> + xattr.remove('/some/file', 'foo', namespace='user') |
202 |
> + self.assertEqual(call_mock.call_args_list[0], call_mock.call_args_list[1]) |
203 |
> + |
204 |
> + # Verify nofollow behavior. |
205 |
> + call_mock.reset() |
206 |
> + xattr.remove('/some/file', 'user.foo', nofollow=True) |
207 |
> + self.assertIn('-h', call_mock.call_args[0][0]) |
208 |
> + |
209 |
> + |
210 |
> +class StubTest(TestCase): |
211 |
> + """Test _XattrStub""" |
212 |
> + |
213 |
> + def testBasic(self): |
214 |
> + """Verify the stub is stubby""" |
215 |
> + # Would be nice to verify raised errno is OperationNotSupported. |
216 |
> + self.assertRaises(OSError, _XattrStub.get, '/', '') |
217 |
> + self.assertRaises(OSError, _XattrStub.set, '/', '', '') |
218 |
> + self.assertRaises(OSError, _XattrStub.get_all, '/') |
219 |
> + self.assertRaises(OSError, _XattrStub.remove, '/', '') |
220 |
> + self.assertRaises(OSError, _XattrStub.list, '/') |
221 |
> + |
222 |
> + |
223 |
> +class StandardTest(TestCase): |
224 |
> + """Test basic xattr API""" |
225 |
> + |
226 |
> + MODULES = (_xattr, _XattrSystemCommands, _XattrStub) |
227 |
> + FUNCS = ('get', 'get_all', 'set', 'remove', 'list') |
228 |
> + |
229 |
> + def testApi(self): |
230 |
> + """Make sure the exported API matches""" |
231 |
> + for mod in self.MODULES: |
232 |
> + for f in self.FUNCS: |
233 |
> + self.assertTrue(hasattr(mod, f), |
234 |
> + '%s func missing in %s' % (f, mod)) |
235 |
> diff --git a/pym/portage/util/_xattr.py b/pym/portage/util/_xattr.py |
236 |
> new file mode 100644 |
237 |
> index 0000000..db3cf6e |
238 |
> --- /dev/null |
239 |
> +++ b/pym/portage/util/_xattr.py |
240 |
> @@ -0,0 +1,205 @@ |
241 |
> +# Copyright 2010-2013 Gentoo Foundation |
242 |
> +# Distributed under the terms of the GNU General Public License v2 |
243 |
> + |
244 |
> +"""Portability shim for xattr support |
245 |
> + |
246 |
> +Exported API is the xattr object with get/get_all/set/remove/list operations. |
247 |
> + |
248 |
> +See the standard xattr module for more documentation. |
249 |
> +""" |
250 |
> + |
251 |
> +from __future__ import print_function |
252 |
> + |
253 |
> +import contextlib |
254 |
> +import os |
255 |
> +import subprocess |
256 |
> + |
257 |
> +from portage.exception import OperationNotSupported |
258 |
> + |
259 |
> + |
260 |
> +class _XattrGetAll(object): |
261 |
> + """Implement get_all() using list()/get() if there is no easy bulk method""" |
262 |
> + |
263 |
> + @classmethod |
264 |
> + def get_all(cls, item, nofollow=False, namespace=None): |
265 |
> + return [(name, cls.get(item, name, nofollow=nofollow, namespace=namespace)) |
266 |
> + for name in cls.list(item, nofollow=nofollow, namespace=namespace)] |
267 |
> + |
268 |
> + |
269 |
> +class _XattrSystemCommands(_XattrGetAll): |
270 |
> + """Implement things with getfattr/setfattr""" |
271 |
> + |
272 |
> + @staticmethod |
273 |
> + def _parse_output(output): |
274 |
> + for line in output.readlines(): |
275 |
> + if line.startswith(b'#'): |
276 |
> + continue |
277 |
> + line = line.rstrip() |
278 |
> + if not line: |
279 |
> + continue |
280 |
> + # The lines will have the format: |
281 |
> + # user.hex=0x12345 |
282 |
> + # user.base64=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA= |
283 |
> + # user.string="value0" |
284 |
> + # But since we don't do interpretation on the value (we just |
285 |
> + # save & restore it), don't bother with decoding here. |
286 |
> + yield line.split(b'=', 1) |
287 |
> + |
288 |
> + @staticmethod |
289 |
> + def _call(*args, **kwargs): |
290 |
> + proc = subprocess.Popen(*args, **kwargs) |
291 |
> + proc.stdin.close() |
292 |
|
293 |
AttributeError: 'NoneType' object has no attribute 'close' |
294 |
|
295 |
If closing stdin is necessary, then I suggest: |
296 |
|
297 |
if proc.stdin is not None: |
298 |
proc.stdin.close() |
299 |
|
300 |
> + proc.wait() |
301 |
> + return proc |
302 |
> + |
303 |
> + @classmethod |
304 |
> + def get(cls, item, name, nofollow=False, namespace=None): |
305 |
> + if namespace: |
306 |
> + name = '%s.%s' % (namespace, name) |
307 |
> + cmd = ['getfattr', '--absolute-names', '-n', name, item] |
308 |
> + if nofollow: |
309 |
> + cmd += ['-h'] |
310 |
> + proc = cls._call(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
311 |
> + |
312 |
> + value = None |
313 |
> + for _, value in cls._parse_output(proc.stdout): |
314 |
> + break |
315 |
> + |
316 |
> + proc.stdout.close() |
317 |
> + return value |
318 |
> + |
319 |
> + @classmethod |
320 |
> + def set(cls, item, name, value, _flags=0, namespace=None): |
321 |
> + if namespace: |
322 |
> + name = '%s.%s' % (namespace, name) |
323 |
> + cmd = ['setfattr', '-n', name, '-v', value, item] |
324 |
> + cls._call(cmd) |
325 |
> + |
326 |
> + @classmethod |
327 |
> + def remove(cls, item, name, nofollow=False, namespace=None): |
328 |
> + if namespace: |
329 |
> + name = '%s.%s' % (namespace, name) |
330 |
> + cmd = ['setfattr', '-x', name, item] |
331 |
> + if nofollow: |
332 |
> + cmd += ['-h'] |
333 |
> + cls._call(cmd) |
334 |
> + |
335 |
> + @classmethod |
336 |
> + def list(cls, item, nofollow=False, namespace=None, _names_only=True): |
337 |
> + cmd = ['getfattr', '-d', '--absolute-names', item] |
338 |
> + if nofollow: |
339 |
> + cmd += ['-h'] |
340 |
> + cmd += ['-m', ('^%s[.]' % namespace) if namespace else ''] |
341 |
> + proc = cls._call(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) |
342 |
> + |
343 |
> + ret = [] |
344 |
> + if namespace: |
345 |
> + namespace = '%s.' % namespace |
346 |
> + for name, value in cls._parse_output(proc.stdout): |
347 |
> + if namespace: |
348 |
> + if name.startswith(namespace): |
349 |
> + name = name[len(namespace):] |
350 |
> + else: |
351 |
> + continue |
352 |
> + if _names_only: |
353 |
> + ret.append(name) |
354 |
> + else: |
355 |
> + ret.append((name, value)) |
356 |
> + |
357 |
> + proc.stdout.close() |
358 |
> + return ret |
359 |
> + |
360 |
> + @classmethod |
361 |
> + def get_all(cls, item, nofollow=False, namespace=None): |
362 |
> + return cls.list(item, nofollow=nofollow, namespace=namespace, |
363 |
> + _names_only=False) |
364 |
> + |
365 |
> + |
366 |
> +# pylint: disable=W0613 |
367 |
> +class _XattrStub(_XattrGetAll): |
368 |
> + """Fake object since system doesn't support xattrs""" |
369 |
> + |
370 |
> + @staticmethod |
371 |
> + def _raise(): |
372 |
> + e = OSError('stub') |
373 |
> + e.errno = OperationNotSupported.errno |
374 |
> + raise e |
375 |
> + |
376 |
> + @classmethod |
377 |
> + def get(cls, item, name, nofollow=False, namespace=None): |
378 |
> + cls._raise() |
379 |
> + |
380 |
> + @classmethod |
381 |
> + def set(cls, item, name, value, flags=0, namespace=None): |
382 |
> + cls._raise() |
383 |
> + |
384 |
> + @classmethod |
385 |
> + def remove(cls, item, name, nofollow=False, namespace=None): |
386 |
> + cls._raise() |
387 |
> + |
388 |
> + @classmethod |
389 |
> + def list(cls, item, nofollow=False, namespace=None): |
390 |
> + cls._raise() |
391 |
> + |
392 |
> + |
393 |
> +if hasattr(os, 'getxattr'): |
394 |
> + # Easy as pie -- active python supports it. |
395 |
> + class xattr(_XattrGetAll): |
396 |
> + """Python >=3.3 and GNU/Linux""" |
397 |
> + get = os.getxattr |
398 |
> + set = os.setxattr |
399 |
> + remove = os.removexattr |
400 |
> + list = os.listxattr |
401 |
> + |
402 |
> +else: |
403 |
> + try: |
404 |
> + # Maybe we have the xattr module. |
405 |
> + import xattr |
406 |
> + |
407 |
> + except ImportError: |
408 |
> + try: |
409 |
> + # Maybe we have the attr package. |
410 |
> + with open(os.devnull, 'wb') as f: |
411 |
> + subprocess.call(['getfattr', '--version'], stdout=f) |
412 |
> + subprocess.call(['setfattr', '--version'], stdout=f) |
413 |
> + xattr = _XattrSystemCommands |
414 |
> + |
415 |
> + except OSError: |
416 |
> + # Stub it out completely. |
417 |
> + xattr = _XattrStub |
418 |
> + |
419 |
> + |
420 |
> +@××××××××××.contextmanager |
421 |
> +def preserve_xattrs(path, nofollow=False, namespace=None): |
422 |
> + """Context manager to save/restore extended attributes on |path| |
423 |
> + |
424 |
> + If you want to rewrite a file (possibly replacing it with a new one), but |
425 |
> + want to preserve the extended attributes, this will do the trick. |
426 |
> + |
427 |
> + # First read all the extended attributes. |
428 |
> + with save_xattrs('/some/file'): |
429 |
> + ... rewrite the file ... |
430 |
> + # Now the extended attributes are restored as needed. |
431 |
> + """ |
432 |
> + kwargs = {'nofollow': nofollow,} |
433 |
> + if namespace: |
434 |
> + # Compiled xattr python module does not like it when namespace=None. |
435 |
> + kwargs['namespace'] = namespace |
436 |
> + |
437 |
> + old_attrs = dict(xattr.get_all(path, **kwargs)) |
438 |
> + try: |
439 |
> + yield |
440 |
> + finally: |
441 |
> + new_attrs = dict(xattr.get_all(path, **kwargs)) |
442 |
> + for name, value in new_attrs.iteritems(): |
443 |
> + if name not in old_attrs: |
444 |
> + # Clear out new ones. |
445 |
> + xattr.remove(path, name, **kwargs) |
446 |
> + elif new_attrs[name] != old_attrs[name]: |
447 |
> + # Update changed ones. |
448 |
> + xattr.set(path, name, value, **kwargs) |
449 |
> + |
450 |
> + for name, value in old_attrs.iteritems(): |
451 |
> + if name not in new_attrs: |
452 |
> + # Re-add missing ones. |
453 |
> + xattr.set(path, name, value, **kwargs) |
454 |
> diff --git a/pym/portage/util/movefile.py b/pym/portage/util/movefile.py |
455 |
> index 452e77f..20859fe 100644 |
456 |
> --- a/pym/portage/util/movefile.py |
457 |
> +++ b/pym/portage/util/movefile.py |
458 |
> @@ -11,7 +11,6 @@ import os as _os |
459 |
> import shutil as _shutil |
460 |
> import stat |
461 |
> import sys |
462 |
> -import subprocess |
463 |
> import textwrap |
464 |
> |
465 |
> import portage |
466 |
> @@ -23,6 +22,7 @@ from portage.exception import OperationNotSupported |
467 |
> from portage.localization import _ |
468 |
> from portage.process import spawn |
469 |
> from portage.util import writemsg |
470 |
> +from portage.util._xattr import xattr |
471 |
> |
472 |
> def _apply_stat(src_stat, dest): |
473 |
> _os.chown(dest, src_stat.st_uid, src_stat.st_gid) |
474 |
> @@ -68,86 +68,32 @@ class _xattr_excluder(object): |
475 |
> |
476 |
> return False |
477 |
> |
478 |
> -if hasattr(_os, "getxattr"): |
479 |
> - # Python >=3.3 and GNU/Linux |
480 |
> - def _copyxattr(src, dest, exclude=None): |
481 |
> - |
482 |
> - try: |
483 |
> - attrs = _os.listxattr(src) |
484 |
> - except OSError as e: |
485 |
> - if e.errno != OperationNotSupported.errno: |
486 |
> - raise |
487 |
> - attrs = () |
488 |
> - if attrs: |
489 |
> - if exclude is not None and isinstance(attrs[0], bytes): |
490 |
> - exclude = exclude.encode(_encodings['fs']) |
491 |
> - exclude = _get_xattr_excluder(exclude) |
492 |
> - |
493 |
> - for attr in attrs: |
494 |
> - if exclude(attr): |
495 |
> - continue |
496 |
> - try: |
497 |
> - _os.setxattr(dest, attr, _os.getxattr(src, attr)) |
498 |
> - raise_exception = False |
499 |
> - except OSError: |
500 |
> - raise_exception = True |
501 |
> - if raise_exception: |
502 |
> - raise OperationNotSupported(_("Filesystem containing file '%s' " |
503 |
> - "does not support extended attribute '%s'") % |
504 |
> - (_unicode_decode(dest), _unicode_decode(attr))) |
505 |
> -else: |
506 |
> +def _copyxattr(src, dest, exclude=None): |
507 |
> + """Copy the extended attributes from |src| to |dest|""" |
508 |
> try: |
509 |
> - import xattr |
510 |
> - except ImportError: |
511 |
> - xattr = None |
512 |
> - if xattr is not None: |
513 |
> - def _copyxattr(src, dest, exclude=None): |
514 |
> - |
515 |
> - try: |
516 |
> - attrs = xattr.list(src) |
517 |
> - except IOError as e: |
518 |
> - if e.errno != OperationNotSupported.errno: |
519 |
> - raise |
520 |
> - attrs = () |
521 |
> + attrs = xattr.list(src) |
522 |
> + except (OSError, IOError) as e: |
523 |
> + if e.errno != OperationNotSupported.errno: |
524 |
> + raise |
525 |
> + attrs = () |
526 |
> |
527 |
> - if attrs: |
528 |
> - if exclude is not None and isinstance(attrs[0], bytes): |
529 |
> - exclude = exclude.encode(_encodings['fs']) |
530 |
> - exclude = _get_xattr_excluder(exclude) |
531 |
> + if attrs: |
532 |
> + if exclude is not None and isinstance(attrs[0], bytes): |
533 |
> + exclude = exclude.encode(_encodings['fs']) |
534 |
> + exclude = _get_xattr_excluder(exclude) |
535 |
> |
536 |
> - for attr in attrs: |
537 |
> - if exclude(attr): |
538 |
> - continue |
539 |
> - try: |
540 |
> - xattr.set(dest, attr, xattr.get(src, attr)) |
541 |
> - raise_exception = False |
542 |
> - except IOError: |
543 |
> - raise_exception = True |
544 |
> - if raise_exception: |
545 |
> - raise OperationNotSupported(_("Filesystem containing file '%s' " |
546 |
> - "does not support extended attribute '%s'") % |
547 |
> - (_unicode_decode(dest), _unicode_decode(attr))) |
548 |
> - else: |
549 |
> + for attr in attrs: |
550 |
> + if exclude(attr): |
551 |
> + continue |
552 |
> try: |
553 |
> - with open(_os.devnull, 'wb') as f: |
554 |
> - subprocess.call(["getfattr", "--version"], stdout=f) |
555 |
> - subprocess.call(["setfattr", "--version"], stdout=f) |
556 |
> - except OSError: |
557 |
> - def _copyxattr(src, dest, exclude=None): |
558 |
> - # TODO: implement exclude |
559 |
> - getfattr_process = subprocess.Popen(["getfattr", "-d", "--absolute-names", src], stdout=subprocess.PIPE) |
560 |
> - getfattr_process.wait() |
561 |
> - extended_attributes = getfattr_process.stdout.readlines() |
562 |
> - getfattr_process.stdout.close() |
563 |
> - if extended_attributes: |
564 |
> - extended_attributes[0] = b"# file: " + _unicode_encode(dest) + b"\n" |
565 |
> - setfattr_process = subprocess.Popen(["setfattr", "--restore=-"], stdin=subprocess.PIPE, stderr=subprocess.PIPE) |
566 |
> - setfattr_process.communicate(input=b"".join(extended_attributes)) |
567 |
> - if setfattr_process.returncode != 0: |
568 |
> - raise OperationNotSupported("Filesystem containing file '%s' does not support extended attributes" % dest) |
569 |
> - else: |
570 |
> - def _copyxattr(src, dest, exclude=None): |
571 |
> - pass |
572 |
> + xattr.set(dest, attr, xattr.get(src, attr)) |
573 |
> + raise_exception = False |
574 |
> + except (OSError, IOError): |
575 |
> + raise_exception = True |
576 |
> + if raise_exception: |
577 |
> + raise OperationNotSupported(_("Filesystem containing file '%s' " |
578 |
> + "does not support extended attribute '%s'") % |
579 |
> + (_unicode_decode(dest), _unicode_decode(attr))) |
580 |
> |
581 |
> def movefile(src, dest, newmtime=None, sstat=None, mysettings=None, |
582 |
> hardlink_candidates=None, encoding=_encodings['fs']): |
583 |
> |
584 |
|
585 |
-- |
586 |
Arfrever Frehtes Taifersar Arahesis |