diff options
author | Sebastien Martini <seb@dbzteam.org> | 2011-01-28 13:47:00 +0100 |
---|---|---|
committer | Sebastien Martini <seb@dbzteam.org> | 2011-01-28 13:47:00 +0100 |
commit | fec920a2146f78802f1a28a7f4c47f5c97426a7e (patch) | |
tree | e21371bc6c78a81ba7e39636d7cd69b32994deca | |
parent | 2900ceda692f590b17a193d1bd97e507abfdc10d (diff) | |
download | pyinotify-fec920a2146f78802f1a28a7f4c47f5c97426a7e.tar.gz |
Added C-coded inotify syscalls interface.
- If compile_ext_mod is set to True in setup.py, this extension
is explicitly compiled.
- If compile_ext_mod is set to False in setup.py (default), this
extension is compiled only if no inotify support has been found
from ctypes.
- Replaced exception type UnsupportedLibcVersionError by
InotifyBindingNotFoundError. WatchManager.__init__() may raise
this exception on error.
- SysCtlINotify (class providing access to inotify /proc variables)
is not instanciated (therefore not available) when the C extension
is used. It is only available from ctypes.
-rw-r--r-- | common/inotify_syscalls.c | 193 | ||||
-rwxr-xr-x | python2/pyinotify.py | 275 | ||||
-rwxr-xr-x | python3/pyinotify.py | 267 | ||||
-rwxr-xr-x | setup.py | 47 |
4 files changed, 619 insertions, 163 deletions
diff --git a/common/inotify_syscalls.c b/common/inotify_syscalls.c new file mode 100644 index 0000000..a4c45f8 --- /dev/null +++ b/common/inotify_syscalls.c @@ -0,0 +1,193 @@ +/* + inotify_syscalls.c - native binding to inotify's syscalls + Copyright (c) 2005-2011 Sebastien Martini <seb@dbzteam.org> + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*/ +#include <Python.h> +#include <sys/syscall.h> + +#if defined(__i386__) +# define __NR_inotify_init 291 +# define __NR_inotify_add_watch 292 +# define __NR_inotify_rm_watch 293 +#elif defined(__x86_64__) +# define __NR_inotify_init 253 +# define __NR_inotify_add_watch 254 +# define __NR_inotify_rm_watch 255 +#elif (defined(__powerpc__) || defined(__powerpc64__)) +# define __NR_inotify_init 275 +# define __NR_inotify_add_watch 276 +# define __NR_inotify_rm_watch 277 +#elif defined (__ia64__) +# define __NR_inotify_init 1277 +# define __NR_inotify_add_watch 1278 +# define __NR_inotify_rm_watch 1279 +#elif defined (__s390__) +# define __NR_inotify_init 284 +# define __NR_inotify_add_watch 285 +# define __NR_inotify_rm_watch 286 +#elif defined (__alpha__) +# define __NR_inotify_init 444 +# define __NR_inotify_add_watch 445 +# define __NR_inotify_rm_watch 446 +#elif defined (__sparc__) || defined (__sparc64__) +# define __NR_inotify_init 151 +# define __NR_inotify_add_watch 152 +# define __NR_inotify_rm_watch 156 +#elif defined (__arm__) +# define __NR_inotify_init 316 +# define __NR_inotify_add_watch 317 +# define __NR_inotify_rm_watch 318 +#elif defined (__sh__) +# define __NR_inotify_init 290 +# define __NR_inotify_add_watch 291 +# define __NR_inotify_rm_watch 292 +#elif defined (__sh64__) +# define __NR_inotify_init 318 +# define __NR_inotify_add_watch 319 +# define __NR_inotify_rm_watch 320 +#elif defined (__hppa__) +# define __NR_inotify_init 269 +# define __NR_inotify_add_watch 270 +# define __NR_inotify_rm_watch 271 +#elif defined (__mips__) +# define _MIPS_SIM_ABI32 1 +# define _MIPS_SIM_NABI32 2 +# define _MIPS_SIM_ABI64 3 +# if _MIPS_SIM == _MIPS_SIM_ABI32 +# define __NR_Linux 4000 +# define __NR_inotify_init (__NR_Linux + 284) +# define __NR_inotify_add_watch (__NR_Linux + 285) +# define __NR_inotify_rm_watch (__NR_Linux + 286) +# endif /* _MIPS_SIM == _MIPS_SIM_ABI32 */ +# if _MIPS_SIM == _MIPS_SIM_ABI64 +# define __NR_Linux 5000 +# define __NR_inotify_init (__NR_Linux + 243) +# define __NR_inotify_add_watch (__NR_Linux + 244) +# define __NR_inotify_rm_watch (__NR_Linux + 245) +# endif /* _MIPS_SIM == _MIPS_SIM_ABI64 */ +# if _MIPS_SIM == _MIPS_SIM_NABI32 +# define __NR_Linux 6000 +# define __NR_inotify_init (__NR_Linux + 247) +# define __NR_inotify_add_watch (__NR_Linux + 248) +# define __NR_inotify_rm_watch (__NR_Linux + 249) +# endif /* _MIPS_SIM == _MIPS_SIM_NABI32 */ +#elif defined (__frv__) +# define __NR_inotify_init 291 +# define __NR_inotify_add_watch 292 +# define __NR_inotify_rm_watch 293 +#elif defined (__parisc__) +# define __NR_Linux 0 +# define __NR_inotify_init (__NR_Linux + 269) +# define __NR_inotify_add_watch (__NR_Linux + 270) +# define __NR_inotify_rm_watch (__NR_Linux + 271) +#elif defined (__mc68000__) +# define __NR_inotify_init 284 +# define __NR_inotify_add_watch 285 +# define __NR_inotify_rm_watch 286 +#else +# error "Unsupported architecture!" +#endif + + +static PyObject* inotify_init(PyObject* self, PyObject* args) { + int result = -1; + + if (!PyArg_ParseTuple(args, "")) + return NULL; + + result = syscall(__NR_inotify_init); + if (result == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + return Py_BuildValue("i", result); +} + +static PyObject* inotify_add_watch(PyObject* self, PyObject* args) { + int fd = -1; + char* name = NULL; + unsigned int mask = 0; + int result = -1; + + if(!PyArg_ParseTuple(args, "isI", &fd, &name, &mask)) + return NULL; + + result = syscall(__NR_inotify_add_watch, fd, name, mask); + if (result == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + return Py_BuildValue("i", result); +} + +static PyObject* inotify_rm_watch(PyObject* self, PyObject* args) { + int fd = -1; + unsigned int wd = 0; + int result = -1; + + if(!PyArg_ParseTuple(args, "iI", &fd, &wd)) + return NULL; + + result = syscall(__NR_inotify_rm_watch, fd, wd); + if (result == -1) { + PyErr_SetFromErrno(PyExc_IOError); + return NULL; + } + + return Py_BuildValue("i", result); +} + +static PyMethodDef inotify_syscalls_functions[] = { + {"inotify_init", inotify_init, METH_VARARGS, "inotify initialization"}, + {"inotify_add_watch", inotify_add_watch, METH_VARARGS, "Add a new watch"}, + {"inotify_rm_watch", inotify_rm_watch, METH_VARARGS, "Remove a watch"}, + {0} +}; + +/* python 2 */ +#if PY_VERSION_HEX < 0x03000000 + +void initinotify_syscalls(void) { + Py_InitModule3("inotify_syscalls", inotify_syscalls_functions, + "module inotify_syscalls"); +} + +#else /* python 3 */ + +static struct PyModuleDef inotify_syscallsmodule = { + {}, /* m_base */ + "inotify_syscalls", /* m_name */ + "module inotify_syscalls", /* m_doc */ + 0, /* m_size */ + inotify_syscalls_functions, /* m_methods */ + 0, /* m_reload */ + 0, /* m_traverse */ + 0, /* m_clear */ + 0, /* m_free */ +}; + +PyObject* PyInit_inotify_syscalls(void) { + return PyModule_Create(&inotify_syscallsmodule); +} + +#endif /* PY_VERSION_HEX */ diff --git a/python2/pyinotify.py b/python2/pyinotify.py index e56be10..6b4c72a 100755 --- a/python2/pyinotify.py +++ b/python2/pyinotify.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # pyinotify.py - python interface to inotify -# Copyright (c) 2010 Sebastien Martini <seb@dbzteam.org> +# Copyright (c) 2005-2011 Sebastien Martini <seb@dbzteam.org> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -42,24 +42,13 @@ class UnsupportedPythonVersionError(PyinotifyError): @param version: Current Python version @type version: string """ - PyinotifyError.__init__(self, - ('Python %s is unsupported, requires ' - 'at least Python 2.4') % version) - - -class UnsupportedLibcVersionError(PyinotifyError): - """ - Raised when libc couldn't be loaded or when inotify functions werent - provided. - """ - def __init__(self): - err = 'libc does not provide required inotify support' - PyinotifyError.__init__(self, err) + err = 'Python %s is unsupported, requires at least Python 2.4' + PyinotifyError.__init__(self, err % version) # Check Python version import sys -if sys.version < '2.4': +if sys.version_info < (2, 4): raise UnsupportedPythonVersionError(sys.version) @@ -78,8 +67,6 @@ from collections import deque from datetime import datetime, timedelta import time import re -import ctypes -import ctypes.util import asyncore import glob @@ -88,6 +75,18 @@ try: except ImportError: pass # Will fail on Python 2.4 which has reduce() builtin anyway. +try: + import ctypes + import ctypes.util +except ImportError: + ctypes = None + +try: + import inotify_syscalls +except ImportError: + inotify_syscalls = None + + __author__ = "seb@dbzteam.org (Sebastien Martini)" __version__ = "0.9.1" @@ -101,37 +100,141 @@ __metaclass__ = type # Use new-style classes by default COMPATIBILITY_MODE = False -# Load libc -LIBC = None -strerrno = None +class InotifyBindingNotFoundError(PyinotifyError): + """ + Raised when no inotify support couldn't be found. + """ + def __init__(self): + err = "Couldn't find any inotify binding" + PyinotifyError.__init__(self, err) -def load_libc(): - global strerrno - global LIBC - libc = None - try: - libc = ctypes.util.find_library('c') - except (OSError, IOError): - pass # Will attemp to load it with None anyway. +class INotifyWrapper: + """Abstract class.""" + @staticmethod + def create(): + # First, try to use ctypes. + if ctypes: + inotify = CtypesLibcINotifyWrapper() + if inotify.init(): + return inotify + # Second, see if C extension is compiled. + if inotify_syscalls: + inotify = INotifySyscallsWrapper() + if inotify.init(): + return inotify - if sys.version_info[0] >= 2 and sys.version_info[1] >= 6: - LIBC = ctypes.CDLL(libc, use_errno=True) - def _strerrno(): - code = ctypes.get_errno() - return ' Errno=%s (%s)' % (os.strerror(code), errno.errorcode[code]) - strerrno = _strerrno - else: - LIBC = ctypes.CDLL(libc) - strerrno = lambda : '' + def get_errno(self): + """ + Return None is no errno code is available. + """ + return self._get_errno() - # Check that libc has needed functions inside. - if (not hasattr(LIBC, 'inotify_init') or - not hasattr(LIBC, 'inotify_add_watch') or - not hasattr(LIBC, 'inotify_rm_watch')): - raise UnsupportedLibcVersionError() + def str_errno(self): + code = self.get_errno() + if code is None: + return 'Errno: no errno support' + return 'Errno=%s (%s)' % (os.strerror(code), errno.errorcode[code]) + + def inotify_init(self): + return self._inotify_init() + + def inotify_add_watch(self, fd, pathname, mask): + # Unicode strings must be encoded to string prior to calling this + # method. + assert isinstance(pathname, str) + return self._inotify_add_watch(fd, pathname, mask) + + def inotify_rm_watch(self, fd, wd): + return self._inotify_rm_watch(fd, wd) + + +class INotifySyscallsWrapper(INotifyWrapper): + def __init__(self): + # Stores the last errno value. + self._last_errno = None -load_libc() + def init(self): + assert inotify_syscalls + return True + + def _get_errno(self): + return self._last_errno + + def _inotify_init(self): + try: + fd = inotify_syscalls.inotify_init() + except IOError, err: + self._last_errno = err.errno + return -1 + return fd + + def _inotify_add_watch(self, fd, pathname, mask): + try: + wd = inotify_syscalls.inotify_add_watch(fd, pathname, mask) + except IOError, err: + self._last_errno = err.errno + return -1 + return wd + + def _inotify_rm_watch(self, fd, wd): + try: + ret = inotify_syscalls.inotify_rm_watch(fd, wd) + except IOError, err: + self._last_errno = err.errno + return -1 + return ret + + +class CtypesLibcINotifyWrapper(INotifyWrapper): + def __init__(self): + self._libc = None + self._get_errno_func = None + + def init(self): + assert ctypes + libc_name = None + try: + libc_name = ctypes.util.find_library('c') + except (OSError, IOError): + pass # Will attemp to load it with None anyway. + + if sys.version_info >= (2, 6): + self._libc = ctypes.CDLL(libc_name, use_errno=True) + self._get_errno_func = ctypes.get_errno + else: + self._libc = ctypes.CDLL(libc_name) + try: + location = self._libc.__errno_location + location.restype = ctypes.POINTER(ctypes.c_int) + self._get_errno_func = lambda: location().contents.value + except AttributeError: + pass + + # Eventually check that libc has needed inotify bindings. + if (not hasattr(self._libc, 'inotify_init') or + not hasattr(self._libc, 'inotify_add_watch') or + not hasattr(self._libc, 'inotify_rm_watch')): + return False + return True + + def _get_errno(self): + if self._get_errno_func is not None: + return self._get_errno_func() + return None + + def _inotify_init(self): + assert self._libc is not None + return self._libc.inotify_init() + + def _inotify_add_watch(self, fd, pathname, mask): + assert self._libc is not None + pathname = ctypes.create_string_buffer(pathname) + return self._libc.inotify_add_watch(fd, pathname, mask) + + def _inotify_rm_watch(self, fd, wd): + assert self._libc is not None + return self._libc.inotify_rm_watch(fd, wd) class PyinotifyLogger(logging.Logger): @@ -215,9 +318,15 @@ class SysCtlINotify: 'max_queued_events': 3} def __init__(self, attrname): + # FIXME: right now only supporting ctypes + if not ctypes: + raise InotifyBindingNotFoundError() sino = ctypes.c_int * 3 self._attrname = attrname self._attr = sino(5, 20, SysCtlINotify.inotify_attrs[attrname]) + self._inotify_wrapper = CtypesLibcINotifyWrapper() + if not self._inotify_wrapper.init(): + raise InotifyBindingNotFoundError() def get_val(self): """ @@ -228,10 +337,10 @@ class SysCtlINotify: """ oldv = ctypes.c_int(0) size = ctypes.c_int(ctypes.sizeof(oldv)) - LIBC.sysctl(self._attr, 3, - ctypes.c_voidp(ctypes.addressof(oldv)), - ctypes.addressof(size), - None, 0) + self._inotify_wrapper.sysctl(self._attr, 3, + ctypes.c_voidp(ctypes.addressof(oldv)), + ctypes.addressof(size), + None, 0) return oldv.value def set_val(self, nval): @@ -245,11 +354,11 @@ class SysCtlINotify: sizeo = ctypes.c_int(ctypes.sizeof(oldv)) newv = ctypes.c_int(nval) sizen = ctypes.c_int(ctypes.sizeof(newv)) - LIBC.sysctl(self._attr, 3, - ctypes.c_voidp(ctypes.addressof(oldv)), - ctypes.addressof(sizeo), - ctypes.c_voidp(ctypes.addressof(newv)), - ctypes.addressof(sizen)) + self._inotify_wrapper.sysctl(self._attr, 3, + ctypes.c_voidp(ctypes.addressof(oldv)), + ctypes.addressof(sizeo), + ctypes.c_voidp(ctypes.addressof(newv)), + ctypes.addressof(sizen)) value = property(get_val, set_val) @@ -262,8 +371,9 @@ class SysCtlINotify: # read: myvar = max_queued_events.value # update: max_queued_events.value = 42 # -for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'): - globals()[attrname] = SysCtlINotify(attrname) +if ctypes: # FIXME: right now only access through ctypes + for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'): + globals()[attrname] = SysCtlINotify(attrname) class EventsCodes: @@ -1576,7 +1686,9 @@ class WatchManager: def __init__(self, exclude_filter=lambda path: False): """ Initialization: init inotify, init watch manager dictionary. - Raise OSError if initialization fails. + Raise OSError if initialization fails, raise InotifyBindingNotFoundError + if no inotify binding was found (through ctypes or from direct access to + syscalls). @param exclude_filter: boolean function, returns True if current path must be excluded from being watched. @@ -1586,10 +1698,15 @@ class WatchManager: """ self._exclude_filter = exclude_filter self._wmd = {} # watch dict key: watch descriptor, value: watch - self._fd = LIBC.inotify_init() # inotify's init, file descriptor + + self._inotify_wrapper = INotifyWrapper.create() + if self._inotify_wrapper is None: + raise InotifyBindingNotFoundError() + + self._fd = self._inotify_wrapper.inotify_init() # file descriptor if self._fd < 0: - err = 'Cannot initialize new instance of inotify%s' % strerrno() - raise OSError(err) + err = 'Cannot initialize new instance of inotify, %s' + raise OSError(err % self._inotify_wrapper.str_errno()) def close(self): """ @@ -1647,8 +1764,8 @@ class WatchManager: """ Format path to its internal (stored in watch manager) representation. """ - # Unicode strings are converted to byte strings, it seems to be - # required because LIBC.inotify_add_watch does not work well when + # Unicode strings are converted back to strings, because it seems + # that inotify_add_watch from ctypes does not work well when # it receives an ctypes.create_unicode_buffer instance as argument. # Therefore even wd are indexed with bytes string and not with # unicode paths. @@ -1661,17 +1778,15 @@ class WatchManager: Add a watch on path, build a Watch object and insert it in the watch manager dictionary. Return the wd value. """ - byte_path = self.__format_path(path) - wd_ = LIBC.inotify_add_watch(self._fd, - ctypes.create_string_buffer(byte_path), - mask) - if wd_ < 0: - return wd_ - watch_ = Watch(wd=wd_, path=byte_path, mask=mask, proc_fun=proc_fun, - auto_add=auto_add, exclude_filter=exclude_filter) - self._wmd[wd_] = watch_ - log.debug('New %s', watch_) - return wd_ + path = self.__format_path(path) + wd = self._inotify_wrapper.inotify_add_watch(self._fd, path, mask) + if wd < 0: + return wd + watch = Watch(wd=wd, path=path, mask=mask, proc_fun=proc_fun, + auto_add=auto_add, exclude_filter=exclude_filter) + self._wmd[wd] = watch + log.debug('New %s', watch) + return wd def __glob(self, path, do_glob): if do_glob: @@ -1750,8 +1865,9 @@ class WatchManager: auto_add, exclude_filter) if wd < 0: - err = 'add_watch: cannot watch %s WD=%d%s' - err = err % (rpath, wd, strerrno()) + err = ('add_watch: cannot watch %s WD=%d, %s' % \ + (rpath, wd, + self._inotify_wrapper.str_errno())) if quiet: log.error(err) else: @@ -1842,12 +1958,12 @@ class WatchManager: raise WatchManagerError(err, ret_) if mask: - addw = LIBC.inotify_add_watch - wd_ = addw(self._fd, ctypes.create_string_buffer(apath), mask) + wd_ = self._inotify_wrapper.inotify_add_watch(self._fd, apath, + mask) if wd_ < 0: ret_[awd] = False - err = 'update_watch: cannot update %s WD=%d%s' - err = err % (apath, wd_, strerrno()) + err = ('update_watch: cannot update %s WD=%d, %s' % \ + (apath, wd_, self._inotify_wrapper.str_errno())) if quiet: log.error(err) continue @@ -1953,10 +2069,11 @@ class WatchManager: ret_ = {} # return {wd: bool, ...} for awd in lwd: # remove watch - wd_ = LIBC.inotify_rm_watch(self._fd, awd) + wd_ = self._inotify_wrapper.inotify_rm_watch(self._fd, awd) if wd_ < 0: ret_[awd] = False - err = 'rm_watch: cannot remove WD=%d%s' % (awd, strerrno()) + err = ('rm_watch: cannot remove WD=%d, %s' % \ + (awd, self._inotify_wrapper.str_errno())) if quiet: log.error(err) continue diff --git a/python3/pyinotify.py b/python3/pyinotify.py index f4b598f..720c2b9 100755 --- a/python3/pyinotify.py +++ b/python3/pyinotify.py @@ -1,7 +1,7 @@ #!/usr/bin/env python # pyinotify.py - python interface to inotify -# Copyright (c) 2010 Sebastien Martini <seb@dbzteam.org> +# Copyright (c) 2005-2011 Sebastien Martini <seb@dbzteam.org> # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal @@ -47,19 +47,9 @@ class UnsupportedPythonVersionError(PyinotifyError): 'at least Python 3.0') % version) -class UnsupportedLibcVersionError(PyinotifyError): - """ - Raised when libc couldn't be loaded or when inotify functions werent - provided. - """ - def __init__(self): - err = 'libc does not provide required inotify support' - PyinotifyError.__init__(self, err) - - # Check Python version import sys -if sys.version < '3.0': +if sys.version_info < (3, 0): raise UnsupportedPythonVersionError(sys.version) @@ -78,8 +68,6 @@ from collections import deque from datetime import datetime, timedelta import time import re -import ctypes -import ctypes.util import asyncore import glob import locale @@ -89,6 +77,18 @@ try: except ImportError: pass # Will fail on Python 2.4 which has reduce() builtin anyway. +try: + import ctypes + import ctypes.util +except ImportError: + ctypes = None + +try: + import inotify_syscalls +except ImportError: + inotify_syscalls = None + + __author__ = "seb@dbzteam.org (Sebastien Martini)" __version__ = "0.9.1" @@ -100,33 +100,136 @@ __version__ = "0.9.1" COMPATIBILITY_MODE = False -# Load libc -LIBC = None +class InotifyBindingNotFoundError(PyinotifyError): + """ + Raised when no inotify support couldn't be found. + """ + def __init__(self): + err = "Couldn't find any inotify binding" + PyinotifyError.__init__(self, err) -def strerrno(): - code = ctypes.get_errno() - return '%s (%s)' % (os.strerror(code), errno.errorcode[code]) -def load_libc(): - global LIBC +class INotifyWrapper: + """Abstract class.""" + @staticmethod + def create(): + # First, try to use ctypes. + if ctypes: + inotify = CtypesLibcINotifyWrapper() + if inotify.init(): + return inotify + # Second, see if C extension is compiled. + if inotify_syscalls: + inotify = INotifySyscallsWrapper() + if inotify.init(): + return inotify - libc = None - try: - libc = ctypes.util.find_library('c') - except OSError as err: - pass # Will attemp to load it with None anyway. - except IOError as err: - pass + def get_errno(self): + """ + Return None is no errno code is available. + """ + return self._get_errno() + + def str_errno(self): + code = self.get_errno() + if code is None: + return 'Errno: no errno support' + return 'Errno=%s (%s)' % (os.strerror(code), errno.errorcode[code]) + + def inotify_init(self): + return self._inotify_init() + + def inotify_add_watch(self, fd, pathname, mask): + # Unicode strings must be encoded to string prior to calling this + # method. + assert isinstance(pathname, str) + return self._inotify_add_watch(fd, pathname, mask) - LIBC = ctypes.CDLL(libc, use_errno=True) + def inotify_rm_watch(self, fd, wd): + return self._inotify_rm_watch(fd, wd) - # Check that libc has needed functions inside. - if (not hasattr(LIBC, 'inotify_init') or - not hasattr(LIBC, 'inotify_add_watch') or - not hasattr(LIBC, 'inotify_rm_watch')): - raise UnsupportedLibcVersionError() -load_libc() +class INotifySyscallsWrapper(INotifyWrapper): + def __init__(self): + # Stores the last errno value. + self._last_errno = None + + def init(self): + assert inotify_syscalls + return True + + def _get_errno(self): + return self._last_errno + + def _inotify_init(self): + try: + fd = inotify_syscalls.inotify_init() + except IOError as err: + self._last_errno = err.errno + return -1 + return fd + + def _inotify_add_watch(self, fd, pathname, mask): + try: + wd = inotify_syscalls.inotify_add_watch(fd, pathname, mask) + except IOError as err: + self._last_errno = err.errno + return -1 + return wd + + def _inotify_rm_watch(self, fd, wd): + try: + ret = inotify_syscalls.inotify_rm_watch(fd, wd) + except IOError as err: + self._last_errno = err.errno + return -1 + return ret + + +class CtypesLibcINotifyWrapper(INotifyWrapper): + def __init__(self): + self._libc = None + self._get_errno_func = None + + def init(self): + assert ctypes + libc_name = None + try: + libc_name = ctypes.util.find_library('c') + except (OSError, IOError): + pass # Will attemp to load it with None anyway. + + self._libc = ctypes.CDLL(libc_name, use_errno=True) + self._get_errno_func = ctypes.get_errno + + # Eventually check that libc has needed inotify bindings. + if (not hasattr(self._libc, 'inotify_init') or + not hasattr(self._libc, 'inotify_add_watch') or + not hasattr(self._libc, 'inotify_rm_watch')): + return False + return True + + def _get_errno(self): + assert self._get_errno_func + return self._get_errno_func() + + def _inotify_init(self): + assert self._libc is not None + return self._libc.inotify_init() + + def _inotify_add_watch(self, fd, pathname, mask): + assert self._libc is not None + # Encodes path to a bytes string. This conversion seems required because + # ctypes.create_string_buffer seems to manipulate bytes internally. + # Moreover it seems that inotify_add_watch does not work very well when + # it receives an ctypes.create_unicode_buffer instance as argument. + pathname = pathname.encode(sys.getfilesystemencoding()) + pathname = ctypes.create_string_buffer(pathname) + return self._libc.inotify_add_watch(fd, pathname, mask) + + def _inotify_rm_watch(self, fd, wd): + assert self._libc is not None + return self._libc.inotify_rm_watch(fd, wd) # Logging @@ -159,9 +262,15 @@ class SysCtlINotify: 'max_queued_events': 3} def __init__(self, attrname): + # FIXME: right now only supporting ctypes + if not ctypes: + raise InotifyBindingNotFoundError() sino = ctypes.c_int * 3 self._attrname = attrname self._attr = sino(5, 20, SysCtlINotify.inotify_attrs[attrname]) + self._inotify_wrapper = CtypesLibcINotifyWrapper() + if not self._inotify_wrapper.init(): + raise InotifyBindingNotFoundError() def get_val(self): """ @@ -172,10 +281,10 @@ class SysCtlINotify: """ oldv = ctypes.c_int(0) size = ctypes.c_int(ctypes.sizeof(oldv)) - LIBC.sysctl(self._attr, 3, - ctypes.c_voidp(ctypes.addressof(oldv)), - ctypes.addressof(size), - None, 0) + self._inotify_wrapper.sysctl(self._attr, 3, + ctypes.c_voidp(ctypes.addressof(oldv)), + ctypes.addressof(size), + None, 0) return oldv.value def set_val(self, nval): @@ -189,11 +298,11 @@ class SysCtlINotify: sizeo = ctypes.c_int(ctypes.sizeof(oldv)) newv = ctypes.c_int(nval) sizen = ctypes.c_int(ctypes.sizeof(newv)) - LIBC.sysctl(self._attr, 3, - ctypes.c_voidp(ctypes.addressof(oldv)), - ctypes.addressof(sizeo), - ctypes.c_voidp(ctypes.addressof(newv)), - ctypes.addressof(sizen)) + self._inotify_wrapper.sysctl(self._attr, 3, + ctypes.c_voidp(ctypes.addressof(oldv)), + ctypes.addressof(sizeo), + ctypes.c_voidp(ctypes.addressof(newv)), + ctypes.addressof(sizen)) value = property(get_val, set_val) @@ -206,8 +315,9 @@ class SysCtlINotify: # read: myvar = max_queued_events.value # update: max_queued_events.value = 42 # -for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'): - globals()[attrname] = SysCtlINotify(attrname) +if ctypes: # FIXME: right now only access through ctypes + for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'): + globals()[attrname] = SysCtlINotify(attrname) class EventsCodes: @@ -1520,7 +1630,9 @@ class WatchManager: def __init__(self, exclude_filter=lambda path: False): """ Initialization: init inotify, init watch manager dictionary. - Raise OSError if initialization fails. + Raise OSError if initialization fails, raise InotifyBindingNotFoundError + if no inotify binding was found (through ctypes or from direct access to + syscalls). @param exclude_filter: boolean function, returns True if current path must be excluded from being watched. @@ -1530,10 +1642,15 @@ class WatchManager: """ self._exclude_filter = exclude_filter self._wmd = {} # watch dict key: watch descriptor, value: watch - self._fd = LIBC.inotify_init() # inotify's init, file descriptor + + self._inotify_wrapper = INotifyWrapper.create() + if self._inotify_wrapper is None: + raise InotifyBindingNotFoundError() + + self._fd = self._inotify_wrapper.inotify_init() # file descriptor if self._fd < 0: - err = 'Cannot initialize new instance of inotify Errno=%s' - raise OSError(err % strerrno()) + err = 'Cannot initialize new instance of inotify, %s' + raise OSError(err % self._inotify_wrapper.str_errno()) def close(self): """ @@ -1600,24 +1717,15 @@ class WatchManager: watch manager dictionary. Return the wd value. """ path = self.__format_path(path) - # path to a bytes string. This conversion seems to be required because - # ctypes.create_string_buffer seems to manipulate bytes - # strings representations internally. - # Moreover it seems that LIBC.inotify_add_watch does not work very - # well when it receives an ctypes.create_unicode_buffer instance as - # argument. However wd are _always_ indexed with their original - # unicode paths in wmd. - byte_path = path.encode(sys.getfilesystemencoding()) - wd_ = LIBC.inotify_add_watch(self._fd, - ctypes.create_string_buffer(byte_path), - mask) - if wd_ < 0: - return wd_ - watch_ = Watch(wd=wd_, path=path, mask=mask, proc_fun=proc_fun, - auto_add=auto_add, exclude_filter=exclude_filter) - self._wmd[wd_] = watch_ - log.debug('New %s', watch_) - return wd_ + wd = self._inotify_wrapper.inotify_add_watch(self._fd, path, mask) + if wd < 0: + return wd + watch = Watch(wd=wd, path=path, mask=mask, proc_fun=proc_fun, + auto_add=auto_add, exclude_filter=exclude_filter) + # wd are _always_ indexed with their original unicode paths in wmd. + self._wmd[wd] = watch + log.debug('New %s', watch) + return wd def __glob(self, path, do_glob): if do_glob: @@ -1697,8 +1805,9 @@ class WatchManager: auto_add, exclude_filter) if wd < 0: - err = 'add_watch: cannot watch %s WD=%d Errno=%s' - err = err % (rpath, wd, strerrno()) + err = ('add_watch: cannot watch %s WD=%d, %s' % \ + (rpath, wd, + self._inotify_wrapper.str_errno())) if quiet: log.error(err) else: @@ -1789,16 +1898,12 @@ class WatchManager: raise WatchManagerError(err, ret_) if mask: - addw = LIBC.inotify_add_watch - # apath is always stored as unicode string so encode it to - # bytes. - byte_path = apath.encode(sys.getfilesystemencoding()) - wd_ = addw(self._fd, ctypes.create_string_buffer(byte_path), - mask) + wd_ = self._inotify_wrapper.inotify_add_watch(self._fd, apath, + mask) if wd_ < 0: ret_[awd] = False - err = 'update_watch: cannot update %s WD=%d Errno=%s' - err = err % (apath, wd_, strerrno()) + err = ('update_watch: cannot update %s WD=%d, %s' % \ + (apath, wd_, self._inotify_wrapper.str_errno())) if quiet: log.error(err) continue @@ -1904,11 +2009,11 @@ class WatchManager: ret_ = {} # return {wd: bool, ...} for awd in lwd: # remove watch - wd_ = LIBC.inotify_rm_watch(self._fd, awd) + wd_ = self._inotify_wrapper.inotify_rm_watch(self._fd, awd) if wd_ < 0: ret_[awd] = False - err = 'rm_watch: cannot remove WD=%d Errno=%s' % (awd, - strerrno()) + err = ('rm_watch: cannot remove WD=%d, %s' % \ + (awd, self._inotify_wrapper.str_errno())) if quiet: log.error(err) continue @@ -1,12 +1,18 @@ #!/usr/bin/env python +# Set True to force compile native C-coded extension providing direct access +# to inotify's syscalls. If set to False this extension will only be compiled +# if no inotify interface from ctypes is found. +compile_ext_mod = False + # check Python's version import sys -if sys.version < '2.4': +if sys.version_info < (2, 4): sys.stderr.write('This module requires at least Python 2.4\n') sys.exit(1) # import statements +import os from distutils.core import setup, Extension from distutils.util import get_platform @@ -42,11 +48,46 @@ classif = [ 'Topic :: System :: Monitoring', ] -if sys.version_info[0] >= 3: + +if sys.version_info >= (3, 0): package_dir = {'': 'python3'} else: package_dir = {'': 'python2'} + +def should_compile_ext_mod(): + try: + import ctypes + import ctypes.util + except: + return True + + libc_name = None + try: + libc_name = ctypes.util.find_library('c') + except: + pass # Will attemp to load it with None anyway. + + libc = ctypes.CDLL(libc_name) + # Eventually check that libc has needed inotify bindings. + if (not hasattr(libc, 'inotify_init') or + not hasattr(libc, 'inotify_add_watch') or + not hasattr(libc, 'inotify_rm_watch')): + return True + return False + + +ext_mod = [] +if compile_ext_mod or should_compile_ext_mod(): + # add -fpic if x86_64 arch + if platform in ["linux-x86_64"]: + os.environ["CFLAGS"] = "-fpic" + # sources for ext module + ext_mod_src = ['common/inotify_syscalls.c'] + # dst for ext module + ext_mod.append(Extension('inotify_syscalls', ext_mod_src)) + + setup( name='pyinotify', version='0.9.1', @@ -58,7 +99,7 @@ setup( classifiers=classif, url='http://github.com/seb-m/pyinotify', #download_url='http://seb.dbzteam.org/pub/pyinotify/releases/pyinotify-0.9.1.tar.gz', + ext_modules=ext_mod, py_modules=['pyinotify'], package_dir=package_dir, - packages=[''], ) |