summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastien Martini <seb@dbzteam.org>2011-01-28 13:47:00 +0100
committerSebastien Martini <seb@dbzteam.org>2011-01-28 13:47:00 +0100
commitfec920a2146f78802f1a28a7f4c47f5c97426a7e (patch)
treee21371bc6c78a81ba7e39636d7cd69b32994deca
parent2900ceda692f590b17a193d1bd97e507abfdc10d (diff)
downloadpyinotify-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.c193
-rwxr-xr-xpython2/pyinotify.py275
-rwxr-xr-xpython3/pyinotify.py267
-rwxr-xr-xsetup.py47
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
diff --git a/setup.py b/setup.py
index 90ed07b..01b864e 100755
--- a/setup.py
+++ b/setup.py
@@ -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=[''],
)