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