diff options
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | CHANGES.txt | 5 | ||||
-rw-r--r-- | _build/_make_constants.py | 20 | ||||
-rw-r--r-- | ez_setup.py | 284 | ||||
-rw-r--r-- | requirements.txt | 1 | ||||
-rw-r--r-- | setup.py | 28 | ||||
-rw-r--r-- | xattr/__init__.py | 74 | ||||
-rw-r--r-- | xattr/constants.py | 16 | ||||
-rw-r--r-- | xattr/lib.py (renamed from xattr/_xattr.c) | 639 | ||||
-rw-r--r-- | xattr/tests/__init__.py | 6 | ||||
-rw-r--r-- | xattr/tests/test_xattr.py | 24 | ||||
-rwxr-xr-x | xattr/tool.py | 33 |
12 files changed, 295 insertions, 837 deletions
@@ -1,6 +1,8 @@ /xattr.egg-info /build /dist +/*.egg *.pyc *.so .\#* +__pycache__ diff --git a/CHANGES.txt b/CHANGES.txt index bfcb0ad..4e5f24a 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -1,3 +1,8 @@ +Version 0.7.0 released 2013-07-19 + +* Rewritten to use cffi + https://github.com/xattr/xattr/pull/11 + Version 0.6.4 released 2012-02-01 * Updated README.txt to match setup.py description diff --git a/_build/_make_constants.py b/_build/_make_constants.py deleted file mode 100644 index c73e8c1..0000000 --- a/_build/_make_constants.py +++ /dev/null @@ -1,20 +0,0 @@ -infile = '/usr/include/sys/xattr.h' -out = file('Lib/xattr/constants.py', 'w') -for line in file(infile): - line = line.rstrip() - if line.startswith('/*') and line.endswith('*/'): - print >>out, '' - print >>out, '# ' + line[2:-2].strip() - elif line.startswith('#define'): - if lastblank: - print >>out, '' - chunks = line.split(None, 3) - if len(chunks) == 3: - print >>out, '%s = %s' % (chunks[1], chunks[2]) - elif len(chunks) == 4: - comment = chunks[3].replace('/*', '').replace('*/', '').strip() - print >>out, '%s = %s # %s' % (chunks[1], chunks[2], comment) - if not line: - lastblank = True - else: - lastblank = False diff --git a/ez_setup.py b/ez_setup.py deleted file mode 100644 index b74adc0..0000000 --- a/ez_setup.py +++ /dev/null @@ -1,284 +0,0 @@ -#!python -"""Bootstrap setuptools installation - -If you want to use setuptools in your package's setup.py, just include this -file in the same directory with it, and add this to the top of your setup.py:: - - from ez_setup import use_setuptools - use_setuptools() - -If you want to require a specific version of setuptools, set a download -mirror, or use an alternate download directory, you can do so by supplying -the appropriate options to ``use_setuptools()``. - -This file can also be run as a script to install or upgrade setuptools. -""" -import sys -DEFAULT_VERSION = "0.6c11" -DEFAULT_URL = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3] - -md5_data = { - 'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca', - 'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb', - 'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b', - 'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a', - 'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618', - 'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac', - 'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5', - 'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4', - 'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c', - 'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b', - 'setuptools-0.6c10-py2.3.egg': 'ce1e2ab5d3a0256456d9fc13800a7090', - 'setuptools-0.6c10-py2.4.egg': '57d6d9d6e9b80772c59a53a8433a5dd4', - 'setuptools-0.6c10-py2.5.egg': 'de46ac8b1c97c895572e5e8596aeb8c7', - 'setuptools-0.6c10-py2.6.egg': '58ea40aef06da02ce641495523a0b7f5', - 'setuptools-0.6c11-py2.3.egg': '2baeac6e13d414a9d28e7ba5b5a596de', - 'setuptools-0.6c11-py2.4.egg': 'bd639f9b0eac4c42497034dec2ec0c2b', - 'setuptools-0.6c11-py2.5.egg': '64c94f3bf7a72a13ec83e0b24f2749b2', - 'setuptools-0.6c11-py2.6.egg': 'bfa92100bd772d5a213eedd356d64086', - 'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27', - 'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277', - 'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa', - 'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e', - 'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e', - 'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f', - 'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2', - 'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc', - 'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167', - 'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64', - 'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d', - 'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20', - 'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab', - 'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53', - 'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2', - 'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e', - 'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372', - 'setuptools-0.6c8-py2.3.egg': '50759d29b349db8cfd807ba8303f1902', - 'setuptools-0.6c8-py2.4.egg': 'cba38d74f7d483c06e9daa6070cce6de', - 'setuptools-0.6c8-py2.5.egg': '1721747ee329dc150590a58b3e1ac95b', - 'setuptools-0.6c9-py2.3.egg': 'a83c4020414807b496e4cfbe08507c03', - 'setuptools-0.6c9-py2.4.egg': '260a2be2e5388d66bdaee06abec6342a', - 'setuptools-0.6c9-py2.5.egg': 'fe67c3e5a17b12c0e7c541b7ea43a8e6', - 'setuptools-0.6c9-py2.6.egg': 'ca37b1ff16fa2ede6e19383e7b59245a', -} - -import sys, os -try: from hashlib import md5 -except ImportError: from md5 import md5 - -def _validate_md5(egg_name, data): - if egg_name in md5_data: - digest = md5(data).hexdigest() - if digest != md5_data[egg_name]: - print >>sys.stderr, ( - "md5 validation of %s failed! (Possible download problem?)" - % egg_name - ) - sys.exit(2) - return data - -def use_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - download_delay=15 -): - """Automatically find/download setuptools and make it available on sys.path - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end with - a '/'). `to_dir` is the directory where setuptools will be downloaded, if - it is not already available. If `download_delay` is specified, it should - be the number of seconds that will be paused before initiating a download, - should one be required. If an older version of setuptools is installed, - this routine will print a message to ``sys.stderr`` and raise SystemExit in - an attempt to abort the calling script. - """ - was_imported = 'pkg_resources' in sys.modules or 'setuptools' in sys.modules - def do_download(): - egg = download_setuptools(version, download_base, to_dir, download_delay) - sys.path.insert(0, egg) - import setuptools; setuptools.bootstrap_install_from = egg - try: - import pkg_resources - except ImportError: - return do_download() - try: - pkg_resources.require("setuptools>="+version); return - except pkg_resources.VersionConflict, e: - if was_imported: - print >>sys.stderr, ( - "The required version of setuptools (>=%s) is not available, and\n" - "can't be installed while this script is running. Please install\n" - " a more recent version first, using 'easy_install -U setuptools'." - "\n\n(Currently using %r)" - ) % (version, e.args[0]) - sys.exit(2) - except pkg_resources.DistributionNotFound: - pass - - del pkg_resources, sys.modules['pkg_resources'] # reload ok - return do_download() - -def download_setuptools( - version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir, - delay = 15 -): - """Download setuptools from a specified location and return its filename - - `version` should be a valid setuptools version number that is available - as an egg for download under the `download_base` URL (which should end - with a '/'). `to_dir` is the directory where the egg will be downloaded. - `delay` is the number of seconds to pause before an actual download attempt. - """ - import urllib2, shutil - egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3]) - url = download_base + egg_name - saveto = os.path.join(to_dir, egg_name) - src = dst = None - if not os.path.exists(saveto): # Avoid repeated downloads - try: - from distutils import log - if delay: - log.warn(""" ---------------------------------------------------------------------------- -This script requires setuptools version %s to run (even to display -help). I will attempt to download it for you (from -%s), but -you may need to enable firewall access for this script first. -I will start the download in %d seconds. - -(Note: if this machine does not have network access, please obtain the file - - %s - -and place it in this directory before rerunning this script.) ----------------------------------------------------------------------------""", - version, download_base, delay, url - ); from time import sleep; sleep(delay) - log.warn("Downloading %s", url) - src = urllib2.urlopen(url) - # Read/write all in one block, so we don't create a corrupt file - # if the download is interrupted. - data = _validate_md5(egg_name, src.read()) - dst = open(saveto,"wb"); dst.write(data) - finally: - if src: src.close() - if dst: dst.close() - return os.path.realpath(saveto) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -def main(argv, version=DEFAULT_VERSION): - """Install or upgrade setuptools and EasyInstall""" - try: - import setuptools - except ImportError: - egg = None - try: - egg = download_setuptools(version, delay=0) - sys.path.insert(0,egg) - from setuptools.command.easy_install import main - return main(list(argv)+[egg]) # we're done here - finally: - if egg and os.path.exists(egg): - os.unlink(egg) - else: - if setuptools.__version__ == '0.0.1': - print >>sys.stderr, ( - "You have an obsolete version of setuptools installed. Please\n" - "remove it from your system entirely before rerunning this script." - ) - sys.exit(2) - - req = "setuptools>="+version - import pkg_resources - try: - pkg_resources.require(req) - except pkg_resources.VersionConflict: - try: - from setuptools.command.easy_install import main - except ImportError: - from easy_install import main - main(list(argv)+[download_setuptools(delay=0)]) - sys.exit(0) # try to force an exit - else: - if argv: - from setuptools.command.easy_install import main - main(argv) - else: - print "Setuptools version",version,"or greater has been installed." - print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)' - -def update_md5(filenames): - """Update our built-in md5 registry""" - - import re - - for name in filenames: - base = os.path.basename(name) - f = open(name,'rb') - md5_data[base] = md5(f.read()).hexdigest() - f.close() - - data = [" %r: %r,\n" % it for it in md5_data.items()] - data.sort() - repl = "".join(data) - - import inspect - srcfile = inspect.getsourcefile(sys.modules[__name__]) - f = open(srcfile, 'rb'); src = f.read(); f.close() - - match = re.search("\nmd5_data = {\n([^}]+)}", src) - if not match: - print >>sys.stderr, "Internal error!" - sys.exit(2) - - src = src[:match.start(1)] + repl + src[match.end(1):] - f = open(srcfile,'w') - f.write(src) - f.close() - - -if __name__=='__main__': - if len(sys.argv)>2 and sys.argv[1]=='--md5update': - update_md5(sys.argv[2:]) - else: - main(sys.argv[1:]) - - - - - - diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..08c056b --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +cffi>=0.4 @@ -1,11 +1,22 @@ #!/usr/bin/env python -import ez_setup -ez_setup.use_setuptools() +import os +import sys -from setuptools import setup, Extension +from setuptools import setup +from distutils.command.build import build -VERSION = '0.6.4' +class cffi_build(build): + """This is a shameful hack to ensure that cffi is present when + we specify ext_modules. We can't do this eagerly because + setup_requires hasn't run yet. + """ + def finalize_options(self): + from xattr.lib import ffi + self.distribution.ext_modules = [ffi.verifier.get_extension()] + build.finalize_options(self) + +VERSION = '0.7.0' DESCRIPTION = "Python wrapper for extended filesystem attributes" LONG_DESCRIPTION = """ Extended attributes extend the basic attributes of files and directories @@ -16,7 +27,7 @@ Extended attributes are currently only available on Darwin 8.0+ (Mac OS X 10.4) and Linux 2.6+. Experimental support is included for Solaris and FreeBSD. """ -CLASSIFIERS = filter(None, map(str.strip, +CLASSIFIERS = filter(bool, map(str.strip, """ Environment :: Console Intended Audience :: Developers @@ -40,15 +51,16 @@ setup( url="http://github.com/xattr/xattr", license="MIT License", packages=['xattr'], + ext_package='xattr', platforms=['MacOS X', 'Linux', 'FreeBSD', 'Solaris'], - ext_modules=[ - Extension("xattr._xattr", ["xattr/_xattr.c"]), - ], entry_points={ 'console_scripts': [ "xattr = xattr.tool:main", ], }, + install_requires=["cffi>=0.4"], + setup_requires=["cffi>=0.4"], test_suite="xattr.tests.all_tests_suite", zip_safe=False, + cmdclass={'build': cffi_build}, ) diff --git a/xattr/__init__.py b/xattr/__init__.py index d9431f9..a452f66 100644 --- a/xattr/__init__.py +++ b/xattr/__init__.py @@ -7,26 +7,20 @@ The xattr type wraps a path or file descriptor with a dict-like interface that exposes these extended attributes. """ -__version__ = '0.6.4' -from constants import XATTR_NOFOLLOW, XATTR_CREATE, XATTR_REPLACE, \ - XATTR_NOSECURITY, XATTR_MAXNAMELEN, XATTR_FINDERINFO_NAME, \ - XATTR_RESOURCEFORK_NAME +__version__ = '0.7.0' -import _xattr +from .lib import (XATTR_NOFOLLOW, XATTR_CREATE, XATTR_REPLACE, + XATTR_NOSECURITY, XATTR_MAXNAMELEN, XATTR_FINDERINFO_NAME, + XATTR_RESOURCEFORK_NAME, _getxattr, _fgetxattr, _setxattr, _fsetxattr, + _removexattr, _fremovexattr, _listxattr, _flistxattr) -def _pyflakes_api(): - # trick pyflakes into thinking these are used. - return [ - XATTR_NOFOLLOW, XATTR_CREATE, XATTR_REPLACE, - XATTR_NOSECURITY, XATTR_MAXNAMELEN, XATTR_FINDERINFO_NAME, - XATTR_RESOURCEFORK_NAME, - ] +__all__ = [ + "XATTR_NOFOLLOW", "XATTR_CREATE", "XATTR_REPLACE", "XATTR_NOSECURITY", + "XATTR_MAXNAMELEN", "XATTR_FINDERINFO_NAME", "XATTR_RESOURCEFORK_NAME", + "xattr", "listxattr", "getxattr", "setxattr", "removexattr" +] -def _boundfunc(func, first): - def _func(*args): - return func(first, *args) - return _func class xattr(object): """ @@ -45,34 +39,24 @@ class xattr(object): """ self.obj = obj self.options = options - self.flavor = None fileno = getattr(obj, 'fileno', None) if fileno is not None: - obj = fileno() - if isinstance(obj, int): - self.flavor = 'fd' - self._bind_any('f%sxattr', obj, options) + self.value = fileno() else: - self.flavor = 'file' - self._bind_any('%sxattr', obj, options) + self.value = obj def __repr__(self): - if self.flavor: - return '<%s %s=%r>' % (type(self).__name__, self.flavor, self.obj) + if isinstance(self.value, int): + flavor = "fd" else: - return object.__repr__(self) - - def _bind_any(self, fmt, obj, options): - options = self.options - for method in ("get", "set", "remove", "list"): - name = '_' + method - func = getattr(_xattr, fmt % (method,)) - meth = _boundfunc(func, obj) - try: - meth.__name__ = name - except TypeError: - pass - setattr(self, name, meth) + flavor = "file" + return "<%s %s=%r>" % (type(self).__name__, flavor, self.value) + + def _call(self, name_func, fd_func, *args): + if isinstance(self.value, int): + return fd_func(self.value, *args) + else: + return name_func(self.value, *args) def get(self, name, options=0): """ @@ -81,7 +65,7 @@ class xattr(object): See x-man-page://2/getxattr for options and possible errors. """ - return self._get(name, 0, 0, options | self.options) + return self._call(_getxattr, _fgetxattr, name, 0, 0, options | self.options) def set(self, name, value, options=0): """ @@ -90,7 +74,7 @@ class xattr(object): See x-man-page://2/setxattr for options and possible errors. """ - self._set(name, value, 0, options | self.options) + return self._call(_setxattr, _fsetxattr, name, value, 0, options | self.options) def remove(self, name, options=0): """ @@ -99,6 +83,7 @@ class xattr(object): See x-man-page://2/removexattr for options and possible errors. """ + return self._call(_removexattr, _fremovexattr, name, options | self.options) self._remove(name, options | self.options) def list(self, options=0): @@ -108,7 +93,7 @@ class xattr(object): See x-man-page://2/listxattr for options and possible errors. """ - res = self._list(options | self.options).split('\x00') + res = self._call(_listxattr, _flistxattr, options | self.options).split('\x00') res.pop() return [unicode(s, 'utf-8') for s in res] @@ -186,20 +171,19 @@ class xattr(object): def listxattr(f, symlink=False): - __doc__ = xattr.list.__doc__ return tuple(xattr(f).list(options=symlink and XATTR_NOFOLLOW or 0)) + def getxattr(f, attr, symlink=False): - __doc__ = xattr.get.__doc__ return xattr(f).get(attr, options=symlink and XATTR_NOFOLLOW or 0) + def setxattr(f, attr, value, options=0, symlink=False): - __doc__ = xattr.set.__doc__ if symlink: options |= XATTR_NOFOLLOW return xattr(f).set(attr, value, options=options) + def removexattr(f, attr, symlink=False): - __doc__ = xattr.remove.__doc__ options = symlink and XATTR_NOFOLLOW or 0 return xattr(f).remove(attr, options=options) diff --git a/xattr/constants.py b/xattr/constants.py deleted file mode 100644 index 8ee1c3b..0000000 --- a/xattr/constants.py +++ /dev/null @@ -1,16 +0,0 @@ - -# Options for pathname based xattr calls -XATTR_NOFOLLOW = 0x0001 # Don't follow symbolic links - -# Options for setxattr calls -XATTR_CREATE = 0x0002 # set the value, fail if attr already exists -XATTR_REPLACE = 0x0004 # set the value, fail if attr does not exist - -# Set this to bypass authorization checking (eg. if doing auth-related work) -XATTR_NOSECURITY = 0x0008 - -XATTR_MAXNAMELEN = 127 - -XATTR_FINDERINFO_NAME = "com.apple.FinderInfo" - -XATTR_RESOURCEFORK_NAME = "com.apple.ResourceFork" diff --git a/xattr/_xattr.c b/xattr/lib.py index 606450d..b72a1ff 100644 --- a/xattr/_xattr.c +++ b/xattr/lib.py @@ -1,3 +1,31 @@ +import os +import sys + +import cffi + +ffi = cffi.FFI() +ffi.cdef(""" +#define XATTR_NOFOLLOW ... +#define XATTR_CREATE ... +#define XATTR_REPLACE ... +#define XATTR_NOSECURITY ... +#define XATTR_MAXNAMELEN ... + +ssize_t xattr_getxattr(const char *, const char *, void *, ssize_t, uint32_t, int); +ssize_t xattr_fgetxattr(int, const char *, void *, ssize_t, uint32_t, int); + +ssize_t xattr_setxattr(const char *, const char *, void *, ssize_t, uint32_t, int); +ssize_t xattr_fsetxattr(int, const char *, void *, ssize_t, uint32_t, int); + +ssize_t xattr_removexattr(const char *, const char *, int); +ssize_t xattr_fremovexattr(int, const char *, int); + +ssize_t xattr_listxattr(const char *, char *, size_t, int); +ssize_t xattr_flistxattr(int, char *, size_t, int); + +""") + +lib = ffi.verify(""" #include "Python.h" #ifdef __FreeBSD__ #include <sys/extattr.h> @@ -21,7 +49,7 @@ /* Converts a freebsd format attribute list into a NULL terminated list. - * While the man page on extattr_list_file says it is NULL terminated, + * While the man page on extattr_list_file says it is NULL terminated, * it is actually the first byte that is the length of the * following attribute. */ @@ -37,10 +65,10 @@ static void convert_bsd_list(char *namebuf, size_t size) } static ssize_t xattr_getxattr(const char *path, const char *name, - void *value, ssize_t size, u_int32_t position, + void *value, ssize_t size, u_int32_t position, int options) { - if (position != 0 || + if (position != 0 || !(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } @@ -73,7 +101,7 @@ static ssize_t xattr_setxattr(const char *path, const char *name, options == XATTR_XATTR_REPLACE) { /* meh. FreeBSD doesn't really have this in it's - * API... Oh well. + * API... Oh well. */ } else if (options != 0) { @@ -179,7 +207,7 @@ static ssize_t xattr_fsetxattr(int fd, const char *name, void *value, return -1; } else { - rv = extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, + rv = extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, name, value, size); } @@ -254,19 +282,19 @@ static ssize_t xattr_fgetxattr(int fd, const char *name, void *value, /* XXX should check that name does not have / characters in it */ xfd = openat(fd, name, O_RDONLY | O_XATTR); if (xfd == -1) { - return -1; + return -1; } if (lseek(xfd, position, SEEK_SET) == -1) { - close(xfd); - return -1; + close(xfd); + return -1; } if (value == NULL) { if (fstat(xfd, &statbuf) == -1) { - close(xfd); - return -1; + close(xfd); + return -1; } - close(xfd); - return statbuf.st_size; + close(xfd); + return statbuf.st_size; } /* XXX should keep reading until the buffer is exhausted or EOF */ bytes = read(xfd, value, size); @@ -275,22 +303,22 @@ static ssize_t xattr_fgetxattr(int fd, const char *name, void *value, } static ssize_t xattr_getxattr(const char *path, const char *name, - void *value, ssize_t size, u_int32_t position, + void *value, ssize_t size, u_int32_t position, int options) { int fd; ssize_t bytes; - if (position != 0 || + if (position != 0 || !(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } fd = open(path, - O_RDONLY | - ((options & XATTR_XATTR_NOFOLLOW) ? O_NOFOLLOW : 0)); + O_RDONLY | + ((options & XATTR_XATTR_NOFOLLOW) ? O_NOFOLLOW : 0)); if (fd == -1) { - return -1; + return -1; } bytes = xattr_fgetxattr(fd, name, value, size, position, options); close(fd); @@ -305,21 +333,21 @@ static ssize_t xattr_fsetxattr(int fd, const char *name, void *value, /* XXX should check that name does not have / characters in it */ xfd = openat(fd, name, O_XATTR | O_TRUNC | - ((options & XATTR_XATTR_CREATE) ? O_EXCL : 0) | - ((options & XATTR_XATTR_NOFOLLOW) ? O_NOFOLLOW : 0) | - ((options & XATTR_XATTR_REPLACE) ? O_RDWR : O_WRONLY|O_CREAT), - 0644); + ((options & XATTR_XATTR_CREATE) ? O_EXCL : 0) | + ((options & XATTR_XATTR_NOFOLLOW) ? O_NOFOLLOW : 0) | + ((options & XATTR_XATTR_REPLACE) ? O_RDWR : O_WRONLY|O_CREAT), + 0644); if (xfd == -1) { - return -1; + return -1; } while (size > 0) { - bytes = write(xfd, value, size); - if (bytes == -1) { - close(xfd); - return -1; - } - size -= bytes; - value += bytes; + bytes = write(xfd, value, size); + if (bytes == -1) { + close(xfd); + return -1; + } + size -= bytes; + value += bytes; } close(xfd); return 0; @@ -337,9 +365,9 @@ static ssize_t xattr_setxattr(const char *path, const char *name, } fd = open(path, - O_RDONLY | (options & XATTR_XATTR_NOFOLLOW) ? O_NOFOLLOW : 0); + O_RDONLY | (options & XATTR_XATTR_NOFOLLOW) ? O_NOFOLLOW : 0); if (fd == -1) { - return -1; + return -1; } bytes = xattr_fsetxattr(fd, name, value, size, position, options); close(fd); @@ -358,7 +386,7 @@ static ssize_t xattr_fremovexattr(int fd, const char *name, int options) } xfd = openat(fd, ".", O_XATTR, 0644); if (xfd == -1) { - return -1; + return -1; } status = unlinkat(xfd, name, 0); close(xfd); @@ -372,9 +400,9 @@ static ssize_t xattr_removexattr(const char *path, const char *name, ssize_t status; fd = open(path, - O_RDONLY | ((options & XATTR_XATTR_NOFOLLOW) ? O_NOFOLLOW : 0)); + O_RDONLY | ((options & XATTR_XATTR_NOFOLLOW) ? O_NOFOLLOW : 0)); if (fd == -1) { - return -1; + return -1; } status = xattr_fremovexattr(fd, name, options); close(fd); @@ -396,20 +424,20 @@ static ssize_t xattr_xflistxattr(int xfd, char *namebuf, size_t size, int option if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; - esize = strlen(entry->d_name); - if (nsize + esize + 1 <= size) { + esize = strlen(entry->d_name); + if (nsize + esize + 1 <= size) { snprintf((char *)(namebuf + nsize), esize + 1, entry->d_name); - } - nsize += esize + 1; /* +1 for \0 */ + } + nsize += esize + 1; /* +1 for \0 */ } closedir(dirp); return nsize; -} +} static ssize_t xattr_flistxattr(int fd, char *namebuf, size_t size, int options) { int xfd; - + xfd = openat(fd, ".", O_RDONLY); return xattr_xflistxattr(xfd, namebuf, size, options); } @@ -549,400 +577,137 @@ static ssize_t xattr_flistxattr(int fd, char *namebuf, size_t size, int options) #define xattr_listxattr listxattr #define xattr_flistxattr flistxattr #endif - -static PyObject *xattr_error(void); -static PyObject *xattr_error_with_filename(char *name); - -static PyObject * -xattr_error(void) -{ - return PyErr_SetFromErrno(PyExc_IOError); -} - -static PyObject * -xattr_error_with_filename(char *name) -{ - return PyErr_SetFromErrnoWithFilename(PyExc_IOError, name); -} - -PyDoc_STRVAR(pydoc_getxattr, - "getxattr(path, name, size=0, position=0, options=0) -> str\n" - "\n" - "..." -); -static PyObject* -py_getxattr(PyObject* self __attribute__((__unused__)), PyObject *args) /* , PyObject *kwds) */ -{ - /* static char *keywords[] = { "path", "name", "size", "position", "options", NULL }; */ - char *path; - char *name; - PyObject *buffer; - int options = 0; - size_t size = 0; - u_int32_t position = 0; - ssize_t res; - if (!PyArg_ParseTuple(args, /* AndKeywords(args, kwds, */ - "etet|IIi:getxattr", /* keywords, */ - Py_FileSystemDefaultEncoding, &path, - Py_FileSystemDefaultEncoding, &name, - &size, - &position, - &options)) { - return NULL; - } - if (size == 0) { - Py_BEGIN_ALLOW_THREADS - res = xattr_getxattr((const char *)path, (const char *)name, NULL, 0, position, options); - Py_END_ALLOW_THREADS - if (res == -1) { - PyObject *tmp = xattr_error_with_filename(path); - PyMem_Free(path); - PyMem_Free(name); - return tmp; - } - size = res; - } - buffer = PyString_FromStringAndSize((char *)NULL, size); - if (buffer == NULL) { - PyMem_Free(path); - PyMem_Free(name); - return NULL; - } - Py_BEGIN_ALLOW_THREADS - res = xattr_getxattr((const char *)path, (const char *)name, (void *)PyString_AS_STRING(buffer), size, position, options); - Py_END_ALLOW_THREADS - if (res == -1) { - PyObject *tmp = xattr_error_with_filename(path); - Py_DECREF(buffer); - PyMem_Free(path); - PyMem_Free(name); - return tmp; - } - PyMem_Free(path); - PyMem_Free(name); - if (res != size) { - _PyString_Resize(&buffer, (int)res); - } - return buffer; -} - -PyDoc_STRVAR(pydoc_fgetxattr, - "fgetxattr(fd, name, size=0, position=0, options=0) -> str\n" - "\n" - "..." -); -static PyObject* -py_fgetxattr(PyObject* self __attribute__((__unused__)), PyObject *args) /* , PyObject *kwds) */ -{ - /* static char *keywords[] = { "fd", "name", "size", "position", "options", NULL }; */ - int fd; - char *name; - PyObject *buffer; - int options = 0; - size_t size = 0; - u_int32_t position = 0; - ssize_t res; - if (!PyArg_ParseTuple(args, /* AndKeywords(args, kwds, */ - "iet|IIi:fgetxattr", /* keywords, */ - &fd, - Py_FileSystemDefaultEncoding, &name, - &size, - &position, - &options)) { - return NULL; - } - if (size == 0) { - Py_BEGIN_ALLOW_THREADS - res = xattr_fgetxattr(fd, (const char *)name, NULL, 0, position, options); - Py_END_ALLOW_THREADS - if (res == -1) { - PyMem_Free(name); - return xattr_error(); - } - size = res; - } - buffer = PyString_FromStringAndSize((char *)NULL, size); - if (buffer == NULL) { - PyMem_Free(name); - return NULL; - } - Py_BEGIN_ALLOW_THREADS - res = xattr_fgetxattr(fd, (const char *)name, (void *)PyString_AS_STRING(buffer), size, position, options); - Py_END_ALLOW_THREADS - PyMem_Free(name); - if (res == -1) { - Py_DECREF(buffer); - return xattr_error(); - } - if (res != size) { - _PyString_Resize(&buffer, (int)res); - } - return buffer; -} - -PyDoc_STRVAR(pydoc_setxattr, - "setxattr(path, name, value, position=0, options=0) -> None\n" - "\n" - "..." -); -static PyObject* -py_setxattr(PyObject* self __attribute__((__unused__)), PyObject *args) /* , PyObject *kwds) */ -{ - /* static char *keywords[] = { "path", "name", "value", "position", "options", NULL }; */ - PyObject *result; - char *path; - char *name; - int options = 0; - char *value; - int size; - u_int32_t position = 0; - int res; - if (!PyArg_ParseTuple(args, /* AndKeywords(args, kwds, */ - "etets#|Ii:setxattr", /* keywords, */ - Py_FileSystemDefaultEncoding, &path, - Py_FileSystemDefaultEncoding, &name, - &value, &size, - &position, - &options)) { - return NULL; - } - Py_BEGIN_ALLOW_THREADS - res = xattr_setxattr((const char *)path, (const char *)name, (void *)value, size, position, options); - Py_END_ALLOW_THREADS - if (res) { - result = xattr_error_with_filename(path); - } else { - Py_INCREF(Py_None); - result = Py_None; - } - PyMem_Free(path); - PyMem_Free(name); - return result; -} - -PyDoc_STRVAR(pydoc_fsetxattr, - "fsetxattr(fd, name, value, position=0, options=0) -> None\n" - "\n" - "..." -); -static PyObject* -py_fsetxattr(PyObject* self __attribute__((__unused__)), PyObject *args) /* , PyObject *kwds) */ -{ - /* static char *keywords[] = { "fd", "name", "value", "position", "options", NULL }; */ - int fd; - char *name; - int options = 0; - char *value; - int size; - u_int32_t position = 0; - int res; - if (!PyArg_ParseTuple(args, /* AndKeywords(args, kwds, */ - "iets#|Ii:fsetxattr", /* keywords, */ - &fd, - Py_FileSystemDefaultEncoding, &name, - &value, &size, - &position, - &options)) { - return NULL; - } - Py_BEGIN_ALLOW_THREADS - res = xattr_fsetxattr(fd, (const char *)name, (void *)value, size, position, options); - Py_END_ALLOW_THREADS - PyMem_Free(name); - if (res) { - return xattr_error(); - } - Py_INCREF(Py_None); - return Py_None; -} - -PyDoc_STRVAR(pydoc_removexattr, - "removexattr(path, name, options=0) -> None\n" - "\n" - "..." -); -static PyObject* -py_removexattr(PyObject* self __attribute__((__unused__)), PyObject *args) /* , PyObject *kwds) */ -{ - /* static char *keywords[] = { "path", "name", "options", NULL }; */ - char *path; - char *name; - int options = 0; - int res; - PyObject *result; - if (!PyArg_ParseTuple(args, /* AndKeywords(args, kwds, */ - "etet|i:removexattr", /* keywords, */ - Py_FileSystemDefaultEncoding, &path, - Py_FileSystemDefaultEncoding, &name, - &options)) { - return NULL; - } - Py_BEGIN_ALLOW_THREADS - res = xattr_removexattr((const char *)path, (const char *)name, options); - Py_END_ALLOW_THREADS - if (res) { - result = xattr_error_with_filename(path); - } else { - Py_INCREF(Py_None); - result = Py_None; - } - PyMem_Free(path); - PyMem_Free(name); - return result; -} - -PyDoc_STRVAR(pydoc_fremovexattr, - "fremovexattr(fd, name, options=0) -> None\n" - "\n" - "..." -); -static PyObject* -py_fremovexattr(PyObject* self __attribute__((__unused__)), PyObject *args) /* , PyObject *kwds) */ -{ - /* static char *keywords[] = { "fd", "name", "options", NULL }; */ - int fd; - char *name; - int options = 0; - int res; - if (!PyArg_ParseTuple(args, /* AndKeywords(args, kwds, */ - "iet|i:fremovexattr", /* keywords, */ - &fd, - Py_FileSystemDefaultEncoding, &name, - &options)) { - return NULL; - } - Py_BEGIN_ALLOW_THREADS - res = xattr_fremovexattr(fd, (const char *)name, options); - Py_END_ALLOW_THREADS - PyMem_Free(name); - if (res) { - return xattr_error(); - } - Py_INCREF(Py_None); - return Py_None; -} - -PyDoc_STRVAR(pydoc_listxattr, - "listxattr(path, options=0) -> str\n" - "\n" - "..." -); -static PyObject* -py_listxattr(PyObject* self __attribute__((__unused__)), PyObject *args) /* , PyObject *kwds) */ -{ - /* static char *keywords[] = { "path", "options", NULL }; */ - PyObject *buffer; - char *path; - int options = 0; - ssize_t res; - if (!PyArg_ParseTuple(args, /* AndKeywords(args, kwds, */ - "et|i:listxattr", /* keywords, */ - Py_FileSystemDefaultEncoding, &path, - &options)) { - return NULL; - } - Py_BEGIN_ALLOW_THREADS - res = xattr_listxattr((const char *)path, NULL, 0, options); - Py_END_ALLOW_THREADS - if (res == -1) { - PyObject *tmp = xattr_error_with_filename(path); - PyMem_Free(path); - return tmp; - } - buffer = PyString_FromStringAndSize((char *)NULL, (int)res); - if (buffer == NULL) { - PyMem_Free(path); - return NULL; - } - /* avoid 2nd listxattr call if the first one returns 0 */ - if (res == 0) { - PyMem_Free(path); - return buffer; - } - Py_BEGIN_ALLOW_THREADS - res = xattr_listxattr((const char *)path, (void *)PyString_AS_STRING(buffer), (size_t)PyString_GET_SIZE(buffer), options); - Py_END_ALLOW_THREADS - if (res == -1) { - PyObject *tmp = xattr_error_with_filename(path); - Py_DECREF(buffer); - PyMem_Free(path); - return tmp; - } - PyMem_Free(path); - if (res != (ssize_t)PyString_GET_SIZE(buffer)) { - _PyString_Resize(&buffer, (int)res); - } - return buffer; -} - -PyDoc_STRVAR(pydoc_flistxattr, - "flistxattr(fd, options=0) -> str\n" - "\n" - "..." -); -static PyObject* -py_flistxattr(PyObject* self __attribute__((__unused__)), PyObject *args) /* , PyObject *kwds) */ -{ - /* static char *keywords[] = { "fd", "options", NULL }; */ - PyObject *buffer; - int fd; - int options = 0; - ssize_t res; - if (!PyArg_ParseTuple(args, /* AndKeywords(args, kwds, */ - "i|i:flistxattr", /* keywords, */ - &fd, - &options)) { - return NULL; - } - Py_BEGIN_ALLOW_THREADS - res = xattr_flistxattr(fd, NULL, 0, options); - Py_END_ALLOW_THREADS - if (res == -1) { - return xattr_error(); - } - buffer = PyString_FromStringAndSize((char *)NULL, (int)res); - if (buffer == NULL) { - return NULL; - } - Py_BEGIN_ALLOW_THREADS - res = xattr_flistxattr(fd, (void *)PyString_AS_STRING(buffer), (size_t)PyString_GET_SIZE(buffer), options); - Py_END_ALLOW_THREADS - if (res == -1) { - Py_DECREF(buffer); - return xattr_error(); - } - if (res != (ssize_t)PyString_GET_SIZE(buffer)) { - _PyString_Resize(&buffer, (int)res); - } - return buffer; -} - - -#define DEFN(n) \ - { \ - #n, \ - (PyCFunction)py_ ##n, \ - METH_VARARGS /* | METH_KEYWORDS */, \ - pydoc_ ##n \ - } -static PyMethodDef xattr_methods[] = { - DEFN(getxattr), - DEFN(fgetxattr), - DEFN(setxattr), - DEFN(fsetxattr), - DEFN(removexattr), - DEFN(fremovexattr), - DEFN(listxattr), - DEFN(flistxattr), - {} -}; -#undef DEFN - -void init_xattr(void); - -void -init_xattr(void) -{ - PyObject *m; - m = Py_InitModule4("_xattr", xattr_methods, NULL, NULL, PYTHON_API_VERSION); -} +""", ext_package='xattr') + +XATTR_NOFOLLOW = lib.XATTR_NOFOLLOW +XATTR_CREATE = lib.XATTR_CREATE +XATTR_REPLACE = lib.XATTR_REPLACE +XATTR_NOSECURITY = lib.XATTR_NOSECURITY +XATTR_MAXNAMELEN = lib.XATTR_MAXNAMELEN + +XATTR_FINDERINFO_NAME = "com.apple.FinderInfo" +XATTR_RESOURCEFORK_NAME = "com.apple.ResourceFork" + + +def fs_encode(val): + if isinstance(val, bytes): + return val.encode(sys.getfilesystemencoding()) + else: + return val + + +def error(path=None): + errno = ffi.errno + strerror = os.strerror(ffi.errno) + if path: + raise IOError(errno, strerror, path) + else: + raise IOError(errno, strerror) + + +def _getxattr(path, name, size=0, position=0, options=0): + """ + getxattr(path, name, size=0, position=0, options=0) -> str + """ + path = fs_encode(path) + name = fs_encode(name) + if size == 0: + res = lib.xattr_getxattr(path, name, ffi.NULL, 0, position, options) + if res == -1: + raise error(path) + size = res + buf = ffi.new("char[]", size) + res = lib.xattr_getxattr(path, name, buf, size, position, options) + if res == -1: + raise error(path) + return ffi.buffer(buf)[:res] + + +def _fgetxattr(fd, name, size=0, position=0, options=0): + """ + fgetxattr(fd, name, size=0, position=0, options=0) -> str + """ + name = fs_encode(name) + if size == 0: + res = lib.xattr_fgetxattr(fd, name, ffi.NULL, 0, position, options) + if res == -1: + raise error() + size = res + buf = ffi.new("char[]", size) + res = lib.xattr_fgetxattr(fd, name, buf, size, position, options) + if res == -1: + raise error() + return ffi.buffer(buf)[:res] + + +def _setxattr(path, name, value, position=0, options=0): + """ + setxattr(path, name, value, position=0, options=0) -> None + """ + path = fs_encode(path) + name = fs_encode(name) + res = lib.xattr_setxattr(path, name, value, len(value), position, options) + if res: + raise error(path) + + +def _fsetxattr(fd, name, value, position=0, options=0): + """ + fsetxattr(fd, name, value, position=0, options=0) -> None + """ + name = fs_encode(name) + res = lib.xattr_fsetxattr(fd, name, value, len(value), position, options) + if res: + raise error() + + +def _removexattr(path, name, options=0): + """ + removexattr(path, name, options=0) -> None + """ + path = fs_encode(path) + name = fs_encode(name) + res = lib.xattr_removexattr(path, name, options) + if res: + raise error(path) + + +def _fremovexattr(fd, name, options=0): + """ + fremovexattr(fd, name, options=0) -> None + """ + name = fs_encode(name) + res = lib.xattr_fremovexattr(fd, name, options) + if res: + raise error() + + +def _listxattr(path, options=0): + """ + listxattr(path, options=0) -> str + """ + path = fs_encode(path) + res = lib.xattr_listxattr(path, ffi.NULL, 0, options) + if res == -1: + raise error(path) + elif res == 0: + return b"" + buf = ffi.new("char[]", res) + res = lib.xattr_listxattr(path, buf, res, options) + if res == -1: + raise error(path) + return ffi.buffer(buf)[:res] + + +def _flistxattr(fd, options=0): + """ + flistxattr(fd, options=0) -> str + """ + res = lib.xattr_flistxattr(fd, ffi.NULL, 0, options) + if res == 1: + raise error() + buf = ffi.new("char[]", res) + res = lib.xattr_flistxattr(fd, buf, res, options) + if res == -1: + raise error() + return ffi.buffer(buf)[:res] diff --git a/xattr/tests/__init__.py b/xattr/tests/__init__.py index 2391ae6..739dd58 100644 --- a/xattr/tests/__init__.py +++ b/xattr/tests/__init__.py @@ -1,3 +1,5 @@ +import os +import sys import unittest @@ -15,7 +17,5 @@ def main(): if __name__ == '__main__': - import os - import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) - main()
\ No newline at end of file + main() diff --git a/xattr/tests/test_xattr.py b/xattr/tests/test_xattr.py index 3a920ec..d75d7ee 100644 --- a/xattr/tests/test_xattr.py +++ b/xattr/tests/test_xattr.py @@ -4,15 +4,9 @@ from tempfile import mkdtemp, NamedTemporaryFile import xattr -class TestFile(TestCase): - def setUp(self): - self.tempfile = NamedTemporaryFile() - self.tempfilename = self.tempfile.name - - def tearDown(self): - self.tempfile.close() - def testAttr(self): +class BaseTestXattr(object): + def test_attr(self): x = xattr.xattr(self.tempfile) self.assertEqual(x.keys(), []) self.assertEqual(dict(x), {}) @@ -33,7 +27,7 @@ class TestFile(TestCase): x = xattr.xattr(self.tempfile) self.assertTrue('user.sop.foo' not in x) - def testSymlinkAttrs(self): + def test_symlink_attrs(self): symlinkPath = self.tempfilename + '.link' os.symlink(self.tempfilename, symlinkPath) try: @@ -45,7 +39,17 @@ class TestFile(TestCase): finally: os.remove(symlinkPath) -class TestDir(TestFile): + +class TestFile(TestCase, BaseTestXattr): + def setUp(self): + self.tempfile = NamedTemporaryFile() + self.tempfilename = self.tempfile.name + + def tearDown(self): + self.tempfile.close() + + +class TestDir(TestCase, BaseTestXattr): def setUp(self): self.tempfile = mkdtemp() self.tempfilename = self.tempfile diff --git a/xattr/tool.py b/xattr/tool.py index 3b1121e..41ea7e1 100755 --- a/xattr/tool.py +++ b/xattr/tool.py @@ -28,9 +28,11 @@ import sys import os import getopt -import xattr import zlib +import xattr + + def usage(e=None): if e: print e @@ -57,34 +59,38 @@ def usage(e=None): else: sys.exit(0) + class NullsInString(Exception): """Nulls in string.""" -_FILTER=''.join([(len(repr(chr(x)))==3) and chr(x) or '.' for x in range(256)]) + +_FILTER = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]) + def _dump(src, length=16): - result=[] + result = [] for i in xrange(0, len(src), length): s = src[i:i+length] - hexa = ' '.join(["%02X"%ord(x) for x in s]) + hexa = ' '.join(["%02X" % ord(x) for x in s]) printable = s.translate(_FILTER) result.append("%04X %-*s %s\n" % (i, length*3, hexa, printable)) return ''.join(result) + def main(): try: (optargs, args) = getopt.getopt(sys.argv[1:], "hlpwdz", ["help"]) except getopt.GetoptError, e: usage(e) - attr_name = None + attr_name = None long_format = False - read = False - write = False - delete = False - compress = lambda x: x - decompress = compress - status = 0 + read = False + write = False + delete = False + compress = lambda x: x + decompress = compress + status = 0 for opt, arg in optargs: if opt in ("-h", "--help"): @@ -104,7 +110,7 @@ def main(): if read or write: usage("-d not allowed with -p or -w") elif opt == "-z": - compress = zlib.compress + compress = zlib.compress decompress = zlib.decompress if write or delete: @@ -132,7 +138,6 @@ def main(): sys.stderr.write("No such file: %s\n" % (filename,)) else: sys.stderr.write(str(e) + "\n") - status = 1 try: attrs = xattr.xattr(filename) @@ -185,7 +190,7 @@ def main(): if long_format: try: if attr_value.find('\0') >= 0: - raise NullsInString; + raise NullsInString print "".join((file_prefix, "%s: " % (attr_name,), attr_value)) except (UnicodeDecodeError, NullsInString): print "".join((file_prefix, "%s:" % (attr_name,))) |