_inotify.c
608 lines
| 14.1 KiB
| text/x-c
|
CLexer
Bryan O'Sullivan
|
r6239 | /* | ||
* _inotify.c - Python extension interfacing to the Linux inotify subsystem | ||||
* | ||||
* Copyright 2006 Bryan O'Sullivan <bos@serpentine.com> | ||||
* | ||||
* This library is free software; you can redistribute it and/or | ||||
* modify it under the terms of version 2.1 of the GNU Lesser General | ||||
* Public License, incorporated herein by reference. | ||||
*/ | ||||
#include <Python.h> | ||||
#include <alloca.h> | ||||
#include <sys/inotify.h> | ||||
#include <stdint.h> | ||||
#include <sys/ioctl.h> | ||||
#include <unistd.h> | ||||
static PyObject *init(PyObject *self, PyObject *args) | ||||
{ | ||||
PyObject *ret = NULL; | ||||
int fd = -1; | ||||
if (!PyArg_ParseTuple(args, ":init")) | ||||
goto bail; | ||||
Py_BEGIN_ALLOW_THREADS | ||||
fd = inotify_init(); | ||||
Py_END_ALLOW_THREADS | ||||
if (fd == -1) { | ||||
PyErr_SetFromErrno(PyExc_OSError); | ||||
goto bail; | ||||
} | ||||
ret = PyInt_FromLong(fd); | ||||
if (ret == NULL) | ||||
goto bail; | ||||
goto done; | ||||
bail: | ||||
if (fd != -1) | ||||
close(fd); | ||||
Py_CLEAR(ret); | ||||
done: | ||||
return ret; | ||||
} | ||||
PyDoc_STRVAR( | ||||
init_doc, | ||||
"init() -> fd\n" | ||||
"\n" | ||||
"Initialise an inotify instance.\n" | ||||
"Return a file descriptor associated with a new inotify event queue."); | ||||
static PyObject *add_watch(PyObject *self, PyObject *args) | ||||
{ | ||||
PyObject *ret = NULL; | ||||
uint32_t mask; | ||||
int wd = -1; | ||||
char *path; | ||||
int fd; | ||||
if (!PyArg_ParseTuple(args, "isI:add_watch", &fd, &path, &mask)) | ||||
goto bail; | ||||
Py_BEGIN_ALLOW_THREADS | ||||
wd = inotify_add_watch(fd, path, mask); | ||||
Py_END_ALLOW_THREADS | ||||
if (wd == -1) { | ||||
PyErr_SetFromErrnoWithFilename(PyExc_OSError, path); | ||||
goto bail; | ||||
} | ||||
ret = PyInt_FromLong(wd); | ||||
if (ret == NULL) | ||||
goto bail; | ||||
goto done; | ||||
bail: | ||||
if (wd != -1) | ||||
inotify_rm_watch(fd, wd); | ||||
Py_CLEAR(ret); | ||||
done: | ||||
return ret; | ||||
} | ||||
PyDoc_STRVAR( | ||||
add_watch_doc, | ||||
"add_watch(fd, path, mask) -> wd\n" | ||||
"\n" | ||||
"Add a watch to an inotify instance, or modify an existing watch.\n" | ||||
"\n" | ||||
" fd: file descriptor returned by init()\n" | ||||
" path: path to watch\n" | ||||
" mask: mask of events to watch for\n" | ||||
"\n" | ||||
"Return a unique numeric watch descriptor for the inotify instance\n" | ||||
"mapped by the file descriptor."); | ||||
static PyObject *remove_watch(PyObject *self, PyObject *args) | ||||
{ | ||||
PyObject *ret = NULL; | ||||
uint32_t wd; | ||||
int fd; | ||||
int r; | ||||
if (!PyArg_ParseTuple(args, "iI:remove_watch", &fd, &wd)) | ||||
goto bail; | ||||
Py_BEGIN_ALLOW_THREADS | ||||
r = inotify_rm_watch(fd, wd); | ||||
Py_END_ALLOW_THREADS | ||||
if (r == -1) { | ||||
PyErr_SetFromErrno(PyExc_OSError); | ||||
goto bail; | ||||
} | ||||
Py_INCREF(Py_None); | ||||
goto done; | ||||
bail: | ||||
Py_CLEAR(ret); | ||||
done: | ||||
return ret; | ||||
} | ||||
PyDoc_STRVAR( | ||||
remove_watch_doc, | ||||
"remove_watch(fd, wd)\n" | ||||
"\n" | ||||
" fd: file descriptor returned by init()\n" | ||||
" wd: watch descriptor returned by add_watch()\n" | ||||
"\n" | ||||
"Remove a watch associated with the watch descriptor wd from the\n" | ||||
"inotify instance associated with the file descriptor fd.\n" | ||||
"\n" | ||||
"Removing a watch causes an IN_IGNORED event to be generated for this\n" | ||||
"watch descriptor."); | ||||
#define bit_name(x) {x, #x} | ||||
static struct { | ||||
int bit; | ||||
const char *name; | ||||
PyObject *pyname; | ||||
} bit_names[] = { | ||||
bit_name(IN_ACCESS), | ||||
bit_name(IN_MODIFY), | ||||
bit_name(IN_ATTRIB), | ||||
bit_name(IN_CLOSE_WRITE), | ||||
bit_name(IN_CLOSE_NOWRITE), | ||||
bit_name(IN_OPEN), | ||||
bit_name(IN_MOVED_FROM), | ||||
bit_name(IN_MOVED_TO), | ||||
bit_name(IN_CREATE), | ||||
bit_name(IN_DELETE), | ||||
bit_name(IN_DELETE_SELF), | ||||
bit_name(IN_MOVE_SELF), | ||||
bit_name(IN_UNMOUNT), | ||||
bit_name(IN_Q_OVERFLOW), | ||||
bit_name(IN_IGNORED), | ||||
bit_name(IN_ONLYDIR), | ||||
bit_name(IN_DONT_FOLLOW), | ||||
bit_name(IN_MASK_ADD), | ||||
bit_name(IN_ISDIR), | ||||
bit_name(IN_ONESHOT), | ||||
{0} | ||||
}; | ||||
static PyObject *decode_mask(int mask) | ||||
{ | ||||
PyObject *ret = PyList_New(0); | ||||
int i; | ||||
if (ret == NULL) | ||||
goto bail; | ||||
for (i = 0; bit_names[i].bit; i++) { | ||||
if (mask & bit_names[i].bit) { | ||||
if (bit_names[i].pyname == NULL) { | ||||
bit_names[i].pyname = PyString_FromString(bit_names[i].name); | ||||
if (bit_names[i].pyname == NULL) | ||||
goto bail; | ||||
} | ||||
Py_INCREF(bit_names[i].pyname); | ||||
if (PyList_Append(ret, bit_names[i].pyname) == -1) | ||||
goto bail; | ||||
} | ||||
} | ||||
goto done; | ||||
bail: | ||||
Py_CLEAR(ret); | ||||
done: | ||||
return ret; | ||||
} | ||||
static PyObject *pydecode_mask(PyObject *self, PyObject *args) | ||||
{ | ||||
int mask; | ||||
if (!PyArg_ParseTuple(args, "i:decode_mask", &mask)) | ||||
return NULL; | ||||
return decode_mask(mask); | ||||
} | ||||
PyDoc_STRVAR( | ||||
decode_mask_doc, | ||||
"decode_mask(mask) -> list_of_strings\n" | ||||
"\n" | ||||
"Decode an inotify mask value into a list of strings that give the\n" | ||||
"name of each bit set in the mask."); | ||||
static char doc[] = "Low-level inotify interface wrappers."; | ||||
static void define_const(PyObject *dict, const char *name, uint32_t val) | ||||
{ | ||||
PyObject *pyval = PyInt_FromLong(val); | ||||
PyObject *pyname = PyString_FromString(name); | ||||
if (!pyname || !pyval) | ||||
goto bail; | ||||
PyDict_SetItem(dict, pyname, pyval); | ||||
bail: | ||||
Py_XDECREF(pyname); | ||||
Py_XDECREF(pyval); | ||||
} | ||||
static void define_consts(PyObject *dict) | ||||
{ | ||||
define_const(dict, "IN_ACCESS", IN_ACCESS); | ||||
define_const(dict, "IN_MODIFY", IN_MODIFY); | ||||
define_const(dict, "IN_ATTRIB", IN_ATTRIB); | ||||
define_const(dict, "IN_CLOSE_WRITE", IN_CLOSE_WRITE); | ||||
define_const(dict, "IN_CLOSE_NOWRITE", IN_CLOSE_NOWRITE); | ||||
define_const(dict, "IN_OPEN", IN_OPEN); | ||||
define_const(dict, "IN_MOVED_FROM", IN_MOVED_FROM); | ||||
define_const(dict, "IN_MOVED_TO", IN_MOVED_TO); | ||||
define_const(dict, "IN_CLOSE", IN_CLOSE); | ||||
define_const(dict, "IN_MOVE", IN_MOVE); | ||||
define_const(dict, "IN_CREATE", IN_CREATE); | ||||
define_const(dict, "IN_DELETE", IN_DELETE); | ||||
define_const(dict, "IN_DELETE_SELF", IN_DELETE_SELF); | ||||
define_const(dict, "IN_MOVE_SELF", IN_MOVE_SELF); | ||||
define_const(dict, "IN_UNMOUNT", IN_UNMOUNT); | ||||
define_const(dict, "IN_Q_OVERFLOW", IN_Q_OVERFLOW); | ||||
define_const(dict, "IN_IGNORED", IN_IGNORED); | ||||
define_const(dict, "IN_ONLYDIR", IN_ONLYDIR); | ||||
define_const(dict, "IN_DONT_FOLLOW", IN_DONT_FOLLOW); | ||||
define_const(dict, "IN_MASK_ADD", IN_MASK_ADD); | ||||
define_const(dict, "IN_ISDIR", IN_ISDIR); | ||||
define_const(dict, "IN_ONESHOT", IN_ONESHOT); | ||||
define_const(dict, "IN_ALL_EVENTS", IN_ALL_EVENTS); | ||||
} | ||||
struct event { | ||||
PyObject_HEAD | ||||
PyObject *wd; | ||||
PyObject *mask; | ||||
PyObject *cookie; | ||||
PyObject *name; | ||||
}; | ||||
static PyObject *event_wd(PyObject *self, void *x) | ||||
{ | ||||
struct event *evt = (struct event *) self; | ||||
Py_INCREF(evt->wd); | ||||
return evt->wd; | ||||
} | ||||
static PyObject *event_mask(PyObject *self, void *x) | ||||
{ | ||||
struct event *evt = (struct event *) self; | ||||
Py_INCREF(evt->mask); | ||||
return evt->mask; | ||||
} | ||||
static PyObject *event_cookie(PyObject *self, void *x) | ||||
{ | ||||
struct event *evt = (struct event *) self; | ||||
Py_INCREF(evt->cookie); | ||||
return evt->cookie; | ||||
} | ||||
static PyObject *event_name(PyObject *self, void *x) | ||||
{ | ||||
struct event *evt = (struct event *) self; | ||||
Py_INCREF(evt->name); | ||||
return evt->name; | ||||
} | ||||
static struct PyGetSetDef event_getsets[] = { | ||||
{"wd", event_wd, NULL, | ||||
"watch descriptor"}, | ||||
{"mask", event_mask, NULL, | ||||
"event mask"}, | ||||
{"cookie", event_cookie, NULL, | ||||
"rename cookie, if rename-related event"}, | ||||
{"name", event_name, NULL, | ||||
"file name"}, | ||||
{NULL} | ||||
}; | ||||
PyDoc_STRVAR( | ||||
event_doc, | ||||
"event: Structure describing an inotify event."); | ||||
static PyObject *event_new(PyTypeObject *t, PyObject *a, PyObject *k) | ||||
{ | ||||
return (*t->tp_alloc)(t, 0); | ||||
} | ||||
static void event_dealloc(struct event *evt) | ||||
{ | ||||
Py_XDECREF(evt->wd); | ||||
Py_XDECREF(evt->mask); | ||||
Py_XDECREF(evt->cookie); | ||||
Py_XDECREF(evt->name); | ||||
(*evt->ob_type->tp_free)(evt); | ||||
} | ||||
static PyObject *event_repr(struct event *evt) | ||||
{ | ||||
int wd = PyInt_AsLong(evt->wd); | ||||
int cookie = evt->cookie == Py_None ? -1 : PyInt_AsLong(evt->cookie); | ||||
PyObject *ret = NULL, *pymasks = NULL, *pymask = NULL; | ||||
PyObject *join = NULL; | ||||
char *maskstr; | ||||
join = PyString_FromString("|"); | ||||
if (join == NULL) | ||||
goto bail; | ||||
pymasks = decode_mask(PyInt_AsLong(evt->mask)); | ||||
if (pymasks == NULL) | ||||
goto bail; | ||||
pymask = _PyString_Join(join, pymasks); | ||||
if (pymask == NULL) | ||||
goto bail; | ||||
maskstr = PyString_AsString(pymask); | ||||
if (evt->name != Py_None) { | ||||
PyObject *pyname = PyString_Repr(evt->name, 1); | ||||
char *name = pyname ? PyString_AsString(pyname) : "???"; | ||||
if (cookie == -1) | ||||
ret = PyString_FromFormat("event(wd=%d, mask=%s, name=%s)", | ||||
wd, maskstr, name); | ||||
else | ||||
ret = PyString_FromFormat("event(wd=%d, mask=%s, " | ||||
"cookie=0x%x, name=%s)", | ||||
wd, maskstr, cookie, name); | ||||
Py_XDECREF(pyname); | ||||
} else { | ||||
if (cookie == -1) | ||||
ret = PyString_FromFormat("event(wd=%d, mask=%s)", | ||||
wd, maskstr); | ||||
else { | ||||
ret = PyString_FromFormat("event(wd=%d, mask=%s, cookie=0x%x)", | ||||
wd, maskstr, cookie); | ||||
} | ||||
} | ||||
goto done; | ||||
bail: | ||||
Py_CLEAR(ret); | ||||
done: | ||||
Py_XDECREF(pymask); | ||||
Py_XDECREF(pymasks); | ||||
Py_XDECREF(join); | ||||
return ret; | ||||
} | ||||
static PyTypeObject event_type = { | ||||
PyObject_HEAD_INIT(NULL) | ||||
0, /*ob_size*/ | ||||
"_inotify.event", /*tp_name*/ | ||||
sizeof(struct event), /*tp_basicsize*/ | ||||
0, /*tp_itemsize*/ | ||||
(destructor)event_dealloc, /*tp_dealloc*/ | ||||
0, /*tp_print*/ | ||||
0, /*tp_getattr*/ | ||||
0, /*tp_setattr*/ | ||||
0, /*tp_compare*/ | ||||
(reprfunc)event_repr, /*tp_repr*/ | ||||
0, /*tp_as_number*/ | ||||
0, /*tp_as_sequence*/ | ||||
0, /*tp_as_mapping*/ | ||||
0, /*tp_hash */ | ||||
0, /*tp_call*/ | ||||
0, /*tp_str*/ | ||||
0, /*tp_getattro*/ | ||||
0, /*tp_setattro*/ | ||||
0, /*tp_as_buffer*/ | ||||
Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ | ||||
event_doc, /* tp_doc */ | ||||
0, /* tp_traverse */ | ||||
0, /* tp_clear */ | ||||
0, /* tp_richcompare */ | ||||
0, /* tp_weaklistoffset */ | ||||
0, /* tp_iter */ | ||||
0, /* tp_iternext */ | ||||
0, /* tp_methods */ | ||||
0, /* tp_members */ | ||||
event_getsets, /* tp_getset */ | ||||
0, /* tp_base */ | ||||
0, /* tp_dict */ | ||||
0, /* tp_descr_get */ | ||||
0, /* tp_descr_set */ | ||||
0, /* tp_dictoffset */ | ||||
0, /* tp_init */ | ||||
0, /* tp_alloc */ | ||||
event_new, /* tp_new */ | ||||
}; | ||||
PyObject *read_events(PyObject *self, PyObject *args) | ||||
{ | ||||
PyObject *ctor_args = NULL; | ||||
PyObject *pybufsize = NULL; | ||||
PyObject *ret = NULL; | ||||
int bufsize = 65536; | ||||
char *buf = NULL; | ||||
int nread, pos; | ||||
int fd; | ||||
if (!PyArg_ParseTuple(args, "i|O:read", &fd, &pybufsize)) | ||||
goto bail; | ||||
if (pybufsize && pybufsize != Py_None) | ||||
bufsize = PyInt_AsLong(pybufsize); | ||||
ret = PyList_New(0); | ||||
if (ret == NULL) | ||||
goto bail; | ||||
if (bufsize <= 0) { | ||||
int r; | ||||
Py_BEGIN_ALLOW_THREADS | ||||
r = ioctl(fd, FIONREAD, &bufsize); | ||||
Py_END_ALLOW_THREADS | ||||
if (r == -1) { | ||||
PyErr_SetFromErrno(PyExc_OSError); | ||||
goto bail; | ||||
} | ||||
if (bufsize == 0) | ||||
goto done; | ||||
} | ||||
else { | ||||
static long name_max; | ||||
static long name_fd = -1; | ||||
long min; | ||||
if (name_fd != fd) { | ||||
name_fd = fd; | ||||
Py_BEGIN_ALLOW_THREADS | ||||
name_max = fpathconf(fd, _PC_NAME_MAX); | ||||
Py_END_ALLOW_THREADS | ||||
} | ||||
min = sizeof(struct inotify_event) + name_max + 1; | ||||
if (bufsize < min) { | ||||
PyErr_Format(PyExc_ValueError, "bufsize must be at least %d", | ||||
(int) min); | ||||
goto bail; | ||||
} | ||||
} | ||||
buf = alloca(bufsize); | ||||
Py_BEGIN_ALLOW_THREADS | ||||
nread = read(fd, buf, bufsize); | ||||
Py_END_ALLOW_THREADS | ||||
if (nread == -1) { | ||||
PyErr_SetFromErrno(PyExc_OSError); | ||||
goto bail; | ||||
} | ||||
ctor_args = PyTuple_New(0); | ||||
if (ctor_args == NULL) | ||||
goto bail; | ||||
pos = 0; | ||||
while (pos < nread) { | ||||
struct inotify_event *in = (struct inotify_event *) (buf + pos); | ||||
struct event *evt; | ||||
PyObject *obj; | ||||
obj = PyObject_CallObject((PyObject *) &event_type, ctor_args); | ||||
if (obj == NULL) | ||||
goto bail; | ||||
evt = (struct event *) obj; | ||||
evt->wd = PyInt_FromLong(in->wd); | ||||
evt->mask = PyInt_FromLong(in->mask); | ||||
if (in->mask & IN_MOVE) | ||||
evt->cookie = PyInt_FromLong(in->cookie); | ||||
else { | ||||
Py_INCREF(Py_None); | ||||
evt->cookie = Py_None; | ||||
} | ||||
if (in->len) | ||||
evt->name = PyString_FromString(in->name); | ||||
else { | ||||
Py_INCREF(Py_None); | ||||
evt->name = Py_None; | ||||
} | ||||
if (!evt->wd || !evt->mask || !evt->cookie || !evt->name) | ||||
goto mybail; | ||||
if (PyList_Append(ret, obj) == -1) | ||||
goto mybail; | ||||
pos += sizeof(struct inotify_event) + in->len; | ||||
continue; | ||||
mybail: | ||||
Py_CLEAR(evt->wd); | ||||
Py_CLEAR(evt->mask); | ||||
Py_CLEAR(evt->cookie); | ||||
Py_CLEAR(evt->name); | ||||
Py_DECREF(obj); | ||||
goto bail; | ||||
} | ||||
goto done; | ||||
bail: | ||||
Py_CLEAR(ret); | ||||
done: | ||||
Py_XDECREF(ctor_args); | ||||
return ret; | ||||
} | ||||
PyDoc_STRVAR( | ||||
read_doc, | ||||
"read(fd, bufsize[=65536]) -> list_of_events\n" | ||||
"\n" | ||||
"\nRead inotify events from a file descriptor.\n" | ||||
"\n" | ||||
" fd: file descriptor returned by init()\n" | ||||
" bufsize: size of buffer to read into, in bytes\n" | ||||
"\n" | ||||
"Return a list of event objects.\n" | ||||
"\n" | ||||
"If bufsize is > 0, block until events are available to be read.\n" | ||||
"Otherwise, immediately return all events that can be read without\n" | ||||
"blocking."); | ||||
static PyMethodDef methods[] = { | ||||
{"init", init, METH_VARARGS, init_doc}, | ||||
{"add_watch", add_watch, METH_VARARGS, add_watch_doc}, | ||||
{"remove_watch", remove_watch, METH_VARARGS, remove_watch_doc}, | ||||
{"read", read_events, METH_VARARGS, read_doc}, | ||||
{"decode_mask", pydecode_mask, METH_VARARGS, decode_mask_doc}, | ||||
{NULL}, | ||||
}; | ||||
void init_inotify(void) | ||||
{ | ||||
PyObject *mod, *dict; | ||||
if (PyType_Ready(&event_type) == -1) | ||||
return; | ||||
mod = Py_InitModule3("_inotify", methods, doc); | ||||
dict = PyModule_GetDict(mod); | ||||
if (dict) | ||||
define_consts(dict); | ||||
} | ||||