#include #include #include /* python has its own ideas about which version to support */ #undef _POSIX_C_SOURCE #undef _XOPEN_SOURCE #include #include #if PY_MAJOR_VERSION >= 3 # define PyInt_FromLong PyLong_FromLong #endif #define EVTMAXSIZE (sizeof(struct inotify_event) + NAME_MAX + 1) #define EVTMINSIZE offsetof(struct inotify_event,name) typedef struct { PyObject_HEAD int fd; char buf[EVTMAXSIZE*16]; } INotify; static int INotify_Init(INotify *self, PyObject *args, PyObject *kws) { int flags; self->fd = inotify_init(); if(self->fd==-1) { PyErr_SetFromErrno(PyExc_OSError); return -1; } flags = fcntl(self->fd, F_GETFL, 0); flags |= O_NONBLOCK; if(fcntl(self->fd, F_SETFL, flags)) { close(self->fd); PyErr_SetFromErrno(PyExc_OSError); return -1; } return 0; } static void INotify_dealloc(INotify *self) { close(self->fd); Py_TYPE(self)->tp_free(self); } static PyObject* INotify_add_watch(INotify* self, PyObject* args) { int ret; const char* path; unsigned long mask; if(!PyArg_ParseTuple(args, "sk", &path, &mask)) return NULL; ret = inotify_add_watch(self->fd, path, mask); if(ret==-1) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } return PyInt_FromLong(ret); } static PyObject* INotify_rm_watch(INotify* self, PyObject* args) { int wd, ret; if(!PyArg_ParseTuple(args, "i", &wd)) return NULL; ret = inotify_rm_watch(self->fd, wd); if(ret==-1) { PyErr_SetFromErrno(PyExc_OSError); return NULL; } Py_RETURN_NONE; } /* Reading inotify events (circa. Linux 3.12) * Based on a reading of copy_event_to_user() in inotify_user.c * * The read() call must be given a buffer big enough for at least one * event. As event size is variable this means the buffer must * be sized for the woust cast (sizeof(inotify_event)+NAME_MAX+1). * We will only be given complete events, but can be sure how many. * * If we don't allocate enough space for one event read() gives EINVAL. * If we don't allocate enough space for two events, read() gives * the size of the first event. * read() should never return zero. */ static PyObject* INotify_read(INotify* self) { PyObject *list = NULL; void *buf = self->buf; ssize_t ret; list = PyList_New(0); if(!list) return NULL; retry: ret = read(self->fd, buf, sizeof(self->buf)); if(ret<0) { if(errno==EAGAIN) return list; /* return empty list */ else if(errno==EINTR) { if(PyErr_CheckSignals()==0) goto retry; } PyErr_SetFromErrno(PyExc_OSError); goto fail; } else if(ret=EVTMINSIZE) { PyObject *tuple; struct inotify_event *evt=buf; ssize_t evtsize; /* paranoia validation */ if(evt->len > ret) { PyErr_Format(PyExc_OSError, "Recieved event length %lu beyond buffer size %lu", (unsigned long)evt->len, (unsigned long)ret); /* oops, we can't recover from this... */ close(self->fd); self->fd = -1; goto fail; } else if(evt->len>0) evt->name[evt->len-1] = '\0'; else evt->name[0] = '\0'; evtsize = (void*)&evt->name[evt->len] - buf; tuple = Py_BuildValue("iIIs", (int)evt->wd, (unsigned int)evt->mask, (unsigned int)evt->cookie, evt->name); if(!tuple) goto fail; if(PyList_Append(list, tuple)) { Py_DECREF(tuple); goto fail; } Py_DECREF(tuple); /* PyList_Append() takes a reference */ buf += evtsize; ret -= evtsize; } if(ret!=0) PyErr_Warn(PyExc_UserWarning, "Stray bytes in INotify_read"); return list; fail: Py_XDECREF(list); return NULL; } static struct PyMemberDef INotify_members[] = { {"fd", T_INT, offsetof(INotify, fd), READONLY, "Underlying file descriptor for notifications"}, {NULL} }; static struct PyMethodDef INotify_methods[] = { {"add", (PyCFunction)INotify_add_watch, METH_VARARGS, "Add a new path to watch"}, {"_del", (PyCFunction)INotify_rm_watch, METH_VARARGS, "Stop watching a path"}, {"read", (PyCFunction)INotify_read, METH_NOARGS, "Read one event"}, {NULL} }; static PyTypeObject INotify_type = { #if PY_MAJOR_VERSION >= 3 PyVarObject_HEAD_INIT(NULL, 0) #else PyObject_HEAD_INIT(NULL) 0, #endif "_inotifyy.INotify", sizeof(INotify), }; #if PY_MAJOR_VERSION >= 3 static struct PyModuleDef inotifymodule = { PyModuleDef_HEAD_INIT, "_inotify", NULL, -1, NULL }; #endif #if PY_MAJOR_VERSION >= 3 # define MODINIT_RET(VAL) return (VAL) #else # define MODINIT_RET(VAL) return #endif #if PY_MAJOR_VERSION >= 3 PyMODINIT_FUNC PyInit__inotifyy(void) #else PyMODINIT_FUNC init_inotifyy(void) #endif { PyObject *mod = NULL; #if PY_MAJOR_VERSION >= 3 mod = PyModule_Create(&inotifymodule); #else mod = Py_InitModule("_inotifyy", NULL); #endif if(!mod) goto fail; INotify_type.tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE; INotify_type.tp_members = INotify_members; INotify_type.tp_methods = INotify_methods; INotify_type.tp_init = (initproc)INotify_Init; INotify_type.tp_dealloc = (destructor)INotify_dealloc; INotify_type.tp_new = PyType_GenericNew; if(PyType_Ready(&INotify_type)<0) { fprintf(stderr, "INotify object not ready\n"); MODINIT_RET(NULL); } PyObject *typeobj=(PyObject*)&INotify_type; Py_INCREF(typeobj); if(PyModule_AddObject(mod, "INotify", typeobj)) { Py_DECREF(typeobj); fprintf(stderr, "Failed to add INotify object to module\n"); MODINIT_RET(NULL); } PyModule_AddIntMacro(mod, IN_ACCESS); PyModule_AddIntMacro(mod, IN_ATTRIB); PyModule_AddIntMacro(mod, IN_CLOSE_WRITE); PyModule_AddIntMacro(mod, IN_CLOSE_NOWRITE); PyModule_AddIntMacro(mod, IN_CREATE); PyModule_AddIntMacro(mod, IN_DELETE); PyModule_AddIntMacro(mod, IN_DELETE_SELF); PyModule_AddIntMacro(mod, IN_MODIFY); PyModule_AddIntMacro(mod, IN_MOVE_SELF); PyModule_AddIntMacro(mod, IN_MOVED_FROM); PyModule_AddIntMacro(mod, IN_MOVED_TO); PyModule_AddIntMacro(mod, IN_OPEN); PyModule_AddIntMacro(mod, IN_ALL_EVENTS); PyModule_AddIntMacro(mod, IN_ONESHOT); /* added in glibc 2.5 */ #ifdef IN_EXCL_UNLINK PyModule_AddIntMacro(mod, IN_EXCL_UNLINK); #endif #ifdef IN_DONT_FOLLOW PyModule_AddIntMacro(mod, IN_DONT_FOLLOW); #endif #ifdef IN_MASK_ADD PyModule_AddIntMacro(mod, IN_MASK_ADD); #endif #ifdef IN_ONLYDIR PyModule_AddIntMacro(mod, IN_ONLYDIR); #endif PyModule_AddIntMacro(mod, IN_IGNORED); PyModule_AddIntMacro(mod, IN_ISDIR); PyModule_AddIntMacro(mod, IN_Q_OVERFLOW); PyModule_AddIntMacro(mod, IN_UNMOUNT); MODINIT_RET(mod); fail: fprintf(stderr, "Failed to initialize _inotify module!\n"); Py_XDECREF(mod); MODINIT_RET(NULL); }