_inotify.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. # -*- test-case-name: twisted.internet.test.test_inotify -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. Very low-level ctypes-based interface to Linux inotify(7).
  6. ctypes and a version of libc which supports inotify system calls are
  7. required.
  8. """
  9. import ctypes
  10. import ctypes.util
  11. class INotifyError(Exception):
  12. """
  13. Unify all the possible exceptions that can be raised by the INotify API.
  14. """
  15. def init():
  16. """
  17. Create an inotify instance and return the associated file descriptor.
  18. """
  19. fd = libc.inotify_init()
  20. if fd < 0:
  21. raise INotifyError("INotify initialization error.")
  22. return fd
  23. def add(fd, path, mask):
  24. """
  25. Add a watch for the given path to the inotify file descriptor, and return
  26. the watch descriptor.
  27. @param fd: The file descriptor returned by C{libc.inotify_init}.
  28. @type fd: L{int}
  29. @param path: The path to watch via inotify.
  30. @type path: L{twisted.python.filepath.FilePath}
  31. @param mask: Bitmask specifying the events that inotify should monitor.
  32. @type mask: L{int}
  33. """
  34. wd = libc.inotify_add_watch(fd, path.asBytesMode().path, mask)
  35. if wd < 0:
  36. raise INotifyError("Failed to add watch on '%r' - (%r)" % (path, wd))
  37. return wd
  38. def remove(fd, wd):
  39. """
  40. Remove the given watch descriptor from the inotify file descriptor.
  41. """
  42. # When inotify_rm_watch returns -1 there's an error:
  43. # The errno for this call can be either one of the following:
  44. # EBADF: fd is not a valid file descriptor.
  45. # EINVAL: The watch descriptor wd is not valid; or fd is
  46. # not an inotify file descriptor.
  47. #
  48. # if we can't access the errno here we cannot even raise
  49. # an exception and we need to ignore the problem, one of
  50. # the most common cases is when you remove a directory from
  51. # the filesystem and that directory is observed. When inotify
  52. # tries to call inotify_rm_watch with a non existing directory
  53. # either of the 2 errors might come up because the files inside
  54. # it might have events generated way before they were handled.
  55. # Unfortunately only ctypes in Python 2.6 supports accessing errno:
  56. # http://bugs.python.org/issue1798 and in order to solve
  57. # the problem for previous versions we need to introduce
  58. # code that is quite complex:
  59. # http://stackoverflow.com/questions/661017/access-to-errno-from-python
  60. #
  61. # See #4310 for future resolution of this issue.
  62. libc.inotify_rm_watch(fd, wd)
  63. def initializeModule(libc):
  64. """
  65. Initialize the module, checking if the expected APIs exist and setting the
  66. argtypes and restype for C{inotify_init}, C{inotify_add_watch}, and
  67. C{inotify_rm_watch}.
  68. """
  69. for function in ("inotify_add_watch", "inotify_init", "inotify_rm_watch"):
  70. if getattr(libc, function, None) is None:
  71. raise ImportError("libc6 2.4 or higher needed")
  72. libc.inotify_init.argtypes = []
  73. libc.inotify_init.restype = ctypes.c_int
  74. libc.inotify_rm_watch.argtypes = [
  75. ctypes.c_int, ctypes.c_int]
  76. libc.inotify_rm_watch.restype = ctypes.c_int
  77. libc.inotify_add_watch.argtypes = [
  78. ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32]
  79. libc.inotify_add_watch.restype = ctypes.c_int
  80. name = ctypes.util.find_library('c')
  81. if not name:
  82. raise ImportError("Can't find C library.")
  83. libc = ctypes.cdll.LoadLibrary(name)
  84. initializeModule(libc)