From 6da354c5eab4a583c225d2dc6c2141d4cbd9ed73 Mon Sep 17 00:00:00 2001 From: Iustin Pop Date: Sun, 29 Jun 2008 20:24:35 +0200 Subject: [PATCH] Add new-style set() function This patch adds a new style set function and a generic merge_ns function for combining a namespace string and an attribute name into the final name. The patch also duplicates all test methods for dual testing of new-style and deprecated functions. --- test/test_xattr.py | 114 +++++++++++++++++++++++++++++++++++++++++---- xattr.c | 97 +++++++++++++++++++++++++++++++++++++- 2 files changed, 202 insertions(+), 9 deletions(-) diff --git a/test/test_xattr.py b/test/test_xattr.py index 9015c27..12eed6c 100644 --- a/test/test_xattr.py +++ b/test/test_xattr.py @@ -11,6 +11,7 @@ import xattr class xattrTest(unittest.TestCase): USER_ATTR = "user.test" USER_VAL = "abc" + MANYOPS_COUNT = 131072 def setUp(self): """set up function""" @@ -44,8 +45,8 @@ class xattrTest(unittest.TestCase): os.symlink(fname + ".non-existent", fname) return fname - def _checkListSetGet(self, item, symlink=False): - """check list, set, get operations against an item""" + def _checkDeprecated(self, item, symlink=False): + """check deprecated list, set, get operations against an item""" self.failUnlessEqual(xattr.listxattr(item, symlink), []) self.failUnlessRaises(EnvironmentError, xattr.setxattr, item, self.USER_ATTR, self.USER_VAL, @@ -72,6 +73,34 @@ class xattrTest(unittest.TestCase): self.failUnlessRaises(EnvironmentError, xattr.removexattr, item, self.USER_ATTR) + def _checkListSetGet(self, item, symlink=False): + """check list, set, get operations against an item""" + self.failUnlessEqual(xattr.listxattr(item, symlink), []) + self.failUnlessRaises(EnvironmentError, xattr.set, item, + self.USER_ATTR, self.USER_VAL, + flags=xattr.XATTR_REPLACE) + try: + xattr.set(item, self.USER_ATTR, self.USER_VAL, nofollow=symlink) + except IOError, err: + if err.errno == errno.EPERM and symlink: + # symlinks may fail, in which case we abort the rest + # of the test for this case + return + raise + self.failUnlessRaises(EnvironmentError, xattr.set, item, + self.USER_ATTR, self.USER_VAL, + flags=xattr.XATTR_CREATE) + self.failUnlessEqual(xattr.listxattr(item, symlink), [self.USER_ATTR]) + self.failUnlessEqual(xattr.getxattr(item, self.USER_ATTR, symlink), + self.USER_VAL) + self.failUnlessEqual(xattr.get_all(item, nofollow=symlink), + [(self.USER_ATTR, self.USER_VAL)]) + xattr.removexattr(item, self.USER_ATTR) + self.failUnlessEqual(xattr.listxattr(item, symlink), []) + self.failUnlessEqual(xattr.get_all(item, nofollow=symlink), []) + self.failUnlessRaises(EnvironmentError, xattr.removexattr, + item, self.USER_ATTR) + def testNoXattr(self): """test no attributes""" fh, fname = self._getfile() @@ -84,18 +113,37 @@ class xattrTest(unittest.TestCase): self.failUnlessEqual(xattr.listxattr(sname, True), []) self.failUnlessEqual(xattr.get_all(sname, nofollow=True), []) + def testFileByNameDeprecated(self): + """test set and retrieve one attribute by file name (deprecated)""" + fh, fname = self._getfile() + self._checkDeprecated(fname) + os.close(fh) + def testFileByName(self): """test set and retrieve one attribute by file name""" fh, fname = self._getfile() self._checkListSetGet(fname) os.close(fh) + def testFileByDescriptorDeprecated(self): + """test file descriptor operations (deprecated functions)""" + fh, fname = self._getfile() + self._checkDeprecated(fh) + os.close(fh) + def testFileByDescriptor(self): """test file descriptor operations""" fh, fname = self._getfile() self._checkListSetGet(fh) os.close(fh) + def testFileByObjectDeprecated(self): + """test file descriptor operations (deprecated functions)""" + fh, fname = self._getfile() + fo = os.fdopen(fh) + self._checkDeprecated(fo) + fo.close() + def testFileByObject(self): """test file descriptor operations""" fh, fname = self._getfile() @@ -103,12 +151,26 @@ class xattrTest(unittest.TestCase): self._checkListSetGet(fo) fo.close() + def testMixedAccessDeprecated(self): + """test mixed access to file (deprecated functions)""" + fh, fname = self._getfile() + fo = os.fdopen(fh) + self.failUnlessEqual(xattr.listxattr(fname), []) + xattr.setxattr(fname, self.USER_ATTR, self.USER_VAL) + self.failUnlessEqual(xattr.listxattr(fh), [self.USER_ATTR]) + self.failUnlessEqual(xattr.getxattr(fo, self.USER_ATTR), + self.USER_VAL) + self.failUnlessEqual(xattr.get_all(fo), + [(self.USER_ATTR, self.USER_VAL)]) + self.failUnlessEqual(xattr.get_all(fname), + [(self.USER_ATTR, self.USER_VAL)]) + def testMixedAccess(self): """test mixed access to file""" fh, fname = self._getfile() fo = os.fdopen(fh) self.failUnlessEqual(xattr.listxattr(fname), []) - xattr.setxattr(fname, self.USER_ATTR, self.USER_VAL) + xattr.set(fname, self.USER_ATTR, self.USER_VAL) self.failUnlessEqual(xattr.listxattr(fh), [self.USER_ATTR]) self.failUnlessEqual(xattr.getxattr(fo, self.USER_ATTR), self.USER_VAL) @@ -117,38 +179,74 @@ class xattrTest(unittest.TestCase): self.failUnlessEqual(xattr.get_all(fname), [(self.USER_ATTR, self.USER_VAL)]) + def testDirOpsDeprecated(self): + """test attribute setting on directories (deprecated functions)""" + dname = self._getdir() + self._checkDeprecated(dname) + def testDirOps(self): """test attribute setting on directories""" dname = self._getdir() self._checkListSetGet(dname) + def testSymlinkOpsDeprecated(self): + """test symlink operations (deprecated functions)""" + sname = self._getsymlink() + self.failUnlessRaises(EnvironmentError, xattr.listxattr, sname) + self._checkDeprecated(sname, symlink=True) + def testSymlinkOps(self): """test symlink operations""" sname = self._getsymlink() self.failUnlessRaises(EnvironmentError, xattr.listxattr, sname) self._checkListSetGet(sname, symlink=True) + def testBinaryPayloadDeprecated(self): + """test binary values (deprecated functions)""" + fh, fname = self._getfile() + os.close(fh) + BINVAL = "abc" + '\0' + "def" + xattr.setxattr(fname, self.USER_ATTR, BINVAL) + self.failUnlessEqual(xattr.listxattr(fname), [self.USER_ATTR]) + self.failUnlessEqual(xattr.getxattr(fname, self.USER_ATTR), BINVAL) + self.failUnlessEqual(xattr.get_all(fname), [(self.USER_ATTR, BINVAL)]) + xattr.removexattr(fname, self.USER_ATTR) + def testBinaryPayload(self): """test binary values""" fh, fname = self._getfile() os.close(fh) BINVAL = "abc" + '\0' + "def" - xattr.setxattr(fname, self.USER_ATTR, BINVAL) + xattr.set(fname, self.USER_ATTR, BINVAL) self.failUnlessEqual(xattr.listxattr(fname), [self.USER_ATTR]) self.failUnlessEqual(xattr.getxattr(fname, self.USER_ATTR), BINVAL) self.failUnlessEqual(xattr.get_all(fname), [(self.USER_ATTR, BINVAL)]) xattr.removexattr(fname, self.USER_ATTR) + def testManyOpsDeprecated(self): + """test many ops (deprecated functions)""" + fh, fname = self._getfile() + xattr.setxattr(fh, self.USER_ATTR, self.USER_VAL) + VL = [self.USER_ATTR] + for i in range(self.MANYOPS_COUNT): + self.failUnlessEqual(xattr.listxattr(fh), VL) + for i in range(self.MANYOPS_COUNT): + self.failUnlessEqual(xattr.getxattr(fh, self.USER_ATTR), + self.USER_VAL) + for i in range(self.MANYOPS_COUNT): + self.failUnlessEqual(xattr.get_all(fh), + [(self.USER_ATTR, self.USER_VAL)]) + def testManyOps(self): """test many ops""" fh, fname = self._getfile() - xattr.setxattr(fh, self.USER_ATTR, self.USER_VAL) + xattr.set(fh, self.USER_ATTR, self.USER_VAL) VL = [self.USER_ATTR] - for i in range(131072): + for i in range(self.MANYOPS_COUNT): self.failUnlessEqual(xattr.listxattr(fh), VL) - for i in range(131072): + for i in range(self.MANYOPS_COUNT): self.failUnlessEqual(xattr.getxattr(fh, self.USER_ATTR), self.USER_VAL) - for i in range(131072): + for i in range(self.MANYOPS_COUNT): self.failUnlessEqual(xattr.get_all(fh), [(self.USER_ATTR, self.USER_VAL)]) diff --git a/xattr.c b/xattr.c index d2b4389..d3a60e1 100644 --- a/xattr.c +++ b/xattr.c @@ -1,5 +1,6 @@ #include #include +#include /* the estimated (startup) attribute buffer size in multi-operations */ @@ -31,6 +32,30 @@ static int convertObj(PyObject *myobj, target_t *tgt, int nofollow) { return 1; } +/* Combine a namespace string and an attribute name into a + fully-qualified name */ +static char* merge_ns(const char *ns, const char *name, char **buf) { + if(ns != NULL) { + int cnt; + size_t new_size = strlen(ns) + 1 + strlen(name) + 1; + if((*buf = PyMem_Malloc(new_size)) == NULL) { + PyErr_NoMemory(); + return NULL; + } + cnt = snprintf(*buf, new_size, "%s.%s", ns, name); + if(cnt > new_size || cnt < 0) { + PyErr_SetString(PyExc_ValueError, + "can't format the attribute name"); + PyMem_Free(*buf); + return NULL; + } + return *buf; + } else { + *buf = NULL; + return name; + } +} + static ssize_t _list_obj(target_t *tgt, char *list, size_t size) { if(tgt->type == T_FD) return flistxattr(tgt->fd, list, size); @@ -299,7 +324,9 @@ static char __pysetxattr_doc__[] = " - (optional) a boolean value (defaults to false), which, if\n" " the file name given is a symbolic link, makes the\n" " function operate on the symbolic link itself instead\n" - " of its target;" + " of its target;\n" + "@deprecated: this function has been deprecated by the new L{set}" + " function\n" ; /* Wrapper for setxattr */ @@ -330,6 +357,72 @@ pysetxattr(PyObject *self, PyObject *args) Py_RETURN_NONE; } +static char __set_doc__[] = + "Set the value of a given extended attribute.\n" + "\n" + "@param item: the item to query; either a string representing the" + " filename, or a file-like object, or a file descriptor\n" + "@param name: the attribute whose value to set; usually in form of" + " system.posix_acl or user.mime_type\n" + "@type name: string\n" + "@param value: a string, possibly with embedded NULLs; note that there" + " are restrictions regarding the size of the value, for" + " example, for ext2/ext3, maximum size is the block size\n" + "@type value: string\n" + "@param flags: if 0 or ommited the attribute will be" + " created or replaced; if L{XATTR_CREATE}, the attribute" + " will be created, giving an error if it already exists;" + " if L{XATTR_REPLACE}, the attribute will be replaced," + " giving an error if it doesn't exists;\n" + "@type flags: integer\n" + "@param nofollow: if given and True, and the function is passed a" + " filename that points to a symlink, the function will act on the symlink" + " itself instead of its target\n" + "@type nofollow: boolean\n" + "@param namespace: if given, the attribute must not contain the namespace" + " itself, but instead the namespace will be taken from this parameter\n" + "@type namespace: string\n" + ; + +/* Wrapper for setxattr */ +static PyObject * +xattr_set(PyObject *self, PyObject *args, PyObject *keywds) +{ + PyObject *myarg; + int nofollow=0; + char *attrname; + char *buf; + int bufsize, nret; + int flags = 0; + target_t tgt; + char *ns = NULL; + char *newname; + char *full_name; + static char *kwlist[] = {"item", "name", "value", "flags", + "nofollow", "namespace", NULL}; + + /* Parse the arguments */ + if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oss#|iiz", kwlist, + &myarg, &attrname, + &buf, &bufsize, &flags, &nofollow, &ns)) + return NULL; + if(!convertObj(myarg, &tgt, nofollow)) + return NULL; + + full_name = merge_ns(ns, attrname, &newname); + /* Set the attribute's value */ + nret = _set_obj(&tgt, full_name, buf, bufsize, flags); + if(newname != NULL) + PyMem_Free(newname); + if(nret == -1) { + return PyErr_SetFromErrno(PyExc_IOError); + } + + /* Return the result */ + Py_RETURN_NONE; +} + + static char __pyremovexattr_doc__[] = "Remove an attribute from a file\n" "\n" @@ -446,6 +539,8 @@ static PyMethodDef xattr_methods[] = { {"get_all", (PyCFunction) get_all, METH_VARARGS | METH_KEYWORDS, __get_all_doc__ }, {"setxattr", pysetxattr, METH_VARARGS, __pysetxattr_doc__ }, + {"set", (PyCFunction) xattr_set, METH_VARARGS | METH_KEYWORDS, + __set_doc__ }, {"removexattr", pyremovexattr, METH_VARARGS, __pyremovexattr_doc__ }, {"listxattr", pylistxattr, METH_VARARGS, __pylistxattr_doc__ }, {NULL, NULL, 0, NULL} /* Sentinel */ -- 2.39.5