From e7025d126f762b4aabf455eee492ce950200caab Mon Sep 17 00:00:00 2001 From: Kyle Butt Date: Wed, 2 Sep 2020 21:56:16 -0600 Subject: [PATCH] Add support for BSD's libinotify libinotify emulates linux's inotify interface via a worker thread and kqueue watches on the files. The interface is compatible, but we need to change two things: First, where we find the symbols for inotify, as they are in libinotify and not in libc. Second, we can't use epoll, so we instead use kqueue on the watch descriptor. There are scaling limits on the number of watches as each watched file consumes a file descriptor, but for many small projects this is sufficient. --- inotify/adapters.py | 82 +++++++++++++++++++++++++++++++-------------- inotify/library.py | 5 +++ 2 files changed, 62 insertions(+), 25 deletions(-) diff --git a/inotify/adapters.py b/inotify/adapters.py index e8301da..9b6ac2e 100644 --- a/inotify/adapters.py +++ b/inotify/adapters.py @@ -57,8 +57,16 @@ def __init__(self, paths=[], block_duration_s=_DEFAULT_EPOLL_BLOCK_DURATION_S): self.__inotify_fd = inotify.calls.inotify_init() _LOGGER.debug("Inotify handle is (%d).", self.__inotify_fd) - self.__epoll = select.epoll() - self.__epoll.register(self.__inotify_fd, select.POLLIN) + + if hasattr(select, 'epoll'): + self.__epoll = select.epoll() + self.__epoll.register(self.__inotify_fd, select.POLLIN) + elif hasattr(select, 'kqueue'): + self.__kqueue = select.kqueue() + kevent = select.kevent(self.__inotify_fd, + filter=select.KQ_FILTER_READ, + flags=select.KQ_EV_ADD | select.KQ_EV_ENABLE) + self.__kqueue.control([kevent], 0, 0) self.__last_success_return = None @@ -204,29 +212,7 @@ def event_gen( while True: block_duration_s = self.__get_block_duration() - # Poll, but manage signal-related errors. - - try: - events = self.__epoll.poll(block_duration_s) - except IOError as e: - if e.errno != EINTR: - raise - - if timeout_s is not None: - time_since_event_s = time.time() - last_hit_s - if time_since_event_s > timeout_s: - break - - continue - - # Process events. - - for fd, event_type in events: - # (fd) looks to always match the inotify FD. - - names = self._get_event_names(event_type) - _LOGGER.debug("Events received from epoll: {}".format(names)) - + def process_fd(fd): for (header, type_names, path, filename) \ in self._handle_inotify_event(fd): last_hit_s = time.time() @@ -242,6 +228,52 @@ def event_gen( yield e + # Poll, but manage signal-related errors. + + if hasattr(select, 'epoll'): + try: + events = self.__epoll.poll(block_duration_s) + except IOError as e: + if e.errno != EINTR: + raise + + if timeout_s is not None: + time_since_event_s = time.time() - last_hit_s + if time_since_event_s > timeout_s: + break + + continue + + # Process events. + + for fd, event_type in events: + # (fd) looks to always match the inotify FD. + + names = self._get_event_names(event_type) + _LOGGER.debug("Events received from epoll: {}".format(names)) + + yield from process_fd(fd) + + + elif hasattr(select, 'kqueue'): + try: + kevents = self.__kqueue.control(None, 1, block_duration_s) + except IOError as e: + if e.errno != EINTR: + raise + + if timeout_s is not None: + time_since_event_s = time.time() - last_hit_s + if time_since_event_s > timeout_s: + break + + continue + + # Process kevents + for kevent in kevents: + if kevent.filter == select.KQ_FILTER_READ: + yield from process_fd(kevent.ident) + if timeout_s is not None: time_since_event_s = time.time() - last_hit_s if time_since_event_s > timeout_s: diff --git a/inotify/library.py b/inotify/library.py index bb718d3..aacf3ef 100644 --- a/inotify/library.py +++ b/inotify/library.py @@ -6,3 +6,8 @@ _FILEPATH = 'libc.so.6' instance = ctypes.cdll.LoadLibrary(_FILEPATH) +if not hasattr(instance, 'inotify_init'): + _FILEPATH = ctypes.util.find_library('inotify') + if _FILEPATH is None: + _FILEPATH = 'libinotify.so.0' + instance = ctypes.cdll.LoadLibrary(_FILEPATH)