Gentoo Archives: gentoo-portage-dev

From: Mike Frysinger <vapier@g.o>
To: gentoo-portage-dev@l.g.o
Subject: [gentoo-portage-dev] [PATCH] xattr: centralize the various shims in one place
Date: Wed, 16 Oct 2013 21:03:29
Message-Id: 1381957406-21749-1-git-send-email-vapier@gentoo.org
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

Replies