Gentoo Archives: gentoo-portage-dev

From: Mike Frysinger <vapier@g.o>
To: gentoo-portage-dev@l.g.o
Subject: [gentoo-portage-dev] [PATCH v4] xattr: centralize the various shims in one place
Date: Sat, 30 May 2015 20:59:42
Message-Id: 1433019576-24115-1-git-send-email-vapier@gentoo.org
In Reply to: [gentoo-portage-dev] [PATCH] xattr: centralize the various shims in one place by Mike Frysinger
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 v4
6 - merge in recent quickpkg changes
7 - add a XATTRS_WORKS symbol for easy testing
8 - use - with -m when matching all
9
10 bin/quickpkg | 12 +-
11 bin/xattr-helper.py | 11 +-
12 pym/portage/dbapi/vartree.py | 10 +-
13 pym/portage/tests/util/test_xattr.py | 178 +++++++++++++++++++++++++++
14 pym/portage/util/_xattr.py | 228 +++++++++++++++++++++++++++++++++++
15 pym/portage/util/movefile.py | 100 ++++-----------
16 pym/portage/util/xattr.py | 20 ---
17 7 files changed, 441 insertions(+), 118 deletions(-)
18 create mode 100644 pym/portage/tests/util/test_xattr.py
19 create mode 100644 pym/portage/util/_xattr.py
20 delete mode 100644 pym/portage/util/xattr.py
21
22 diff --git a/bin/quickpkg b/bin/quickpkg
23 index 726abff..262fda4 100755
24 --- a/bin/quickpkg
25 +++ b/bin/quickpkg
26 @@ -21,8 +21,8 @@ from portage.dbapi.dep_expand import dep_expand
27 from portage.dep import Atom, use_reduce
28 from portage.exception import (AmbiguousPackageName, InvalidAtom, InvalidData,
29 InvalidDependString, PackageSetNotFound, PermissionDenied)
30 -from portage.util import ConfigProtect, ensure_dirs, shlex_split
31 -import portage.util.xattr as _xattr
32 +from portage.util import ConfigProtect, ensure_dirs, shlex_split, _xattr
33 +xattr = _xattr.xattr
34 from portage.dbapi.vartree import dblink, tar_contents
35 from portage.checksum import perform_md5
36 from portage._sets import load_default_config, SETPREFIX
37 @@ -36,7 +36,7 @@ def quickpkg_atom(options, infos, arg, eout):
38 vartree = trees["vartree"]
39 vardb = vartree.dbapi
40 bintree = trees["bintree"]
41 - xattr = 'xattr' in settings.features
42 + xattrs = 'xattr' in settings.features
43
44 include_config = options.include_config == "y"
45 include_unmodified_config = options.include_unmodified_config == "y"
46 @@ -137,8 +137,8 @@ def quickpkg_atom(options, infos, arg, eout):
47 # The tarfile module will write pax headers holding the
48 # xattrs only if PAX_FORMAT is specified here.
49 tar = tarfile.open(binpkg_tmpfile, "w:bz2",
50 - format=tarfile.PAX_FORMAT if xattr else tarfile.DEFAULT_FORMAT)
51 - tar_contents(contents, root, tar, protect=protect, xattr=xattr)
52 + format=tarfile.PAX_FORMAT if xattrs else tarfile.DEFAULT_FORMAT)
53 + tar_contents(contents, root, tar, protect=protect, xattrs=xattrs)
54 tar.close()
55 xpak.tbz2(binpkg_tmpfile).recompose_mem(xpdata)
56 finally:
57 @@ -238,7 +238,7 @@ def quickpkg_main(options, args, eout):
58 eout.eerror("No write access to '%s'" % bintree.pkgdir)
59 return errno.EACCES
60
61 - if 'xattr' in portage.settings.features and not hasattr(_xattr, 'getxattr'):
62 + if 'xattr' in portage.settings.features and not _xattr.XATTRS_WORKS:
63 eout.eerror("No xattr support library was found, "
64 "so xattrs will not be preserved!")
65 portage.settings.unlock()
66 diff --git a/bin/xattr-helper.py b/bin/xattr-helper.py
67 index 3e9b81e..19f25f9 100755
68 --- a/bin/xattr-helper.py
69 +++ b/bin/xattr-helper.py
70 @@ -19,16 +19,7 @@ import re
71 import sys
72
73 from portage.util._argparse import ArgumentParser
74 -
75 -if hasattr(os, "getxattr"):
76 -
77 - class xattr(object):
78 - get = os.getxattr
79 - set = os.setxattr
80 - list = os.listxattr
81 -
82 -else:
83 - import xattr
84 +from portage.util._xattr import xattr
85
86
87 _UNQUOTE_RE = re.compile(br'\\[0-7]{3}')
88 diff --git a/pym/portage/dbapi/vartree.py b/pym/portage/dbapi/vartree.py
89 index 62d880e..e755fde 100644
90 --- a/pym/portage/dbapi/vartree.py
91 +++ b/pym/portage/dbapi/vartree.py
92 @@ -35,7 +35,7 @@ portage.proxy.lazyimport.lazyimport(globals(),
93 'portage.util.movefile:movefile',
94 'portage.util.path:first_existing,iter_parents',
95 'portage.util.writeable_check:get_ro_checker',
96 - 'portage.util:xattr@_xattr',
97 + 'portage.util._xattr:xattr',
98 'portage.util._dyn_libs.PreservedLibsRegistry:PreservedLibsRegistry',
99 'portage.util._dyn_libs.LinkageMapELF:LinkageMapELF@LinkageMap',
100 'portage.util._async.SchedulerInterface:SchedulerInterface',
101 @@ -5268,7 +5268,7 @@ def write_contents(contents, root, f):
102 f.write(line)
103
104 def tar_contents(contents, root, tar, protect=None, onProgress=None,
105 - xattr=False):
106 + xattrs=False):
107 os = _os_merge
108 encoding = _encodings['merge']
109
110 @@ -5390,13 +5390,13 @@ def tar_contents(contents, root, tar, protect=None, onProgress=None,
111 encoding=encoding,
112 errors='strict')
113
114 - if xattr:
115 + if xattrs:
116 # Compatible with GNU tar, which saves the xattrs
117 # under the SCHILY.xattr namespace.
118 - for k in _xattr.listxattr(path_bytes):
119 + for k in xattr.list(path_bytes):
120 tarinfo.pax_headers['SCHILY.xattr.' +
121 _unicode_decode(k)] = _unicode_decode(
122 - _xattr.getxattr(path_bytes, _unicode_encode(k)))
123 + xattr.get(path_bytes, _unicode_encode(k)))
124
125 with open(path_bytes, 'rb') as f:
126 tar.addfile(tarinfo, f)
127 diff --git a/pym/portage/tests/util/test_xattr.py b/pym/portage/tests/util/test_xattr.py
128 new file mode 100644
129 index 0000000..2e2564a
130 --- /dev/null
131 +++ b/pym/portage/tests/util/test_xattr.py
132 @@ -0,0 +1,178 @@
133 +# Copyright 2010-2015 Gentoo Foundation
134 +# Distributed under the terms of the GNU General Public License v2
135 +
136 +"""Tests for the portage.util._xattr module"""
137 +
138 +from __future__ import print_function
139 +
140 +try:
141 + # Try python-3.3 module first.
142 + # pylint: disable=no-name-in-module
143 + from unittest import mock
144 +except ImportError:
145 + try:
146 + # Try standalone module.
147 + import mock
148 + except ImportError:
149 + mock = None
150 +
151 +import subprocess
152 +
153 +import portage
154 +from portage.tests import TestCase
155 +from portage.util._xattr import (xattr as _xattr, _XattrSystemCommands,
156 + _XattrStub)
157 +
158 +
159 +orig_popen = subprocess.Popen
160 +def MockSubprocessPopen(stdin):
161 + """Helper to mock (closely) a subprocess.Popen call
162 +
163 + The module has minor tweaks in behavior when it comes to encoding and
164 + python versions, so use a real subprocess.Popen call to fake out the
165 + runtime behavior. This way we don't have to also implement different
166 + encodings as that gets ugly real fast.
167 + """
168 + # pylint: disable=protected-access
169 + proc = orig_popen(['cat'], stdout=subprocess.PIPE, stdin=subprocess.PIPE)
170 + proc.stdin.write(portage._unicode_encode(stdin, portage._encodings['stdio']))
171 + return proc
172 +
173 +
174 +class SystemCommandsTest(TestCase):
175 + """Test _XattrSystemCommands"""
176 +
177 + OUTPUT = '\n'.join((
178 + '# file: /bin/ping',
179 + 'security.capability=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=',
180 + 'user.foo="asdf"',
181 + '',
182 + ))
183 +
184 + def _setUp(self):
185 + if mock is None:
186 + self.skipTest('need mock for testing')
187 +
188 + return _XattrSystemCommands
189 +
190 + def _testGetBasic(self):
191 + """Verify the get() behavior"""
192 + xattr = self._setUp()
193 + with mock.patch.object(subprocess, 'Popen') as call_mock:
194 + # Verify basic behavior, and namespace arg works as expected.
195 + xattr.get('/some/file', 'user.foo')
196 + xattr.get('/some/file', 'foo', namespace='user')
197 + self.assertEqual(call_mock.call_args_list[0], call_mock.call_args_list[1])
198 +
199 + # Verify nofollow behavior.
200 + call_mock.reset()
201 + xattr.get('/some/file', 'user.foo', nofollow=True)
202 + self.assertIn('-h', call_mock.call_args[0][0])
203 +
204 + def testGetParsing(self):
205 + """Verify get() parses output sanely"""
206 + xattr = self._setUp()
207 + with mock.patch.object(subprocess, 'Popen') as call_mock:
208 + # Verify output parsing.
209 + call_mock.return_value = MockSubprocessPopen('\n'.join([
210 + '# file: /some/file',
211 + 'user.foo="asdf"',
212 + '',
213 + ]))
214 + call_mock.reset()
215 + self.assertEqual(xattr.get('/some/file', 'user.foo'), b'"asdf"')
216 +
217 + def testGetAllBasic(self):
218 + """Verify the get_all() behavior"""
219 + xattr = self._setUp()
220 + with mock.patch.object(subprocess, 'Popen') as call_mock:
221 + # Verify basic behavior.
222 + xattr.get_all('/some/file')
223 +
224 + # Verify nofollow behavior.
225 + call_mock.reset()
226 + xattr.get_all('/some/file', nofollow=True)
227 + self.assertIn('-h', call_mock.call_args[0][0])
228 +
229 + def testGetAllParsing(self):
230 + """Verify get_all() parses output sanely"""
231 + xattr = self._setUp()
232 + with mock.patch.object(subprocess, 'Popen') as call_mock:
233 + # Verify output parsing.
234 + call_mock.return_value = MockSubprocessPopen(self.OUTPUT)
235 + exp = [
236 + (b'security.capability', b'0sAQAAAgAgAAAAAAAAAAAAAAAAAAA='),
237 + (b'user.foo', b'"asdf"'),
238 + ]
239 + self.assertEqual(exp, xattr.get_all('/some/file'))
240 +
241 + def testSetBasic(self):
242 + """Verify the set() behavior"""
243 + xattr = self._setUp()
244 + with mock.patch.object(subprocess, 'Popen') as call_mock:
245 + # Verify basic behavior, and namespace arg works as expected.
246 + xattr.set('/some/file', 'user.foo', 'bar')
247 + xattr.set('/some/file', 'foo', 'bar', namespace='user')
248 + self.assertEqual(call_mock.call_args_list[0], call_mock.call_args_list[1])
249 +
250 + def testListBasic(self):
251 + """Verify the list() behavior"""
252 + xattr = self._setUp()
253 + with mock.patch.object(subprocess, 'Popen') as call_mock:
254 + # Verify basic behavior.
255 + xattr.list('/some/file')
256 +
257 + # Verify nofollow behavior.
258 + call_mock.reset()
259 + xattr.list('/some/file', nofollow=True)
260 + self.assertIn('-h', call_mock.call_args[0][0])
261 +
262 + def testListParsing(self):
263 + """Verify list() parses output sanely"""
264 + xattr = self._setUp()
265 + with mock.patch.object(subprocess, 'Popen') as call_mock:
266 + # Verify output parsing.
267 + call_mock.return_value = MockSubprocessPopen(self.OUTPUT)
268 + exp = [b'security.capability', b'user.foo']
269 + self.assertEqual(exp, xattr.list('/some/file'))
270 +
271 + def testRemoveBasic(self):
272 + """Verify the remove() behavior"""
273 + xattr = self._setUp()
274 + with mock.patch.object(subprocess, 'Popen') as call_mock:
275 + # Verify basic behavior, and namespace arg works as expected.
276 + xattr.remove('/some/file', 'user.foo')
277 + xattr.remove('/some/file', 'foo', namespace='user')
278 + self.assertEqual(call_mock.call_args_list[0], call_mock.call_args_list[1])
279 +
280 + # Verify nofollow behavior.
281 + call_mock.reset()
282 + xattr.remove('/some/file', 'user.foo', nofollow=True)
283 + self.assertIn('-h', call_mock.call_args[0][0])
284 +
285 +
286 +class StubTest(TestCase):
287 + """Test _XattrStub"""
288 +
289 + def testBasic(self):
290 + """Verify the stub is stubby"""
291 + # Would be nice to verify raised errno is OperationNotSupported.
292 + self.assertRaises(OSError, _XattrStub.get, '/', '')
293 + self.assertRaises(OSError, _XattrStub.set, '/', '', '')
294 + self.assertRaises(OSError, _XattrStub.get_all, '/')
295 + self.assertRaises(OSError, _XattrStub.remove, '/', '')
296 + self.assertRaises(OSError, _XattrStub.list, '/')
297 +
298 +
299 +class StandardTest(TestCase):
300 + """Test basic xattr API"""
301 +
302 + MODULES = (_xattr, _XattrSystemCommands, _XattrStub)
303 + FUNCS = ('get', 'get_all', 'set', 'remove', 'list')
304 +
305 + def testApi(self):
306 + """Make sure the exported API matches"""
307 + for mod in self.MODULES:
308 + for f in self.FUNCS:
309 + self.assertTrue(hasattr(mod, f),
310 + '%s func missing in %s' % (f, mod))
311 diff --git a/pym/portage/util/_xattr.py b/pym/portage/util/_xattr.py
312 new file mode 100644
313 index 0000000..9a8704d
314 --- /dev/null
315 +++ b/pym/portage/util/_xattr.py
316 @@ -0,0 +1,228 @@
317 +# Copyright 2010-2015 Gentoo Foundation
318 +# Distributed under the terms of the GNU General Public License v2
319 +
320 +"""Portability shim for xattr support
321 +
322 +Exported API is the xattr object with get/get_all/set/remove/list operations.
323 +We do not include the functions that Python 3.3+ provides in the os module as
324 +the signature there is different compared to xattr.
325 +
326 +See the standard xattr module for more documentation:
327 + https://pypi.python.org/pypi/pyxattr
328 +"""
329 +
330 +from __future__ import print_function
331 +
332 +import contextlib
333 +import os
334 +import subprocess
335 +
336 +from portage.exception import OperationNotSupported
337 +
338 +
339 +class _XattrGetAll(object):
340 + """Implement get_all() using list()/get() if there is no easy bulk method"""
341 +
342 + @classmethod
343 + def get_all(cls, item, nofollow=False, namespace=None):
344 + return [(name, cls.get(item, name, nofollow=nofollow, namespace=namespace))
345 + for name in cls.list(item, nofollow=nofollow, namespace=namespace)]
346 +
347 +
348 +class _XattrSystemCommands(_XattrGetAll):
349 + """Implement things with getfattr/setfattr"""
350 +
351 + @staticmethod
352 + def _parse_output(output):
353 + for line in output.readlines():
354 + if line.startswith(b'#'):
355 + continue
356 + line = line.rstrip()
357 + if not line:
358 + continue
359 + # The lines will have the format:
360 + # user.hex=0x12345
361 + # user.base64=0sAQAAAgAgAAAAAAAAAAAAAAAAAAA=
362 + # user.string="value0"
363 + # But since we don't do interpretation on the value (we just
364 + # save & restore it), don't bother with decoding here.
365 + yield line.split(b'=', 1)
366 +
367 + @staticmethod
368 + def _call(*args, **kwargs):
369 + proc = subprocess.Popen(*args, **kwargs)
370 + if proc.stdin:
371 + proc.stdin.close()
372 + proc.wait()
373 + return proc
374 +
375 + @classmethod
376 + def get(cls, item, name, nofollow=False, namespace=None):
377 + if namespace:
378 + name = '%s.%s' % (namespace, name)
379 + cmd = ['getfattr', '--absolute-names', '-n', name, item]
380 + if nofollow:
381 + cmd += ['-h']
382 + proc = cls._call(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
383 +
384 + value = None
385 + for _, value in cls._parse_output(proc.stdout):
386 + break
387 +
388 + proc.stdout.close()
389 + return value
390 +
391 + @classmethod
392 + def set(cls, item, name, value, _flags=0, namespace=None):
393 + if namespace:
394 + name = '%s.%s' % (namespace, name)
395 + cmd = ['setfattr', '-n', name, '-v', value, item]
396 + cls._call(cmd)
397 +
398 + @classmethod
399 + def remove(cls, item, name, nofollow=False, namespace=None):
400 + if namespace:
401 + name = '%s.%s' % (namespace, name)
402 + cmd = ['setfattr', '-x', name, item]
403 + if nofollow:
404 + cmd += ['-h']
405 + cls._call(cmd)
406 +
407 + @classmethod
408 + def list(cls, item, nofollow=False, namespace=None, _names_only=True):
409 + cmd = ['getfattr', '-d', '--absolute-names', item]
410 + if nofollow:
411 + cmd += ['-h']
412 + cmd += ['-m', ('^%s[.]' % namespace) if namespace else '-']
413 + proc = cls._call(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
414 +
415 + ret = []
416 + if namespace:
417 + namespace = '%s.' % namespace
418 + for name, value in cls._parse_output(proc.stdout):
419 + if namespace:
420 + if name.startswith(namespace):
421 + name = name[len(namespace):]
422 + else:
423 + continue
424 + if _names_only:
425 + ret.append(name)
426 + else:
427 + ret.append((name, value))
428 +
429 + proc.stdout.close()
430 + return ret
431 +
432 + @classmethod
433 + def get_all(cls, item, nofollow=False, namespace=None):
434 + return cls.list(item, nofollow=nofollow, namespace=namespace,
435 + _names_only=False)
436 +
437 +
438 +class _XattrStub(_XattrGetAll):
439 + """Fake object since system doesn't support xattrs"""
440 +
441 + # pylint: disable=unused-argument
442 +
443 + @staticmethod
444 + def _raise():
445 + e = OSError('stub')
446 + e.errno = OperationNotSupported.errno
447 + raise e
448 +
449 + @classmethod
450 + def get(cls, item, name, nofollow=False, namespace=None):
451 + cls._raise()
452 +
453 + @classmethod
454 + def set(cls, item, name, value, flags=0, namespace=None):
455 + cls._raise()
456 +
457 + @classmethod
458 + def remove(cls, item, name, nofollow=False, namespace=None):
459 + cls._raise()
460 +
461 + @classmethod
462 + def list(cls, item, nofollow=False, namespace=None):
463 + cls._raise()
464 +
465 +
466 +if hasattr(os, 'getxattr'):
467 + # Easy as pie -- active python supports it.
468 + class xattr(_XattrGetAll):
469 + """Python >=3.3 and GNU/Linux"""
470 +
471 + # pylint: disable=unused-argument
472 +
473 + @staticmethod
474 + def get(item, name, nofollow=False, namespace=None):
475 + return os.getxattr(item, name, follow_symlinks=not nofollow)
476 +
477 + @staticmethod
478 + def set(item, name, value, flags=0, namespace=None):
479 + return os.setxattr(item, name, value, flags=flags)
480 +
481 + @staticmethod
482 + def remove(item, name, nofollow=False, namespace=None):
483 + return os.removexattr(item, name, follow_symlinks=not nofollow)
484 +
485 + @staticmethod
486 + def list(item, nofollow=False, namespace=None):
487 + return os.listxattr(item, follow_symlinks=not nofollow)
488 +
489 +else:
490 + try:
491 + # Maybe we have the xattr module.
492 + import xattr
493 +
494 + except ImportError:
495 + try:
496 + # Maybe we have the attr package.
497 + with open(os.devnull, 'wb') as f:
498 + subprocess.call(['getfattr', '--version'], stdout=f)
499 + subprocess.call(['setfattr', '--version'], stdout=f)
500 + xattr = _XattrSystemCommands
501 +
502 + except OSError:
503 + # Stub it out completely.
504 + xattr = _XattrStub
505 +
506 +
507 +# Add a knob so code can take evasive action as needed.
508 +XATTRS_WORKS = xattr != _XattrStub
509 +
510 +
511 +@××××××××××.contextmanager
512 +def preserve_xattrs(path, nofollow=False, namespace=None):
513 + """Context manager to save/restore extended attributes on |path|
514 +
515 + If you want to rewrite a file (possibly replacing it with a new one), but
516 + want to preserve the extended attributes, this will do the trick.
517 +
518 + # First read all the extended attributes.
519 + with save_xattrs('/some/file'):
520 + ... rewrite the file ...
521 + # Now the extended attributes are restored as needed.
522 + """
523 + kwargs = {'nofollow': nofollow,}
524 + if namespace:
525 + # Compiled xattr python module does not like it when namespace=None.
526 + kwargs['namespace'] = namespace
527 +
528 + old_attrs = dict(xattr.get_all(path, **kwargs))
529 + try:
530 + yield
531 + finally:
532 + new_attrs = dict(xattr.get_all(path, **kwargs))
533 + for name, value in new_attrs.items():
534 + if name not in old_attrs:
535 + # Clear out new ones.
536 + xattr.remove(path, name, **kwargs)
537 + elif new_attrs[name] != old_attrs[name]:
538 + # Update changed ones.
539 + xattr.set(path, name, value, **kwargs)
540 +
541 + for name, value in old_attrs.items():
542 + if name not in new_attrs:
543 + # Re-add missing ones.
544 + xattr.set(path, name, value, **kwargs)
545 diff --git a/pym/portage/util/movefile.py b/pym/portage/util/movefile.py
546 index d00f624..0cb1977 100644
547 --- a/pym/portage/util/movefile.py
548 +++ b/pym/portage/util/movefile.py
549 @@ -11,7 +11,6 @@ import os as _os
550 import shutil as _shutil
551 import stat
552 import sys
553 -import subprocess
554 import textwrap
555
556 import portage
557 @@ -23,6 +22,7 @@ from portage.exception import OperationNotSupported
558 from portage.localization import _
559 from portage.process import spawn
560 from portage.util import writemsg
561 +from portage.util._xattr import xattr
562
563 def _apply_stat(src_stat, dest):
564 _os.chown(dest, src_stat.st_uid, src_stat.st_gid)
565 @@ -68,86 +68,32 @@ class _xattr_excluder(object):
566
567 return False
568
569 -if hasattr(_os, "getxattr"):
570 - # Python >=3.3 and GNU/Linux
571 - def _copyxattr(src, dest, exclude=None):
572 -
573 - try:
574 - attrs = _os.listxattr(src)
575 - except OSError as e:
576 - if e.errno != OperationNotSupported.errno:
577 - raise
578 - attrs = ()
579 - if attrs:
580 - if exclude is not None and isinstance(attrs[0], bytes):
581 - exclude = exclude.encode(_encodings['fs'])
582 - exclude = _get_xattr_excluder(exclude)
583 -
584 - for attr in attrs:
585 - if exclude(attr):
586 - continue
587 - try:
588 - _os.setxattr(dest, attr, _os.getxattr(src, attr))
589 - raise_exception = False
590 - except OSError:
591 - raise_exception = True
592 - if raise_exception:
593 - raise OperationNotSupported(_("Filesystem containing file '%s' "
594 - "does not support extended attribute '%s'") %
595 - (_unicode_decode(dest), _unicode_decode(attr)))
596 -else:
597 +def _copyxattr(src, dest, exclude=None):
598 + """Copy the extended attributes from |src| to |dest|"""
599 try:
600 - import xattr
601 - except ImportError:
602 - xattr = None
603 - if xattr is not None:
604 - def _copyxattr(src, dest, exclude=None):
605 -
606 - try:
607 - attrs = xattr.list(src)
608 - except IOError as e:
609 - if e.errno != OperationNotSupported.errno:
610 - raise
611 - attrs = ()
612 + attrs = xattr.list(src)
613 + except (OSError, IOError) as e:
614 + if e.errno != OperationNotSupported.errno:
615 + raise
616 + attrs = ()
617
618 - if attrs:
619 - if exclude is not None and isinstance(attrs[0], bytes):
620 - exclude = exclude.encode(_encodings['fs'])
621 - exclude = _get_xattr_excluder(exclude)
622 + if attrs:
623 + if exclude is not None and isinstance(attrs[0], bytes):
624 + exclude = exclude.encode(_encodings['fs'])
625 + exclude = _get_xattr_excluder(exclude)
626
627 - for attr in attrs:
628 - if exclude(attr):
629 - continue
630 - try:
631 - xattr.set(dest, attr, xattr.get(src, attr))
632 - raise_exception = False
633 - except IOError:
634 - raise_exception = True
635 - if raise_exception:
636 - raise OperationNotSupported(_("Filesystem containing file '%s' "
637 - "does not support extended attribute '%s'") %
638 - (_unicode_decode(dest), _unicode_decode(attr)))
639 - else:
640 + for attr in attrs:
641 + if exclude(attr):
642 + continue
643 try:
644 - with open(_os.devnull, 'wb') as f:
645 - subprocess.call(["getfattr", "--version"], stdout=f)
646 - subprocess.call(["setfattr", "--version"], stdout=f)
647 - except OSError:
648 - def _copyxattr(src, dest, exclude=None):
649 - # TODO: implement exclude
650 - getfattr_process = subprocess.Popen(["getfattr", "-d", "--absolute-names", src], stdout=subprocess.PIPE)
651 - getfattr_process.wait()
652 - extended_attributes = getfattr_process.stdout.readlines()
653 - getfattr_process.stdout.close()
654 - if extended_attributes:
655 - extended_attributes[0] = b"# file: " + _unicode_encode(dest) + b"\n"
656 - setfattr_process = subprocess.Popen(["setfattr", "--restore=-"], stdin=subprocess.PIPE, stderr=subprocess.PIPE)
657 - setfattr_process.communicate(input=b"".join(extended_attributes))
658 - if setfattr_process.returncode != 0:
659 - raise OperationNotSupported("Filesystem containing file '%s' does not support extended attributes" % dest)
660 - else:
661 - def _copyxattr(src, dest, exclude=None):
662 - pass
663 + xattr.set(dest, attr, xattr.get(src, attr))
664 + raise_exception = False
665 + except (OSError, IOError):
666 + raise_exception = True
667 + if raise_exception:
668 + raise OperationNotSupported(_("Filesystem containing file '%s' "
669 + "does not support extended attribute '%s'") %
670 + (_unicode_decode(dest), _unicode_decode(attr)))
671
672 def movefile(src, dest, newmtime=None, sstat=None, mysettings=None,
673 hardlink_candidates=None, encoding=_encodings['fs']):
674 diff --git a/pym/portage/util/xattr.py b/pym/portage/util/xattr.py
675 deleted file mode 100644
676 index b8c4620..0000000
677 --- a/pym/portage/util/xattr.py
678 +++ /dev/null
679 @@ -1,20 +0,0 @@
680 -# Copyright 2015 Gentoo Foundation
681 -# Distributed under the terms of the GNU General Public License v2
682 -
683 -from __future__ import absolute_import
684 -
685 -import os as _os
686 -
687 -if hasattr(_os, "getxattr"):
688 - getxattr = _os.getxattr
689 - listxattr = _os.listxattr
690 - setxattr = _os.setxattr
691 -else:
692 - try:
693 - import xattr as _xattr
694 - except ImportError:
695 - pass
696 - else:
697 - getxattr = _xattr.get
698 - listxattr = _xattr.list
699 - setxattr = _xattr.set
700 --
701 2.4.1

Replies