From 163b95004fb674b6ed49def816de362c11c038d9 Mon Sep 17 00:00:00 2001 From: Iustin Pop Date: Mon, 21 Jul 2014 03:34:31 +0200 Subject: [PATCH] Imported Upstream version 0.5.3 --- NEWS | 17 +++++ PKG-INFO | 4 +- README | 4 +- doc/conf.py | 6 +- pyxattr.egg-info/PKG-INFO | 4 +- setup.py | 4 +- test/test_xattr.py | 129 +++++++++++++++++++++++--------------- xattr.c | 23 ++++++- 8 files changed, 129 insertions(+), 62 deletions(-) diff --git a/NEWS b/NEWS index 84d4ca4..06d2627 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,23 @@ News ==== +Version 0.5.3 +------------- + +Small optimisations release: + +* ari edelkind contributed a speed-up optimisation for handling of files + without xattrs (which is, in general, the expected case) +* Jonas Borgström contributed a behaviour change to the handling of file + names: under Python 3 and up, unicode paths are encoded/decoded using + the 'surogatee' handler, instead of the 'strict' handler; while this + can hide encoding errors, it mirrors what Python libraries do + (e.g. see os.fsencode/fsdecode) +* Sean Patrick Santos contributed improvements to the test suite so that + it can be used even on files systems which have built-in attributes + (e.g. when using SELinux, or NFSv4); to enable this, define the + attributes in the TEST_IGNORE_XATTRS environment variable + Version 0.5.2 ------------- diff --git a/PKG-INFO b/PKG-INFO index c63cad3..66b8fda 100644 --- a/PKG-INFO +++ b/PKG-INFO @@ -1,12 +1,12 @@ Metadata-Version: 1.1 Name: pyxattr -Version: 0.5.2 +Version: 0.5.3 Summary: Filesystem extended attributes for python Home-page: http://pyxattr.k1024.org/ Author: Iustin Pop Author-email: iusty@k1024.org License: LGPL -Download-URL: https://github.com/iustin/pyxattr/downloads +Download-URL: http://pyxattr.k1024.org/downloads/ Description: This is a C extension module for Python which implements extended attributes manipulation. It is a wrapper on top of the attr C library - see attr(5). diff --git a/README b/README index 5988580..492645d 100644 --- a/README +++ b/README @@ -6,7 +6,7 @@ to the extended attributes for filesystem objects available in some operating systems. Downloads: go to http://pyxattr.k1024.org/downloads/. Latest -version is 0.5.2. The source repository is either at +version is 0.5.3. The source repository is either at http://git.k1024.org/pyxattr.git or at https://github.com/iustin/pyxattr. @@ -24,7 +24,7 @@ installed in order to build and install the module. License ------- -pyxattr is Copyright 2002-2008, 2012, 2013 Iustin Pop. +pyxattr is Copyright 2002-2008, 2012, 2013, 2014 Iustin Pop. pyxattr is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free diff --git a/doc/conf.py b/doc/conf.py index 0d2e64b..d0733cb 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -41,16 +41,16 @@ master_doc = 'index' # General information about the project. project = u'pyxattr' -copyright = u'2002, 2003, 2006, 2008, 2012, 2013, Iustin Pop' +copyright = u'2002, 2003, 2006, 2008, 2012, 2013, 2014, Iustin Pop' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '0.5.2' +version = '0.5.3' # The full version, including alpha/beta/rc tags. -release = '0.5.2' +release = '0.5.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/pyxattr.egg-info/PKG-INFO b/pyxattr.egg-info/PKG-INFO index c63cad3..66b8fda 100644 --- a/pyxattr.egg-info/PKG-INFO +++ b/pyxattr.egg-info/PKG-INFO @@ -1,12 +1,12 @@ Metadata-Version: 1.1 Name: pyxattr -Version: 0.5.2 +Version: 0.5.3 Summary: Filesystem extended attributes for python Home-page: http://pyxattr.k1024.org/ Author: Iustin Pop Author-email: iusty@k1024.org License: LGPL -Download-URL: https://github.com/iustin/pyxattr/downloads +Download-URL: http://pyxattr.k1024.org/downloads/ Description: This is a C extension module for Python which implements extended attributes manipulation. It is a wrapper on top of the attr C library - see attr(5). diff --git a/setup.py b/setup.py index c18e054..82c9747 100755 --- a/setup.py +++ b/setup.py @@ -9,7 +9,7 @@ except ImportError: long_desc = """This is a C extension module for Python which implements extended attributes manipulation. It is a wrapper on top of the attr C library - see attr(5).""" -version = "0.5.2" +version = "0.5.3" author = "Iustin Pop" author_email = "iusty@k1024.org" macros = [ @@ -24,7 +24,7 @@ setup(name = "pyxattr", author = author, author_email = author_email, url = "http://pyxattr.k1024.org/", - download_url = "https://github.com/iustin/pyxattr/downloads", + download_url = "http://pyxattr.k1024.org/downloads/", license = "LGPL", ext_modules = [Extension("xattr", ["xattr.c"], libraries=["attr"], diff --git a/test/test_xattr.py b/test/test_xattr.py index 0bca576..69e939e 100644 --- a/test/test_xattr.py +++ b/test/test_xattr.py @@ -17,8 +17,15 @@ else: PY3K = False EMPTY_NS = '' -TEST_DIR = os.environ.get("TESTDIR", ".") - +TEST_DIR = os.environ.get("TEST_DIR", ".") +TEST_IGNORE_XATTRS = os.environ.get("TEST_IGNORE_XATTRS", "") +if TEST_IGNORE_XATTRS == "": + TEST_IGNORE_XATTRS = [] +else: + TEST_IGNORE_XATTRS = TEST_IGNORE_XATTRS.split(",") + # The following has to be a list comprehension, not a generator, to + # avoid weird consequences of lazy evaluation. + TEST_IGNORE_XATTRS.extend([a.encode() for a in TEST_IGNORE_XATTRS]) class xattrTest(unittest.TestCase): USER_NN = "test" @@ -31,6 +38,26 @@ class xattrTest(unittest.TestCase): USER_VAL = USER_VAL.encode() USER_ATTR = USER_ATTR.encode() + @staticmethod + def _ignore_tuples(attrs): + """Remove ignored attributes from the output of xattr.get_all.""" + return [attr for attr in attrs + if attr[0] not in TEST_IGNORE_XATTRS] + + @staticmethod + def _ignore(attrs): + """Remove ignored attributes from the output of xattr.list""" + return [attr for attr in attrs + if attr not in TEST_IGNORE_XATTRS] + + def checkList(self, attrs, value): + """Helper to check attribute list equivalence, skipping TEST_IGNORE_XATTRS.""" + self.assertEqual(self._ignore(attrs), value) + + def checkTuples(self, attrs, value): + """Helper to check attribute list equivalence, skipping TEST_IGNORE_XATTRS.""" + self.assertEqual(self._ignore_tuples(attrs), value) + def setUp(self): """set up function""" self.rmfiles = [] @@ -74,7 +101,7 @@ class xattrTest(unittest.TestCase): def _checkDeprecated(self, item, symlink=False): """check deprecated list, set, get operations against an item""" - self.assertEqual(xattr.listxattr(item, symlink), []) + self.checkList(xattr.listxattr(item, symlink), []) self.assertRaises(EnvironmentError, xattr.setxattr, item, self.USER_ATTR, self.USER_VAL, XATTR_REPLACE) @@ -89,20 +116,21 @@ class xattrTest(unittest.TestCase): raise self.assertRaises(EnvironmentError, xattr.setxattr, item, self.USER_ATTR, self.USER_VAL, XATTR_CREATE) - self.assertEqual(xattr.listxattr(item, symlink), [self.USER_ATTR]) + self.checkList(xattr.listxattr(item, symlink), [self.USER_ATTR]) self.assertEqual(xattr.getxattr(item, self.USER_ATTR, symlink), self.USER_VAL) - self.assertEqual(xattr.get_all(item, nofollow=symlink), - [(self.USER_ATTR, self.USER_VAL)]) + self.checkTuples(xattr.get_all(item, nofollow=symlink), + [(self.USER_ATTR, self.USER_VAL)]) xattr.removexattr(item, self.USER_ATTR) - self.assertEqual(xattr.listxattr(item, symlink), []) - self.assertEqual(xattr.get_all(item, nofollow=symlink), []) + self.checkList(xattr.listxattr(item, symlink), []) + self.checkTuples(xattr.get_all(item, nofollow=symlink), + []) self.assertRaises(EnvironmentError, xattr.removexattr, item, self.USER_ATTR) def _checkListSetGet(self, item, symlink=False, use_ns=False): """check list, set, get operations against an item""" - self.assertEqual(xattr.list(item, symlink), []) + self.checkList(xattr.list(item, symlink), []) self.assertRaises(EnvironmentError, xattr.set, item, self.USER_ATTR, self.USER_VAL, flags=XATTR_REPLACE) self.assertRaises(EnvironmentError, xattr.set, item, @@ -128,10 +156,9 @@ class xattrTest(unittest.TestCase): self.assertRaises(EnvironmentError, xattr.set, item, self.USER_NN, self.USER_VAL, flags=XATTR_CREATE, namespace=NS_USER) - self.assertEqual(xattr.list(item, nofollow=symlink), - [self.USER_ATTR]) - self.assertEqual(xattr.list(item, nofollow=symlink, - namespace=EMPTY_NS), + self.checkList(xattr.list(item, nofollow=symlink), [self.USER_ATTR]) + self.checkList(xattr.list(item, nofollow=symlink, + namespace=EMPTY_NS), [self.USER_ATTR]) self.assertEqual(xattr.list(item, namespace=NS_USER, nofollow=symlink), [self.USER_NN]) @@ -139,7 +166,7 @@ class xattrTest(unittest.TestCase): self.USER_VAL) self.assertEqual(xattr.get(item, self.USER_NN, nofollow=symlink, namespace=NS_USER), self.USER_VAL) - self.assertEqual(xattr.get_all(item, nofollow=symlink), + self.checkTuples(xattr.get_all(item, nofollow=symlink), [(self.USER_ATTR, self.USER_VAL)]) self.assertEqual(xattr.get_all(item, nofollow=symlink, namespace=NS_USER), @@ -148,8 +175,9 @@ class xattrTest(unittest.TestCase): xattr.remove(item, self.USER_NN, namespace=NS_USER) else: xattr.remove(item, self.USER_ATTR) - self.assertEqual(xattr.list(item, symlink), []) - self.assertEqual(xattr.get_all(item, nofollow=symlink), []) + self.checkList(xattr.list(item, symlink), []) + self.checkTuples(xattr.get_all(item, nofollow=symlink), + []) self.assertRaises(EnvironmentError, xattr.remove, item, self.USER_ATTR, nofollow=symlink) self.assertRaises(EnvironmentError, xattr.remove, item, @@ -158,32 +186,32 @@ class xattrTest(unittest.TestCase): def testNoXattrDeprecated(self): """test no attributes (deprecated functions)""" fh, fname = self._getfile() - self.assertEqual(xattr.listxattr(fname), []) - self.assertEqual(xattr.get_all(fname), []) + self.checkList(xattr.listxattr(fname), []) + self.checkTuples(xattr.get_all(fname), []) dname = self._getdir() - self.assertEqual(xattr.listxattr(dname), []) - self.assertEqual(xattr.get_all(dname), []) + self.checkList(xattr.listxattr(dname), []) + self.checkTuples(xattr.get_all(dname), []) _, sname = self._getsymlink() - self.assertEqual(xattr.listxattr(sname, True), []) - self.assertEqual(xattr.get_all(sname, nofollow=True), []) + self.checkList(xattr.listxattr(sname, True), []) + self.checkTuples(xattr.get_all(sname, nofollow=True), []) def testNoXattr(self): """test no attributes""" fh, fname = self._getfile() - self.assertEqual(xattr.list(fname), []) + self.checkList(xattr.list(fname), []) self.assertEqual(xattr.list(fname, namespace=NS_USER), []) - self.assertEqual(xattr.get_all(fname), []) + self.checkTuples(xattr.get_all(fname), []) self.assertEqual(xattr.get_all(fname, namespace=NS_USER), []) dname = self._getdir() - self.assertEqual(xattr.list(dname), []) + self.checkList(xattr.list(dname), []) self.assertEqual(xattr.list(dname, namespace=NS_USER), []) - self.assertEqual(xattr.get_all(dname), []) + self.checkTuples(xattr.get_all(dname), []) self.assertEqual(xattr.get_all(dname, namespace=NS_USER), []) _, sname = self._getsymlink() - self.assertEqual(xattr.list(sname, nofollow=True), []) + self.checkList(xattr.list(sname, nofollow=True), []) self.assertEqual(xattr.list(sname, nofollow=True, - namespace=NS_USER), []) - self.assertEqual(xattr.get_all(sname, nofollow=True), []) + namespace=NS_USER), []) + self.checkTuples(xattr.get_all(sname, nofollow=True), []) self.assertEqual(xattr.get_all(sname, nofollow=True, namespace=NS_USER), []) @@ -232,12 +260,12 @@ class xattrTest(unittest.TestCase): """test mixed access to file (deprecated functions)""" fh, fname = self._getfile() fo = os.fdopen(fh) - self.assertEqual(xattr.listxattr(fname), []) + self.checkList(xattr.listxattr(fname), []) xattr.setxattr(fname, self.USER_ATTR, self.USER_VAL) - self.assertEqual(xattr.listxattr(fh), [self.USER_ATTR]) + self.checkList(xattr.listxattr(fh), [self.USER_ATTR]) self.assertEqual(xattr.getxattr(fo, self.USER_ATTR), self.USER_VAL) - self.assertEqual(xattr.get_all(fo), [(self.USER_ATTR, self.USER_VAL)]) - self.assertEqual(xattr.get_all(fname), + self.checkTuples(xattr.get_all(fo), [(self.USER_ATTR, self.USER_VAL)]) + self.checkTuples(xattr.get_all(fname), [(self.USER_ATTR, self.USER_VAL)]) fo.close() @@ -245,17 +273,18 @@ class xattrTest(unittest.TestCase): """test mixed access to file""" fh, fname = self._getfile() fo = os.fdopen(fh) - self.assertEqual(xattr.list(fname), []) + self.checkList(xattr.list(fname), []) xattr.set(fname, self.USER_ATTR, self.USER_VAL) - self.assertEqual(xattr.list(fh), [self.USER_ATTR]) + self.checkList(xattr.list(fh), [self.USER_ATTR]) self.assertEqual(xattr.list(fh, namespace=NS_USER), [self.USER_NN]) self.assertEqual(xattr.get(fo, self.USER_ATTR), self.USER_VAL) self.assertEqual(xattr.get(fo, self.USER_NN, namespace=NS_USER), self.USER_VAL) - self.assertEqual(xattr.get_all(fo), [(self.USER_ATTR, self.USER_VAL)]) + self.checkTuples(xattr.get_all(fo), + [(self.USER_ATTR, self.USER_VAL)]) self.assertEqual(xattr.get_all(fo, namespace=NS_USER), [(self.USER_NN, self.USER_VAL)]) - self.assertEqual(xattr.get_all(fname), + self.checkTuples(xattr.get_all(fname), [(self.USER_ATTR, self.USER_VAL)]) self.assertEqual(xattr.get_all(fname, namespace=NS_USER), [(self.USER_NN, self.USER_VAL)]) @@ -279,8 +308,8 @@ class xattrTest(unittest.TestCase): self._checkDeprecated(sname, symlink=True) target, sname = self._getsymlink(dangling=False) xattr.setxattr(target, self.USER_ATTR, self.USER_VAL) - self.assertEqual(xattr.listxattr(target), [self.USER_ATTR]) - self.assertEqual(xattr.listxattr(sname, True), []) + self.checkList(xattr.listxattr(target), [self.USER_ATTR]) + self.checkList(xattr.listxattr(sname, True), []) self.assertRaises(EnvironmentError, xattr.removexattr, sname, self.USER_ATTR, True) xattr.removexattr(sname, self.USER_ATTR, False) @@ -293,8 +322,8 @@ class xattrTest(unittest.TestCase): self._checkListSetGet(sname, symlink=True, use_ns=True) target, sname = self._getsymlink(dangling=False) xattr.set(target, self.USER_ATTR, self.USER_VAL) - self.assertEqual(xattr.list(target), [self.USER_ATTR]) - self.assertEqual(xattr.list(sname, nofollow=True), []) + self.checkList(xattr.list(target), [self.USER_ATTR]) + self.checkList(xattr.list(sname, nofollow=True), []) self.assertRaises(EnvironmentError, xattr.remove, sname, self.USER_ATTR, nofollow=True) xattr.remove(sname, self.USER_ATTR, nofollow=False) @@ -307,9 +336,9 @@ class xattrTest(unittest.TestCase): if PY3K: BINVAL = BINVAL.encode() xattr.setxattr(fname, self.USER_ATTR, BINVAL) - self.assertEqual(xattr.listxattr(fname), [self.USER_ATTR]) + self.checkList(xattr.listxattr(fname), [self.USER_ATTR]) self.assertEqual(xattr.getxattr(fname, self.USER_ATTR), BINVAL) - self.assertEqual(xattr.get_all(fname), [(self.USER_ATTR, BINVAL)]) + self.checkTuples(xattr.get_all(fname), [(self.USER_ATTR, BINVAL)]) xattr.removexattr(fname, self.USER_ATTR) def testBinaryPayload(self): @@ -320,12 +349,12 @@ class xattrTest(unittest.TestCase): if PY3K: BINVAL = BINVAL.encode() xattr.set(fname, self.USER_ATTR, BINVAL) - self.assertEqual(xattr.list(fname), [self.USER_ATTR]) + self.checkList(xattr.list(fname), [self.USER_ATTR]) self.assertEqual(xattr.list(fname, namespace=NS_USER), [self.USER_NN]) self.assertEqual(xattr.get(fname, self.USER_ATTR), BINVAL) self.assertEqual(xattr.get(fname, self.USER_NN, namespace=NS_USER), BINVAL) - self.assertEqual(xattr.get_all(fname), [(self.USER_ATTR, BINVAL)]) + self.checkTuples(xattr.get_all(fname), [(self.USER_ATTR, BINVAL)]) self.assertEqual(xattr.get_all(fname, namespace=NS_USER), [(self.USER_NN, BINVAL)]) xattr.remove(fname, self.USER_ATTR) @@ -336,11 +365,11 @@ class xattrTest(unittest.TestCase): xattr.setxattr(fh, self.USER_ATTR, self.USER_VAL) VL = [self.USER_ATTR] for i in range(self.MANYOPS_COUNT): - self.assertEqual(xattr.listxattr(fh), VL) + self.checkList(xattr.listxattr(fh), VL) for i in range(self.MANYOPS_COUNT): self.assertEqual(xattr.getxattr(fh, self.USER_ATTR), self.USER_VAL) for i in range(self.MANYOPS_COUNT): - self.assertEqual(xattr.get_all(fh), + self.checkTuples(xattr.get_all(fh), [(self.USER_ATTR, self.USER_VAL)]) def testManyOps(self): @@ -350,15 +379,15 @@ class xattrTest(unittest.TestCase): VL = [self.USER_ATTR] VN = [self.USER_NN] for i in range(self.MANYOPS_COUNT): - self.assertEqual(xattr.list(fh), VL) - self.assertEqual(xattr.list(fh, namespace=EMPTY_NS), VL) + self.checkList(xattr.list(fh), VL) + self.checkList(xattr.list(fh, namespace=EMPTY_NS), VL) self.assertEqual(xattr.list(fh, namespace=NS_USER), VN) for i in range(self.MANYOPS_COUNT): self.assertEqual(xattr.get(fh, self.USER_ATTR), self.USER_VAL) self.assertEqual(xattr.get(fh, self.USER_NN, namespace=NS_USER), self.USER_VAL) for i in range(self.MANYOPS_COUNT): - self.assertEqual(xattr.get_all(fh), + self.checkTuples(xattr.get_all(fh), [(self.USER_ATTR, self.USER_VAL)]) self.assertEqual(xattr.get_all(fh, namespace=NS_USER), [(self.USER_NN, self.USER_VAL)]) diff --git a/xattr.c b/xattr.c index 57257bb..cc1fa44 100644 --- a/xattr.c +++ b/xattr.c @@ -147,7 +147,13 @@ static int convert_obj(PyObject *myobj, target_t *tgt, int nofollow) { tgt->type = nofollow ? T_LINK : T_PATH; tgt->tmp = \ PyUnicode_AsEncodedString(myobj, - Py_FileSystemDefaultEncoding, "strict"); + Py_FileSystemDefaultEncoding, +#ifdef IS_PY3K + "surrogateescape" +#else + "strict" +#endif + ); if(tgt->tmp == NULL) return -1; tgt->name = PyBytes_AS_STRING(tgt->tmp); @@ -467,6 +473,11 @@ get_all(PyObject *self, PyObject *args, PyObject *keywds) goto freetgt; } + if(nalloc == 0) { + res = PyList_New(0); + goto freetgt; + } + /* Try to allocate the memory, using Python's allocator */ if((buf_list = PyMem_Malloc(nalloc)) == NULL) { res = PyErr_NoMemory(); @@ -864,6 +875,11 @@ pylistxattr(PyObject *self, PyObject *args) goto freetgt; } + if(nalloc == 0) { + mylist = PyList_New(0); + goto freetgt; + } + /* Try to allocate the memory, using Python's allocator */ if((buf = PyMem_Malloc(nalloc)) == NULL) { mylist = PyErr_NoMemory(); @@ -964,6 +980,11 @@ xattr_list(PyObject *self, PyObject *args, PyObject *keywds) goto freetgt; } + if(nalloc == 0) { + res = PyList_New(0); + goto freetgt; + } + /* Try to allocate the memory, using Python's allocator */ if((buf = PyMem_Malloc(nalloc)) == NULL) { res = PyErr_NoMemory(); -- 2.39.5