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);
99 Checks if an attribute name matches an optional namespace.
101 If the namespace is NULL, it will return the name itself. If the
102 namespace is non-NULL and the name matches, it will return a
103 pointer to the offset in the name after the namespace and the
104 separator. If however the name doesn't match the namespace, it will
107 const char *matches_ns(const char *ns, const char *name) {
111 ns_size = strlen(ns);
113 if (strlen(name) > (ns_size+1) && !strncmp(name, ns, ns_size) &&
114 name[ns_size] == '.')
115 return name + ns_size + 1;
119 /* Wrapper for getxattr */
120 static char __pygetxattr_doc__[] =
121 "Get the value of a given extended attribute (deprecated).\n"
124 " - a string representing filename, or a file-like object,\n"
125 " or a file descriptor; this represents the file on \n"
127 " - a string, representing the attribute whose value to retrieve;\n"
128 " usually in form of system.posix_acl or user.mime_type\n"
129 " - (optional) a boolean value (defaults to false), which, if\n"
130 " the file name given is a symbolic link, makes the\n"
131 " function operate on the symbolic link itself instead\n"
133 "@deprecated: this function has been deprecated by the L{get} function\n"
137 pygetxattr(PyObject *self, PyObject *args)
147 /* Parse the arguments */
148 if (!PyArg_ParseTuple(args, "Os|i", &myarg, &attrname, &nofollow))
150 if(!convertObj(myarg, &tgt, nofollow))
153 /* Find out the needed size of the buffer */
154 if((nalloc = _get_obj(&tgt, attrname, NULL, 0)) == -1) {
155 return PyErr_SetFromErrno(PyExc_IOError);
158 /* Try to allocate the memory, using Python's allocator */
159 if((buf = PyMem_Malloc(nalloc)) == NULL) {
164 /* Now retrieve the attribute value */
165 if((nret = _get_obj(&tgt, attrname, buf, nalloc)) == -1) {
167 return PyErr_SetFromErrno(PyExc_IOError);
170 /* Create the string which will hold the result */
171 res = PyString_FromStringAndSize(buf, nret);
173 /* Free the buffer, now it is no longer needed */
176 /* Return the result */
180 /* Wrapper for getxattr */
181 static char __get_doc__[] =
182 "Get the value of a given extended attribute.\n"
184 "@param item: the item to query; either a string representing the"
185 " filename, or a file-like object, or a file descriptor\n"
186 "@param name: the attribute whose value to set; usually in form of"
187 " system.posix_acl or user.mime_type\n"
188 "@type name: string\n"
189 "@param nofollow: if given and True, and the function is passed a"
190 " filename that points to a symlink, the function will act on the symlink"
191 " itself instead of its target\n"
192 "@type nofollow: boolean\n"
193 "@param namespace: if given, the attribute must not contain the namespace"
194 " itself, but instead the namespace will be taken from this parameter\n"
195 "@type namespace: string\n"
196 "@return: the value of the extended attribute (can contain NULLs)\n"
198 "@raise EnvironmentError: system errors will raise an exception\n"
203 xattr_get(PyObject *self, PyObject *args, PyObject *keywds)
208 char *attrname, *namebuf;
209 const char *fullname;
214 static char *kwlist[] = {"item", "name", "nofollow", "namespace", NULL};
216 /* Parse the arguments */
217 if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os|iz", kwlist,
218 &myarg, &attrname, &nofollow, &ns))
220 if(!convertObj(myarg, &tgt, nofollow))
223 fullname = merge_ns(ns, attrname, &namebuf);
225 /* Find out the needed size of the buffer */
226 if((nalloc = _get_obj(&tgt, fullname, NULL, 0)) == -1) {
227 return PyErr_SetFromErrno(PyExc_IOError);
230 /* Try to allocate the memory, using Python's allocator */
231 if((buf = PyMem_Malloc(nalloc)) == NULL) {
237 /* Now retrieve the attribute value */
238 if((nret = _get_obj(&tgt, fullname, buf, nalloc)) == -1) {
241 return PyErr_SetFromErrno(PyExc_IOError);
244 /* Create the string which will hold the result */
245 res = PyString_FromStringAndSize(buf, nret);
247 /* Free the buffers, they are no longer needed */
251 /* Return the result */
255 /* Wrapper for getxattr */
256 static char __get_all_doc__[] =
257 "Get all the extended attributes of an item.\n"
259 "This function performs a bulk-get of all extended attribute names\n"
260 "and the corresponding value.\n"
262 " >>> xattr.get_all('/path/to/file')\n"
263 " [('user.mime-type', 'plain/text'), ('user.comment', 'test'),"
264 " ('system.posix_acl_access', '\\x02\\x00...')]\n"
265 " >>> xattr.get_all('/path/to/file', namespace=xattr.NS_USER)\n"
266 " [('user.mime-type', 'plain/text'), ('user.comment', 'test')]\n"
268 "@param item: the item to query; either a string representing the"
269 " filename, or a file-like object, or a file descriptor\n"
270 "@keyword namespace: an optional namespace for filtering the"
271 " attributes; for example, querying all user attributes can be"
272 " accomplished by passing namespace=L{NS_USER}\n"
273 "@type namespace: string\n"
274 "@keyword nofollow: if passed and true, if the target file is a symbolic"
276 " the attributes for the link itself will be returned, instead of the"
277 " attributes of the target\n"
278 "@type nofollow: boolean\n"
279 "@return: list of tuples (name, value)\n"
281 "@raise EnvironmentError: system errors will raise an exception\n"
286 get_all(PyObject *self, PyObject *args, PyObject *keywds)
291 char *buf_list, *buf_val;
293 size_t nalloc, nlist, nval;
296 static char *kwlist[] = {"item", "nofollow", "namespace", NULL};
298 /* Parse the arguments */
299 if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iz", kwlist,
300 &myarg, &dolink, &ns))
302 if(!convertObj(myarg, &tgt, dolink))
305 /* Compute first the list of attributes */
307 /* Find out the needed size of the buffer for the attribute list */
308 nalloc = _list_obj(&tgt, NULL, 0);
311 return PyErr_SetFromErrno(PyExc_IOError);
314 /* Try to allocate the memory, using Python's allocator */
315 if((buf_list = PyMem_Malloc(nalloc)) == NULL) {
320 /* Now retrieve the list of attributes */
321 nlist = _list_obj(&tgt, buf_list, nalloc);
324 PyErr_SetFromErrno(PyExc_IOError);
328 /* Create the list which will hold the result */
329 mylist = PyList_New(0);
330 nalloc = ESTIMATE_ATTR_SIZE;
331 if((buf_val = PyMem_Malloc(nalloc)) == NULL) {
336 /* Create and insert the attributes as strings in the list */
337 for(s = buf_list; s - buf_list < nlist; s += strlen(s) + 1) {
341 if(matches_ns(ns, s)==NULL)
343 /* Now retrieve the attribute value */
346 nval = _get_obj(&tgt, s, buf_val, nalloc);
349 if(errno == ERANGE) {
350 nval = _get_obj(&tgt, s, NULL, 0);
351 if((buf_val = PyMem_Realloc(buf_val, nval)) == NULL)
355 } else if(errno == ENODATA || errno == ENOATTR) {
356 /* this attribute has gone away since we queried
357 the attribute list */
367 my_tuple = Py_BuildValue("ss#", s, buf_val, nval);
369 PyList_Append(mylist, my_tuple);
373 /* Free the buffers, now they are no longer needed */
375 PyMem_Free(buf_list);
377 /* Return the result */
380 PyErr_SetFromErrno(PyExc_IOError);
385 PyMem_Free(buf_list);
390 static char __pysetxattr_doc__[] =
391 "Set the value of a given extended attribute (deprecated).\n"
392 "Be carefull in case you want to set attributes on symbolic\n"
393 "links, you have to use all the 5 parameters; use 0 for the \n"
394 "flags value if you want the default behavior (create or "
398 " - a string representing filename, or a file-like object,\n"
399 " or a file descriptor; this represents the file on \n"
401 " - a string, representing the attribute whose value to set;\n"
402 " usually in form of system.posix_acl or user.mime_type\n"
403 " - a string, possibly with embedded NULLs; note that there\n"
404 " are restrictions regarding the size of the value, for\n"
405 " example, for ext2/ext3, maximum size is the block size\n"
406 " - (optional) flags; if 0 or ommited the attribute will be \n"
407 " created or replaced; if XATTR_CREATE, the attribute \n"
408 " will be created, giving an error if it already exists;\n"
409 " of XATTR_REPLACE, the attribute will be replaced,\n"
410 " giving an error if it doesn't exists;\n"
411 " - (optional) a boolean value (defaults to false), which, if\n"
412 " the file name given is a symbolic link, makes the\n"
413 " function operate on the symbolic link itself instead\n"
415 "@deprecated: this function has been deprecated by the L{set} function\n"
418 /* Wrapper for setxattr */
420 pysetxattr(PyObject *self, PyObject *args)
430 /* Parse the arguments */
431 if (!PyArg_ParseTuple(args, "Oss#|bi", &myarg, &attrname,
432 &buf, &bufsize, &flags, &nofollow))
434 if(!convertObj(myarg, &tgt, nofollow))
437 /* Set the attribute's value */
438 if((nret = _set_obj(&tgt, attrname, buf, bufsize, flags)) == -1) {
439 return PyErr_SetFromErrno(PyExc_IOError);
442 /* Return the result */
446 static char __set_doc__[] =
447 "Set the value of a given extended attribute.\n"
449 "@param item: the item to query; either a string representing the"
450 " filename, or a file-like object, or a file descriptor\n"
451 "@param name: the attribute whose value to set; usually in form of"
452 " system.posix_acl or user.mime_type\n"
453 "@type name: string\n"
454 "@param value: a string, possibly with embedded NULLs; note that there"
455 " are restrictions regarding the size of the value, for"
456 " example, for ext2/ext3, maximum size is the block size\n"
457 "@type value: string\n"
458 "@param flags: if 0 or ommited the attribute will be"
459 " created or replaced; if L{XATTR_CREATE}, the attribute"
460 " will be created, giving an error if it already exists;"
461 " if L{XATTR_REPLACE}, the attribute will be replaced,"
462 " giving an error if it doesn't exists;\n"
463 "@type flags: integer\n"
464 "@param nofollow: if given and True, and the function is passed a"
465 " filename that points to a symlink, the function will act on the symlink"
466 " itself instead of its target\n"
467 "@type nofollow: boolean\n"
468 "@param namespace: if given, the attribute must not contain the namespace"
469 " itself, but instead the namespace will be taken from this parameter\n"
470 "@type namespace: string\n"
472 "@raise EnvironmentError: system errors will raise an exception\n"
476 /* Wrapper for setxattr */
478 xattr_set(PyObject *self, PyObject *args, PyObject *keywds)
489 const char *full_name;
490 static char *kwlist[] = {"item", "name", "value", "flags",
491 "nofollow", "namespace", NULL};
493 /* Parse the arguments */
494 if (!PyArg_ParseTupleAndKeywords(args, keywds, "Oss#|iiz", kwlist,
496 &buf, &bufsize, &flags, &nofollow, &ns))
498 if(!convertObj(myarg, &tgt, nofollow))
501 full_name = merge_ns(ns, attrname, &newname);
502 /* Set the attribute's value */
503 nret = _set_obj(&tgt, full_name, buf, bufsize, flags);
507 return PyErr_SetFromErrno(PyExc_IOError);
510 /* Return the result */
515 static char __pyremovexattr_doc__[] =
516 "Remove an attribute from a file (deprecated)\n"
519 " - a string representing filename, or a file-like object,\n"
520 " or a file descriptor; this represents the file on \n"
522 " - a string, representing the attribute to be removed;\n"
523 " usually in form of system.posix_acl or user.mime_type\n"
524 " - (optional) a boolean value (defaults to false), which, if\n"
525 " the file name given is a symbolic link, makes the\n"
526 " function operate on the symbolic link itself instead\n"
528 "@deprecated: this function has been deprecated by the L{remove}"
532 /* Wrapper for removexattr */
534 pyremovexattr(PyObject *self, PyObject *args)
542 /* Parse the arguments */
543 if (!PyArg_ParseTuple(args, "Os|i", &myarg, &attrname, &nofollow))
546 if(!convertObj(myarg, &tgt, nofollow))
549 /* Remove the attribute */
550 if((nret = _remove_obj(&tgt, attrname)) == -1) {
551 return PyErr_SetFromErrno(PyExc_IOError);
554 /* Return the result */
558 static char __remove_doc__[] =
559 "Remove an attribute from a file\n"
561 "@param item: the item to query; either a string representing the"
562 " filename, or a file-like object, or a file descriptor\n"
563 "@param name: the attribute whose value to set; usually in form of"
564 " system.posix_acl or user.mime_type\n"
565 "@type name: string\n"
566 "@param nofollow: if given and True, and the function is passed a"
567 " filename that points to a symlink, the function will act on the symlink"
568 " itself instead of its target\n"
569 "@type nofollow: boolean\n"
570 "@param namespace: if given, the attribute must not contain the namespace"
571 " itself, but instead the namespace will be taken from this parameter\n"
572 "@type namespace: string\n"
575 "@raise EnvironmentError: system errors will raise an exception\n"
578 /* Wrapper for removexattr */
580 xattr_remove(PyObject *self, PyObject *args, PyObject *keywds)
584 char *attrname, *name_buf;
586 const char *full_name;
589 static char *kwlist[] = {"item", "name", "nofollow", "namespace", NULL};
591 /* Parse the arguments */
592 if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os|iz", kwlist,
593 &myarg, &attrname, &nofollow, &ns))
596 if(!convertObj(myarg, &tgt, nofollow))
598 full_name = merge_ns(ns, attrname, &name_buf);
599 if(full_name == NULL)
602 /* Remove the attribute */
603 nret = _remove_obj(&tgt, full_name);
604 PyMem_Free(name_buf);
606 return PyErr_SetFromErrno(PyExc_IOError);
609 /* Return the result */
613 static char __pylistxattr_doc__[] =
614 "Return the list of attribute names for a file (deprecated)\n"
617 " - a string representing filename, or a file-like object,\n"
618 " or a file descriptor; this represents the file to \n"
620 " - (optional) a boolean value (defaults to false), which, if\n"
621 " the file name given is a symbolic link, makes the\n"
622 " function operate on the symbolic link itself instead\n"
624 "@deprecated: this function has been deprecated by the L{list}"
628 /* Wrapper for listxattr */
630 pylistxattr(PyObject *self, PyObject *args)
641 /* Parse the arguments */
642 if (!PyArg_ParseTuple(args, "O|i", &myarg, &nofollow))
644 if(!convertObj(myarg, &tgt, nofollow))
647 /* Find out the needed size of the buffer */
648 if((nalloc = _list_obj(&tgt, NULL, 0)) == -1) {
649 return PyErr_SetFromErrno(PyExc_IOError);
652 /* Try to allocate the memory, using Python's allocator */
653 if((buf = PyMem_Malloc(nalloc)) == NULL) {
658 /* Now retrieve the list of attributes */
659 if((nret = _list_obj(&tgt, buf, nalloc)) == -1) {
661 return PyErr_SetFromErrno(PyExc_IOError);
664 /* Compute the number of attributes in the list */
665 for(s = buf, nattrs = 0; (s - buf) < nret; s += strlen(s) + 1) {
669 /* Create the list which will hold the result */
670 mylist = PyList_New(nattrs);
672 /* Create and insert the attributes as strings in the list */
673 for(s = buf, nattrs = 0; s - buf < nret; s += strlen(s) + 1) {
674 PyList_SET_ITEM(mylist, nattrs, PyString_FromString(s));
678 /* Free the buffer, now it is no longer needed */
681 /* Return the result */
685 static char __list_doc__[] =
686 "Return the list of attribute names for a file\n"
689 " >>> xattr.list('/path/to/file')\n"
690 " ['user.test', 'user.comment', 'system.posix_acl_access']\n"
691 " >>> xattr.list('/path/to/file', namespace=xattr.NS_USER)\n"
692 " ['test', 'comment']\n"
694 "@param item: the item to query; either a string representing the"
695 " filename, or a file-like object, or a file descriptor\n"
696 "@param nofollow: if given and True, and the function is passed a"
697 " filename that points to a symlink, the function will act on the symlink"
698 " itself instead of its target\n"
699 "@type nofollow: boolean\n"
700 "@param namespace: if given, the attribute must not contain the namespace"
701 " itself, but instead the namespace will be taken from this parameter\n"
702 "@type namespace: string\n"
703 "@return: list of strings; note that if the namespace attribute has been\n"
704 "passed, the names will have the namespace prefix stripped\n"
706 "@raise EnvironmentError: system errors will raise an exception\n"
710 /* Wrapper for listxattr */
712 xattr_list(PyObject *self, PyObject *args, PyObject *keywds)
723 static char *kwlist[] = {"item", "nofollow", "namespace", NULL};
725 /* Parse the arguments */
726 if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iz", kwlist,
727 &myarg, &nofollow, &ns))
729 if(!convertObj(myarg, &tgt, nofollow))
732 /* Find out the needed size of the buffer */
733 if((nalloc = _list_obj(&tgt, NULL, 0)) == -1) {
734 return PyErr_SetFromErrno(PyExc_IOError);
737 /* Try to allocate the memory, using Python's allocator */
738 if((buf = PyMem_Malloc(nalloc)) == NULL) {
743 /* Now retrieve the list of attributes */
744 if((nret = _list_obj(&tgt, buf, nalloc)) == -1) {
746 return PyErr_SetFromErrno(PyExc_IOError);
749 /* Compute the number of attributes in the list */
750 for(s = buf, nattrs = 0; (s - buf) < nret; s += strlen(s) + 1) {
751 if(matches_ns(ns, s)!=NULL)
754 /* Create the list which will hold the result */
755 mylist = PyList_New(nattrs);
757 /* Create and insert the attributes as strings in the list */
758 for(s = buf, nattrs = 0; s - buf < nret; s += strlen(s) + 1) {
759 const char *name = matches_ns(ns, s);
761 PyList_SET_ITEM(mylist, nattrs, PyString_FromString(name));
766 /* Free the buffer, now it is no longer needed */
769 /* Return the result */
773 static PyMethodDef xattr_methods[] = {
774 {"getxattr", pygetxattr, METH_VARARGS, __pygetxattr_doc__ },
775 {"get", (PyCFunction) xattr_get, METH_VARARGS | METH_KEYWORDS,
777 {"get_all", (PyCFunction) get_all, METH_VARARGS | METH_KEYWORDS,
779 {"setxattr", pysetxattr, METH_VARARGS, __pysetxattr_doc__ },
780 {"set", (PyCFunction) xattr_set, METH_VARARGS | METH_KEYWORDS,
782 {"removexattr", pyremovexattr, METH_VARARGS, __pyremovexattr_doc__ },
783 {"remove", (PyCFunction) xattr_remove, METH_VARARGS | METH_KEYWORDS,
785 {"listxattr", pylistxattr, METH_VARARGS, __pylistxattr_doc__ },
786 {"list", (PyCFunction) xattr_list, METH_VARARGS | METH_KEYWORDS,
788 {NULL, NULL, 0, NULL} /* Sentinel */
791 static char __xattr_doc__[] = \
792 "Access extended filesystem attributes\n"
794 "This module gives access to the extended attributes present\n"
795 "in some operating systems/filesystems. You can list attributes,\n"
796 "get, set and remove them.\n"
797 "The last and optional parameter for all functions is a boolean \n"
798 "value which enables the 'l-' version of the functions - acting\n"
799 "on symbolic links and not their destination.\n"
802 " >>> import xattr\n"
803 " >>> xattr.listxattr(\"file.txt\")\n"
804 " ['user.mime_type']\n"
805 " >>> xattr.getxattr(\"file.txt\", \"user.mime_type\")\n"
807 " >>> xattr.setxattr(\"file.txt\", \"user.comment\", "
808 "\"Simple text file\")\n"
809 " >>> xattr.listxattr(\"file.txt\")\n"
810 " ['user.mime_type', 'user.comment']\n"
811 " >>> xattr.removexattr (\"file.txt\", \"user.comment\")\n"
818 PyObject *m = Py_InitModule3("xattr", xattr_methods, __xattr_doc__);
820 PyModule_AddIntConstant(m, "XATTR_CREATE", XATTR_CREATE);
821 PyModule_AddIntConstant(m, "XATTR_REPLACE", XATTR_REPLACE);
823 /* namespace constants */
824 PyModule_AddStringConstant(m, "NS_SECURITY", "security");
825 PyModule_AddStringConstant(m, "NS_SYSTEM", "system");
826 PyModule_AddStringConstant(m, "NS_TRUSTED", "trusted");
827 PyModule_AddStringConstant(m, "NS_USER", "user");