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