1 #define PY_SSIZE_T_CLEAN
3 #include <attr/xattr.h>
6 /* the estimated (startup) attribute buffer size in
8 #define ESTIMATE_ATTR_SIZE 256
10 /* Compatibility with python 2.4 regarding python size type (PEP 353) */
11 #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
12 typedef int Py_ssize_t;
13 #define PY_SSIZE_T_MAX INT_MAX
14 #define PY_SSIZE_T_MIN INT_MIN
17 typedef enum {T_FD, T_PATH, T_LINK} target_e;
27 /** Converts from a string, file or int argument to what we need. */
28 static int convertObj(PyObject *myobj, target_t *tgt, int nofollow) {
30 if(PyString_Check(myobj)) {
31 tgt->type = nofollow ? T_LINK : T_PATH;
32 tgt->name = PyString_AS_STRING(myobj);
33 } else if((fd = PyObject_AsFileDescriptor(myobj)) != -1) {
37 PyErr_SetString(PyExc_TypeError, "argument must be string or int");
43 /* Combine a namespace string and an attribute name into a
44 fully-qualified name */
45 static const char* merge_ns(const char *ns, const char *name, char **buf) {
48 size_t new_size = strlen(ns) + 1 + strlen(name) + 1;
49 if((*buf = PyMem_Malloc(new_size)) == NULL) {
53 cnt = snprintf(*buf, new_size, "%s.%s", ns, name);
54 if(cnt > new_size || cnt < 0) {
55 PyErr_SetString(PyExc_ValueError,
56 "can't format the attribute name");
67 static ssize_t _list_obj(target_t *tgt, char *list, size_t size) {
69 return flistxattr(tgt->fd, list, size);
70 else if (tgt->type == T_LINK)
71 return llistxattr(tgt->name, list, size);
73 return listxattr(tgt->name, list, size);
76 static ssize_t _get_obj(target_t *tgt, const char *name, void *value,
79 return fgetxattr(tgt->fd, name, value, size);
80 else if (tgt->type == T_LINK)
81 return lgetxattr(tgt->name, name, value, size);
83 return getxattr(tgt->name, name, value, size);
86 static int _set_obj(target_t *tgt, const char *name,
87 const void *value, size_t size,
90 return fsetxattr(tgt->fd, name, value, size, flags);
91 else if (tgt->type == T_LINK)
92 return lsetxattr(tgt->name, name, value, size, flags);
94 return setxattr(tgt->name, name, value, size, flags);
97 static int _remove_obj(target_t *tgt, const char *name) {
99 return fremovexattr(tgt->fd, name);
100 else if (tgt->type == T_LINK)
101 return lremovexattr(tgt->name, name);
103 return removexattr(tgt->name, name);
107 Checks if an attribute name matches an optional namespace.
109 If the namespace is NULL, it will return the name itself. If the
110 namespace is non-NULL and the name matches, it will return a
111 pointer to the offset in the name after the namespace and the
112 separator. If however the name doesn't match the namespace, it will
115 const char *matches_ns(const char *ns, const char *name) {
119 ns_size = strlen(ns);
121 if (strlen(name) > (ns_size+1) && !strncmp(name, ns, ns_size) &&
122 name[ns_size] == '.')
123 return name + ns_size + 1;
127 /* Wrapper for getxattr */
128 static char __pygetxattr_doc__[] =
129 "Get the value of a given extended attribute (deprecated).\n"
132 " - a string representing filename, or a file-like object,\n"
133 " or a file descriptor; this represents the file on \n"
135 " - a string, representing the attribute whose value to retrieve;\n"
136 " usually in form of system.posix_acl or user.mime_type\n"
137 " - (optional) a boolean value (defaults to false), which, if\n"
138 " the file name given is a symbolic link, makes the\n"
139 " function operate on the symbolic link itself instead\n"
141 "@deprecated: this function has been deprecated by the L{get} function\n"
145 pygetxattr(PyObject *self, PyObject *args)
152 ssize_t nalloc, nret;
155 /* Parse the arguments */
156 if (!PyArg_ParseTuple(args, "Os|i", &myarg, &attrname, &nofollow))
158 if(!convertObj(myarg, &tgt, nofollow))
161 /* Find out the needed size of the buffer */
162 if((nalloc = _get_obj(&tgt, attrname, NULL, 0)) == -1) {
163 return PyErr_SetFromErrno(PyExc_IOError);
166 /* Try to allocate the memory, using Python's allocator */
167 if((buf = PyMem_Malloc(nalloc)) == NULL) {
172 /* Now retrieve the attribute value */
173 if((nret = _get_obj(&tgt, attrname, buf, nalloc)) == -1) {
175 return PyErr_SetFromErrno(PyExc_IOError);
178 /* Create the string which will hold the result */
179 res = PyString_FromStringAndSize(buf, nret);
181 /* Free the buffer, now it is no longer needed */
184 /* Return the result */
188 /* Wrapper for getxattr */
189 static char __get_doc__[] =
190 "Get the value of a given extended attribute.\n"
192 "@param item: the item to query; either a string representing the"
193 " filename, or a file-like object, or a file descriptor\n"
194 "@param name: the attribute whose value to set; usually in form of"
195 " system.posix_acl or user.mime_type\n"
196 "@type name: string\n"
197 "@param nofollow: if given and True, and the function is passed a"
198 " filename that points to a symlink, the function will act on the symlink"
199 " itself instead of its target\n"
200 "@type nofollow: boolean\n"
201 "@param namespace: if given, the attribute must not contain the namespace"
202 " itself, but instead the namespace will be taken from this parameter\n"
203 "@type namespace: string\n"
204 "@return: the value of the extended attribute (can contain NULLs)\n"
206 "@raise EnvironmentError: system errors will raise an exception\n"
211 xattr_get(PyObject *self, PyObject *args, PyObject *keywds)
216 char *attrname, *namebuf;
217 const char *fullname;
222 static char *kwlist[] = {"item", "name", "nofollow", "namespace", NULL};
224 /* Parse the arguments */
225 if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os|iz", kwlist,
226 &myarg, &attrname, &nofollow, &ns))
228 if(!convertObj(myarg, &tgt, nofollow))
231 fullname = merge_ns(ns, attrname, &namebuf);
233 /* Find out the needed size of the buffer */
234 if((nalloc = _get_obj(&tgt, fullname, NULL, 0)) == -1) {
235 return PyErr_SetFromErrno(PyExc_IOError);
238 /* Try to allocate the memory, using Python's allocator */
239 if((buf = PyMem_Malloc(nalloc)) == NULL) {
245 /* Now retrieve the attribute value */
246 if((nret = _get_obj(&tgt, fullname, buf, nalloc)) == -1) {
249 return PyErr_SetFromErrno(PyExc_IOError);
252 /* Create the string which will hold the result */
253 res = PyString_FromStringAndSize(buf, nret);
255 /* Free the buffers, they are no longer needed */
259 /* Return the result */
263 /* Wrapper for getxattr */
264 static char __get_all_doc__[] =
265 "Get all the extended attributes of an item.\n"
267 "This function performs a bulk-get of all extended attribute names\n"
268 "and the corresponding value.\n"
270 " >>> xattr.get_all('/path/to/file')\n"
271 " [('user.mime-type', 'plain/text'), ('user.comment', 'test'),"
272 " ('system.posix_acl_access', '\\x02\\x00...')]\n"
273 " >>> xattr.get_all('/path/to/file', namespace=xattr.NS_USER)\n"
274 " [('mime-type', 'plain/text'), ('comment', 'test')]\n"
276 "@param item: the item to query; either a string representing the"
277 " filename, or a file-like object, or a file descriptor\n"
278 "@keyword namespace: an optional namespace for filtering the"
279 " attributes; for example, querying all user attributes can be"
280 " accomplished by passing namespace=L{NS_USER}\n"
281 "@type namespace: string\n"
282 "@keyword nofollow: if passed and true, if the target file is a symbolic"
284 " the attributes for the link itself will be returned, instead of the"
285 " attributes of the target\n"
286 "@type nofollow: boolean\n"
287 "@return: list of tuples (name, value); note that if a namespace\n"
288 "argument was passed, it (and the separator) will be stripped from\n"
289 "the names returned\n"
291 "@raise EnvironmentError: system errors will raise an exception\n"
296 get_all(PyObject *self, PyObject *args, PyObject *keywds)
301 char *buf_list, *buf_val;
303 size_t nalloc, nlist, nval;
306 static char *kwlist[] = {"item", "nofollow", "namespace", NULL};
308 /* Parse the arguments */
309 if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iz", kwlist,
310 &myarg, &dolink, &ns))
312 if(!convertObj(myarg, &tgt, dolink))
315 /* Compute first the list of attributes */
317 /* Find out the needed size of the buffer for the attribute list */
318 nalloc = _list_obj(&tgt, NULL, 0);
321 return PyErr_SetFromErrno(PyExc_IOError);
324 /* Try to allocate the memory, using Python's allocator */
325 if((buf_list = PyMem_Malloc(nalloc)) == NULL) {
330 /* Now retrieve the list of attributes */
331 nlist = _list_obj(&tgt, buf_list, nalloc);
334 PyErr_SetFromErrno(PyExc_IOError);
338 /* Create the list which will hold the result */
339 mylist = PyList_New(0);
340 nalloc = ESTIMATE_ATTR_SIZE;
341 if((buf_val = PyMem_Malloc(nalloc)) == NULL) {
346 /* Create and insert the attributes as strings in the list */
347 for(s = buf_list; s - buf_list < nlist; s += strlen(s) + 1) {
352 if((name=matches_ns(ns, s))==NULL)
354 /* Now retrieve the attribute value */
357 nval = _get_obj(&tgt, s, buf_val, nalloc);
360 if(errno == ERANGE) {
361 nval = _get_obj(&tgt, s, NULL, 0);
362 if((buf_val = PyMem_Realloc(buf_val, nval)) == NULL)
366 } else if(errno == ENODATA || errno == ENOATTR) {
367 /* this attribute has gone away since we queried
368 the attribute list */
378 my_tuple = Py_BuildValue("ss#", name, buf_val, nval);
380 PyList_Append(mylist, my_tuple);
384 /* Free the buffers, now they are no longer needed */
386 PyMem_Free(buf_list);
388 /* Return the result */
391 PyErr_SetFromErrno(PyExc_IOError);
396 PyMem_Free(buf_list);
401 static char __pysetxattr_doc__[] =
402 "Set the value of a given extended attribute (deprecated).\n"
403 "Be carefull in case you want to set attributes on symbolic\n"
404 "links, you have to use all the 5 parameters; use 0 for the \n"
405 "flags value if you want the default behavior (create or "
409 " - a string representing filename, or a file-like object,\n"
410 " or a file descriptor; this represents the file on \n"
412 " - a string, representing the attribute whose value to set;\n"
413 " usually in form of system.posix_acl or user.mime_type\n"
414 " - a string, possibly with embedded NULLs; note that there\n"
415 " are restrictions regarding the size of the value, for\n"
416 " example, for ext2/ext3, maximum size is the block size\n"
417 " - (optional) flags; if 0 or ommited the attribute will be \n"
418 " created or replaced; if XATTR_CREATE, the attribute \n"
419 " will be created, giving an error if it already exists;\n"
420 " of XATTR_REPLACE, the attribute will be replaced,\n"
421 " giving an error if it doesn't exists;\n"
422 " - (optional) a boolean value (defaults to false), which, if\n"
423 " the file name given is a symbolic link, makes the\n"
424 " function operate on the symbolic link itself instead\n"
426 "@deprecated: this function has been deprecated by the L{set} function\n"
429 /* Wrapper for setxattr */
431 pysetxattr(PyObject *self, PyObject *args)
442 /* Parse the arguments */
443 if (!PyArg_ParseTuple(args, "Oss#|bi", &myarg, &attrname,
444 &buf, &bufsize, &flags, &nofollow))
446 if(!convertObj(myarg, &tgt, nofollow))
449 /* Set the attribute's value */
450 if((nret = _set_obj(&tgt, attrname, buf, bufsize, flags)) == -1) {
451 return PyErr_SetFromErrno(PyExc_IOError);
454 /* Return the result */
458 static char __set_doc__[] =
459 "Set the value of a given extended attribute.\n"
461 "@param item: the item to query; either a string representing the"
462 " filename, or a file-like object, or a file descriptor\n"
463 "@param name: the attribute whose value to set; usually in form of"
464 " system.posix_acl or user.mime_type\n"
465 "@type name: string\n"
466 "@param value: a string, possibly with embedded NULLs; note that there"
467 " are restrictions regarding the size of the value, for"
468 " example, for ext2/ext3, maximum size is the block size\n"
469 "@type value: string\n"
470 "@param flags: if 0 or ommited the attribute will be"
471 " created or replaced; if L{XATTR_CREATE}, the attribute"
472 " will be created, giving an error if it already exists;"
473 " if L{XATTR_REPLACE}, the attribute will be replaced,"
474 " giving an error if it doesn't exists;\n"
475 "@type flags: integer\n"
476 "@param nofollow: if given and True, and the function is passed a"
477 " filename that points to a symlink, the function will act on the symlink"
478 " itself instead of its target\n"
479 "@type nofollow: boolean\n"
480 "@param namespace: if given, the attribute must not contain the namespace"
481 " itself, but instead the namespace will be taken from this parameter\n"
482 "@type namespace: string\n"
484 "@raise EnvironmentError: system errors will raise an exception\n"
488 /* Wrapper for setxattr */
490 xattr_set(PyObject *self, PyObject *args, PyObject *keywds)
501 const char *full_name;
502 static char *kwlist[] = {"item", "name", "value", "flags",
503 "nofollow", "namespace", NULL};
505 /* Parse the arguments */
506 if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oss#|iiz", kwlist,
508 &buf, &bufsize, &flags, &nofollow, &ns))
510 if(!convertObj(myarg, &tgt, nofollow))
513 full_name = merge_ns(ns, attrname, &newname);
514 /* Set the attribute's value */
515 nret = _set_obj(&tgt, full_name, buf, bufsize, flags);
519 return PyErr_SetFromErrno(PyExc_IOError);
522 /* Return the result */
527 static char __pyremovexattr_doc__[] =
528 "Remove an attribute from a file (deprecated)\n"
531 " - a string representing filename, or a file-like object,\n"
532 " or a file descriptor; this represents the file on \n"
534 " - a string, representing the attribute to be removed;\n"
535 " usually in form of system.posix_acl or user.mime_type\n"
536 " - (optional) a boolean value (defaults to false), which, if\n"
537 " the file name given is a symbolic link, makes the\n"
538 " function operate on the symbolic link itself instead\n"
540 "@deprecated: this function has been deprecated by the L{remove}"
544 /* Wrapper for removexattr */
546 pyremovexattr(PyObject *self, PyObject *args)
554 /* Parse the arguments */
555 if (!PyArg_ParseTuple(args, "Os|i", &myarg, &attrname, &nofollow))
558 if(!convertObj(myarg, &tgt, nofollow))
561 /* Remove the attribute */
562 if((nret = _remove_obj(&tgt, attrname)) == -1) {
563 return PyErr_SetFromErrno(PyExc_IOError);
566 /* Return the result */
570 static char __remove_doc__[] =
571 "Remove an attribute from a file\n"
573 "@param item: the item to query; either a string representing the"
574 " filename, or a file-like object, or a file descriptor\n"
575 "@param name: the attribute whose value to set; usually in form of"
576 " system.posix_acl or user.mime_type\n"
577 "@type name: string\n"
578 "@param nofollow: if given and True, and the function is passed a"
579 " filename that points to a symlink, the function will act on the symlink"
580 " itself instead of its target\n"
581 "@type nofollow: boolean\n"
582 "@param namespace: if given, the attribute must not contain the namespace"
583 " itself, but instead the namespace will be taken from this parameter\n"
584 "@type namespace: string\n"
587 "@raise EnvironmentError: system errors will raise an exception\n"
590 /* Wrapper for removexattr */
592 xattr_remove(PyObject *self, PyObject *args, PyObject *keywds)
596 char *attrname, *name_buf;
598 const char *full_name;
601 static char *kwlist[] = {"item", "name", "nofollow", "namespace", NULL};
603 /* Parse the arguments */
604 if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os|iz", kwlist,
605 &myarg, &attrname, &nofollow, &ns))
608 if(!convertObj(myarg, &tgt, nofollow))
610 full_name = merge_ns(ns, attrname, &name_buf);
611 if(full_name == NULL)
614 /* Remove the attribute */
615 nret = _remove_obj(&tgt, full_name);
616 PyMem_Free(name_buf);
618 return PyErr_SetFromErrno(PyExc_IOError);
621 /* Return the result */
625 static char __pylistxattr_doc__[] =
626 "Return the list of attribute names for a file (deprecated)\n"
629 " - a string representing filename, or a file-like object,\n"
630 " or a file descriptor; this represents the file to \n"
632 " - (optional) a boolean value (defaults to false), which, if\n"
633 " the file name given is a symbolic link, makes the\n"
634 " function operate on the symbolic link itself instead\n"
636 "@deprecated: this function has been deprecated by the L{list}"
640 /* Wrapper for listxattr */
642 pylistxattr(PyObject *self, PyObject *args)
646 ssize_t nalloc, nret;
653 /* Parse the arguments */
654 if (!PyArg_ParseTuple(args, "O|i", &myarg, &nofollow))
656 if(!convertObj(myarg, &tgt, nofollow))
659 /* Find out the needed size of the buffer */
660 if((nalloc = _list_obj(&tgt, NULL, 0)) == -1) {
661 return PyErr_SetFromErrno(PyExc_IOError);
664 /* Try to allocate the memory, using Python's allocator */
665 if((buf = PyMem_Malloc(nalloc)) == NULL) {
670 /* Now retrieve the list of attributes */
671 if((nret = _list_obj(&tgt, buf, nalloc)) == -1) {
673 return PyErr_SetFromErrno(PyExc_IOError);
676 /* Compute the number of attributes in the list */
677 for(s = buf, nattrs = 0; (s - buf) < nret; s += strlen(s) + 1) {
681 /* Create the list which will hold the result */
682 mylist = PyList_New(nattrs);
684 /* Create and insert the attributes as strings in the list */
685 for(s = buf, nattrs = 0; s - buf < nret; s += strlen(s) + 1) {
686 PyList_SET_ITEM(mylist, nattrs, PyString_FromString(s));
690 /* Free the buffer, now it is no longer needed */
693 /* Return the result */
697 static char __list_doc__[] =
698 "Return the list of attribute names for a file\n"
701 " >>> xattr.list('/path/to/file')\n"
702 " ['user.test', 'user.comment', 'system.posix_acl_access']\n"
703 " >>> xattr.list('/path/to/file', namespace=xattr.NS_USER)\n"
704 " ['test', 'comment']\n"
706 "@param item: the item to query; either a string representing the"
707 " filename, or a file-like object, or a file descriptor\n"
708 "@param nofollow: if given and True, and the function is passed a"
709 " filename that points to a symlink, the function will act on the symlink"
710 " itself instead of its target\n"
711 "@type nofollow: boolean\n"
712 "@param namespace: if given, the attribute must not contain the namespace"
713 " itself, but instead the namespace will be taken from this parameter\n"
714 "@type namespace: string\n"
715 "@return: list of strings; note that if a namespace argument was\n"
716 "passed, it (and the separator) will be stripped from the names returned\n"
718 "@raise EnvironmentError: system errors will raise an exception\n"
722 /* Wrapper for listxattr */
724 xattr_list(PyObject *self, PyObject *args, PyObject *keywds)
735 static char *kwlist[] = {"item", "nofollow", "namespace", NULL};
737 /* Parse the arguments */
738 if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iz", kwlist,
739 &myarg, &nofollow, &ns))
741 if(!convertObj(myarg, &tgt, nofollow))
744 /* Find out the needed size of the buffer */
745 if((nalloc = _list_obj(&tgt, NULL, 0)) == -1) {
746 return PyErr_SetFromErrno(PyExc_IOError);
749 /* Try to allocate the memory, using Python's allocator */
750 if((buf = PyMem_Malloc(nalloc)) == NULL) {
755 /* Now retrieve the list of attributes */
756 if((nret = _list_obj(&tgt, buf, nalloc)) == -1) {
758 return PyErr_SetFromErrno(PyExc_IOError);
761 /* Compute the number of attributes in the list */
762 for(s = buf, nattrs = 0; (s - buf) < nret; s += strlen(s) + 1) {
763 if(matches_ns(ns, s)!=NULL)
766 /* Create the list which will hold the result */
767 mylist = PyList_New(nattrs);
769 /* Create and insert the attributes as strings in the list */
770 for(s = buf, nattrs = 0; s - buf < nret; s += strlen(s) + 1) {
771 const char *name = matches_ns(ns, s);
773 PyList_SET_ITEM(mylist, nattrs, PyString_FromString(name));
778 /* Free the buffer, now it is no longer needed */
781 /* Return the result */
785 static PyMethodDef xattr_methods[] = {
786 {"getxattr", pygetxattr, METH_VARARGS, __pygetxattr_doc__ },
787 {"get", (PyCFunction) xattr_get, METH_VARARGS | METH_KEYWORDS,
789 {"get_all", (PyCFunction) get_all, METH_VARARGS | METH_KEYWORDS,
791 {"setxattr", pysetxattr, METH_VARARGS, __pysetxattr_doc__ },
792 {"set", (PyCFunction) xattr_set, METH_VARARGS | METH_KEYWORDS,
794 {"removexattr", pyremovexattr, METH_VARARGS, __pyremovexattr_doc__ },
795 {"remove", (PyCFunction) xattr_remove, METH_VARARGS | METH_KEYWORDS,
797 {"listxattr", pylistxattr, METH_VARARGS, __pylistxattr_doc__ },
798 {"list", (PyCFunction) xattr_list, METH_VARARGS | METH_KEYWORDS,
800 {NULL, NULL, 0, NULL} /* Sentinel */
803 static char __xattr_doc__[] = \
804 "Access extended filesystem attributes\n"
806 "This module gives access to the extended attributes present\n"
807 "in some operating systems/filesystems. You can list attributes,\n"
808 "get, set and remove them.\n"
809 "The last and optional parameter for all functions is a boolean \n"
810 "value which enables the 'l-' version of the functions - acting\n"
811 "on symbolic links and not their destination.\n"
814 " >>> import xattr\n"
815 " >>> xattr.listxattr(\"file.txt\")\n"
816 " ['user.mime_type']\n"
817 " >>> xattr.getxattr(\"file.txt\", \"user.mime_type\")\n"
819 " >>> xattr.setxattr(\"file.txt\", \"user.comment\", "
820 "\"Simple text file\")\n"
821 " >>> xattr.listxattr(\"file.txt\")\n"
822 " ['user.mime_type', 'user.comment']\n"
823 " >>> xattr.removexattr (\"file.txt\", \"user.comment\")\n"
830 PyObject *m = Py_InitModule3("xattr", xattr_methods, __xattr_doc__);
832 PyModule_AddIntConstant(m, "XATTR_CREATE", XATTR_CREATE);
833 PyModule_AddIntConstant(m, "XATTR_REPLACE", XATTR_REPLACE);
835 /* namespace constants */
836 PyModule_AddStringConstant(m, "NS_SECURITY", "security");
837 PyModule_AddStringConstant(m, "NS_SYSTEM", "system");
838 PyModule_AddStringConstant(m, "NS_TRUSTED", "trusted");
839 PyModule_AddStringConstant(m, "NS_USER", "user");