]> git.k1024.org Git - pyxattr.git/blob - xattr.c
Some small fixes in get_all
[pyxattr.git] / xattr.c
1 #include <Python.h>
2 #include <attr/xattr.h>
3
4 /* the estimated (startup) attribute buffer size in
5    multi-operations */
6 #define ESTIMATE_ATTR_SIZE 256
7
8 typedef enum {T_FD, T_PATH, T_LINK} target_e;
9
10 typedef struct {
11     target_e type;
12     union {
13         const char *name;
14         int fd;
15     };
16 } target_t;
17
18 /** Converts from a string, file or int argument to what we need. */
19 static int convertObj(PyObject *myobj, target_t *tgt, int nofollow) {
20     int fd;
21     if(PyString_Check(myobj)) {
22         tgt->type = nofollow ? T_LINK : T_PATH;
23         tgt->name = PyString_AS_STRING(myobj);
24     } else if((fd = PyObject_AsFileDescriptor(myobj)) != -1) {
25         tgt->type = T_FD;
26         tgt->fd = fd;
27     } else {
28         PyErr_SetString(PyExc_TypeError, "argument must be string or int");
29         return 0;
30     }
31     return 1;
32 }
33
34 static ssize_t _list_obj(target_t *tgt, char *list, size_t size) {
35     if(tgt->type == T_FD)
36         return flistxattr(tgt->fd, list, size);
37     else if (tgt->type == T_LINK)
38         return llistxattr(tgt->name, list, size);
39     else
40         return listxattr(tgt->name, list, size);
41 }
42
43 static ssize_t _get_obj(target_t *tgt, char *name, void *value, size_t size) {
44     if(tgt->type == T_FD)
45         return fgetxattr(tgt->fd, name, value, size);
46     else if (tgt->type == T_LINK)
47         return lgetxattr(tgt->name, name, value, size);
48     else
49         return getxattr(tgt->name, name, value, size);
50 }
51
52 static ssize_t _set_obj(target_t *tgt, char *name, void *value, size_t size,
53                         int flags) {
54     if(tgt->type == T_FD)
55         return fsetxattr(tgt->fd, name, value, size, flags);
56     else if (tgt->type == T_LINK)
57         return lsetxattr(tgt->name, name, value, size, flags);
58     else
59         return setxattr(tgt->name, name, value, size, flags);
60 }
61
62 static ssize_t _remove_obj(target_t *tgt, char *name) {
63     if(tgt->type == T_FD)
64         return fremovexattr(tgt->fd, name);
65     else if (tgt->type == T_LINK)
66         return lremovexattr(tgt->name, name);
67     else
68         return removexattr(tgt->name, name);
69 }
70
71 /* Checks if an attribute name matches an optional namespace */
72 static int matches_ns(const char *name, const char *ns) {
73     size_t ns_size;
74     if (ns == NULL)
75         return 1;
76     ns_size = strlen(ns);
77
78     if (strlen(name) > ns_size && !strncmp(name, ns, ns_size) &&
79         name[ns_size] == '.')
80         return 1;
81     return 0;
82 }
83
84 /* Wrapper for getxattr */
85 static char __pygetxattr_doc__[] =
86     "Get the value of a given extended attribute.\n"
87     "\n"
88     "Parameters:\n"
89     "  - a string representing filename, or a file-like object,\n"
90     "    or a file descriptor; this represents the file on \n"
91     "    which to act\n"
92     "  - a string, representing the attribute whose value to retrieve;\n"
93     "    usually in form of system.posix_acl or user.mime_type\n"
94     "  - (optional) a boolean value (defaults to false), which, if\n"
95     "    the file name given is a symbolic link, makes the\n"
96     "    function operate on the symbolic link itself instead\n"
97     "    of its target;\n"
98     "@deprecated: this function has been replace with the L{get_all} function"
99     " which replaces the positional parameters with keyword ones\n"
100     ;
101
102 static PyObject *
103 pygetxattr(PyObject *self, PyObject *args)
104 {
105     PyObject *myarg;
106     target_t tgt;
107     int nofollow=0;
108     char *attrname;
109     char *buf;
110     int nalloc, nret;
111     PyObject *res;
112
113     /* Parse the arguments */
114     if (!PyArg_ParseTuple(args, "Os|i", &myarg, &attrname, &nofollow))
115         return NULL;
116     if(!convertObj(myarg, &tgt, nofollow))
117         return NULL;
118
119     /* Find out the needed size of the buffer */
120     if((nalloc = _get_obj(&tgt, attrname, NULL, 0)) == -1) {
121         return PyErr_SetFromErrno(PyExc_IOError);
122     }
123
124     /* Try to allocate the memory, using Python's allocator */
125     if((buf = PyMem_Malloc(nalloc)) == NULL) {
126         PyErr_NoMemory();
127         return NULL;
128     }
129
130     /* Now retrieve the attribute value */
131     if((nret = _get_obj(&tgt, attrname, buf, nalloc)) == -1) {
132         PyMem_Free(buf);
133         return PyErr_SetFromErrno(PyExc_IOError);
134     }
135
136     /* Create the string which will hold the result */
137     res = PyString_FromStringAndSize(buf, nret);
138
139     /* Free the buffer, now it is no longer needed */
140     PyMem_Free(buf);
141
142     /* Return the result */
143     return res;
144 }
145
146 /* Wrapper for getxattr */
147 static char __get_all_doc__[] =
148     "Get all the extended attributes of an item.\n"
149     "\n"
150     "This function performs a bulk-get of all extended attribute names\n"
151     "and the corresponding value.\n"
152     "Example:\n"
153     "  >>> xattr.get_all('/path/to/file')\n"
154     "  [('user.mime-type', 'plain/text'), ('user.comment', 'test'),"
155     " ('system.posix_acl_access', '\\x02\\x00...')]\n"
156     "  >>> xattr.get_all('/path/to/file', namespace=xattr.NS_USER)\n"
157     "  [('user.mime-type', 'plain/text'), ('user.comment', 'test')]\n"
158     "\n"
159     "@param item: the item to query; either a string representing the"
160     " filename, or a file-like object, or a file descriptor\n"
161     "@keyword namespace: an optional namespace for filtering the"
162     " attributes; for example, querying all user attributes can be"
163     " accomplished by passing namespace=L{NS_USER}\n"
164     "@type namespace: string\n"
165     "@keyword nofollow: if passed and true, if the target file is a symbolic"
166     " link,"
167     " the attributes for the link itself will be returned, instead of the"
168     " attributes of the target\n"
169     "@type nofollow: boolean\n"
170     "@return: list of tuples (name, value)\n"
171     ;
172
173 static PyObject *
174 get_all(PyObject *self, PyObject *args, PyObject *keywds)
175 {
176     PyObject *myarg;
177     int dolink=0;
178     char *ns = NULL;
179     char *buf_list, *buf_val;
180     char *s;
181     size_t nalloc, nlist, nval;
182     PyObject *mylist;
183     target_t tgt;
184     static char *kwlist[] = {"item", "nofollow", "namespace", NULL};
185
186     /* Parse the arguments */
187     if (!PyArg_ParseTupleAndKeywords(args, keywds, "O|iz", kwlist,
188                                      &myarg, &dolink, &ns))
189         return NULL;
190     if(!convertObj(myarg, &tgt, dolink))
191         return NULL;
192
193     /* Compute first the list of attributes */
194
195     /* Find out the needed size of the buffer for the attribute list */
196     nalloc = _list_obj(&tgt, NULL, 0);
197
198     if(nalloc == -1) {
199         return PyErr_SetFromErrno(PyExc_IOError);
200     }
201
202     /* Try to allocate the memory, using Python's allocator */
203     if((buf_list = PyMem_Malloc(nalloc)) == NULL) {
204         PyErr_NoMemory();
205         return NULL;
206     }
207
208     /* Now retrieve the list of attributes */
209     nlist = _list_obj(&tgt, buf_list, nalloc);
210
211     if(nlist == -1) {
212         PyErr_SetFromErrno(PyExc_IOError);
213         goto free_buf_list;
214     }
215
216     /* Create the list which will hold the result */
217     mylist = PyList_New(0);
218     nalloc = ESTIMATE_ATTR_SIZE;
219     if((buf_val = PyMem_Malloc(nalloc)) == NULL) {
220         PyErr_NoMemory();
221         goto free_list;
222     }
223
224     /* Create and insert the attributes as strings in the list */
225     for(s = buf_list; s - buf_list < nlist; s += strlen(s) + 1) {
226         PyObject *my_tuple;
227         int missing;
228
229         if(!matches_ns(s, ns))
230             continue;
231         /* Now retrieve the attribute value */
232         missing = 0;
233         while(1) {
234             nval = _get_obj(&tgt, s, buf_val, nalloc);
235
236             if(nval == -1) {
237                 if(errno == ERANGE) {
238                     nval = _get_obj(&tgt, s, NULL, 0);
239                     if((buf_val = PyMem_Realloc(buf_val, nval)) == NULL)
240                         goto free_list;
241                     nalloc = nval;
242                     continue;
243                 } else if(errno == ENODATA || errno == ENOATTR) {
244                     /* this attribute has gone away since we queried
245                        the attribute list */
246                     missing = 1;
247                     break;
248                 }
249                 goto exit_errno;
250             }
251             break;
252         }
253         if(missing)
254             continue;
255         my_tuple = Py_BuildValue("ss#", s, buf_val, nval);
256
257         PyList_Append(mylist, my_tuple);
258         Py_DECREF(my_tuple);
259     }
260
261     /* Free the buffers, now they are no longer needed */
262     PyMem_Free(buf_val);
263     PyMem_Free(buf_list);
264
265     /* Return the result */
266     return mylist;
267  exit_errno:
268     PyErr_SetFromErrno(PyExc_IOError);
269     PyMem_Free(buf_val);
270  free_list:
271     Py_DECREF(mylist);
272  free_buf_list:
273     PyMem_Free(buf_list);
274     return NULL;
275 }
276
277
278 static char __pysetxattr_doc__[] =
279     "Set the value of a given extended attribute.\n"
280     "Be carefull in case you want to set attributes on symbolic\n"
281     "links, you have to use all the 5 parameters; use 0 for the \n"
282     "flags value if you want the default behavior (create or "
283     "replace)\n"
284     "\n"
285     "Parameters:\n"
286     "  - a string representing filename, or a file-like object,\n"
287     "    or a file descriptor; this represents the file on \n"
288     "    which to act\n"
289     "  - a string, representing the attribute whose value to set;\n"
290     "    usually in form of system.posix_acl or user.mime_type\n"
291     "  - a string, possibly with embedded NULLs; note that there\n"
292     "    are restrictions regarding the size of the value, for\n"
293     "    example, for ext2/ext3, maximum size is the block size\n"
294     "  - (optional) flags; if 0 or ommited the attribute will be \n"
295     "    created or replaced; if XATTR_CREATE, the attribute \n"
296     "    will be created, giving an error if it already exists;\n"
297     "    of XATTR_REPLACE, the attribute will be replaced,\n"
298     "    giving an error if it doesn't exists;\n"
299     "  - (optional) a boolean value (defaults to false), which, if\n"
300     "    the file name given is a symbolic link, makes the\n"
301     "    function operate on the symbolic link itself instead\n"
302     "    of its target;"
303     ;
304
305 /* Wrapper for setxattr */
306 static PyObject *
307 pysetxattr(PyObject *self, PyObject *args)
308 {
309     PyObject *myarg;
310     int nofollow=0;
311     char *attrname;
312     char *buf;
313     int bufsize, nret;
314     int flags = 0;
315     target_t tgt;
316
317     /* Parse the arguments */
318     if (!PyArg_ParseTuple(args, "Oss#|bi", &myarg, &attrname,
319                           &buf, &bufsize, &flags, &nofollow))
320         return NULL;
321     if(!convertObj(myarg, &tgt, nofollow))
322         return NULL;
323
324     /* Set the attribute's value */
325     if((nret = _set_obj(&tgt, attrname, buf, bufsize, flags)) == -1) {
326         return PyErr_SetFromErrno(PyExc_IOError);
327     }
328
329     /* Return the result */
330     Py_RETURN_NONE;
331 }
332
333 static char __pyremovexattr_doc__[] =
334     "Remove an attribute from a file\n"
335     "\n"
336     "Parameters:\n"
337     "  - a string representing filename, or a file-like object,\n"
338     "    or a file descriptor; this represents the file on \n"
339     "    which to act\n"
340     "  - a string, representing the attribute to be removed;\n"
341     "    usually in form of system.posix_acl or user.mime_type\n"
342     "  - (optional) a boolean value (defaults to false), which, if\n"
343     "    the file name given is a symbolic link, makes the\n"
344     "    function operate on the symbolic link itself instead\n"
345     "    of its target;\n"
346     ;
347
348 /* Wrapper for removexattr */
349 static PyObject *
350 pyremovexattr(PyObject *self, PyObject *args)
351 {
352     PyObject *myarg;
353     int nofollow=0;
354     char *attrname;
355     int nret;
356     target_t tgt;
357
358     /* Parse the arguments */
359     if (!PyArg_ParseTuple(args, "Os|i", &myarg, &attrname, &nofollow))
360         return NULL;
361
362     if(!convertObj(myarg, &tgt, nofollow))
363         return NULL;
364
365     /* Remove the attribute */
366     if((nret = _remove_obj(&tgt, attrname)) == -1) {
367         return PyErr_SetFromErrno(PyExc_IOError);
368     }
369
370     /* Return the result */
371     Py_RETURN_NONE;
372 }
373
374 static char __pylistxattr_doc__[] =
375     "Return the list of attribute names for a file\n"
376     "\n"
377     "Parameters:\n"
378     "  - a string representing filename, or a file-like object,\n"
379     "    or a file descriptor; this represents the file to \n"
380     "    be queried\n"
381     "  - (optional) a boolean value (defaults to false), which, if\n"
382     "    the file name given is a symbolic link, makes the\n"
383     "    function operate on the symbolic link itself instead\n"
384     "    of its target;\n"
385     ;
386
387 /* Wrapper for listxattr */
388 static PyObject *
389 pylistxattr(PyObject *self, PyObject *args)
390 {
391     char *buf;
392     int nofollow=0;
393     int nalloc, nret;
394     PyObject *myarg;
395     PyObject *mylist;
396     int nattrs;
397     char *s;
398     target_t tgt;
399
400     /* Parse the arguments */
401     if (!PyArg_ParseTuple(args, "O|i", &myarg, &nofollow))
402         return NULL;
403     if(!convertObj(myarg, &tgt, nofollow))
404         return NULL;
405
406     /* Find out the needed size of the buffer */
407     if((nalloc = _list_obj(&tgt, NULL, 0)) == -1) {
408         return PyErr_SetFromErrno(PyExc_IOError);
409     }
410
411     /* Try to allocate the memory, using Python's allocator */
412     if((buf = PyMem_Malloc(nalloc)) == NULL) {
413         PyErr_NoMemory();
414         return NULL;
415     }
416
417     /* Now retrieve the list of attributes */
418     if((nret = _list_obj(&tgt, buf, nalloc)) == -1) {
419         PyMem_Free(buf);
420         return PyErr_SetFromErrno(PyExc_IOError);
421     }
422
423     /* Compute the number of attributes in the list */
424     for(s = buf, nattrs = 0; (s - buf) < nret; s += strlen(s) + 1) {
425         nattrs++;
426     }
427
428     /* Create the list which will hold the result */
429     mylist = PyList_New(nattrs);
430
431     /* Create and insert the attributes as strings in the list */
432     for(s = buf, nattrs = 0; s - buf < nret; s += strlen(s) + 1) {
433         PyList_SET_ITEM(mylist, nattrs, PyString_FromString(s));
434         nattrs++;
435     }
436
437     /* Free the buffer, now it is no longer needed */
438     PyMem_Free(buf);
439
440     /* Return the result */
441     return mylist;
442 }
443
444 static PyMethodDef xattr_methods[] = {
445     {"getxattr",  pygetxattr, METH_VARARGS, __pygetxattr_doc__ },
446     {"get_all", (PyCFunction) get_all, METH_VARARGS | METH_KEYWORDS,
447      __get_all_doc__ },
448     {"setxattr",  pysetxattr, METH_VARARGS, __pysetxattr_doc__ },
449     {"removexattr",  pyremovexattr, METH_VARARGS, __pyremovexattr_doc__ },
450     {"listxattr",  pylistxattr, METH_VARARGS, __pylistxattr_doc__ },
451     {NULL, NULL, 0, NULL}        /* Sentinel */
452 };
453
454 static char __xattr_doc__[] = \
455     "Access extended filesystem attributes\n"
456     "\n"
457     "This module gives access to the extended attributes present\n"
458     "in some operating systems/filesystems. You can list attributes,\n"
459     "get, set and remove them.\n"
460     "The last and optional parameter for all functions is a boolean \n"
461     "value which enables the 'l-' version of the functions - acting\n"
462     "on symbolic links and not their destination.\n"
463     "\n"
464     "Example: \n\n"
465     "  >>> import xattr\n"
466     "  >>> xattr.listxattr(\"file.txt\")\n"
467     "  ['user.mime_type']\n"
468     "  >>> xattr.getxattr(\"file.txt\", \"user.mime_type\")\n"
469     "  'text/plain'\n"
470     "  >>> xattr.setxattr(\"file.txt\", \"user.comment\", "
471     "\"Simple text file\")\n"
472     "  >>> xattr.listxattr(\"file.txt\")\n"
473     "  ['user.mime_type', 'user.comment']\n"
474     "  >>> xattr.removexattr (\"file.txt\", \"user.comment\")\n"
475     ""
476     ;
477
478 void
479 initxattr(void)
480 {
481     PyObject *m = Py_InitModule3("xattr", xattr_methods, __xattr_doc__);
482
483     PyModule_AddIntConstant(m, "XATTR_CREATE", XATTR_CREATE);
484     PyModule_AddIntConstant(m, "XATTR_REPLACE", XATTR_REPLACE);
485
486     /* namespace constants */
487     PyModule_AddStringConstant(m, "NS_SECURITY", "security");
488     PyModule_AddStringConstant(m, "NS_SYSTEM", "system");
489     PyModule_AddStringConstant(m, "NS_TRUSTED", "trusted");
490     PyModule_AddStringConstant(m, "NS_USER", "user");
491
492 }