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