/* xattr - a python module for manipulating filesystem extended attributes Copyright (C) 2002, 2003, 2006, 2008, 2012, 2013, 2015 Iustin Pop This library 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 Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #define PY_SSIZE_T_CLEAN #include #if defined(__APPLE__) || defined(__linux__) || defined(__GLIBC__) #include #endif #include #define ITEM_DOC \ ":param item: a string representing a file-name, a file-like\n" \ " object, a file descriptor, or (in Python 3.6+) a path-like\n" \ " object; this represents the file on which to act\n" #define NOFOLLOW_DOC \ ":param nofollow: if true and if\n" \ " the file name given is a symbolic link, the\n" \ " function will operate on the symbolic link itself instead\n" \ " of its target; defaults to false\n" \ ":type nofollow: boolean, optional\n" \ #define NS_DOC \ ":param namespace: if given, the attribute must not contain the\n" \ " namespace, but instead it will be taken from this parameter\n" \ ":type namespace: bytes\n" #define NAME_GET_DOC \ ":param string name: the attribute whose value to retrieve;\n" \ " usually in the form of ``system.posix_acl`` or ``user.mime_type``\n" #define NAME_SET_DOC \ ":param string name: the attribute whose value to set;\n" \ " usually in the form of ``system.posix_acl`` or ``user.mime_type``\n" #define NAME_REMOVE_DOC \ ":param string name: the attribute to remove;\n" \ " usually in the form of ``system.posix_acl`` or \n" \ " ``user.mime_type``\n" #define VALUE_DOC \ ":param string value: possibly with embedded NULLs; note that there\n" \ " are restrictions regarding the size of the value, for\n" \ " example, for ext2/ext3, maximum size is the block size\n" \ #define FLAGS_DOC \ ":param flags: if 0 or omitted the attribute will be\n" \ " created or replaced; if :const:`XATTR_CREATE`, the attribute\n" \ " will be created, giving an error if it already exists;\n" \ " if :const:`XATTR_REPLACE`, the attribute will be replaced,\n" \ " giving an error if it doesn't exist;\n" \ ":type flags: integer\n" #define NS_CHANGED_DOC \ ".. versionchanged:: 0.5.1\n" \ " The namespace argument, if passed, cannot be None anymore; to\n" \ " explicitly specify an empty namespace, pass an empty\n" \ " string (byte string under Python 3)." #define DOCTEST_SETUP \ ".. code-block:: python\n\n" \ " >>> import xattr\n" \ " >>> import tempfile\n" \ " >>> f = tempfile.NamedTemporaryFile()\n" /* The initial I/O buffer size for list and get operations; if the * actual values will be smaller than this, we save a syscall out of * two and allocate more memory upfront than needed, otherwise we * incur three syscalls (get with ENORANGE, get with 0 to compute * actual size, final get). The test suite is marginally faster (5%) * with this, so it seems worth doing. */ #define ESTIMATE_ATTR_SIZE 1024 typedef enum {T_FD, T_PATH, T_LINK} target_e; typedef struct { target_e type; union { const char *name; int fd; }; PyObject *tmp; } target_t; /* Cleans up a tgt structure */ static void free_tgt(target_t *tgt) { if (tgt->tmp != NULL) { Py_DECREF(tgt->tmp); } } /* Used for cpychecker: */ /* The checker automatically defines this preprocessor name when creating the custom attribute: */ #if defined(WITH_CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION_ATTRIBUTE) #define CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION \ __attribute__((cpychecker_negative_result_sets_exception)) #else #define CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION #endif static int convert_obj(PyObject *myobj, target_t *tgt, int nofollow) CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; static int merge_ns(const char *ns, const char *name, const char **result, char **buf) CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; /** Converts from a string, file or int argument to what we need. * * Returns -1 on failure, 0 on success. */ static int convert_obj(PyObject *myobj, target_t *tgt, int nofollow) { int fd; tgt->tmp = NULL; if((fd = PyObject_AsFileDescriptor(myobj)) != -1) { tgt->type = T_FD; tgt->fd = fd; return 0; } // PyObject_AsFileDescriptor sets an error when failing, so clear // it such that further code works; some method lookups fail if an // error already occured when called, which breaks at least // PyOS_FSPath (called by FSConverter). PyErr_Clear(); if(PyUnicode_FSConverter(myobj, &(tgt->tmp))) { tgt->type = nofollow ? T_LINK : T_PATH; tgt->name = PyBytes_AS_STRING(tgt->tmp); return 0; } else { // Don't set our own exception type, since we'd ignore the // FSConverter-generated one. tgt->type = T_PATH; tgt->name = NULL; return -1; } } /* Combine a namespace string and an attribute name into a fully-qualified name */ static int merge_ns(const char *ns, const char *name, const char **result, char **buf) { if(ns != NULL && *ns != '\0') { int cnt; /* The value of new_size is related to/must be kept in-sync with the format string below */ size_t new_size = strlen(ns) + 1 + strlen(name) + 1; if((*buf = PyMem_Malloc(new_size)) == NULL) { PyErr_NoMemory(); return -1; } cnt = snprintf(*buf, new_size, "%s.%s", ns, name); if((size_t) cnt >= new_size || cnt < 0) { PyErr_SetString(PyExc_ValueError, "unexpected: can't format the attribute name"); PyMem_Free(*buf); return -1; } *result = *buf; } else { *buf = NULL; *result = name; } return 0; } #if defined(__APPLE__) static inline ssize_t _listxattr(const char *path, char *namebuf, size_t size) { return listxattr(path, namebuf, size, 0); } static inline ssize_t _llistxattr(const char *path, char *namebuf, size_t size) { return listxattr(path, namebuf, size, XATTR_NOFOLLOW); } static inline ssize_t _flistxattr(int fd, char *namebuf, size_t size) { return flistxattr(fd, namebuf, size, 0); } static inline ssize_t _getxattr (const char *path, const char *name, void *value, size_t size) { return getxattr(path, name, value, size, 0, 0); } static inline ssize_t _lgetxattr (const char *path, const char *name, void *value, size_t size) { return getxattr(path, name, value, size, 0, XATTR_NOFOLLOW); } static inline ssize_t _fgetxattr (int filedes, const char *name, void *value, size_t size) { return fgetxattr(filedes, name, value, size, 0, 0); } // [fl]setxattr: Both OS X and Linux define XATTR_CREATE and XATTR_REPLACE for the last option. static inline int _setxattr(const char *path, const char *name, const void *value, size_t size, int flags) { return setxattr(path, name, value, size, 0, flags); } static inline int _lsetxattr(const char *path, const char *name, const void *value, size_t size, int flags) { return setxattr(path, name, value, size, 0, flags | XATTR_NOFOLLOW); } static inline int _fsetxattr(int filedes, const char *name, const void *value, size_t size, int flags) { return fsetxattr(filedes, name, value, size, 0, flags); } static inline int _removexattr(const char *path, const char *name) { return removexattr(path, name, 0); } static inline int _lremovexattr(const char *path, const char *name) { return removexattr(path, name, XATTR_NOFOLLOW); } static inline int _fremovexattr(int filedes, const char *name) { return fremovexattr(filedes, name, 0); } #elif defined(__linux__) || defined(__GLIBC__) #define _listxattr(path, list, size) listxattr(path, list, size) #define _llistxattr(path, list, size) llistxattr(path, list, size) #define _flistxattr(fd, list, size) flistxattr(fd, list, size) #define _getxattr(path, name, value, size) getxattr(path, name, value, size) #define _lgetxattr(path, name, value, size) lgetxattr(path, name, value, size) #define _fgetxattr(fd, name, value, size) fgetxattr(fd, name, value, size) #define _setxattr(path, name, value, size, flags) setxattr(path, name, value, size, flags) #define _lsetxattr(path, name, value, size, flags) lsetxattr(path, name, value, size, flags) #define _fsetxattr(fd, name, value, size, flags) fsetxattr(fd, name, value, size, flags) #define _removexattr(path, name) removexattr(path, name) #define _lremovexattr(path, name) lremovexattr(path, name) #define _fremovexattr(fd, name) fremovexattr(fd, name) #endif typedef ssize_t (*buf_getter)(target_t *tgt, const char *name, void *output, size_t size); static ssize_t _list_obj(target_t *tgt, const char *unused, void *list, size_t size) { ssize_t ret; Py_BEGIN_ALLOW_THREADS; if(tgt->type == T_FD) ret = _flistxattr(tgt->fd, list, size); else if (tgt->type == T_LINK) ret = _llistxattr(tgt->name, list, size); else ret = _listxattr(tgt->name, list, size); Py_END_ALLOW_THREADS; return ret; } static ssize_t _get_obj(target_t *tgt, const char *name, void *value, size_t size) { ssize_t ret; Py_BEGIN_ALLOW_THREADS; if(tgt->type == T_FD) ret = _fgetxattr(tgt->fd, name, value, size); else if (tgt->type == T_LINK) ret = _lgetxattr(tgt->name, name, value, size); else ret = _getxattr(tgt->name, name, value, size); Py_END_ALLOW_THREADS; return ret; } static int _set_obj(target_t *tgt, const char *name, const void *value, size_t size, int flags) { int ret; Py_BEGIN_ALLOW_THREADS; if(tgt->type == T_FD) ret = _fsetxattr(tgt->fd, name, value, size, flags); else if (tgt->type == T_LINK) ret = _lsetxattr(tgt->name, name, value, size, flags); else ret = _setxattr(tgt->name, name, value, size, flags); Py_END_ALLOW_THREADS; return ret; } static int _remove_obj(target_t *tgt, const char *name) { int ret; Py_BEGIN_ALLOW_THREADS; if(tgt->type == T_FD) ret = _fremovexattr(tgt->fd, name); else if (tgt->type == T_LINK) ret = _lremovexattr(tgt->name, name); else ret = _removexattr(tgt->name, name); Py_END_ALLOW_THREADS; return ret; } /* Perform a get/list operation with appropriate buffer size, * determined dynamically. * * Arguments: * - getter: the function that actually does the I/O. * - tgt, name: passed to the getter. * - buffer: pointer to either an already allocated memory area (in * which case size contains its current size), or NULL to * allocate. In all cases (success or failure), the caller should * deallocate the buffer, using PyMem_Free(). * - size: either size of current buffer (if non-NULL), or size for * initial allocation; zero means use a hardcoded initial buffer * size (ESTIMATE_ATTR_SIZE). The value will be updated upon return * with the current buffer size. * - io_errno: if non-NULL, the actual errno will be recorded here; if * zero, the call was successful and the output/size/nval are valid. * * Return value: if positive or zero, buffer will contain the read * value. Otherwise, io_errno will contain the I/O errno, or zero * to signify a Python-level error. In all cases, the Python-level * error is set to the appropriate value. */ static ssize_t _generic_get(buf_getter getter, target_t *tgt, const char *name, char **buffer, size_t *size, int *io_errno) CPYCHECKER_NEGATIVE_RESULT_SETS_EXCEPTION; static ssize_t _generic_get(buf_getter getter, target_t *tgt, const char *name, char **buffer, size_t *size, int *io_errno) { ssize_t res; /* Clear errno for now, will only set it when it fails in I/O. */ if (io_errno != NULL) { *io_errno = 0; } #define EXIT_IOERROR() \ { \ if (io_errno != NULL) { \ *io_errno = errno; \ } \ PyErr_SetFromErrno(PyExc_IOError); \ return -1; \ } /* Initialize the buffer, if needed. */ if (*buffer == NULL) { if (*size == 0) *size = ESTIMATE_ATTR_SIZE; if((*buffer = PyMem_Malloc(*size)) == NULL) { PyErr_NoMemory(); return -1; } } // Try to get the value, while increasing the buffer if too small. while((res = getter(tgt, name, *buffer, *size)) == -1) { if(errno == ERANGE) { ssize_t realloc_size_s = getter(tgt, name, NULL, 0); /* ERANGE + proper size _should_ not fail, but... */ if(realloc_size_s == -1) { EXIT_IOERROR(); } size_t realloc_size = (size_t) realloc_size_s; char *tmp_buf; if((tmp_buf = PyMem_Realloc(*buffer, realloc_size)) == NULL) { PyErr_NoMemory(); return -1; } *buffer = tmp_buf; *size = realloc_size; continue; } else { /* else we're dealing with a different error, which we don't know how to handle nicely, so we return */ EXIT_IOERROR(); } } return res; #undef EXIT_IOERROR } /* Checks if an attribute name matches an optional namespace. If the namespace is NULL or an empty string, it will return the name itself. If the namespace is non-NULL and the name matches, it will return a pointer to the offset in the name after the namespace and the separator. If however the name doesn't match the namespace, it will return NULL. */ const char *matches_ns(const char *ns, const char *name) { size_t ns_size; if (ns == NULL || *ns == '\0') return name; ns_size = strlen(ns); if (strlen(name) > (ns_size+1) && !strncmp(name, ns, ns_size) && name[ns_size] == '.') return name + ns_size + 1; return NULL; } /* Wrapper for getxattr */ static char __pygetxattr_doc__[] = "getxattr(item, attribute[, nofollow=False])\n" "Get the value of a given extended attribute (deprecated).\n" "\n" ITEM_DOC NAME_GET_DOC NOFOLLOW_DOC "\n" ".. deprecated:: 0.4\n" " this function has been deprecated\n" " by the :func:`get` function.\n" ; static PyObject * pygetxattr(PyObject *self, PyObject *args) { PyObject *myarg; target_t tgt; int nofollow = 0; char *attrname = NULL; char *buf = NULL; ssize_t nret; size_t nalloc = 0; PyObject *res; /* Parse the arguments */ if (!PyArg_ParseTuple(args, "Oet|i", &myarg, NULL, &attrname, &nofollow)) return NULL; if(convert_obj(myarg, &tgt, nofollow) < 0) { res = NULL; goto free_arg; } nret = _generic_get(_get_obj, &tgt, attrname, &buf, &nalloc, NULL); if (nret == -1) { res = NULL; goto free_buf; } /* Create the string which will hold the result */ res = PyBytes_FromStringAndSize(buf, nret); free_buf: /* Free the buffer, now it is no longer needed */ PyMem_Free(buf); free_tgt(&tgt); free_arg: PyMem_Free(attrname); /* Return the result */ return res; } /* Wrapper for getxattr */ static char __get_doc__[] = "get(item, name[, nofollow=False, namespace=None])\n" "Get the value of a given extended attribute.\n" "\n" "Example:\n" DOCTEST_SETUP " >>> xattr.set(f.name, 'user.comment', 'test')\n" " >>> xattr.get(f.name, 'comment', namespace=xattr.NS_USER)\n" " b'test'\n" "\n" ITEM_DOC NAME_GET_DOC NOFOLLOW_DOC NS_DOC ":return: the value of the extended attribute (can contain NULLs)\n" ":rtype: bytes\n" ":raises EnvironmentError: caused by any system errors\n" "\n" ".. versionadded:: 0.4\n" NS_CHANGED_DOC ; static PyObject * xattr_get(PyObject *self, PyObject *args, PyObject *keywds) { PyObject *myarg; target_t tgt; int nofollow = 0; char *attrname = NULL, *namebuf; const char *fullname; char *buf = NULL; const char *ns = NULL; ssize_t nret; size_t nalloc = 0; PyObject *res = NULL; static char *kwlist[] = {"item", "name", "nofollow", "namespace", NULL}; /* Parse the arguments */ if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oet|iy", kwlist, &myarg, NULL, &attrname, &nofollow, &ns)) return NULL; res = NULL; if(convert_obj(myarg, &tgt, nofollow) < 0) { goto free_arg; } if(merge_ns(ns, attrname, &fullname, &namebuf) < 0) { goto free_tgt; } nret = _generic_get(_get_obj, &tgt, fullname, &buf, &nalloc, NULL); if(nret == -1) { goto free_buf; } /* Create the string which will hold the result */ res = PyBytes_FromStringAndSize(buf, nret); /* Free the buffers, they are no longer needed */ free_buf: PyMem_Free(buf); PyMem_Free(namebuf); free_tgt: free_tgt(&tgt); free_arg: PyMem_Free(attrname); /* Return the result */ return res; } /* Wrapper for getxattr */ static char __get_all_doc__[] = "get_all(item[, nofollow=False, namespace=None])\n" "Get all the extended attributes of an item.\n" "\n" "This function performs a bulk-get of all extended attribute names\n" "and the corresponding value.\n" "Example:\n" "\n" DOCTEST_SETUP " >>> xattr.set(f.name, 'user.mime-type', 'text/plain')\n" " >>> xattr.set(f.name, 'user.comment', 'test file')\n" " >>> xattr.get_all(f.name)\n" " [(b'user.mime-type', b'text/plain'), (b'user.comment', b'test file')]\n" " >>> xattr.get_all(f.name, namespace=xattr.NS_USER)\n" " [(b'mime-type', b'text/plain'), (b'comment', b'test file')]\n" "\n" ITEM_DOC ":keyword namespace: an optional namespace for filtering the\n" " attributes; for example, querying all user attributes can be\n" " accomplished by passing namespace=:const:`NS_USER`\n" ":type namespace: string\n" NOFOLLOW_DOC ":return: list of tuples (name, value); note that if a namespace\n" " argument was passed, it (and the separator) will be stripped from\n" " the names returned\n" ":rtype: list[(bytes, bytes)]\n" ":raises EnvironmentError: caused by any system errors\n" "\n" ".. note:: Since reading the whole attribute list is not an atomic\n" " operation, it might be possible that attributes are added\n" " or removed between the initial query and the actual reading\n" " of the attributes; the returned list will contain only the\n" " attributes that were present at the initial listing of the\n" " attribute names and that were still present when the read\n" " attempt for the value is made.\n" ".. versionadded:: 0.4\n" NS_CHANGED_DOC ; static PyObject * get_all(PyObject *self, PyObject *args, PyObject *keywds) { PyObject *myarg, *res; int nofollow=0; const char *ns = NULL; char *buf_list = NULL, *buf_val = NULL; const char *s; size_t nalloc = 0; ssize_t nlist, nval; PyObject *mylist; target_t tgt; static char *kwlist[] = {"item", "nofollow", "namespace", NULL}; int io_errno; /* Parse the arguments */ if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iy", kwlist, &myarg, &nofollow, &ns)) return NULL; if(convert_obj(myarg, &tgt, nofollow) < 0) return NULL; res = NULL; /* Compute first the list of attributes */ nlist = _generic_get(_list_obj, &tgt, NULL, &buf_list, &nalloc, &io_errno); if (nlist == -1) { /* We can't handle any errors, and the Python error is already set, just bail out. */ goto free_tgt; } /* Create the list which will hold the result. */ mylist = PyList_New(0); if(mylist == NULL) { goto free_buf_list; } nalloc = 0; /* Create and insert the attributes as strings in the list */ for(s = buf_list; s - buf_list < nlist; s += strlen(s) + 1) { PyObject *my_tuple; const char *name; if((name = matches_ns(ns, s)) == NULL) continue; /* Now retrieve the attribute value */ nval = _generic_get(_get_obj, &tgt, s, &buf_val, &nalloc, &io_errno); if (nval == -1) { if (io_errno == ENODATA) { PyErr_Clear(); continue; } else { Py_DECREF(mylist); goto free_buf_val; } } my_tuple = Py_BuildValue("yy#", name, buf_val, nval); if (my_tuple == NULL) { Py_DECREF(mylist); goto free_buf_val; } int lappend_ret = PyList_Append(mylist, my_tuple); Py_DECREF(my_tuple); if(lappend_ret < 0) { Py_DECREF(mylist); goto free_buf_val; } } /* Successful exit */ res = mylist; free_buf_val: PyMem_Free(buf_val); free_buf_list: PyMem_Free(buf_list); free_tgt: free_tgt(&tgt); /* Return the result */ return res; } static char __pysetxattr_doc__[] = "setxattr(item, name, value[, flags=0, nofollow=False])\n" "Set the value of a given extended attribute (deprecated).\n" "\n" "Be careful in case you want to set attributes on symbolic\n" "links, you have to use all the 5 parameters; use 0 for the \n" "flags value if you want the default behaviour (create or " "replace)\n" "\n" ITEM_DOC NAME_SET_DOC VALUE_DOC FLAGS_DOC NOFOLLOW_DOC "\n" ".. deprecated:: 0.4\n" " this function has been deprecated\n" " by the :func:`set` function.\n" ; /* Wrapper for setxattr */ static PyObject * pysetxattr(PyObject *self, PyObject *args) { PyObject *myarg, *res; int nofollow = 0; char *attrname = NULL; char *buf = NULL; Py_ssize_t bufsize_s; size_t bufsize; int nret; int flags = 0; target_t tgt; /* Parse the arguments */ if (!PyArg_ParseTuple(args, "Oetet#|ii", &myarg, NULL, &attrname, NULL, &buf, &bufsize_s, &flags, &nofollow)) return NULL; if (bufsize_s < 0) { PyErr_SetString(PyExc_ValueError, "negative value size?!"); res = NULL; goto free_arg; } bufsize = (size_t) bufsize_s; if(convert_obj(myarg, &tgt, nofollow) < 0) { res = NULL; goto free_arg; } /* Set the attribute's value */ nret = _set_obj(&tgt, attrname, buf, bufsize, flags); free_tgt(&tgt); if(nret == -1) { res = PyErr_SetFromErrno(PyExc_IOError); goto free_arg; } Py_INCREF(Py_None); res = Py_None; free_arg: PyMem_Free(attrname); PyMem_Free(buf); /* Return the result */ return res; } static char __set_doc__[] = "set(item, name, value[, flags=0, namespace=None])\n" "Set the value of a given extended attribute.\n" "\n" "Example:\n" "\n" DOCTEST_SETUP " >>> xattr.set(f.name, 'user.comment', 'test')\n" " >>> xattr.set(f.name, 'comment', 'test', namespace=xattr.NS_USER)\n" "\n" ITEM_DOC NAME_SET_DOC VALUE_DOC FLAGS_DOC NOFOLLOW_DOC NS_DOC ":returns: None\n" ":raises EnvironmentError: caused by any system errors\n" "\n" ".. versionadded:: 0.4\n" NS_CHANGED_DOC ; /* Wrapper for setxattr */ static PyObject * xattr_set(PyObject *self, PyObject *args, PyObject *keywds) { PyObject *myarg, *res; int nofollow = 0; char *attrname = NULL; char *buf = NULL; Py_ssize_t bufsize_s; size_t bufsize; int nret; int flags = 0; target_t tgt; const char *ns = NULL; char *newname; const char *full_name; static char *kwlist[] = {"item", "name", "value", "flags", "nofollow", "namespace", NULL}; /* Parse the arguments */ if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oetet#|iiy", kwlist, &myarg, NULL, &attrname, NULL, &buf, &bufsize_s, &flags, &nofollow, &ns)) return NULL; if (bufsize_s < 0) { PyErr_SetString(PyExc_ValueError, "negative value size?!"); res = NULL; goto free_arg; } bufsize = (size_t) bufsize_s; if(convert_obj(myarg, &tgt, nofollow) < 0) { res = NULL; goto free_arg; } if(merge_ns(ns, attrname, &full_name, &newname) < 0) { res = NULL; goto free_arg; } /* Set the attribute's value */ nret = _set_obj(&tgt, full_name, buf, bufsize, flags); PyMem_Free(newname); free_tgt(&tgt); if(nret == -1) { res = PyErr_SetFromErrno(PyExc_IOError); goto free_arg; } Py_INCREF(Py_None); res = Py_None; free_arg: PyMem_Free(attrname); PyMem_Free(buf); /* Return the result */ return res; } static char __pyremovexattr_doc__[] = "removexattr(item, name[, nofollow])\n" "Remove an attribute from a file (deprecated).\n" "\n" ITEM_DOC NAME_REMOVE_DOC NOFOLLOW_DOC "\n" ".. deprecated:: 0.4\n" " this function has been deprecated by the :func:`remove` function.\n" ; /* Wrapper for removexattr */ static PyObject * pyremovexattr(PyObject *self, PyObject *args) { PyObject *myarg, *res; int nofollow = 0; char *attrname = NULL; int nret; target_t tgt; /* Parse the arguments */ if (!PyArg_ParseTuple(args, "Oet|i", &myarg, NULL, &attrname, &nofollow)) return NULL; if(convert_obj(myarg, &tgt, nofollow) < 0) { res = NULL; goto free_arg; } /* Remove the attribute */ nret = _remove_obj(&tgt, attrname); free_tgt(&tgt); if(nret == -1) { res = PyErr_SetFromErrno(PyExc_IOError); goto free_arg; } Py_INCREF(Py_None); res = Py_None; free_arg: PyMem_Free(attrname); /* Return the result */ return res; } static char __remove_doc__[] = "remove(item, name[, nofollow=False, namespace=None])\n" "Remove an attribute from a file.\n" "\n" "Example:\n" "\n" DOCTEST_SETUP " >>> xattr.set(f.name, 'user.comment', 'test')\n" " >>> xattr.list(f.name)\n" " [b'user.comment']\n" " >>> xattr.remove(f.name, 'user.comment')\n" " >>> xattr.list(f.name)\n" " []\n" "\n" ITEM_DOC NAME_REMOVE_DOC NOFOLLOW_DOC NS_DOC ":returns: None\n" ":raises EnvironmentError: caused by any system errors\n" "\n" ".. versionadded:: 0.4\n" NS_CHANGED_DOC ; /* Wrapper for removexattr */ static PyObject * xattr_remove(PyObject *self, PyObject *args, PyObject *keywds) { PyObject *myarg, *res; int nofollow = 0; char *attrname = NULL, *name_buf; const char *ns = NULL; const char *full_name; int nret; target_t tgt; static char *kwlist[] = {"item", "name", "nofollow", "namespace", NULL}; /* Parse the arguments */ if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oet|iy", kwlist, &myarg, NULL, &attrname, &nofollow, &ns)) return NULL; if(convert_obj(myarg, &tgt, nofollow) < 0) { res = NULL; goto free_arg; } if(merge_ns(ns, attrname, &full_name, &name_buf) < 0) { res = NULL; goto free_arg; } /* Remove the attribute */ nret = _remove_obj(&tgt, full_name); PyMem_Free(name_buf); free_tgt(&tgt); if(nret == -1) { res = PyErr_SetFromErrno(PyExc_IOError); goto free_arg; } Py_INCREF(Py_None); res = Py_None; free_arg: PyMem_Free(attrname); /* Return the result */ return res; } static char __pylistxattr_doc__[] = "listxattr(item[, nofollow=False])\n" "Return the list of attribute names for a file (deprecated).\n" "\n" ITEM_DOC NOFOLLOW_DOC "\n" ".. deprecated:: 0.4\n" " this function has been deprecated by the :func:`list` function.\n" ; /* Wrapper for listxattr */ static PyObject * pylistxattr(PyObject *self, PyObject *args) { char *buf = NULL; int nofollow = 0; ssize_t nret; size_t nalloc = 0; PyObject *myarg; PyObject *mylist; Py_ssize_t nattrs; char *s; target_t tgt; /* Parse the arguments */ if (!PyArg_ParseTuple(args, "O|i", &myarg, &nofollow)) return NULL; if(convert_obj(myarg, &tgt, nofollow) < 0) return NULL; nret = _generic_get(_list_obj, &tgt, NULL, &buf, &nalloc, NULL); if (nret == -1) { mylist = NULL; goto free_buf; } /* Compute the number of attributes in the list */ for(s = buf, nattrs = 0; (s - buf) < nret; s += strlen(s) + 1) { nattrs++; } /* Create the list which will hold the result */ mylist = PyList_New(nattrs); if(mylist == NULL) { goto free_buf; } /* Create and insert the attributes as strings in the list */ for(s = buf, nattrs = 0; s - buf < nret; s += strlen(s) + 1) { PyObject *item = PyBytes_FromString(s); if(item == NULL) { Py_DECREF(mylist); mylist = NULL; goto free_buf; } PyList_SET_ITEM(mylist, nattrs, item); nattrs++; } free_buf: /* Free the buffer, now it is no longer needed */ PyMem_Free(buf); free_tgt(&tgt); /* Return the result */ return mylist; } static char __list_doc__[] = "list(item[, nofollow=False, namespace=None])\n" "Return the list of attribute names for a file.\n" "\n" "Example:\n" "\n" DOCTEST_SETUP " >>> xattr.list(f.name, namespace=xattr.NS_USER)\n" " []\n" " >>> xattr.set(f.name, 'comment', 'test', namespace=xattr.NS_USER)\n" " >>> xattr.list(f.name, namespace=xattr.NS_USER)\n" " [b'comment']\n" "\n" ITEM_DOC NOFOLLOW_DOC NS_DOC ":returns: the list of attributes; note that if a namespace \n" " argument was passed, it (and the separator) will be stripped\n" " from the names\n" " returned\n" ":rtype: list[bytes]\n" ":raises EnvironmentError: caused by any system errors\n" "\n" ".. versionadded:: 0.4\n" NS_CHANGED_DOC ; /* Wrapper for listxattr */ static PyObject * xattr_list(PyObject *self, PyObject *args, PyObject *keywds) { char *buf = NULL; int nofollow = 0; ssize_t nret; size_t nalloc = 0; PyObject *myarg; PyObject *res; const char *ns = NULL; Py_ssize_t nattrs; char *s; target_t tgt; static char *kwlist[] = {"item", "nofollow", "namespace", NULL}; /* Parse the arguments */ if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iy", kwlist, &myarg, &nofollow, &ns)) return NULL; res = NULL; if(convert_obj(myarg, &tgt, nofollow) < 0) { goto free_arg; } nret = _generic_get(_list_obj, &tgt, NULL, &buf, &nalloc, NULL); if (nret == -1) { goto free_tgt; } /* Compute the number of attributes in the list */ for(s = buf, nattrs = 0; (s - buf) < nret; s += strlen(s) + 1) { if(matches_ns(ns, s) != NULL) nattrs++; } /* Create the list which will hold the result */ if((res = PyList_New(nattrs)) == NULL) { goto free_buf; } /* Create and insert the attributes as strings in the list */ for(s = buf, nattrs = 0; s - buf < nret; s += strlen(s) + 1) { const char *name = matches_ns(ns, s); if(name != NULL) { PyObject *item = PyBytes_FromString(name); if(item == NULL) { Py_DECREF(res); res = NULL; goto free_buf; } PyList_SET_ITEM(res, nattrs, item); nattrs++; } } free_buf: /* Free the buffer, now it is no longer needed */ PyMem_Free(buf); free_tgt: free_tgt(&tgt); free_arg: /* Return the result */ return res; } static PyMethodDef xattr_methods[] = { {"getxattr", pygetxattr, METH_VARARGS, __pygetxattr_doc__ }, {"get", (PyCFunction) xattr_get, METH_VARARGS | METH_KEYWORDS, __get_doc__ }, {"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__ }, {"remove", (PyCFunction) xattr_remove, METH_VARARGS | METH_KEYWORDS, __remove_doc__ }, {"listxattr", pylistxattr, METH_VARARGS, __pylistxattr_doc__ }, {"list", (PyCFunction) xattr_list, METH_VARARGS | METH_KEYWORDS, __list_doc__ }, {NULL, NULL, 0, NULL} /* Sentinel */ }; static char __xattr_doc__[] = \ "This module gives access to the extended attributes present\n" "in some operating systems/filesystems. You can list attributes,\n" "get, set and remove them.\n" "\n" "The module exposes two sets of functions:\n" " - the 'old' :func:`listxattr`, :func:`getxattr`, :func:`setxattr`,\n" " :func:`removexattr`\n" " functions which are deprecated since version 0.4\n" " - the new :func:`list`, :func:`get`, :func:`get_all`, :func:`set`,\n" " :func:`remove` functions\n" " which expose a namespace-aware API and simplify a bit the calling\n" " model by using keyword arguments\n" "\n" "Example: \n\n" DOCTEST_SETUP " >>> xattr.set(f.name, 'mime_type', 'text/plain', namespace=xattr.NS_USER)\n" " >>> xattr.list(f.name, namespace=xattr.NS_USER)\n" " [b'mime_type']\n" " >>> xattr.get(f.name, 'user.mime_type')\n" " b'text/plain'\n" " >>> xattr.set(f.name, 'user.comment', 'Simple text file')\n" " >>> xattr.list(f.name, namespace=xattr.NS_USER)\n" " [b'mime_type', b'comment']\n" " >>> xattr.remove (f.name, 'user.comment')\n" " >>> xattr.list(f.name, namespace=xattr.NS_USER)\n" " [b'mime_type']\n" "\n" ".. note:: Most or all errors reported by the system while using\n" " the ``xattr`` library will be reported by raising\n" " a :exc:`EnvironmentError`; under\n" " Linux, the following ``errno`` values are used:\n" "\n" " - ``ENODATA`` means that the attribute name is invalid\n" " - ``ENOTSUP`` and ``EOPNOTSUPP`` mean that the filesystem does not\n" " support extended attributes, or that the namespace is invalid\n" " - ``E2BIG`` mean that the attribute value is too big\n" " - ``ERANGE`` mean that the attribute name is too big (it might also\n" " mean an error in the xattr module itself)\n" " - ``ENOSPC`` and ``EDQUOT`` are documented as meaning out of disk\n" " space or out of disk space because of quota limits\n" ".. note:: Under Python 3, the namespace argument is a byte string,\n" " not a unicode string, and all return values are bytes, not strings.\n" "\n" ; static struct PyModuleDef xattrmodule = { PyModuleDef_HEAD_INIT, "xattr", __xattr_doc__, 0, xattr_methods, }; #define INITERROR return NULL PyMODINIT_FUNC PyInit_xattr(void) { PyObject *ns_security = NULL; PyObject *ns_system = NULL; PyObject *ns_trusted = NULL; PyObject *ns_user = NULL; PyObject *m = PyModule_Create(&xattrmodule); if (m==NULL) return NULL; PyModule_AddStringConstant(m, "__author__", _XATTR_AUTHOR); PyModule_AddStringConstant(m, "__contact__", _XATTR_EMAIL); PyModule_AddStringConstant(m, "__version__", _XATTR_VERSION); PyModule_AddStringConstant(m, "__license__", "GNU Lesser General Public License (LGPL)"); PyModule_AddStringConstant(m, "__docformat__", "restructuredtext en"); PyModule_AddIntConstant(m, "XATTR_CREATE", XATTR_CREATE); PyModule_AddIntConstant(m, "XATTR_REPLACE", XATTR_REPLACE); /* namespace constants */ if((ns_security = PyBytes_FromString("security")) == NULL) goto err_out; if((ns_system = PyBytes_FromString("system")) == NULL) goto err_out; if((ns_trusted = PyBytes_FromString("trusted")) == NULL) goto err_out; if((ns_user = PyBytes_FromString("user")) == NULL) goto err_out; /* Add the new objects to the module */ /* TODO: after switching to min 3.10, use the *Ref version, and simplify the error handling. */ if(PyModule_AddObject(m, "NS_SECURITY", ns_security) < 0) goto err_out; ns_security = NULL; if(PyModule_AddObject(m, "NS_SYSTEM", ns_system) < 0) goto err_out; ns_system = NULL; if(PyModule_AddObject(m, "NS_TRUSTED", ns_trusted) < 0) goto err_out; ns_trusted = NULL; if(PyModule_AddObject(m, "NS_USER", ns_user) < 0) goto err_out; ns_user = NULL; return m; err_out: Py_XDECREF(ns_user); Py_XDECREF(ns_trusted); Py_XDECREF(ns_system); Py_XDECREF(ns_security); Py_DECREF(m); INITERROR; }