pax_global_header00006660000000000000000000000064143566030240014515gustar00rootroot0000000000000052 comment=46d5b25fa4cc31a1d764ea305352a5cc01e10080 python-xattr-0.10.1/000077500000000000000000000000001435660302400142555ustar00rootroot00000000000000python-xattr-0.10.1/.github/000077500000000000000000000000001435660302400156155ustar00rootroot00000000000000python-xattr-0.10.1/.github/workflows/000077500000000000000000000000001435660302400176525ustar00rootroot00000000000000python-xattr-0.10.1/.github/workflows/build-and-deploy.yml000066400000000000000000000071001435660302400235240ustar00rootroot00000000000000name: Build and upload to PyPI # Build on every branch push, tag push, and pull request change: on: [push, pull_request] # Alternatively, to publish when a (published) GitHub Release is created, use the following: # on: # push: # pull_request: # release: # types: # - published jobs: build_wheels: name: Build wheels on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: - 'ubuntu-latest' - 'macos-latest' steps: - uses: actions/checkout@v2 - name: Set up QEMU if: runner.os == 'Linux' uses: docker/setup-qemu-action@v2 with: platforms: arm64 - name: Build wheels uses: pypa/cibuildwheel@v2.11.2 env: CIBW_BUILD_FRONTEND: "build" CIBW_MANYLINUX_X86_64_IMAGE: manylinux2014 CIBW_MANYLINUX_I686_IMAGE: manylinux2014 CIBW_MANYLINUX_AARCH64_IMAGE: manylinux2014 CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: manylinux2014 CIBW_MANYLINUX_PYPY_X86_64_IMAGE: manylinux2014 CIBW_MANYLINUX_PYPY_I686_IMAGE: manylinux2014 CIBW_ARCHS_LINUX: "auto aarch64" CIBW_ARCHS_MACOS: "x86_64 universal2 arm64" - name: Build Python 2.7 wheels if: runner.os != 'Windows' uses: pypa/cibuildwheel@v1.12.0 env: CIBW_MANYLINUX_X86_64_IMAGE: manylinux2010 CIBW_MANYLINUX_I686_IMAGE: manylinux2010 CIBW_BUILD: "cp27-*" CIBW_SKIP: "pp*" CIBW_ARCHS_LINUX: "auto aarch64" - uses: actions/upload-artifact@v2 if: "github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/')" with: path: ./wheelhouse/*.whl build_sdist: name: Build source distribution runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 name: Install Python with: python-version: '3.11' - name: Build sdist run: python setup.py sdist - uses: actions/upload-artifact@v2 if: "github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/')" with: path: dist/*.tar.gz upload_pypi: needs: [build_wheels, build_sdist] runs-on: ubuntu-latest # upload to PyPI on every tag starting with 'v' if: "github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v')" # alternatively, to publish when a GitHub Release is created, use the following rule: # if: github.event_name == 'release' && github.event.action == 'published' steps: - uses: actions/download-artifact@v2 with: name: artifact path: dist - uses: pypa/gh-action-pypi-publish@v1.6.1 with: user: __token__ password: ${{ secrets.PYPI_PASSWORD }} # To test: repository_url: https://test.pypi.org/legacy/ upload_pypi_test: needs: [build_wheels, build_sdist] runs-on: ubuntu-latest # upload to PyPI on every tag starting with 'v' if: "github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/test-v')" # alternatively, to publish when a GitHub Release is created, use the following rule: # if: github.event_name == 'release' && github.event.action == 'published' steps: - uses: actions/download-artifact@v2 with: name: artifact path: dist - uses: pypa/gh-action-pypi-publish@v1.6.1 with: user: __token__ password: ${{ secrets.PYPI_PASSWORD_TEST }} repository_url: https://test.pypi.org/legacy/ python-xattr-0.10.1/CHANGES.txt000066400000000000000000000101021435660302400160600ustar00rootroot00000000000000Version 0.10.1 released 2022-12-03 * Update github actions https://github.com/xattr/xattr/issues/112 * Updated README to add reference to osxmetadata https://github.com/xattr/xattr/pull/110 Version 0.10.0 released 2022-10-24 * Remove exec flag from tool.py https://github.com/xattr/xattr/pull/106 * Update the documentation to mention the attr package and its getfattr and setfattr tools https://github.com/xattr/xattr/pull/103 Version 0.9.9 released 2021-12-11 * Fix regression in xattr console script https://github.com/xattr/xattr/pull/100 * Add -c clear option https://github.com/xattr/xattr/pull/98 * Add note about Linux namespace requirement https://github.com/xattr/xattr/pull/96 Version 0.9.8 released 2021-11-19 * Update build to use Github Actions https://github.com/xattr/xattr/pull/95 * Various dump related fixes https://github.com/xattr/xattr/pull/93 * Fix classifiers list https://github.com/xattr/xattr/pull/89 Version 0.9.7 released 2019-12-02 * Fix xattr().update() in Python 3 https://github.com/xattr/xattr/pull/87 Version 0.9.6 released 2018-07-31 * Fix release build by including *.[ch] in Manifest.in https://github.com/xattr/xattr/pull/77 https://github.com/xattr/xattr/pull/78 https://github.com/xattr/xattr/pull/79 Version 0.9.4 released 2018-07-30 * Extract inline C code for syntax highlighting and easier maintenance https://github.com/xattr/xattr/pull/75 * Fix Travis build https://github.com/xattr/xattr/pull/74 * Always include sys/types.h (musl compatibility) https://github.com/xattr/xattr/pull/73 Version 0.9.3 released 2018-01-28 * Do not attempt to use surrogateescape unless it is available https://github.com/xattr/xattr/issues/59 Version 0.9.2 released 2017-04-02 * Fix BSD issue w/ lsattr and long attrs https://github.com/xattr/xattr/pull/57 * Remove unreachable code https://github.com/xattr/xattr/pull/56 Version 0.9.1 released 2016-10-23 * Allow (Python 2) long for fd https://github.com/xattr/xattr/pull/51 * Fix Python 3 bytes handling in xattr.tool https://github.com/xattr/xattr/pull/50 * Use cffi 1.X features to build native module for faster import https://github.com/xattr/xattr/pull/47 * NOTE: Version 0.9.0 is the same, was momentarily uploaded with incomplete CHANGES.txt Version 0.8.0 released 2016-02-28 * Use os.fsencode where available to better handle filesystem quirks related to surrogates https://github.com/xattr/xattr/pull/46 * Options bugfix and compatibility module for pyxattr API https://github.com/xattr/xattr/pull/38 Version 0.7.9 released 2016-02-12 * Added xattr/tests/*.py to MANIFEST.in https://github.com/xattr/xattr/issues/43 Version 0.7.8 released 2015-06-25 * Added MANIFEST.in to ensure that the .txt files are included https://github.com/xattr/xattr/issues/40 Version 0.7.7 released 2015-06-19 * Fixed FreeBSD build https://github.com/xattr/xattr/pull/32 Version 0.7.6 released 2014-03-27 * Fixed Solaris & Solaris Studio support and Python 2.6 compatibility https://github.com/xattr/xattr/pull/29 Version 0.7.5 released 2014-03-23 * Improved error message when str/unicode is passed where bytes is required https://github.com/xattr/xattr/pull/24 Version 0.7.4 released 2014-03-03 * Improved Python 3 compatibility https://github.com/xattr/xattr/pull/22 Version 0.7.3 released 2014-01-06 * Added some unicode-specific tests Version 0.7.2 released 2013-07-22 * Added Python 3 support. https://github.com/xattr/xattr/commit/14795a47b1dc4bb994faf520888c51c5886b8187 Version 0.7.1 released 2013-07-19 * Fixed compilation on some platforms https://github.com/xattr/xattr/issues/12 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 https://github.com/xattr/xattr/issues/5 * Bug fixes for Solaris port https://github.com/xattr/xattr/pull/2 Version 0.6.3 released 2012-01-23 * Fix tests for Linux, allow xattr on symlinks https://github.com/xattr/xattr/pull/4 Version 0.6.2 released 2011-08-17 * Bug fix in Solaris support python-xattr-0.10.1/INSTALLING.txt000066400000000000000000000001401435660302400164550ustar00rootroot00000000000000Installing ========== From the xattr source directory, type: sudo python setup.py install python-xattr-0.10.1/LICENSE.txt000066400000000000000000000022241435660302400161000ustar00rootroot00000000000000This is the MIT license. This software may also be distributed under the same terms as Python (the PSF license). Copyright (c) 2004 Bob Ippolito. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. python-xattr-0.10.1/MANIFEST.in000066400000000000000000000001541435660302400160130ustar00rootroot00000000000000include *.py include xattr/*.c include xattr/*.h include *.txt include MANIFEST.in include xattr/tests/*.py python-xattr-0.10.1/README.rst000066400000000000000000000023521435660302400157460ustar00rootroot00000000000000xattr ----- .. image:: https://travis-ci.org/xattr/xattr.svg?branch=master :target: https://travis-ci.org/xattr/xattr xattr is a Python wrapper for extended filesystem attributes. xattr also ships with an `xattr` command line tool for viewing and editing extended filesystem attributes. On platforms that support or ship with the attr package, you may prefer to use the `getfattr` and `setfattr` command line tools from the attr package. Extended attributes extend the basic attributes of files and directories in the file system. They are stored as name:data pairs associated with file system objects (files, directories, symlinks, etc). 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. Note: On Linux, custom xattr keys need to be prefixed with the `user` namespace, ie: `user.your_attr`. Note: If you need to read or write Spotlight metadata attributes on macOS, see osxmetadata_ which provides a native macOS means to do so without directly manipulating extended attributes. osxmetadata also provides access to other macOS metadata attributes and extended attributes via xattr. .. _osxmetadata: https://github.com/RhetTbull/osxmetadata python-xattr-0.10.1/TODO.txt000066400000000000000000000000371435660302400155630ustar00rootroot00000000000000* Write tests * Write examples python-xattr-0.10.1/pyproject.toml000066400000000000000000000005431435660302400171730ustar00rootroot00000000000000[tool.cibuildwheel] test-requires = "pytest" test-command = "pytest {project}/xattr/tests" [tool.cibuildwheel.linux] before-all = "yum install -y libffi-devel" [[tool.cibuildwheel.overrides]] select = "*-manylinux2_*" before-all = "apt-get -y install libffi-dev" [[tool.cibuildwheel.overrides]] select = "*-musllinux*" before-all = "apk add libffi-dev"python-xattr-0.10.1/requirements.txt000066400000000000000000000000121435660302400175320ustar00rootroot00000000000000cffi>=1.0 python-xattr-0.10.1/scripts/000077500000000000000000000000001435660302400157445ustar00rootroot00000000000000python-xattr-0.10.1/scripts/artifacts.py000077500000000000000000000027761435660302400203150ustar00rootroot00000000000000#!/usr/bin/env python3 from urllib.request import urlopen import json import os import subprocess import sys import getpass def get_json(url): return json.loads(urlopen(url).read().decode('utf-8')) def download_file(src_url, dest_path): print(dest_path) subprocess.call( ['curl', '-L', '-#', '-o', dest_path, src_url]) def download_github_artifacts(): release = get_json( 'https://api.github.com/repos/xattr/xattr/releases/latest') for asset in release['assets']: download_file(asset['browser_download_url'], 'dist/{name}'.format(**asset)) def get_version(): return subprocess.check_output( [sys.executable, 'setup.py', '--version'], encoding='utf8' ).strip() def artifact_matcher(version): prefix = 'xattr-{}'.format(version) def matches(fn): return ( fn.startswith(prefix) and os.path.splitext(fn)[1] in ('.exe', '.whl') and not fn.endswith('-none-any.whl') ) or fn == '{}.tar.gz'.format(prefix) return matches def upload_artifacts(version): artifacts = set(os.listdir('dist')) matches = artifact_matcher(version) args = ['twine', 'upload'] for fn in filter(matches, artifacts): args.append(os.path.join('dist', fn)) subprocess.check_call(args) def main(): try: os.makedirs('dist') except OSError: pass download_github_artifacts() version = get_version() upload_artifacts(version) if __name__ == '__main__': main() python-xattr-0.10.1/setup.py000066400000000000000000000033031435660302400157660ustar00rootroot00000000000000#!/usr/bin/env python import os import sys from setuptools import setup VERSION = '0.10.1' DESCRIPTION = "Python wrapper for extended filesystem attributes" LONG_DESCRIPTION = """ Extended attributes extend the basic attributes of files and directories in the file system. They are stored as name:data pairs associated with file system objects (files, directories, symlinks, etc). 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 = list(filter(bool, map(str.strip, """ Environment :: Console Intended Audience :: Developers License :: OSI Approved :: MIT License Natural Language :: English Operating System :: MacOS :: MacOS X Operating System :: POSIX :: Linux Operating System :: POSIX :: BSD :: FreeBSD Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 3 Topic :: Software Development :: Libraries :: Python Modules """.splitlines()))) setup( name="xattr", version=VERSION, description=DESCRIPTION, long_description=LONG_DESCRIPTION, classifiers=CLASSIFIERS, author="Bob Ippolito", author_email="bob@redivi.com", url="http://github.com/xattr/xattr", license="MIT License", packages=['xattr'], ext_package='xattr', platforms=['MacOS X', 'Linux', 'FreeBSD', 'Solaris'], entry_points={ 'console_scripts': [ "xattr = xattr.tool:main", ], }, # Keep this in sync with pyproject.toml install_requires=["cffi>=1.0"], setup_requires=["cffi>=1.0"], cffi_modules=["xattr/lib_build.py:ffi"], test_suite="xattr.tests.all_tests_suite", zip_safe=False, ) python-xattr-0.10.1/xattr/000077500000000000000000000000001435660302400154175ustar00rootroot00000000000000python-xattr-0.10.1/xattr/__init__.py000066400000000000000000000123101435660302400175250ustar00rootroot00000000000000""" Extended attributes extend the basic attributes of files and directories in the file system. They are stored as name:data pairs associated with file system objects (files, directories, symlinks, etc). The xattr type wraps a path or file descriptor with a dict-like interface that exposes these extended attributes. """ __version__ = '0.10.1' from .compat import integer_types 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) __all__ = [ "XATTR_NOFOLLOW", "XATTR_CREATE", "XATTR_REPLACE", "XATTR_NOSECURITY", "XATTR_MAXNAMELEN", "XATTR_FINDERINFO_NAME", "XATTR_RESOURCEFORK_NAME", "xattr", "listxattr", "getxattr", "setxattr", "removexattr" ] class xattr(object): """ A wrapper for paths or file descriptors to access their extended attributes with a dict-like interface """ def __init__(self, obj, options=0): """ obj should be a path, a file descriptor, or an object that implements fileno() and returns a file descriptor. options should be 0 or XATTR_NOFOLLOW. If set, it will be OR'ed with the options passed to getxattr, setxattr, etc. """ self.obj = obj self.options = options fileno = getattr(obj, 'fileno', None) if fileno is not None: self.value = fileno() else: self.value = obj def __repr__(self): if isinstance(self.value, integer_types): flavor = "fd" else: flavor = "file" return "<%s %s=%r>" % (type(self).__name__, flavor, self.value) def _call(self, name_func, fd_func, *args): if isinstance(self.value, integer_types): return fd_func(self.value, *args) else: return name_func(self.value, *args) def get(self, name, options=0): """ Retrieve the extended attribute ``name`` as a ``str``. Raises ``IOError`` on failure. See x-man-page://2/getxattr for options and possible errors. """ return self._call(_getxattr, _fgetxattr, name, 0, 0, options | self.options) def set(self, name, value, options=0): """ Set the extended attribute ``name`` to ``value`` Raises ``IOError`` on failure. See x-man-page://2/setxattr for options and possible errors. """ return self._call(_setxattr, _fsetxattr, name, value, 0, options | self.options) def remove(self, name, options=0): """ Remove the extended attribute ``name`` Raises ``IOError`` on failure. See x-man-page://2/removexattr for options and possible errors. """ return self._call(_removexattr, _fremovexattr, name, options | self.options) def list(self, options=0): """ Retrieves the extended attributes currently set as a list of strings. Raises ``IOError`` on failure. See x-man-page://2/listxattr for options and possible errors. """ res = self._call(_listxattr, _flistxattr, options | self.options).split(b'\x00') res.pop() return [s.decode('utf-8') for s in res] # dict-like methods def __len__(self): return len(self.list()) def __delitem__(self, item): try: self.remove(item) except IOError: raise KeyError(item) def __setitem__(self, item, value): self.set(item, value) def __getitem__(self, item): try: return self.get(item) except IOError: raise KeyError(item) def iterkeys(self): return iter(self.list()) __iter__ = iterkeys def has_key(self, item): try: self.get(item) except IOError: return False else: return True __contains__ = has_key def clear(self): for k in self.keys(): del self[k] def update(self, seq): if not hasattr(seq, 'items'): seq = dict(seq) for k, v in seq.items(): self[k] = v def copy(self): return dict(self.iteritems()) def setdefault(self, k, d=''): try: d = self.get(k) except IOError: self[k] = d return d def keys(self): return self.list() def itervalues(self): for k, v in self.iteritems(): yield v def values(self): return list(self.itervalues()) def iteritems(self): for k in self.list(): yield k, self.get(k) def items(self): return list(self.iteritems()) def listxattr(f, symlink=False): return tuple(xattr(f).list(options=symlink and XATTR_NOFOLLOW or 0)) def getxattr(f, attr, symlink=False): return xattr(f).get(attr, options=symlink and XATTR_NOFOLLOW or 0) def setxattr(f, attr, value, options=0, symlink=False): if symlink: options |= XATTR_NOFOLLOW return xattr(f).set(attr, value, options=options) def removexattr(f, attr, symlink=False): options = symlink and XATTR_NOFOLLOW or 0 return xattr(f).remove(attr, options=options) python-xattr-0.10.1/xattr/compat.py000066400000000000000000000012501435660302400172520ustar00rootroot00000000000000"""Python 3 compatibility shims """ import os import sys import codecs if sys.version_info[0] < 3: integer_types = (int, long) text_type = unicode binary_type = str else: integer_types = (int,) text_type = str binary_type = bytes fs_encoding = sys.getfilesystemencoding() fs_errors = 'strict' if fs_encoding != 'mbcs': try: codecs.lookup('surrogateescape') fs_errors = 'surrogateescape' except LookupError: pass try: fs_encode = os.fsencode except AttributeError: def fs_encode(val): if not isinstance(val, bytes): return val.encode(fs_encoding, fs_errors) else: return val python-xattr-0.10.1/xattr/lib.py000066400000000000000000000072501435660302400165430ustar00rootroot00000000000000import os import sys from .compat import fs_encode try: from ._lib import lib, ffi except ImportError: from .lib_build import ffi, c_source lib = ffi.verify(c_source) XATTR_NOFOLLOW = lib.XATTR_XATTR_NOFOLLOW XATTR_CREATE = lib.XATTR_XATTR_CREATE XATTR_REPLACE = lib.XATTR_XATTR_REPLACE XATTR_NOSECURITY = lib.XATTR_XATTR_NOSECURITY XATTR_MAXNAMELEN = lib.XATTR_MAXNAMELEN XATTR_FINDERINFO_NAME = "com.apple.FinderInfo" XATTR_RESOURCEFORK_NAME = "com.apple.ResourceFork" def _check_bytes(val): if not isinstance(val, bytes): raise TypeError( "Value must be bytes, %s was passed." % type(val).__name__ ) 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 """ _check_bytes(value) 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 """ _check_bytes(value) 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] python-xattr-0.10.1/xattr/lib_build.c000066400000000000000000000343761435660302400175250ustar00rootroot00000000000000#include "Python.h" #include #ifdef __FreeBSD__ #include #elif defined(__SUN__) || defined(__sun__) || defined(__sun) #include #include #include #include #include #else #include #endif #ifdef __FreeBSD__ /* FreeBSD compatibility API */ #define XATTR_XATTR_NOFOLLOW 0x0001 #define XATTR_XATTR_CREATE 0x0002 #define XATTR_XATTR_REPLACE 0x0004 #define XATTR_XATTR_NOSECURITY 0x0008 #define XATTR_CREATE 0x1 #define XATTR_REPLACE 0x2 /* Converts a freebsd format attribute list into a NULL terminated list. * The first byte is the length of the following attribute. */ static void convert_bsd_list(char *namebuf, size_t size) { size_t offset = 0; while(offset < size) { int length = (int) (unsigned char)namebuf[offset]; memmove(namebuf+offset, namebuf+offset+1, length); namebuf[offset+length] = '\0'; offset += length+1; } } static ssize_t xattr_getxattr(const char *path, const char *name, void *value, ssize_t size, u_int32_t position, int options) { if (position != 0 || !(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return extattr_get_link(path, EXTATTR_NAMESPACE_USER, name, value, size); } else { return extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, value, size); } } static ssize_t xattr_setxattr(const char *path, const char *name, void *value, ssize_t size, u_int32_t position, int options) { int rv = 0; int nofollow; if (position != 0) { return -1; } nofollow = options & XATTR_XATTR_NOFOLLOW; options &= ~XATTR_XATTR_NOFOLLOW; if (options == XATTR_XATTR_CREATE || options == XATTR_XATTR_REPLACE) { /* meh. FreeBSD doesn't really have this in its * API... Oh well. */ } else if (options != 0) { return -1; } if (nofollow) { rv = extattr_set_link(path, EXTATTR_NAMESPACE_USER, name, value, size); } else { rv = extattr_set_file(path, EXTATTR_NAMESPACE_USER, name, value, size); } /* freebsd returns the written length on success, not zero. */ if (rv >= 0) { return 0; } else { return rv; } } static ssize_t xattr_removexattr(const char *path, const char *name, int options) { if (!(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return extattr_delete_link(path, EXTATTR_NAMESPACE_USER, name); } else { return extattr_delete_file(path, EXTATTR_NAMESPACE_USER, name); } } static ssize_t xattr_listxattr(const char *path, char *namebuf, size_t size, int options) { ssize_t rv = 0; if (!(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { rv = extattr_list_link(path, EXTATTR_NAMESPACE_USER, namebuf, size); } else { rv = extattr_list_file(path, EXTATTR_NAMESPACE_USER, namebuf, size); } if (rv > 0 && namebuf) { convert_bsd_list(namebuf, rv); } return rv; } static ssize_t xattr_fgetxattr(int fd, const char *name, void *value, ssize_t size, u_int32_t position, int options) { if (position != 0 || !(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return -1; } else { return extattr_get_fd(fd, EXTATTR_NAMESPACE_USER, name, value, size); } } static ssize_t xattr_fsetxattr(int fd, const char *name, void *value, ssize_t size, u_int32_t position, int options) { int rv = 0; int nofollow; if (position != 0) { return -1; } nofollow = options & XATTR_XATTR_NOFOLLOW; options &= ~XATTR_XATTR_NOFOLLOW; if (options == XATTR_XATTR_CREATE || options == XATTR_XATTR_REPLACE) { /* freebsd noop */ } else if (options != 0) { return -1; } if (nofollow) { return -1; } else { rv = extattr_set_fd(fd, EXTATTR_NAMESPACE_USER, name, value, size); } /* freebsd returns the written length on success, not zero. */ if (rv >= 0) { return 0; } else { return rv; } } static ssize_t xattr_fremovexattr(int fd, const char *name, int options) { if (!(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return -1; } else { return extattr_delete_fd(fd, EXTATTR_NAMESPACE_USER, name); } } static ssize_t xattr_flistxattr(int fd, char *namebuf, size_t size, int options) { ssize_t rv = 0; if (!(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return -1; } else { rv = extattr_list_fd(fd, EXTATTR_NAMESPACE_USER, namebuf, size); } if (rv > 0 && namebuf) { convert_bsd_list(namebuf, rv); } return rv; } #elif defined(__SUN__) || defined(__sun__) || defined(__sun) /* Solaris 9 and later compatibility API */ #define XATTR_XATTR_NOFOLLOW 0x0001 #define XATTR_XATTR_CREATE 0x0002 #define XATTR_XATTR_REPLACE 0x0004 #define XATTR_XATTR_NOSECURITY 0x0008 #define XATTR_CREATE 0x1 #define XATTR_REPLACE 0x2 #ifndef u_int32_t #define u_int32_t uint32_t #endif static ssize_t xattr_fgetxattr(int fd, const char *name, void *value, ssize_t size, u_int32_t position, int options) { int xfd; ssize_t bytes; struct stat statbuf; /* XXX should check that name does not have / characters in it */ xfd = openat(fd, name, O_RDONLY | O_XATTR); if (xfd == -1) { return -1; } if (lseek(xfd, position, SEEK_SET) == -1) { close(xfd); return -1; } if (value == NULL) { if (fstat(xfd, &statbuf) == -1) { close(xfd); return -1; } close(xfd); return statbuf.st_size; } /* XXX should keep reading until the buffer is exhausted or EOF */ bytes = read(xfd, value, size); close(xfd); return bytes; } static ssize_t xattr_getxattr(const char *path, const char *name, void *value, ssize_t size, u_int32_t position, int options) { int fd; ssize_t bytes; if (position != 0 || !(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } fd = open(path, O_RDONLY | ((options & XATTR_XATTR_NOFOLLOW) ? O_NOFOLLOW : 0)); if (fd == -1) { return -1; } bytes = xattr_fgetxattr(fd, name, value, size, position, options); close(fd); return bytes; } static ssize_t xattr_fsetxattr(int fd, const char *name, void *value, ssize_t size, u_int32_t position, int options) { int xfd; ssize_t bytes = 0; /* 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); if (xfd == -1) { return -1; } while (size > 0) { bytes = write(xfd, value, size); if (bytes == -1) { close(xfd); return -1; } size -= bytes; value += bytes; } close(xfd); return 0; } static ssize_t xattr_setxattr(const char *path, const char *name, void *value, ssize_t size, u_int32_t position, int options) { int fd; ssize_t bytes; if (position != 0) { return -1; } fd = open(path, O_RDONLY | (options & XATTR_XATTR_NOFOLLOW) ? O_NOFOLLOW : 0); if (fd == -1) { return -1; } bytes = xattr_fsetxattr(fd, name, value, size, position, options); close(fd); return bytes; } static ssize_t xattr_fremovexattr(int fd, const char *name, int options) { int xfd, status; /* XXX should check that name does not have / characters in it */ if (!(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return -1; } xfd = openat(fd, ".", O_XATTR, 0644); if (xfd == -1) { return -1; } status = unlinkat(xfd, name, 0); close(xfd); return status; } static ssize_t xattr_removexattr(const char *path, const char *name, int options) { int fd; ssize_t status; fd = open(path, O_RDONLY | ((options & XATTR_XATTR_NOFOLLOW) ? O_NOFOLLOW : 0)); if (fd == -1) { return -1; } status = xattr_fremovexattr(fd, name, options); close(fd); return status; } static ssize_t xattr_xflistxattr(int xfd, char *namebuf, size_t size, int options) { int esize; DIR *dirp; struct dirent *entry; ssize_t nsize = 0; dirp = fdopendir(xfd); if (dirp == NULL) { return (-1); } while (entry = readdir(dirp)) { if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) continue; 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 */ } 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 | O_XATTR); return xattr_xflistxattr(xfd, namebuf, size, options); } static ssize_t xattr_listxattr(const char *path, char *namebuf, size_t size, int options) { int xfd; xfd = attropen(path, ".", O_RDONLY); return xattr_xflistxattr(xfd, namebuf, size, options); } #elif !defined(XATTR_NOFOLLOW) /* Linux compatibility API */ #define XATTR_XATTR_NOFOLLOW 0x0001 #define XATTR_XATTR_CREATE 0x0002 #define XATTR_XATTR_REPLACE 0x0004 #define XATTR_XATTR_NOSECURITY 0x0008 static ssize_t xattr_getxattr(const char *path, const char *name, void *value, ssize_t size, u_int32_t position, int options) { if (position != 0 || !(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return lgetxattr(path, name, value, size); } else { return getxattr(path, name, value, size); } } static ssize_t xattr_setxattr(const char *path, const char *name, void *value, ssize_t size, u_int32_t position, int options) { int nofollow; if (position != 0) { return -1; } nofollow = options & XATTR_XATTR_NOFOLLOW; options &= ~XATTR_XATTR_NOFOLLOW; if (options == XATTR_XATTR_CREATE) { options = XATTR_CREATE; } else if (options == XATTR_XATTR_REPLACE) { options = XATTR_REPLACE; } else if (options != 0) { return -1; } if (nofollow) { return lsetxattr(path, name, value, size, options); } else { return setxattr(path, name, value, size, options); } } static ssize_t xattr_removexattr(const char *path, const char *name, int options) { if (!(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return lremovexattr(path, name); } else { return removexattr(path, name); } } static ssize_t xattr_listxattr(const char *path, char *namebuf, size_t size, int options) { if (!(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return llistxattr(path, namebuf, size); } else { return listxattr(path, namebuf, size); } } static ssize_t xattr_fgetxattr(int fd, const char *name, void *value, ssize_t size, u_int32_t position, int options) { if (position != 0 || !(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return -1; } else { return fgetxattr(fd, name, value, size); } } static ssize_t xattr_fsetxattr(int fd, const char *name, void *value, ssize_t size, u_int32_t position, int options) { int nofollow; if (position != 0) { return -1; } nofollow = options & XATTR_XATTR_NOFOLLOW; options &= ~XATTR_XATTR_NOFOLLOW; if (options == XATTR_XATTR_CREATE) { options = XATTR_CREATE; } else if (options == XATTR_XATTR_REPLACE) { options = XATTR_REPLACE; } else if (options != 0) { return -1; } if (nofollow) { return -1; } else { return fsetxattr(fd, name, value, size, options); } } static ssize_t xattr_fremovexattr(int fd, const char *name, int options) { if (!(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return -1; } else { return fremovexattr(fd, name); } } static ssize_t xattr_flistxattr(int fd, char *namebuf, size_t size, int options) { if (!(options == 0 || options == XATTR_XATTR_NOFOLLOW)) { return -1; } if (options & XATTR_XATTR_NOFOLLOW) { return -1; } else { return flistxattr(fd, namebuf, size); } } #else /* Mac OS X assumed */ #define xattr_getxattr getxattr #define xattr_fgetxattr fgetxattr #define xattr_removexattr removexattr #define xattr_fremovexattr fremovexattr #define xattr_setxattr setxattr #define xattr_fsetxattr fsetxattr #define xattr_listxattr listxattr #define xattr_flistxattr flistxattr /* define these for use in python (see below) */ #define XATTR_XATTR_NOFOLLOW XATTR_NOFOLLOW #define XATTR_XATTR_CREATE XATTR_CREATE #define XATTR_XATTR_REPLACE XATTR_REPLACE #define XATTR_XATTR_NOSECURITY XATTR_NOSECURITY #endif #ifndef XATTR_MAXNAMELEN #define XATTR_MAXNAMELEN 127 #endif python-xattr-0.10.1/xattr/lib_build.h000066400000000000000000000013041435660302400175130ustar00rootroot00000000000000#define XATTR_XATTR_NOFOLLOW ... #define XATTR_XATTR_CREATE ... #define XATTR_XATTR_REPLACE ... #define XATTR_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); python-xattr-0.10.1/xattr/lib_build.py000066400000000000000000000005261435660302400177210ustar00rootroot00000000000000import sys import os from cffi import FFI PATH = os.path.dirname(__file__) with open(os.path.join(PATH, 'lib_build.h')) as hf: c_header = hf.read() with open(os.path.join(PATH, 'lib_build.c')) as cf: c_source = cf.read() ffi = FFI() ffi.cdef(c_header) ffi.set_source('_lib', c_source) if __name__ == '__main__': ffi.compile() python-xattr-0.10.1/xattr/pyxattr_compat.py000066400000000000000000000072371435660302400210600ustar00rootroot00000000000000""" pyxattr and xattr have differing API, for example xattr assumes that (like on OSX) attribute keys are valid UTF-8, while pyxattr just passes through the raw bytestring. This module provides compatibility for the pyxattr API. """ import sys from .compat import (binary_type, integer_types, text_type) 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) __all__ = [ "NS_SECURITY", "NS_USER", "NS_SYSTEM", "NS_TRUSTED", "getxattr", "get", "get_all", "setxattr", "set", "removexattr", "remove", "listxattr", "list" ] NS_SECURITY = b"security" NS_USER = b"user" NS_SYSTEM = b"system" NS_TRUSTED = b"trusted" _NO_NS = object() _fsencoding = sys.getfilesystemencoding() def _call(item, name_func, fd_func, *args): if isinstance(item, integer_types): return fd_func(item, *args) elif hasattr(item, 'fileno'): return fd_func(item.fileno(), *args) elif isinstance(item, binary_type): return name_func(item, *args) elif isinstance(item, text_type): item = item.encode(_fsencoding) return name_func(item, *args) else: raise TypeError("argument must be string, int or file object") def _add_ns(item, ns): if ns is None: raise TypeError("namespace must not be None") if ns == _NO_NS: return item return b'.'.join((ns, item)) def getxattr(item, attribute, nofollow=False): options = nofollow and XATTR_NOFOLLOW or 0 return _call(item, _getxattr, _fgetxattr, attribute, 0, 0, options) def get(item, name, nofollow=False, namespace=_NO_NS): name = _add_ns(name, namespace) return getxattr(item, name, nofollow=nofollow) def get_all(item, nofollow=False, namespace=_NO_NS): if namespace is not None and namespace != _NO_NS: namespace += b'.' l = listxattr(item, nofollow=nofollow) result = [] for name in l: try: if namespace is not None and namespace != _NO_NS: if not name.startswith(namespace): continue result.append((name[len(namespace):], getxattr(item, name, nofollow=nofollow))) else: result.append((name, getxattr(item, name, nofollow=nofollow))) except IOError: pass return result def setxattr(item, name, value, flags=0, nofollow=False): options = nofollow and XATTR_NOFOLLOW or 0 options |= flags return _call(item, _setxattr, _fsetxattr, name, value, 0, options) def set(item, name, value, nofollow=False, flags=0, namespace=_NO_NS): name = _add_ns(name, namespace) return setxattr(item, name, value, flags=flags, nofollow=nofollow) def removexattr(item, name, nofollow=False): options = nofollow and XATTR_NOFOLLOW or 0 return _call(item, _removexattr, _fremovexattr, name, options) def remove(item, name, nofollow=False, namespace=_NO_NS): name = _add_ns(name, namespace) return removexattr(item, name, nofollow=nofollow) def listxattr(item, nofollow=False): options = nofollow and XATTR_NOFOLLOW or 0 res = _call(item, _listxattr, _flistxattr, options).split(b'\x00') res.pop() return res def list(item, nofollow=False, namespace=_NO_NS): if not namespace or namespace == _NO_NS: return listxattr(item, nofollow=nofollow) namespace += b'.' l = listxattr(item, nofollow=nofollow) result = [] for name in l: if not name.startswith(namespace): continue result.append(name[len(namespace):]) return result python-xattr-0.10.1/xattr/tests/000077500000000000000000000000001435660302400165615ustar00rootroot00000000000000python-xattr-0.10.1/xattr/tests/__init__.py000066400000000000000000000007061435660302400206750ustar00rootroot00000000000000import os import sys import unittest def all_tests_suite(): suite = unittest.TestLoader().loadTestsFromNames([ 'xattr.tests.test_xattr', 'xattr.tests.test_tool', ]) return suite def main(): runner = unittest.TextTestRunner() suite = all_tests_suite() runner.run(suite) if __name__ == '__main__': sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))) main() python-xattr-0.10.1/xattr/tests/test_tool.py000066400000000000000000000071561435660302400211600ustar00rootroot00000000000000import contextlib import errno import io import os import shutil import sys import tempfile import unittest import uuid import xattr import xattr.tool class TestTool(unittest.TestCase): def setUp(self): self.test_dir = tempfile.mkdtemp() self.addCleanup(shutil.rmtree, self.test_dir) orig_stdout = sys.stdout def unpatch_stdout(sys=sys, orig_stdout=orig_stdout): sys.stdout = orig_stdout self.addCleanup(unpatch_stdout) sys.stdout = self.mock_stdout = io.StringIO() def getoutput(self): value = self.mock_stdout.getvalue() self.mock_stdout.seek(0) self.mock_stdout.truncate(0) return value @contextlib.contextmanager def temp_file(self): test_file = os.path.join(self.test_dir, str(uuid.uuid4())) fd = os.open(test_file, os.O_CREAT | os.O_WRONLY) try: yield test_file, fd finally: os.close(fd) def set_xattr(self, fd, name, value): try: xattr.setxattr(fd, name, value) except OSError as e: if e.errno == errno.ENOTSUP: raise unittest.SkipTest('xattrs are not supported') raise def test_utf8(self): with self.temp_file() as (file_path, fd): self.set_xattr(fd, 'user.test-utf8', u'\N{SNOWMAN}'.encode('utf8')) self.set_xattr(fd, 'user.test-utf8-and-nul', u'\N{SNOWMAN}\0'.encode('utf8')) xattr.tool.main(['prog', '-p', 'user.test-utf8', file_path]) self.assertEqual(u'\N{SNOWMAN}\n', self.getoutput()) xattr.tool.main(['prog', '-p', 'user.test-utf8-and-nul', file_path]) self.assertEqual(u''' 0000 E2 98 83 00 .... '''.lstrip(), self.getoutput()) xattr.tool.main(['prog', '-l', file_path]) output = self.getoutput() self.assertIn(u'user.test-utf8: \N{SNOWMAN}\n', output) self.assertIn(u''' user.test-utf8-and-nul: 0000 E2 98 83 00 .... '''.lstrip(), output) def test_non_utf8(self): with self.temp_file() as (file_path, fd): self.set_xattr(fd, 'user.test-not-utf8', b'cannot\xffdecode') xattr.tool.main(['prog', '-p', 'user.test-not-utf8', file_path]) self.assertEqual(u''' 0000 63 61 6E 6E 6F 74 FF 64 65 63 6F 64 65 cannot.decode '''.lstrip(), self.getoutput()) xattr.tool.main(['prog', '-l', file_path]) self.assertIn(u''' user.test-not-utf8: 0000 63 61 6E 6E 6F 74 FF 64 65 63 6F 64 65 cannot.decode '''.lstrip(), self.getoutput()) def test_nul(self): with self.temp_file() as (file_path, fd): self.set_xattr(fd, 'user.test', b'foo\0bar') self.set_xattr(fd, 'user.test-long', b'some rather long value with\0nul\0chars in it') xattr.tool.main(['prog', '-p', 'user.test', file_path]) self.assertEqual(u''' 0000 66 6F 6F 00 62 61 72 foo.bar '''.lstrip(), self.getoutput()) xattr.tool.main(['prog', '-p', 'user.test-long', file_path]) self.assertEqual(u''' 0000 73 6F 6D 65 20 72 61 74 68 65 72 20 6C 6F 6E 67 some rather long 0010 20 76 61 6C 75 65 20 77 69 74 68 00 6E 75 6C 00 value with.nul. 0020 63 68 61 72 73 20 69 6E 20 69 74 chars in it '''.lstrip(), self.getoutput()) xattr.tool.main(['prog', '-l', file_path]) self.assertIn(u''' 0000 66 6F 6F 00 62 61 72 foo.bar '''.lstrip(), self.getoutput()) python-xattr-0.10.1/xattr/tests/test_xattr.py000066400000000000000000000120471435660302400213400ustar00rootroot00000000000000import os import sys import unittest from unittest import TestCase from tempfile import mkdtemp, NamedTemporaryFile import xattr class BaseTestXattr(object): # TESTDIR for temporary files usually defaults to "/tmp", # which may not have XATTR support (e.g. tmpfs); # manual override here. TESTDIR = None def test_attr_fs_encoding_unicode(self): # Not using setlocale(LC_ALL, ..) to set locale because # sys.getfilesystemencoding() implementation falls back # to user's preferred locale by calling setlocale(LC_ALL, ''). xattr.compat.fs_encoding = 'UTF-8' self._test_attr() def test_attr_fs_encoding_ascii(self): xattr.compat.fs_encoding = 'US-ASCII' if sys.version_info[0] < 3: with self.assertRaises(UnicodeEncodeError): self._test_attr() else: self._test_attr() def test_update(self): x = xattr.xattr(self.tempfile) attrs = { 'user.test.key1': b'test_value1', 'user.test.key2': b'test_value2' } x.update(attrs) for k, v in attrs.items(): self.assertEqual(x[k], v) def _test_attr(self): x = xattr.xattr(self.tempfile) # Solaris 11 and forward contain system attributes (file flags) in # extended attributes present on all files, so cons them up into a # comparison dict. d = {} if sys.platform == 'sunos5' and 'SUNWattr_ro' in x: d['SUNWattr_ro'] = x['SUNWattr_ro'] d['SUNWattr_rw'] = x['SUNWattr_rw'] # SELinux systems use an attribute which must be accounted for if sys.platform.startswith('linux') and 'security.selinux' in x: d['security.selinux'] = x['security.selinux'] self.assertEqual(list(x.keys()), list(d.keys())) self.assertEqual(list(x.list()), list(d.keys())) self.assertEqual(dict(x), d) x['user.sopal'] = b'foo' x['user.sop.foo'] = b'bar' x[u'user.\N{SNOWMAN}'] = b'not a snowman' del x x = xattr.xattr(self.tempfile) attrs = set(x.list()) self.assertTrue('user.sopal' in x) self.assertTrue(u'user.sopal' in attrs) self.assertEqual(x['user.sopal'], b'foo') self.assertTrue('user.sop.foo' in x) self.assertTrue(u'user.sop.foo' in attrs) self.assertEqual(x['user.sop.foo'], b'bar') self.assertTrue(u'user.\N{SNOWMAN}' in x) self.assertTrue(u'user.\N{SNOWMAN}' in attrs) self.assertEqual(x[u'user.\N{SNOWMAN}'], b'not a snowman') del x[u'user.\N{SNOWMAN}'] del x['user.sop.foo'] del x x = xattr.xattr(self.tempfile) self.assertTrue('user.sop.foo' not in x) def test_setxattr_unicode_error(self): x = xattr.xattr(self.tempfile) def assign(): x['abc'] = u'abc' self.assertRaises(TypeError, assign) if sys.version_info[0] >= 3: msg = "Value must be bytes, str was passed." else: msg = "Value must be bytes, unicode was passed." try: assign() except TypeError: e = sys.exc_info()[1] self.assertEqual(str(e), msg) def test_symlink_attrs(self): symlinkPath = self.tempfilename + '.link' os.symlink(self.tempfilename, symlinkPath) try: symlink = xattr.xattr(symlinkPath, options=xattr.XATTR_NOFOLLOW) realfile = xattr.xattr(self.tempfilename) try: symlink['user.islink'] = b'true' except IOError: # Solaris, Linux don't support extended attributes on symlinks raise unittest.SkipTest("XATTRs on symlink not allowed" " on filesystem/platform") self.assertEqual(dict(realfile), {}) self.assertEqual(symlink['user.islink'], b'true') finally: os.remove(symlinkPath) class TestFile(TestCase, BaseTestXattr): def setUp(self): self.tempfile = NamedTemporaryFile(dir=self.TESTDIR) self.tempfilename = self.tempfile.name def tearDown(self): self.tempfile.close() class TestDir(TestCase, BaseTestXattr): def setUp(self): self.tempfile = mkdtemp(dir=self.TESTDIR) self.tempfilename = self.tempfile def tearDown(self): os.rmdir(self.tempfile) try: # SkipTest is only available in Python 2.7+ unittest.SkipTest except AttributeError: pass else: class TestFileWithSurrogates(TestFile): def setUp(self): if sys.platform not in ('linux', 'linux2'): raise unittest.SkipTest('Files with invalid encoded names are only supported under linux') if sys.version_info[0] < 3: raise unittest.SkipTest('Test is only available on Python3') # surrogateescape not avail in py2 self.tempfile = NamedTemporaryFile(prefix=b'invalid-\xe9'.decode('utf8','surrogateescape'), dir=self.TESTDIR) self.tempfilename = self.tempfile.name python-xattr-0.10.1/xattr/tool.py000066400000000000000000000175601435660302400167570ustar00rootroot00000000000000## # Copyright (c) 2007 Apple Inc. # # This is the MIT license. This software may also be distributed under the # same terms as Python (the PSF license). # # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), # to deal in the Software without restriction, including without limitation # the rights to use, copy, modify, merge, publish, distribute, sublicense, # and/or sell copies of the Software, and to permit persons to whom the # Software is furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in # all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS # IN THE SOFTWARE. ## from __future__ import print_function import sys import os import getopt import zlib import xattr class NullsInString(Exception): """Nulls in string.""" def usage(e=None): if e: print(e) print("") name = os.path.basename(sys.argv[0]) print("usage: %s [-slz] file [file ...]" % (name,)) print(" %s -p [-slz] attr_name file [file ...]" % (name,)) print(" %s -w [-sz] attr_name attr_value file [file ...]" % (name,)) print(" %s -d [-s] attr_name file [file ...]" % (name,)) print(" %s -c [-s] file [file ...]" % (name,)) print("") print("The first form lists the names of all xattrs on the given file(s).") print("The second form (-p) prints the value of the xattr attr_name.") print("The third form (-w) sets the value of the xattr attr_name to attr_value.") print("The fourth form (-d) deletes the xattr attr_name.") print("The fifth form (-c) deletes (clears) all xattrs.") print("") print("options:") print(" -h: print this help") print(" -s: act on symbolic links themselves rather than their targets") print(" -l: print long format (attr_name: attr_value)") print(" -z: compress or decompress (if compressed) attribute value in zip format") if e: return 64 else: return 0 if sys.version_info < (3,): ascii = repr uchr = unichr else: uchr = chr _FILTER = u''.join([(len(ascii(chr(x))) == 3) and uchr(x) or u'.' for x in range(256)]) def _dump(src, length=16): result = [] for i in range(0, len(src), length): s = src[i:i+length] 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(argv=sys.argv): try: (optargs, args) = getopt.getopt(argv[1:], "hlpwdzsc", ["help"]) except getopt.GetoptError as e: return usage(e) attr_name = None long_format = False read = False write = False delete = False clear = False nofollow = False compress = lambda x: x decompress = compress errors = [] for opt, arg in optargs: if opt in ("-h", "--help"): return usage() elif opt == "-l": long_format = True elif opt == "-s": nofollow = True elif opt == "-p": read = True if write or delete or clear: return usage("-p not allowed with -w, -d or -c") elif opt == "-w": write = True if read or delete or clear: return usage("-w not allowed with -p, -d or -c") elif opt == "-d": delete = True if read or write or clear: return usage("-d not allowed with -p, -w or -c") elif opt == "-c": clear = True if read or write or delete: return usage("-c not allowed with -p, -w or -d") elif opt == "-z": compress = zlib.compress decompress = zlib.decompress if write or delete or clear: if long_format: return usage("-l not allowed with -w, -d or -c") if read or write or delete: if not args: return usage("No attr_name") attr_name = args.pop(0) if write: if not args: return usage("No attr_value") attr_value = args.pop(0).encode('utf-8') if len(args) == 0: return usage("No file") if len(args) > 1: multiple_files = True else: multiple_files = False options = 0 if nofollow: options |= xattr.XATTR_NOFOLLOW for filename in args: def onError(e): errors.append(e) if not os.path.exists(filename): sys.stderr.write("No such file: %s\n" % (filename,)) else: sys.stderr.write(str(e) + "\n") try: attrs = xattr.xattr(filename, options=options) except (IOError, OSError) as e: onError(e) continue if write: try: attrs[attr_name] = compress(attr_value) except (IOError, OSError) as e: onError(e) continue elif delete: try: del attrs[attr_name] except (IOError, OSError) as e: onError(e) continue except KeyError: onError("No such xattr: %s" % (attr_name,)) continue elif clear: try: attrs.clear() except (IOError, OSError) as e: onError(e) continue else: try: if read: attr_names = (attr_name,) else: attr_names = attrs.keys() except (IOError, OSError) as e: onError(e) continue if multiple_files: file_prefix = "%s: " % (filename,) else: file_prefix = "" for attr_name in attr_names: should_dump = False try: try: attr_value = decompress(attrs[attr_name]) except zlib.error: attr_value = attrs[attr_name] try: if b'\0' in attr_value: # force dumping raise NullsInString attr_value = attr_value.decode('utf-8') except (UnicodeDecodeError, NullsInString): attr_value = attr_value.decode('latin-1') should_dump = True except KeyError: onError("%sNo such xattr: %s" % (file_prefix, attr_name)) continue if long_format: if should_dump: print("".join((file_prefix, "%s:" % (attr_name,)))) print(_dump(attr_value)) else: print("".join((file_prefix, "%s: " % (attr_name,), attr_value))) else: if read: if should_dump: if file_prefix: print(file_prefix) print(_dump(attr_value)) else: print("".join((file_prefix, attr_value))) else: print("".join((file_prefix, attr_name))) return 1 if errors else 0 if __name__ == "__main__": sys.exit(main(sys.argv))