1 #define PY_SSIZE_T_CLEAN
3 #include <attr/xattr.h>
6 /* Compatibility with python 2.4 regarding python size type (PEP 353) */
7 #if PY_VERSION_HEX < 0x02050000 && !defined(PY_SSIZE_T_MIN)
8 typedef int Py_ssize_t;
9 #define PY_SSIZE_T_MAX INT_MAX
10 #define PY_SSIZE_T_MIN INT_MIN
13 /* the estimated (startup) attribute buffer size in
15 #define ESTIMATE_ATTR_SIZE 256
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, int flags) {
89 return fsetxattr(tgt->fd, name, value, size, flags);
90 else if (tgt->type == T_LINK)
91 return lsetxattr(tgt->name, name, value, size, flags);
93 return setxattr(tgt->name, name, value, size, flags);
96 static int _remove_obj(target_t *tgt, const char *name) {
98 return fremovexattr(tgt->fd, name);
99 else if (tgt->type == T_LINK)
100 return lremovexattr(tgt->name, name);
102 return removexattr(tgt->name, name);
106 Checks if an attribute name matches an optional namespace.
108 If the namespace is NULL, it will return the name itself. If the
109 namespace is non-NULL and the name matches, it will return a
110 pointer to the offset in the name after the namespace and the
111 separator. If however the name doesn't match the namespace, it will
114 const char *matches_ns(const char *ns, const char *name) {
118 ns_size = strlen(ns);
120 if (strlen(name) > (ns_size+1) && !strncmp(name, ns, ns_size) &&
121 name[ns_size] == '.')
122 return name + ns_size + 1;
126 /* Wrapper for getxattr */
127 static char __pygetxattr_doc__[] =
128 "Get the value of a given extended attribute (deprecated).\n"
131 " - a string representing filename, or a file-like object,\n"
132 " or a file descriptor; this represents the file on \n"
134 " - a string, representing the attribute whose value to retrieve;\n"
135 " usually in form of system.posix_acl or user.mime_type\n"
136 " - (optional) a boolean value (defaults to false), which, if\n"
137 " the file name given is a symbolic link, makes the\n"
138 " function operate on the symbolic link itself instead\n"
140 "@deprecated: this function has been deprecated by the L{get} function\n"
144 pygetxattr(PyObject *self, PyObject *args)
151 ssize_t nalloc, nret;
154 /* Parse the arguments */
155 if (!PyArg_ParseTuple(args, "Os|i", &myarg, &attrname, &nofollow))
157 if(!convertObj(myarg, &tgt, nofollow))
160 /* Find out the needed size of the buffer */
161 if((nalloc = _get_obj(&tgt, attrname, NULL, 0)) == -1) {
162 return PyErr_SetFromErrno(PyExc_IOError);
165 /* Try to allocate the memory, using Python's allocator */
166 if((buf = PyMem_Malloc(nalloc)) == NULL) {
171 /* Now retrieve the attribute value */
172 if((nret = _get_obj(&tgt, attrname, buf, nalloc)) == -1) {
174 return PyErr_SetFromErrno(PyExc_IOError);
177 /* Create the string which will hold the result */
178 res = PyString_FromStringAndSize(buf, nret);
180 /* Free the buffer, now it is no longer needed */
183 /* Return the result */
187 /* Wrapper for getxattr */
188 static char __get_doc__[] =
189 "Get the value of a given extended attribute.\n"
191 "@param item: the item to query; either a string representing the"
192 " filename, or a file-like object, or a file descriptor\n"
193 "@param name: the attribute whose value to set; usually in form of"
194 " system.posix_acl or user.mime_type\n"
195 "@type name: string\n"
196 "@param nofollow: if given and True, and the function is passed a"
197 " filename that points to a symlink, the function will act on the symlink"
198 " itself instead of its target\n"
199 "@type nofollow: boolean\n"
200 "@param namespace: if given, the attribute must not contain the namespace"
201 " itself, but instead the namespace will be taken from this parameter\n"
202 "@type namespace: string\n"
203 "@return: the value of the extended attribute (can contain NULLs)\n"
205 "@raise EnvironmentError: system errors will raise an exception\n"
210 xattr_get(PyObject *self, PyObject *args, PyObject *keywds)
215 char *attrname, *namebuf;
216 const char *fullname;
219 ssize_t nalloc, nret;
221 static char *kwlist[] = {"item", "name", "nofollow", "namespace", NULL};
223 /* Parse the arguments */
224 if (!PyArg_ParseTupleAndKeywords(args, keywds, "Os|iz", kwlist,
225 &myarg, &attrname, &nofollow, &ns))
227 if(!convertObj(myarg, &tgt, nofollow))
230 fullname = merge_ns(ns, attrname, &namebuf);
232 /* Find out the needed size of the buffer */
233 if((nalloc = _get_obj(&tgt, fullname, NULL, 0)) == -1) {
234 return PyErr_SetFromErrno(PyExc_IOError);
237 /* Try to allocate the memory, using Python's allocator */
238 if((buf = PyMem_Malloc(nalloc)) == NULL) {
244 /* Now retrieve the attribute value */
245 if((nret = _get_obj(&tgt, fullname, buf, nalloc)) == -1) {
248 return PyErr_SetFromErrno(PyExc_IOError);
251 /* Create the string which will hold the result */
252 res = PyString_FromStringAndSize(buf, nret);
254 /* Free the buffers, they are no longer needed */
258 /* Return the result */
262 /* Wrapper for getxattr */
263 static char __get_all_doc__[] =
264 "Get all the extended attributes of an item.\n"
266 "This function performs a bulk-get of all extended attribute names\n"
267 "and the corresponding value.\n"
269 " >>> xattr.get_all('/path/to/file')\n"
270 " [('user.mime-type', 'plain/text'), ('user.comment', 'test'),"
271 " ('system.posix_acl_access', '\\x02\\x00...')]\n"
272 " >>> xattr.get_all('/path/to/file', namespace=xattr.NS_USER)\n"
273 " [('mime-type', 'plain/text'), ('comment', 'test')]\n"
275 "@param item: the item to query; either a string representing the"
276 " filename, or a file-like object, or a file descriptor\n"
277 "@keyword namespace: an optional namespace for filtering the"
278 " attributes; for example, querying all user attributes can be"
279 " accomplished by passing namespace=L{NS_USER}\n"
280 "@type namespace: string\n"
281 "@keyword nofollow: if passed and true, if the target file is a symbolic"
283 " the attributes for the link itself will be returned, instead of the"
284 " attributes of the target\n"
285 "@type nofollow: boolean\n"
286 "@return: list of tuples (name, value); note that if a namespace\n"
287 "argument was passed, it (and the separator) will be stripped from\n"
288 "the names returned\n"
290 "@raise EnvironmentError: system errors will raise an exception\n"
295 get_all(PyObject *self, PyObject *args, PyObject *keywds)
300 char *buf_list, *buf_val;
302 size_t nalloc, nlist, nval;
305 static char *kwlist[] = {"item", "nofollow", "namespace", NULL};
307 /* Parse the arguments */
308 if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iz", kwlist,
309 &myarg, &dolink, &ns))
311 if(!convertObj(myarg, &tgt, dolink))
314 /* Compute first the list of attributes */
316 /* Find out the needed size of the buffer for the attribute list */
317 nalloc = _list_obj(&tgt, NULL, 0);
320 return PyErr_SetFromErrno(PyExc_IOError);
323 /* Try to allocate the memory, using Python's allocator */
324 if((buf_list = PyMem_Malloc(nalloc)) == NULL) {
329 /* Now retrieve the list of attributes */
330 nlist = _list_obj(&tgt, buf_list, nalloc);
333 PyErr_SetFromErrno(PyExc_IOError);
337 /* Create the list which will hold the result */
338 mylist = PyList_New(0);
339 nalloc = ESTIMATE_ATTR_SIZE;
340 if((buf_val = PyMem_Malloc(nalloc)) == NULL) {
345 /* Create and insert the attributes as strings in the list */
346 for(s = buf_list; s - buf_list < nlist; s += strlen(s) + 1) {
351 if((name=matches_ns(ns, s))==NULL)
353 /* Now retrieve the attribute value */
356 nval = _get_obj(&tgt, s, buf_val, nalloc);
359 if(errno == ERANGE) {
360 nval = _get_obj(&tgt, s, NULL, 0);
361 if((buf_val = PyMem_Realloc(buf_val, nval)) == NULL)
365 } else if(errno == ENODATA || errno == ENOATTR) {
366 /* this attribute has gone away since we queried
367 the attribute list */
377 my_tuple = Py_BuildValue("ss#", name, buf_val, nval);
379 PyList_Append(mylist, my_tuple);
383 /* Free the buffers, now they are no longer needed */
385 PyMem_Free(buf_list);
387 /* Return the result */
390 PyErr_SetFromErrno(PyExc_IOError);
395 PyMem_Free(buf_list);
400 static char __pysetxattr_doc__[] =
401 "Set the value of a given extended attribute (deprecated).\n"
402 "Be carefull in case you want to set attributes on symbolic\n"
403 "links, you have to use all the 5 parameters; use 0 for the \n"
404 "flags value if you want the default behavior (create or "
408 " - a string representing filename, or a file-like object,\n"
409 " or a file descriptor; this represents the file on \n"
411 " - a string, representing the attribute whose value to set;\n"
412 " usually in form of system.posix_acl or user.mime_type\n"
413 " - a string, possibly with embedded NULLs; note that there\n"
414 " are restrictions regarding the size of the value, for\n"
415 " example, for ext2/ext3, maximum size is the block size\n"
416 " - (optional) flags; if 0 or ommited the attribute will be \n"
417 " created or replaced; if XATTR_CREATE, the attribute \n"
418 " will be created, giving an error if it already exists;\n"
419 " of XATTR_REPLACE, the attribute will be replaced,\n"
420 " giving an error if it doesn't exists;\n"
421 " - (optional) a boolean value (defaults to false), which, if\n"
422 " the file name given is a symbolic link, makes the\n"
423 " function operate on the symbolic link itself instead\n"
425 "@deprecated: this function has been deprecated by the L{set} function\n"
428 /* Wrapper for setxattr */
430 pysetxattr(PyObject *self, PyObject *args)
441 /* Parse the arguments */
442 if (!PyArg_ParseTuple(args, "Oss#|bi", &myarg, &attrname,
443 &buf, &bufsize, &flags, &nofollow))
445 if(!convertObj(myarg, &tgt, nofollow))
448 /* Set the attribute's value */
449 if((nret = _set_obj(&tgt, attrname, buf, bufsize, flags)) == -1) {
450 return PyErr_SetFromErrno(PyExc_IOError);
453 /* Return the result */
457 static char __set_doc__[] =
458 "Set the value of a given extended attribute.\n"
460 "@param item: the item to query; either a string representing the"
461 " filename, or a file-like object, or a file descriptor\n"
462 "@param name: the attribute whose value to set; usually in form of"
463 " system.posix_acl or user.mime_type\n"
464 "@type name: string\n"
465 "@param value: a string, possibly with embedded NULLs; note that there"
466 " are restrictions regarding the size of the value, for"
467 " example, for ext2/ext3, maximum size is the block size\n"
468 "@type value: string\n"
469 "@param flags: if 0 or ommited the attribute will be"
470 " created or replaced; if L{XATTR_CREATE}, the attribute"
471 " will be created, giving an error if it already exists;"
472 " if L{XATTR_REPLACE}, the attribute will be replaced,"
473 " giving an error if it doesn't exists;\n"
474 "@type flags: integer\n"
475 "@param nofollow: if given and True, and the function is passed a"
476 " filename that points to a symlink, the function will act on the symlink"
477 " itself instead of its target\n"
478 "@type nofollow: boolean\n"
479 "@param namespace: if given, the attribute must not contain the namespace"
480 " itself, but instead the namespace will be taken from this parameter\n"
481 "@type namespace: string\n"
483 "@raise EnvironmentError: system errors will raise an exception\n"
487 /* Wrapper for setxattr */
489 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)
728 ssize_t nalloc, nret;
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");