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