2 #include <attr/xattr.h>
5 /* the estimated (startup) attribute buffer size in
7 #define ESTIMATE_ATTR_SIZE 256
9 typedef enum {T_FD, T_PATH, T_LINK} target_e;
19 /** Converts from a string, file or int argument to what we need. */
20 static int convertObj(PyObject *myobj, target_t *tgt, int nofollow) {
22 if(PyString_Check(myobj)) {
23 tgt->type = nofollow ? T_LINK : T_PATH;
24 tgt->name = PyString_AS_STRING(myobj);
25 } else if((fd = PyObject_AsFileDescriptor(myobj)) != -1) {
29 PyErr_SetString(PyExc_TypeError, "argument must be string or int");
35 /* Combine a namespace string and an attribute name into a
36 fully-qualified name */
37 static const char* merge_ns(const char *ns, const char *name, char **buf) {
40 size_t new_size = strlen(ns) + 1 + strlen(name) + 1;
41 if((*buf = PyMem_Malloc(new_size)) == NULL) {
45 cnt = snprintf(*buf, new_size, "%s.%s", ns, name);
46 if(cnt > new_size || cnt < 0) {
47 PyErr_SetString(PyExc_ValueError,
48 "can't format the attribute name");
59 static ssize_t _list_obj(target_t *tgt, char *list, size_t size) {
61 return flistxattr(tgt->fd, list, size);
62 else if (tgt->type == T_LINK)
63 return llistxattr(tgt->name, list, size);
65 return listxattr(tgt->name, list, size);
68 static ssize_t _get_obj(target_t *tgt, const char *name, void *value,
71 return fgetxattr(tgt->fd, name, value, size);
72 else if (tgt->type == T_LINK)
73 return lgetxattr(tgt->name, name, value, size);
75 return getxattr(tgt->name, name, value, size);
78 static ssize_t _set_obj(target_t *tgt, const char *name,
79 const void *value, size_t size,
82 return fsetxattr(tgt->fd, name, value, size, flags);
83 else if (tgt->type == T_LINK)
84 return lsetxattr(tgt->name, name, value, size, flags);
86 return setxattr(tgt->name, name, value, size, flags);
89 static ssize_t _remove_obj(target_t *tgt, const char *name) {
91 return fremovexattr(tgt->fd, name);
92 else if (tgt->type == T_LINK)
93 return lremovexattr(tgt->name, name);
95 return removexattr(tgt->name, name);
98 /* Checks if an attribute name matches an optional namespace */
99 static int matches_ns(const char *name, const char *ns) {
103 ns_size = strlen(ns);
105 if (strlen(name) > ns_size && !strncmp(name, ns, ns_size) &&
106 name[ns_size] == '.')
111 /* Wrapper for getxattr */
112 static char __pygetxattr_doc__[] =
113 "Get the value of a given extended attribute (deprecated).\n"
116 " - a string representing filename, or a file-like object,\n"
117 " or a file descriptor; this represents the file on \n"
119 " - a string, representing the attribute whose value to retrieve;\n"
120 " usually in form of system.posix_acl or user.mime_type\n"
121 " - (optional) a boolean value (defaults to false), which, if\n"
122 " the file name given is a symbolic link, makes the\n"
123 " function operate on the symbolic link itself instead\n"
125 "@deprecated: this function has been deprecated by the L{get} function\n"
129 pygetxattr(PyObject *self, PyObject *args)
139 /* Parse the arguments */
140 if (!PyArg_ParseTuple(args, "Os|i", &myarg, &attrname, &nofollow))
142 if(!convertObj(myarg, &tgt, nofollow))
145 /* Find out the needed size of the buffer */
146 if((nalloc = _get_obj(&tgt, attrname, NULL, 0)) == -1) {
147 return PyErr_SetFromErrno(PyExc_IOError);
150 /* Try to allocate the memory, using Python's allocator */
151 if((buf = PyMem_Malloc(nalloc)) == NULL) {
156 /* Now retrieve the attribute value */
157 if((nret = _get_obj(&tgt, attrname, buf, nalloc)) == -1) {
159 return PyErr_SetFromErrno(PyExc_IOError);
162 /* Create the string which will hold the result */
163 res = PyString_FromStringAndSize(buf, nret);
165 /* Free the buffer, now it is no longer needed */
168 /* Return the result */
172 /* Wrapper for getxattr */
173 static char __get_doc__[] =
174 "Get the value of a given extended attribute.\n"
176 "@param item: the item to query; either a string representing the"
177 " filename, or a file-like object, or a file descriptor\n"
178 "@param name: the attribute whose value to set; usually in form of"
179 " system.posix_acl or user.mime_type\n"
180 "@type name: string\n"
181 "@param nofollow: if given and True, and the function is passed a"
182 " filename that points to a symlink, the function will act on the symlink"
183 " itself instead of its target\n"
184 "@type nofollow: boolean\n"
185 "@param namespace: if given, the attribute must not contain the namespace"
186 " itself, but instead the namespace will be taken from this parameter\n"
187 "@type namespace: string\n"
188 "@return: the value of the extended attribute (can contain NULLs)\n"
190 "@raise EnvironmentError: system errors will raise an exception\n"
195 xattr_get(PyObject *self, PyObject *args, PyObject *keywds)
200 char *attrname, *namebuf;
201 const char *fullname;
206 static char *kwlist[] = {"item", "name", "nofollow", "namespace", NULL};
208 /* Parse the arguments */
209 if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os|iz", kwlist,
210 &myarg, &attrname, &nofollow, &ns))
212 if(!convertObj(myarg, &tgt, nofollow))
215 fullname = merge_ns(ns, attrname, &namebuf);
217 /* Find out the needed size of the buffer */
218 if((nalloc = _get_obj(&tgt, fullname, NULL, 0)) == -1) {
219 return PyErr_SetFromErrno(PyExc_IOError);
222 /* Try to allocate the memory, using Python's allocator */
223 if((buf = PyMem_Malloc(nalloc)) == NULL) {
229 /* Now retrieve the attribute value */
230 if((nret = _get_obj(&tgt, fullname, buf, nalloc)) == -1) {
233 return PyErr_SetFromErrno(PyExc_IOError);
236 /* Create the string which will hold the result */
237 res = PyString_FromStringAndSize(buf, nret);
239 /* Free the buffers, they are no longer needed */
243 /* Return the result */
247 /* Wrapper for getxattr */
248 static char __get_all_doc__[] =
249 "Get all the extended attributes of an item.\n"
251 "This function performs a bulk-get of all extended attribute names\n"
252 "and the corresponding value.\n"
254 " >>> xattr.get_all('/path/to/file')\n"
255 " [('user.mime-type', 'plain/text'), ('user.comment', 'test'),"
256 " ('system.posix_acl_access', '\\x02\\x00...')]\n"
257 " >>> xattr.get_all('/path/to/file', namespace=xattr.NS_USER)\n"
258 " [('user.mime-type', 'plain/text'), ('user.comment', 'test')]\n"
260 "@param item: the item to query; either a string representing the"
261 " filename, or a file-like object, or a file descriptor\n"
262 "@keyword namespace: an optional namespace for filtering the"
263 " attributes; for example, querying all user attributes can be"
264 " accomplished by passing namespace=L{NS_USER}\n"
265 "@type namespace: string\n"
266 "@keyword nofollow: if passed and true, if the target file is a symbolic"
268 " the attributes for the link itself will be returned, instead of the"
269 " attributes of the target\n"
270 "@type nofollow: boolean\n"
271 "@return: list of tuples (name, value)\n"
273 "@raise EnvironmentError: system errors will raise an exception\n"
278 get_all(PyObject *self, PyObject *args, PyObject *keywds)
283 char *buf_list, *buf_val;
285 size_t nalloc, nlist, nval;
288 static char *kwlist[] = {"item", "nofollow", "namespace", NULL};
290 /* Parse the arguments */
291 if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iz", kwlist,
292 &myarg, &dolink, &ns))
294 if(!convertObj(myarg, &tgt, dolink))
297 /* Compute first the list of attributes */
299 /* Find out the needed size of the buffer for the attribute list */
300 nalloc = _list_obj(&tgt, NULL, 0);
303 return PyErr_SetFromErrno(PyExc_IOError);
306 /* Try to allocate the memory, using Python's allocator */
307 if((buf_list = PyMem_Malloc(nalloc)) == NULL) {
312 /* Now retrieve the list of attributes */
313 nlist = _list_obj(&tgt, buf_list, nalloc);
316 PyErr_SetFromErrno(PyExc_IOError);
320 /* Create the list which will hold the result */
321 mylist = PyList_New(0);
322 nalloc = ESTIMATE_ATTR_SIZE;
323 if((buf_val = PyMem_Malloc(nalloc)) == NULL) {
328 /* Create and insert the attributes as strings in the list */
329 for(s = buf_list; s - buf_list < nlist; s += strlen(s) + 1) {
333 if(!matches_ns(s, ns))
335 /* Now retrieve the attribute value */
338 nval = _get_obj(&tgt, s, buf_val, nalloc);
341 if(errno == ERANGE) {
342 nval = _get_obj(&tgt, s, NULL, 0);
343 if((buf_val = PyMem_Realloc(buf_val, nval)) == NULL)
347 } else if(errno == ENODATA || errno == ENOATTR) {
348 /* this attribute has gone away since we queried
349 the attribute list */
359 my_tuple = Py_BuildValue("ss#", s, buf_val, nval);
361 PyList_Append(mylist, my_tuple);
365 /* Free the buffers, now they are no longer needed */
367 PyMem_Free(buf_list);
369 /* Return the result */
372 PyErr_SetFromErrno(PyExc_IOError);
377 PyMem_Free(buf_list);
382 static char __pysetxattr_doc__[] =
383 "Set the value of a given extended attribute (deprecated).\n"
384 "Be carefull in case you want to set attributes on symbolic\n"
385 "links, you have to use all the 5 parameters; use 0 for the \n"
386 "flags value if you want the default behavior (create or "
390 " - a string representing filename, or a file-like object,\n"
391 " or a file descriptor; this represents the file on \n"
393 " - a string, representing the attribute whose value to set;\n"
394 " usually in form of system.posix_acl or user.mime_type\n"
395 " - a string, possibly with embedded NULLs; note that there\n"
396 " are restrictions regarding the size of the value, for\n"
397 " example, for ext2/ext3, maximum size is the block size\n"
398 " - (optional) flags; if 0 or ommited the attribute will be \n"
399 " created or replaced; if XATTR_CREATE, the attribute \n"
400 " will be created, giving an error if it already exists;\n"
401 " of XATTR_REPLACE, the attribute will be replaced,\n"
402 " giving an error if it doesn't exists;\n"
403 " - (optional) a boolean value (defaults to false), which, if\n"
404 " the file name given is a symbolic link, makes the\n"
405 " function operate on the symbolic link itself instead\n"
407 "@deprecated: this function has been deprecated by the L{set} function\n"
410 /* Wrapper for setxattr */
412 pysetxattr(PyObject *self, PyObject *args)
422 /* Parse the arguments */
423 if (!PyArg_ParseTuple(args, "Oss#|bi", &myarg, &attrname,
424 &buf, &bufsize, &flags, &nofollow))
426 if(!convertObj(myarg, &tgt, nofollow))
429 /* Set the attribute's value */
430 if((nret = _set_obj(&tgt, attrname, buf, bufsize, flags)) == -1) {
431 return PyErr_SetFromErrno(PyExc_IOError);
434 /* Return the result */
438 static char __set_doc__[] =
439 "Set the value of a given extended attribute.\n"
441 "@param item: the item to query; either a string representing the"
442 " filename, or a file-like object, or a file descriptor\n"
443 "@param name: the attribute whose value to set; usually in form of"
444 " system.posix_acl or user.mime_type\n"
445 "@type name: string\n"
446 "@param value: a string, possibly with embedded NULLs; note that there"
447 " are restrictions regarding the size of the value, for"
448 " example, for ext2/ext3, maximum size is the block size\n"
449 "@type value: string\n"
450 "@param flags: if 0 or ommited the attribute will be"
451 " created or replaced; if L{XATTR_CREATE}, the attribute"
452 " will be created, giving an error if it already exists;"
453 " if L{XATTR_REPLACE}, the attribute will be replaced,"
454 " giving an error if it doesn't exists;\n"
455 "@type flags: integer\n"
456 "@param nofollow: if given and True, and the function is passed a"
457 " filename that points to a symlink, the function will act on the symlink"
458 " itself instead of its target\n"
459 "@type nofollow: boolean\n"
460 "@param namespace: if given, the attribute must not contain the namespace"
461 " itself, but instead the namespace will be taken from this parameter\n"
462 "@type namespace: string\n"
464 "@raise EnvironmentError: system errors will raise an exception\n"
468 /* Wrapper for setxattr */
470 xattr_set(PyObject *self, PyObject *args, PyObject *keywds)
481 const char *full_name;
482 static char *kwlist[] = {"item", "name", "value", "flags",
483 "nofollow", "namespace", NULL};
485 /* Parse the arguments */
486 if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oss#|iiz", kwlist,
488 &buf, &bufsize, &flags, &nofollow, &ns))
490 if(!convertObj(myarg, &tgt, nofollow))
493 full_name = merge_ns(ns, attrname, &newname);
494 /* Set the attribute's value */
495 nret = _set_obj(&tgt, full_name, buf, bufsize, flags);
499 return PyErr_SetFromErrno(PyExc_IOError);
502 /* Return the result */
507 static char __pyremovexattr_doc__[] =
508 "Remove an attribute from a file (deprecated)\n"
511 " - a string representing filename, or a file-like object,\n"
512 " or a file descriptor; this represents the file on \n"
514 " - a string, representing the attribute to be removed;\n"
515 " usually in form of system.posix_acl or user.mime_type\n"
516 " - (optional) a boolean value (defaults to false), which, if\n"
517 " the file name given is a symbolic link, makes the\n"
518 " function operate on the symbolic link itself instead\n"
520 "@deprecated: this function has been deprecated by the L{remove}"
524 /* Wrapper for removexattr */
526 pyremovexattr(PyObject *self, PyObject *args)
534 /* Parse the arguments */
535 if (!PyArg_ParseTuple(args, "Os|i", &myarg, &attrname, &nofollow))
538 if(!convertObj(myarg, &tgt, nofollow))
541 /* Remove the attribute */
542 if((nret = _remove_obj(&tgt, attrname)) == -1) {
543 return PyErr_SetFromErrno(PyExc_IOError);
546 /* Return the result */
550 static char __remove_doc__[] =
551 "Remove an attribute from a file\n"
553 "@param item: the item to query; either a string representing the"
554 " filename, or a file-like object, or a file descriptor\n"
555 "@param name: the attribute whose value to set; usually in form of"
556 " system.posix_acl or user.mime_type\n"
557 "@type name: string\n"
558 "@param nofollow: if given and True, and the function is passed a"
559 " filename that points to a symlink, the function will act on the symlink"
560 " itself instead of its target\n"
561 "@type nofollow: boolean\n"
562 "@param namespace: if given, the attribute must not contain the namespace"
563 " itself, but instead the namespace will be taken from this parameter\n"
564 "@type namespace: string\n"
567 "@raise EnvironmentError: system errors will raise an exception\n"
570 /* Wrapper for removexattr */
572 xattr_remove(PyObject *self, PyObject *args, PyObject *keywds)
576 char *attrname, *name_buf;
578 const char *full_name;
581 static char *kwlist[] = {"item", "name", "nofollow", "namespace", NULL};
583 /* Parse the arguments */
584 if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os|iz", kwlist,
585 &myarg, &attrname, &nofollow, &ns))
588 if(!convertObj(myarg, &tgt, nofollow))
590 full_name = merge_ns(ns, attrname, &name_buf);
591 if(full_name == NULL)
594 /* Remove the attribute */
595 nret = _remove_obj(&tgt, full_name);
596 PyMem_Free(name_buf);
598 return PyErr_SetFromErrno(PyExc_IOError);
601 /* Return the result */
605 static char __pylistxattr_doc__[] =
606 "Return the list of attribute names for a file (deprecated)\n"
609 " - a string representing filename, or a file-like object,\n"
610 " or a file descriptor; this represents the file to \n"
612 " - (optional) a boolean value (defaults to false), which, if\n"
613 " the file name given is a symbolic link, makes the\n"
614 " function operate on the symbolic link itself instead\n"
616 "@deprecated: this function has been deprecated by the L{list}"
620 /* Wrapper for listxattr */
622 pylistxattr(PyObject *self, PyObject *args)
633 /* Parse the arguments */
634 if (!PyArg_ParseTuple(args, "O|i", &myarg, &nofollow))
636 if(!convertObj(myarg, &tgt, nofollow))
639 /* Find out the needed size of the buffer */
640 if((nalloc = _list_obj(&tgt, NULL, 0)) == -1) {
641 return PyErr_SetFromErrno(PyExc_IOError);
644 /* Try to allocate the memory, using Python's allocator */
645 if((buf = PyMem_Malloc(nalloc)) == NULL) {
650 /* Now retrieve the list of attributes */
651 if((nret = _list_obj(&tgt, buf, nalloc)) == -1) {
653 return PyErr_SetFromErrno(PyExc_IOError);
656 /* Compute the number of attributes in the list */
657 for(s = buf, nattrs = 0; (s - buf) < nret; s += strlen(s) + 1) {
661 /* Create the list which will hold the result */
662 mylist = PyList_New(nattrs);
664 /* Create and insert the attributes as strings in the list */
665 for(s = buf, nattrs = 0; s - buf < nret; s += strlen(s) + 1) {
666 PyList_SET_ITEM(mylist, nattrs, PyString_FromString(s));
670 /* Free the buffer, now it is no longer needed */
673 /* Return the result */
677 static char __list_doc__[] =
678 "Return the list of attribute names for a file\n"
681 " >>> xattr.list('/path/to/file')\n"
682 " ['user.test', 'user.comment', 'system.posix_acl_access']\n"
683 " >>> xattr.list('/path/to/file', namespace=xattr.NS_USER)\n"
684 " ['test', 'comment']\n"
686 "@param item: the item to query; either a string representing the"
687 " filename, or a file-like object, or a file descriptor\n"
688 "@param nofollow: if given and True, and the function is passed a"
689 " filename that points to a symlink, the function will act on the symlink"
690 " itself instead of its target\n"
691 "@type nofollow: boolean\n"
692 "@param namespace: if given, the attribute must not contain the namespace"
693 " itself, but instead the namespace will be taken from this parameter\n"
694 "@type namespace: string\n"
695 "@return: list of strings; note that if the namespace attribute has been\n"
696 "passed, the names will have the namespace prefix stripped\n"
698 "@raise EnvironmentError: system errors will raise an exception\n"
702 /* Wrapper for listxattr */
704 xattr_list(PyObject *self, PyObject *args, PyObject *keywds)
715 static char *kwlist[] = {"item", "nofollow", "namespace", NULL};
717 /* Parse the arguments */
718 if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iz", kwlist,
719 &myarg, &nofollow, &ns))
721 if(!convertObj(myarg, &tgt, nofollow))
724 /* Find out the needed size of the buffer */
725 if((nalloc = _list_obj(&tgt, NULL, 0)) == -1) {
726 return PyErr_SetFromErrno(PyExc_IOError);
729 /* Try to allocate the memory, using Python's allocator */
730 if((buf = PyMem_Malloc(nalloc)) == NULL) {
735 /* Now retrieve the list of attributes */
736 if((nret = _list_obj(&tgt, buf, nalloc)) == -1) {
738 return PyErr_SetFromErrno(PyExc_IOError);
741 /* Compute the number of attributes in the list */
742 for(s = buf, nattrs = 0; (s - buf) < nret; s += strlen(s) + 1) {
743 if(matches_ns(s, ns))
746 /* Create the list which will hold the result */
747 mylist = PyList_New(nattrs);
749 /* Create and insert the attributes as strings in the list */
750 for(s = buf, nattrs = 0; s - buf < nret; s += strlen(s) + 1) {
751 if(matches_ns(s, ns)) {
752 char *short_form = ns == NULL ? s : s + strlen(ns) + 1;
753 PyList_SET_ITEM(mylist, nattrs, PyString_FromString(short_form));
758 /* Free the buffer, now it is no longer needed */
761 /* Return the result */
765 static PyMethodDef xattr_methods[] = {
766 {"getxattr", pygetxattr, METH_VARARGS, __pygetxattr_doc__ },
767 {"get", (PyCFunction) xattr_get, METH_VARARGS | METH_KEYWORDS,
769 {"get_all", (PyCFunction) get_all, METH_VARARGS | METH_KEYWORDS,
771 {"setxattr", pysetxattr, METH_VARARGS, __pysetxattr_doc__ },
772 {"set", (PyCFunction) xattr_set, METH_VARARGS | METH_KEYWORDS,
774 {"removexattr", pyremovexattr, METH_VARARGS, __pyremovexattr_doc__ },
775 {"remove", (PyCFunction) xattr_remove, METH_VARARGS | METH_KEYWORDS,
777 {"listxattr", pylistxattr, METH_VARARGS, __pylistxattr_doc__ },
778 {"list", (PyCFunction) xattr_list, METH_VARARGS | METH_KEYWORDS,
780 {NULL, NULL, 0, NULL} /* Sentinel */
783 static char __xattr_doc__[] = \
784 "Access extended filesystem attributes\n"
786 "This module gives access to the extended attributes present\n"
787 "in some operating systems/filesystems. You can list attributes,\n"
788 "get, set and remove them.\n"
789 "The last and optional parameter for all functions is a boolean \n"
790 "value which enables the 'l-' version of the functions - acting\n"
791 "on symbolic links and not their destination.\n"
794 " >>> import xattr\n"
795 " >>> xattr.listxattr(\"file.txt\")\n"
796 " ['user.mime_type']\n"
797 " >>> xattr.getxattr(\"file.txt\", \"user.mime_type\")\n"
799 " >>> xattr.setxattr(\"file.txt\", \"user.comment\", "
800 "\"Simple text file\")\n"
801 " >>> xattr.listxattr(\"file.txt\")\n"
802 " ['user.mime_type', 'user.comment']\n"
803 " >>> xattr.removexattr (\"file.txt\", \"user.comment\")\n"
810 PyObject *m = Py_InitModule3("xattr", xattr_methods, __xattr_doc__);
812 PyModule_AddIntConstant(m, "XATTR_CREATE", XATTR_CREATE);
813 PyModule_AddIntConstant(m, "XATTR_REPLACE", XATTR_REPLACE);
815 /* namespace constants */
816 PyModule_AddStringConstant(m, "NS_SECURITY", "security");
817 PyModule_AddStringConstant(m, "NS_SYSTEM", "system");
818 PyModule_AddStringConstant(m, "NS_TRUSTED", "trusted");
819 PyModule_AddStringConstant(m, "NS_USER", "user");