123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100 |
- # -*- test-case-name: twisted.internet.test.test_inotify -*-
- # Copyright (c) Twisted Matrix Laboratories.
- # See LICENSE for details.
-
- """
- Very low-level ctypes-based interface to Linux inotify(7).
-
- ctypes and a version of libc which supports inotify system calls are
- required.
- """
-
- import ctypes
- import ctypes.util
- from typing import cast
-
- from twisted.python.filepath import FilePath
-
-
- class INotifyError(Exception):
- """
- Unify all the possible exceptions that can be raised by the INotify API.
- """
-
-
- def init() -> int:
- """
- Create an inotify instance and return the associated file descriptor.
- """
- fd = cast(int, libc.inotify_init())
- if fd < 0:
- raise INotifyError("INotify initialization error.")
- return fd
-
-
- def add(fd: int, path: FilePath, mask: int) -> int:
- """
- Add a watch for the given path to the inotify file descriptor, and return
- the watch descriptor.
-
- @param fd: The file descriptor returned by C{libc.inotify_init}.
- @param path: The path to watch via inotify.
- @param mask: Bitmask specifying the events that inotify should monitor.
- """
- wd = cast(int, libc.inotify_add_watch(fd, path.asBytesMode().path, mask))
- if wd < 0:
- raise INotifyError(f"Failed to add watch on '{path!r}' - ({wd!r})")
- return wd
-
-
- def remove(fd: int, wd: int) -> None:
- """
- Remove the given watch descriptor from the inotify file descriptor.
- """
- # When inotify_rm_watch returns -1 there's an error:
- # The errno for this call can be either one of the following:
- # EBADF: fd is not a valid file descriptor.
- # EINVAL: The watch descriptor wd is not valid; or fd is
- # not an inotify file descriptor.
- #
- # if we can't access the errno here we cannot even raise
- # an exception and we need to ignore the problem, one of
- # the most common cases is when you remove a directory from
- # the filesystem and that directory is observed. When inotify
- # tries to call inotify_rm_watch with a non existing directory
- # either of the 2 errors might come up because the files inside
- # it might have events generated way before they were handled.
- # Unfortunately only ctypes in Python 2.6 supports accessing errno:
- # http://bugs.python.org/issue1798 and in order to solve
- # the problem for previous versions we need to introduce
- # code that is quite complex:
- # http://stackoverflow.com/questions/661017/access-to-errno-from-python
- #
- # See #4310 for future resolution of this issue.
- libc.inotify_rm_watch(fd, wd)
-
-
- def initializeModule(libc: ctypes.CDLL) -> None:
- """
- Initialize the module, checking if the expected APIs exist and setting the
- argtypes and restype for C{inotify_init}, C{inotify_add_watch}, and
- C{inotify_rm_watch}.
- """
- for function in ("inotify_add_watch", "inotify_init", "inotify_rm_watch"):
- if getattr(libc, function, None) is None:
- raise ImportError("libc6 2.4 or higher needed")
- libc.inotify_init.argtypes = []
- libc.inotify_init.restype = ctypes.c_int
-
- libc.inotify_rm_watch.argtypes = [ctypes.c_int, ctypes.c_int]
- libc.inotify_rm_watch.restype = ctypes.c_int
-
- libc.inotify_add_watch.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32]
- libc.inotify_add_watch.restype = ctypes.c_int
-
-
- name = ctypes.util.find_library("c")
- if not name:
- raise ImportError("Can't find C library.")
- libc = ctypes.cdll.LoadLibrary(name)
- initializeModule(libc)
|