blob: 5c9b6d0fe2d2579efb78eb8b2b71f722b16dcccd [file] [log] [blame]
Brad Bishop96ff1982019-08-19 13:50:42 -04001#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002# pyinotify.py - python interface to inotify
3# Copyright (c) 2005-2015 Sebastien Martini <seb@dbzteam.org>
4#
Brad Bishopc342db32019-05-15 21:57:59 -04005# SPDX-License-Identifier: MIT
Patrick Williamsc124f4f2015-09-15 14:41:29 -05006#
Patrick Williamsc124f4f2015-09-15 14:41:29 -05007"""
8pyinotify
9
10@author: Sebastien Martini
11@license: MIT License
12@contact: seb@dbzteam.org
13"""
14
15class PyinotifyError(Exception):
16 """Indicates exceptions raised by a Pyinotify class."""
17 pass
18
19
20class UnsupportedPythonVersionError(PyinotifyError):
21 """
22 Raised on unsupported Python versions.
23 """
24 def __init__(self, version):
25 """
26 @param version: Current Python version
27 @type version: string
28 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -060029 PyinotifyError.__init__(self,
30 ('Python %s is unsupported, requires '
31 'at least Python 3.0') % version)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050032
33
34# Check Python version
35import sys
Patrick Williamsc0f7c042017-02-23 20:41:17 -060036if sys.version_info < (3, 0):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050037 raise UnsupportedPythonVersionError(sys.version)
38
39
40# Import directives
41import threading
42import os
43import select
44import struct
45import fcntl
46import errno
47import termios
48import array
49import logging
50import atexit
51from collections import deque
52from datetime import datetime, timedelta
53import time
54import re
Patrick Williamsc0f7c042017-02-23 20:41:17 -060055import glob
56import locale
Patrick Williamsc124f4f2015-09-15 14:41:29 -050057import subprocess
58
59try:
60 from functools import reduce
61except ImportError:
62 pass # Will fail on Python 2.4 which has reduce() builtin anyway.
63
64try:
Patrick Williamsc124f4f2015-09-15 14:41:29 -050065 import ctypes
66 import ctypes.util
67except ImportError:
68 ctypes = None
69
70try:
71 import inotify_syscalls
72except ImportError:
73 inotify_syscalls = None
74
75
76__author__ = "seb@dbzteam.org (Sebastien Martini)"
77
Patrick Williamsc0f7c042017-02-23 20:41:17 -060078__version__ = "0.9.6"
Patrick Williamsc124f4f2015-09-15 14:41:29 -050079
80
81# Compatibity mode: set to True to improve compatibility with
82# Pyinotify 0.7.1. Do not set this variable yourself, call the
83# function compatibility_mode() instead.
84COMPATIBILITY_MODE = False
85
86
87class InotifyBindingNotFoundError(PyinotifyError):
88 """
89 Raised when no inotify support couldn't be found.
90 """
91 def __init__(self):
92 err = "Couldn't find any inotify binding"
93 PyinotifyError.__init__(self, err)
94
95
96class INotifyWrapper:
97 """
98 Abstract class wrapping access to inotify's functions. This is an
99 internal class.
100 """
101 @staticmethod
102 def create():
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600103 """
104 Factory method instanciating and returning the right wrapper.
105 """
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500106 # First, try to use ctypes.
107 if ctypes:
108 inotify = _CtypesLibcINotifyWrapper()
109 if inotify.init():
110 return inotify
111 # Second, see if C extension is compiled.
112 if inotify_syscalls:
113 inotify = _INotifySyscallsWrapper()
114 if inotify.init():
115 return inotify
116
117 def get_errno(self):
118 """
119 Return None is no errno code is available.
120 """
121 return self._get_errno()
122
123 def str_errno(self):
124 code = self.get_errno()
125 if code is None:
126 return 'Errno: no errno support'
127 return 'Errno=%s (%s)' % (os.strerror(code), errno.errorcode[code])
128
129 def inotify_init(self):
130 return self._inotify_init()
131
132 def inotify_add_watch(self, fd, pathname, mask):
133 # Unicode strings must be encoded to string prior to calling this
134 # method.
135 assert isinstance(pathname, str)
136 return self._inotify_add_watch(fd, pathname, mask)
137
138 def inotify_rm_watch(self, fd, wd):
139 return self._inotify_rm_watch(fd, wd)
140
141
142class _INotifySyscallsWrapper(INotifyWrapper):
143 def __init__(self):
144 # Stores the last errno value.
145 self._last_errno = None
146
147 def init(self):
148 assert inotify_syscalls
149 return True
150
151 def _get_errno(self):
152 return self._last_errno
153
154 def _inotify_init(self):
155 try:
156 fd = inotify_syscalls.inotify_init()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600157 except IOError as err:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500158 self._last_errno = err.errno
159 return -1
160 return fd
161
162 def _inotify_add_watch(self, fd, pathname, mask):
163 try:
164 wd = inotify_syscalls.inotify_add_watch(fd, pathname, mask)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600165 except IOError as err:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500166 self._last_errno = err.errno
167 return -1
168 return wd
169
170 def _inotify_rm_watch(self, fd, wd):
171 try:
172 ret = inotify_syscalls.inotify_rm_watch(fd, wd)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600173 except IOError as err:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500174 self._last_errno = err.errno
175 return -1
176 return ret
177
178
179class _CtypesLibcINotifyWrapper(INotifyWrapper):
180 def __init__(self):
181 self._libc = None
182 self._get_errno_func = None
183
184 def init(self):
185 assert ctypes
186
187 try_libc_name = 'c'
188 if sys.platform.startswith('freebsd'):
189 try_libc_name = 'inotify'
190
191 libc_name = None
192 try:
193 libc_name = ctypes.util.find_library(try_libc_name)
194 except (OSError, IOError):
195 pass # Will attemp to load it with None anyway.
196
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600197 self._libc = ctypes.CDLL(libc_name, use_errno=True)
198 self._get_errno_func = ctypes.get_errno
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500199
200 # Eventually check that libc has needed inotify bindings.
201 if (not hasattr(self._libc, 'inotify_init') or
202 not hasattr(self._libc, 'inotify_add_watch') or
203 not hasattr(self._libc, 'inotify_rm_watch')):
204 return False
205
206 self._libc.inotify_init.argtypes = []
207 self._libc.inotify_init.restype = ctypes.c_int
208 self._libc.inotify_add_watch.argtypes = [ctypes.c_int, ctypes.c_char_p,
209 ctypes.c_uint32]
210 self._libc.inotify_add_watch.restype = ctypes.c_int
211 self._libc.inotify_rm_watch.argtypes = [ctypes.c_int, ctypes.c_int]
212 self._libc.inotify_rm_watch.restype = ctypes.c_int
213 return True
214
215 def _get_errno(self):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600216 assert self._get_errno_func
217 return self._get_errno_func()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500218
219 def _inotify_init(self):
220 assert self._libc is not None
221 return self._libc.inotify_init()
222
223 def _inotify_add_watch(self, fd, pathname, mask):
224 assert self._libc is not None
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600225 # Encodes path to a bytes string. This conversion seems required because
226 # ctypes.create_string_buffer seems to manipulate bytes internally.
227 # Moreover it seems that inotify_add_watch does not work very well when
228 # it receives an ctypes.create_unicode_buffer instance as argument.
229 pathname = pathname.encode(sys.getfilesystemencoding())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500230 pathname = ctypes.create_string_buffer(pathname)
231 return self._libc.inotify_add_watch(fd, pathname, mask)
232
233 def _inotify_rm_watch(self, fd, wd):
234 assert self._libc is not None
235 return self._libc.inotify_rm_watch(fd, wd)
236
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500237
238# Logging
239def logger_init():
240 """Initialize logger instance."""
241 log = logging.getLogger("pyinotify")
242 console_handler = logging.StreamHandler()
243 console_handler.setFormatter(
244 logging.Formatter("[%(asctime)s %(name)s %(levelname)s] %(message)s"))
245 log.addHandler(console_handler)
246 log.setLevel(20)
247 return log
248
249log = logger_init()
250
251
252# inotify's variables
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600253class ProcINotify:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500254 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600255 Access (read, write) inotify's variables through /proc/sys/. Note that
256 usually it requires administrator rights to update them.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500257
258 Examples:
259 - Read max_queued_events attribute: myvar = max_queued_events.value
260 - Update max_queued_events attribute: max_queued_events.value = 42
261 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600262 def __init__(self, attr):
263 self._base = "/proc/sys/fs/inotify"
264 self._attr = attr
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500265
266 def get_val(self):
267 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600268 Gets attribute's value.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500269
270 @return: stored value.
271 @rtype: int
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600272 @raise IOError: if corresponding file in /proc/sys cannot be read.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500273 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600274 with open(os.path.join(self._base, self._attr), 'r') as file_obj:
275 return int(file_obj.readline())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500276
277 def set_val(self, nval):
278 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600279 Sets new attribute's value.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500280
281 @param nval: replaces current value by nval.
282 @type nval: int
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600283 @raise IOError: if corresponding file in /proc/sys cannot be written.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500284 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600285 with open(os.path.join(self._base, self._attr), 'w') as file_obj:
286 file_obj.write(str(nval) + '\n')
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500287
288 value = property(get_val, set_val)
289
290 def __repr__(self):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600291 return '<%s=%d>' % (self._attr, self.get_val())
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500292
293
294# Inotify's variables
295#
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600296# Note: may raise IOError if the corresponding value in /proc/sys
297# cannot be accessed.
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500298#
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600299# Examples:
300# - read: myvar = max_queued_events.value
301# - update: max_queued_events.value = 42
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500302#
303for attrname in ('max_queued_events', 'max_user_instances', 'max_user_watches'):
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600304 globals()[attrname] = ProcINotify(attrname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500305
306
307class EventsCodes:
308 """
309 Set of codes corresponding to each kind of events.
310 Some of these flags are used to communicate with inotify, whereas
311 the others are sent to userspace by inotify notifying some events.
312
313 @cvar IN_ACCESS: File was accessed.
314 @type IN_ACCESS: int
315 @cvar IN_MODIFY: File was modified.
316 @type IN_MODIFY: int
317 @cvar IN_ATTRIB: Metadata changed.
318 @type IN_ATTRIB: int
319 @cvar IN_CLOSE_WRITE: Writtable file was closed.
320 @type IN_CLOSE_WRITE: int
321 @cvar IN_CLOSE_NOWRITE: Unwrittable file closed.
322 @type IN_CLOSE_NOWRITE: int
323 @cvar IN_OPEN: File was opened.
324 @type IN_OPEN: int
325 @cvar IN_MOVED_FROM: File was moved from X.
326 @type IN_MOVED_FROM: int
327 @cvar IN_MOVED_TO: File was moved to Y.
328 @type IN_MOVED_TO: int
329 @cvar IN_CREATE: Subfile was created.
330 @type IN_CREATE: int
331 @cvar IN_DELETE: Subfile was deleted.
332 @type IN_DELETE: int
333 @cvar IN_DELETE_SELF: Self (watched item itself) was deleted.
334 @type IN_DELETE_SELF: int
335 @cvar IN_MOVE_SELF: Self (watched item itself) was moved.
336 @type IN_MOVE_SELF: int
337 @cvar IN_UNMOUNT: Backing fs was unmounted.
338 @type IN_UNMOUNT: int
339 @cvar IN_Q_OVERFLOW: Event queued overflowed.
340 @type IN_Q_OVERFLOW: int
341 @cvar IN_IGNORED: File was ignored.
342 @type IN_IGNORED: int
343 @cvar IN_ONLYDIR: only watch the path if it is a directory (new
344 in kernel 2.6.15).
345 @type IN_ONLYDIR: int
346 @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15).
347 IN_ONLYDIR we can make sure that we don't watch
348 the target of symlinks.
349 @type IN_DONT_FOLLOW: int
350 @cvar IN_EXCL_UNLINK: Events are not generated for children after they
351 have been unlinked from the watched directory.
352 (new in kernel 2.6.36).
353 @type IN_EXCL_UNLINK: int
354 @cvar IN_MASK_ADD: add to the mask of an already existing watch (new
355 in kernel 2.6.14).
356 @type IN_MASK_ADD: int
357 @cvar IN_ISDIR: Event occurred against dir.
358 @type IN_ISDIR: int
359 @cvar IN_ONESHOT: Only send event once.
360 @type IN_ONESHOT: int
361 @cvar ALL_EVENTS: Alias for considering all of the events.
362 @type ALL_EVENTS: int
363 """
364
365 # The idea here is 'configuration-as-code' - this way, we get our nice class
366 # constants, but we also get nice human-friendly text mappings to do lookups
367 # against as well, for free:
368 FLAG_COLLECTIONS = {'OP_FLAGS': {
369 'IN_ACCESS' : 0x00000001, # File was accessed
370 'IN_MODIFY' : 0x00000002, # File was modified
371 'IN_ATTRIB' : 0x00000004, # Metadata changed
372 'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed
373 'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed
374 'IN_OPEN' : 0x00000020, # File was opened
375 'IN_MOVED_FROM' : 0x00000040, # File was moved from X
376 'IN_MOVED_TO' : 0x00000080, # File was moved to Y
377 'IN_CREATE' : 0x00000100, # Subfile was created
378 'IN_DELETE' : 0x00000200, # Subfile was deleted
379 'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself)
380 # was deleted
381 'IN_MOVE_SELF' : 0x00000800, # Self (watched item itself) was moved
382 },
383 'EVENT_FLAGS': {
384 'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted
385 'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed
386 'IN_IGNORED' : 0x00008000, # File was ignored
387 },
388 'SPECIAL_FLAGS': {
389 'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a
390 # directory
391 'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink
392 'IN_EXCL_UNLINK' : 0x04000000, # exclude events on unlinked objects
393 'IN_MASK_ADD' : 0x20000000, # add to the mask of an already
394 # existing watch
395 'IN_ISDIR' : 0x40000000, # event occurred against dir
396 'IN_ONESHOT' : 0x80000000, # only send event once
397 },
398 }
399
400 def maskname(mask):
401 """
402 Returns the event name associated to mask. IN_ISDIR is appended to
403 the result when appropriate. Note: only one event is returned, because
404 only one event can be raised at a given time.
405
406 @param mask: mask.
407 @type mask: int
408 @return: event name.
409 @rtype: str
410 """
411 ms = mask
412 name = '%s'
413 if mask & IN_ISDIR:
414 ms = mask - IN_ISDIR
415 name = '%s|IN_ISDIR'
416 return name % EventsCodes.ALL_VALUES[ms]
417
418 maskname = staticmethod(maskname)
419
420
421# So let's now turn the configuration into code
422EventsCodes.ALL_FLAGS = {}
423EventsCodes.ALL_VALUES = {}
424for flagc, valc in EventsCodes.FLAG_COLLECTIONS.items():
425 # Make the collections' members directly accessible through the
426 # class dictionary
427 setattr(EventsCodes, flagc, valc)
428
429 # Collect all the flags under a common umbrella
430 EventsCodes.ALL_FLAGS.update(valc)
431
432 # Make the individual masks accessible as 'constants' at globals() scope
433 # and masknames accessible by values.
434 for name, val in valc.items():
435 globals()[name] = val
436 EventsCodes.ALL_VALUES[val] = name
437
438
439# all 'normal' events
440ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.values())
441EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS
442EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'
443
444
445class _Event:
446 """
447 Event structure, represent events raised by the system. This
448 is the base class and should be subclassed.
449
450 """
451 def __init__(self, dict_):
452 """
453 Attach attributes (contained in dict_) to self.
454
455 @param dict_: Set of attributes.
456 @type dict_: dictionary
457 """
458 for tpl in dict_.items():
459 setattr(self, *tpl)
460
461 def __repr__(self):
462 """
463 @return: Generic event string representation.
464 @rtype: str
465 """
466 s = ''
467 for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]):
468 if attr.startswith('_'):
469 continue
470 if attr == 'mask':
471 value = hex(getattr(self, attr))
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600472 elif isinstance(value, str) and not value:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500473 value = "''"
474 s += ' %s%s%s' % (output_format.field_name(attr),
475 output_format.punctuation('='),
476 output_format.field_value(value))
477
478 s = '%s%s%s %s' % (output_format.punctuation('<'),
479 output_format.class_name(self.__class__.__name__),
480 s,
481 output_format.punctuation('>'))
482 return s
483
484 def __str__(self):
485 return repr(self)
486
487
488class _RawEvent(_Event):
489 """
490 Raw event, it contains only the informations provided by the system.
491 It doesn't infer anything.
492 """
493 def __init__(self, wd, mask, cookie, name):
494 """
495 @param wd: Watch Descriptor.
496 @type wd: int
497 @param mask: Bitmask of events.
498 @type mask: int
499 @param cookie: Cookie.
500 @type cookie: int
501 @param name: Basename of the file or directory against which the
502 event was raised in case where the watched directory
503 is the parent directory. None if the event was raised
504 on the watched item itself.
505 @type name: string or None
506 """
507 # Use this variable to cache the result of str(self), this object
508 # is immutable.
509 self._str = None
510 # name: remove trailing '\0'
511 d = {'wd': wd,
512 'mask': mask,
513 'cookie': cookie,
514 'name': name.rstrip('\0')}
515 _Event.__init__(self, d)
516 log.debug(str(self))
517
518 def __str__(self):
519 if self._str is None:
520 self._str = _Event.__str__(self)
521 return self._str
522
523
524class Event(_Event):
525 """
526 This class contains all the useful informations about the observed
527 event. However, the presence of each field is not guaranteed and
528 depends on the type of event. In effect, some fields are irrelevant
529 for some kind of event (for example 'cookie' is meaningless for
530 IN_CREATE whereas it is mandatory for IN_MOVE_TO).
531
532 The possible fields are:
533 - wd (int): Watch Descriptor.
534 - mask (int): Mask.
535 - maskname (str): Readable event name.
536 - path (str): path of the file or directory being watched.
537 - name (str): Basename of the file or directory against which the
538 event was raised in case where the watched directory
539 is the parent directory. None if the event was raised
540 on the watched item itself. This field is always provided
541 even if the string is ''.
542 - pathname (str): Concatenation of 'path' and 'name'.
543 - src_pathname (str): Only present for IN_MOVED_TO events and only in
544 the case where IN_MOVED_FROM events are watched too. Holds the
545 source pathname from where pathname was moved from.
546 - cookie (int): Cookie.
547 - dir (bool): True if the event was raised against a directory.
548
549 """
550 def __init__(self, raw):
551 """
552 Concretely, this is the raw event plus inferred infos.
553 """
554 _Event.__init__(self, raw)
555 self.maskname = EventsCodes.maskname(self.mask)
556 if COMPATIBILITY_MODE:
557 self.event_name = self.maskname
558 try:
559 if self.name:
560 self.pathname = os.path.abspath(os.path.join(self.path,
561 self.name))
562 else:
563 self.pathname = os.path.abspath(self.path)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600564 except AttributeError as err:
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500565 # Usually it is not an error some events are perfectly valids
566 # despite the lack of these attributes.
567 log.debug(err)
568
569
570class ProcessEventError(PyinotifyError):
571 """
572 ProcessEventError Exception. Raised on ProcessEvent error.
573 """
574 def __init__(self, err):
575 """
576 @param err: Exception error description.
577 @type err: string
578 """
579 PyinotifyError.__init__(self, err)
580
581
582class _ProcessEvent:
583 """
584 Abstract processing event class.
585 """
586 def __call__(self, event):
587 """
588 To behave like a functor the object must be callable.
589 This method is a dispatch method. Its lookup order is:
590 1. process_MASKNAME method
591 2. process_FAMILY_NAME method
592 3. otherwise calls process_default
593
594 @param event: Event to be processed.
595 @type event: Event object
596 @return: By convention when used from the ProcessEvent class:
597 - Returning False or None (default value) means keep on
598 executing next chained functors (see chain.py example).
599 - Returning True instead means do not execute next
600 processing functions.
601 @rtype: bool
602 @raise ProcessEventError: Event object undispatchable,
603 unknown event.
604 """
605 stripped_mask = event.mask - (event.mask & IN_ISDIR)
Patrick Williamsde0582f2022-04-08 10:23:27 -0500606 # Bitbake hack - we see event masks of 0x6, IN_MODIFY & IN_ATTRIB
607 # The kernel inotify code can set more than one of the bits in the mask,
608 # fsnotify_change() in linux/fsnotify.h is quite clear that IN_ATTRIB,
609 # IN_MODIFY and IN_ACCESS can arrive together.
610 # This breaks the code below which assume only one mask bit is ever
611 # set in an event. We don't care about attrib or access in bitbake so drop those
612 if (stripped_mask & IN_MODIFY) and (stripped_mask & IN_ATTRIB):
613 stripped_mask = stripped_mask - (stripped_mask & IN_ATTRIB)
614 if (stripped_mask & IN_MODIFY) and (stripped_mask & IN_ACCESS):
615 stripped_mask = stripped_mask - (stripped_mask & IN_ACCESS)
616
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500617 maskname = EventsCodes.ALL_VALUES.get(stripped_mask)
618 if maskname is None:
619 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask)
620
621 # 1- look for process_MASKNAME
622 meth = getattr(self, 'process_' + maskname, None)
623 if meth is not None:
624 return meth(event)
625 # 2- look for process_FAMILY_NAME
626 meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None)
627 if meth is not None:
628 return meth(event)
629 # 3- default call method process_default
630 return self.process_default(event)
631
632 def __repr__(self):
633 return '<%s>' % self.__class__.__name__
634
635
636class _SysProcessEvent(_ProcessEvent):
637 """
638 There is three kind of processing according to each event:
639
640 1. special handling (deletion from internal container, bug, ...).
641 2. default treatment: which is applied to the majority of events.
642 3. IN_ISDIR is never sent alone, he is piggybacked with a standard
643 event, he is not processed as the others events, instead, its
644 value is captured and appropriately aggregated to dst event.
645 """
646 def __init__(self, wm, notifier):
647 """
648
649 @param wm: Watch Manager.
650 @type wm: WatchManager instance
651 @param notifier: Notifier.
652 @type notifier: Notifier instance
653 """
654 self._watch_manager = wm # watch manager
655 self._notifier = notifier # notifier
656 self._mv_cookie = {} # {cookie(int): (src_path(str), date), ...}
657 self._mv = {} # {src_path(str): (dst_path(str), date), ...}
658
659 def cleanup(self):
660 """
661 Cleanup (delete) old (>1mn) records contained in self._mv_cookie
662 and self._mv.
663 """
664 date_cur_ = datetime.now()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600665 for seq in (self._mv_cookie, self._mv):
666 for k in list(seq.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500667 if (date_cur_ - seq[k][1]) > timedelta(minutes=1):
668 log.debug('Cleanup: deleting entry %s', seq[k][0])
669 del seq[k]
670
671 def process_IN_CREATE(self, raw_event):
672 """
673 If the event affects a directory and the auto_add flag of the
674 targetted watch is set to True, a new watch is added on this
675 new directory, with the same attribute values than those of
676 this watch.
677 """
678 if raw_event.mask & IN_ISDIR:
679 watch_ = self._watch_manager.get_watch(raw_event.wd)
680 created_dir = os.path.join(watch_.path, raw_event.name)
681 if watch_.auto_add and not watch_.exclude_filter(created_dir):
682 addw = self._watch_manager.add_watch
683 # The newly monitored directory inherits attributes from its
684 # parent directory.
685 addw_ret = addw(created_dir, watch_.mask,
686 proc_fun=watch_.proc_fun,
687 rec=False, auto_add=watch_.auto_add,
688 exclude_filter=watch_.exclude_filter)
689
690 # Trick to handle mkdir -p /d1/d2/t3 where d1 is watched and
691 # d2 and t3 (directory or file) are created.
692 # Since the directory d2 is new, then everything inside it must
693 # also be new.
694 created_dir_wd = addw_ret.get(created_dir)
695 if ((created_dir_wd is not None) and (created_dir_wd > 0) and
696 os.path.isdir(created_dir)):
697 try:
698 for name in os.listdir(created_dir):
699 inner = os.path.join(created_dir, name)
700 if self._watch_manager.get_wd(inner) is not None:
701 continue
702 # Generate (simulate) creation events for sub-
703 # directories and files.
704 if os.path.isfile(inner):
705 # symlinks are handled as files.
706 flags = IN_CREATE
707 elif os.path.isdir(inner):
708 flags = IN_CREATE | IN_ISDIR
709 else:
710 # This path should not be taken.
711 continue
712 rawevent = _RawEvent(created_dir_wd, flags, 0, name)
713 self._notifier.append_event(rawevent)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600714 except OSError as err:
715 msg = "process_IN_CREATE, invalid directory: %s"
716 log.debug(msg % str(err))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500717 return self.process_default(raw_event)
718
719 def process_IN_MOVED_FROM(self, raw_event):
720 """
721 Map the cookie with the source path (+ date for cleaning).
722 """
723 watch_ = self._watch_manager.get_watch(raw_event.wd)
724 path_ = watch_.path
725 src_path = os.path.normpath(os.path.join(path_, raw_event.name))
726 self._mv_cookie[raw_event.cookie] = (src_path, datetime.now())
727 return self.process_default(raw_event, {'cookie': raw_event.cookie})
728
729 def process_IN_MOVED_TO(self, raw_event):
730 """
731 Map the source path with the destination path (+ date for
732 cleaning).
733 """
734 watch_ = self._watch_manager.get_watch(raw_event.wd)
735 path_ = watch_.path
736 dst_path = os.path.normpath(os.path.join(path_, raw_event.name))
737 mv_ = self._mv_cookie.get(raw_event.cookie)
738 to_append = {'cookie': raw_event.cookie}
739 if mv_ is not None:
740 self._mv[mv_[0]] = (dst_path, datetime.now())
741 # Let's assume that IN_MOVED_FROM event is always queued before
742 # that its associated (they share a common cookie) IN_MOVED_TO
743 # event is queued itself. It is then possible in that scenario
744 # to provide as additional information to the IN_MOVED_TO event
745 # the original pathname of the moved file/directory.
746 to_append['src_pathname'] = mv_[0]
747 elif (raw_event.mask & IN_ISDIR and watch_.auto_add and
748 not watch_.exclude_filter(dst_path)):
749 # We got a diretory that's "moved in" from an unknown source and
750 # auto_add is enabled. Manually add watches to the inner subtrees.
751 # The newly monitored directory inherits attributes from its
752 # parent directory.
753 self._watch_manager.add_watch(dst_path, watch_.mask,
754 proc_fun=watch_.proc_fun,
755 rec=True, auto_add=True,
756 exclude_filter=watch_.exclude_filter)
757 return self.process_default(raw_event, to_append)
758
759 def process_IN_MOVE_SELF(self, raw_event):
760 """
761 STATUS: the following bug has been fixed in recent kernels (FIXME:
762 which version ?). Now it raises IN_DELETE_SELF instead.
763
764 Old kernels were bugged, this event raised when the watched item
765 were moved, so we had to update its path, but under some circumstances
766 it was impossible: if its parent directory and its destination
767 directory wasn't watched. The kernel (see include/linux/fsnotify.h)
768 doesn't bring us enough informations like the destination path of
769 moved items.
770 """
771 watch_ = self._watch_manager.get_watch(raw_event.wd)
772 src_path = watch_.path
773 mv_ = self._mv.get(src_path)
774 if mv_:
775 dest_path = mv_[0]
776 watch_.path = dest_path
777 # add the separator to the source path to avoid overlapping
778 # path issue when testing with startswith()
779 src_path += os.path.sep
780 src_path_len = len(src_path)
781 # The next loop renames all watches with src_path as base path.
782 # It seems that IN_MOVE_SELF does not provide IN_ISDIR information
783 # therefore the next loop is iterated even if raw_event is a file.
784 for w in self._watch_manager.watches.values():
785 if w.path.startswith(src_path):
786 # Note that dest_path is a normalized path.
787 w.path = os.path.join(dest_path, w.path[src_path_len:])
788 else:
789 log.error("The pathname '%s' of this watch %s has probably changed "
790 "and couldn't be updated, so it cannot be trusted "
791 "anymore. To fix this error move directories/files only "
792 "between watched parents directories, in this case e.g. "
793 "put a watch on '%s'.",
794 watch_.path, watch_,
795 os.path.normpath(os.path.join(watch_.path,
796 os.path.pardir)))
797 if not watch_.path.endswith('-unknown-path'):
798 watch_.path += '-unknown-path'
799 return self.process_default(raw_event)
800
801 def process_IN_Q_OVERFLOW(self, raw_event):
802 """
803 Only signal an overflow, most of the common flags are irrelevant
804 for this event (path, wd, name).
805 """
806 return Event({'mask': raw_event.mask})
807
808 def process_IN_IGNORED(self, raw_event):
809 """
810 The watch descriptor raised by this event is now ignored (forever),
811 it can be safely deleted from the watch manager dictionary.
812 After this event we can be sure that neither the event queue nor
813 the system will raise an event associated to this wd again.
814 """
815 event_ = self.process_default(raw_event)
816 self._watch_manager.del_watch(raw_event.wd)
817 return event_
818
819 def process_default(self, raw_event, to_append=None):
820 """
821 Commons handling for the followings events:
822
823 IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE,
824 IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT.
825 """
826 watch_ = self._watch_manager.get_watch(raw_event.wd)
827 if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF):
828 # Unfornulately this information is not provided by the kernel
829 dir_ = watch_.dir
830 else:
831 dir_ = bool(raw_event.mask & IN_ISDIR)
832 dict_ = {'wd': raw_event.wd,
833 'mask': raw_event.mask,
834 'path': watch_.path,
835 'name': raw_event.name,
836 'dir': dir_}
837 if COMPATIBILITY_MODE:
838 dict_['is_dir'] = dir_
839 if to_append is not None:
840 dict_.update(to_append)
841 return Event(dict_)
842
843
844class ProcessEvent(_ProcessEvent):
845 """
846 Process events objects, can be specialized via subclassing, thus its
847 behavior can be overriden:
848
849 Note: you should not override __init__ in your subclass instead define
850 a my_init() method, this method will be called automatically from the
851 constructor of this class with its optionals parameters.
852
853 1. Provide specialized individual methods, e.g. process_IN_DELETE for
854 processing a precise type of event (e.g. IN_DELETE in this case).
855 2. Or/and provide methods for processing events by 'family', e.g.
856 process_IN_CLOSE method will process both IN_CLOSE_WRITE and
857 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and
858 process_IN_CLOSE_NOWRITE aren't defined though).
859 3. Or/and override process_default for catching and processing all
860 the remaining types of events.
861 """
862 pevent = None
863
864 def __init__(self, pevent=None, **kargs):
865 """
866 Enable chaining of ProcessEvent instances.
867
868 @param pevent: Optional callable object, will be called on event
869 processing (before self).
870 @type pevent: callable
871 @param kargs: This constructor is implemented as a template method
872 delegating its optionals keyworded arguments to the
873 method my_init().
874 @type kargs: dict
875 """
876 self.pevent = pevent
877 self.my_init(**kargs)
878
879 def my_init(self, **kargs):
880 """
881 This method is called from ProcessEvent.__init__(). This method is
882 empty here and must be redefined to be useful. In effect, if you
883 need to specifically initialize your subclass' instance then you
884 just have to override this method in your subclass. Then all the
885 keyworded arguments passed to ProcessEvent.__init__() will be
886 transmitted as parameters to this method. Beware you MUST pass
887 keyword arguments though.
888
889 @param kargs: optional delegated arguments from __init__().
890 @type kargs: dict
891 """
892 pass
893
894 def __call__(self, event):
895 stop_chaining = False
896 if self.pevent is not None:
897 # By default methods return None so we set as guideline
898 # that methods asking for stop chaining must explicitely
899 # return non None or non False values, otherwise the default
900 # behavior will be to accept chain call to the corresponding
901 # local method.
902 stop_chaining = self.pevent(event)
903 if not stop_chaining:
904 return _ProcessEvent.__call__(self, event)
905
906 def nested_pevent(self):
907 return self.pevent
908
909 def process_IN_Q_OVERFLOW(self, event):
910 """
911 By default this method only reports warning messages, you can overredide
912 it by subclassing ProcessEvent and implement your own
913 process_IN_Q_OVERFLOW method. The actions you can take on receiving this
914 event is either to update the variable max_queued_events in order to
915 handle more simultaneous events or to modify your code in order to
916 accomplish a better filtering diminishing the number of raised events.
917 Because this method is defined, IN_Q_OVERFLOW will never get
918 transmitted as arguments to process_default calls.
919
920 @param event: IN_Q_OVERFLOW event.
921 @type event: dict
922 """
923 log.warning('Event queue overflowed.')
924
925 def process_default(self, event):
926 """
927 Default processing event method. By default does nothing. Subclass
928 ProcessEvent and redefine this method in order to modify its behavior.
929
930 @param event: Event to be processed. Can be of any type of events but
931 IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
932 @type event: Event instance
933 """
934 pass
935
936
937class PrintAllEvents(ProcessEvent):
938 """
939 Dummy class used to print events strings representations. For instance this
940 class is used from command line to print all received events to stdout.
941 """
942 def my_init(self, out=None):
943 """
944 @param out: Where events will be written.
945 @type out: Object providing a valid file object interface.
946 """
947 if out is None:
948 out = sys.stdout
949 self._out = out
950
951 def process_default(self, event):
952 """
953 Writes event string representation to file object provided to
954 my_init().
955
956 @param event: Event to be processed. Can be of any type of events but
957 IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
958 @type event: Event instance
959 """
960 self._out.write(str(event))
961 self._out.write('\n')
962 self._out.flush()
963
964
965class ChainIfTrue(ProcessEvent):
966 """
967 Makes conditional chaining depending on the result of the nested
968 processing instance.
969 """
970 def my_init(self, func):
971 """
972 Method automatically called from base class constructor.
973 """
974 self._func = func
975
976 def process_default(self, event):
977 return not self._func(event)
978
979
980class Stats(ProcessEvent):
981 """
982 Compute and display trivial statistics about processed events.
983 """
984 def my_init(self):
985 """
986 Method automatically called from base class constructor.
987 """
988 self._start_time = time.time()
989 self._stats = {}
990 self._stats_lock = threading.Lock()
991
992 def process_default(self, event):
993 """
994 Processes |event|.
995 """
996 self._stats_lock.acquire()
997 try:
998 events = event.maskname.split('|')
999 for event_name in events:
1000 count = self._stats.get(event_name, 0)
1001 self._stats[event_name] = count + 1
1002 finally:
1003 self._stats_lock.release()
1004
1005 def _stats_copy(self):
1006 self._stats_lock.acquire()
1007 try:
1008 return self._stats.copy()
1009 finally:
1010 self._stats_lock.release()
1011
1012 def __repr__(self):
1013 stats = self._stats_copy()
1014
1015 elapsed = int(time.time() - self._start_time)
1016 elapsed_str = ''
1017 if elapsed < 60:
1018 elapsed_str = str(elapsed) + 'sec'
1019 elif 60 <= elapsed < 3600:
1020 elapsed_str = '%dmn%dsec' % (elapsed / 60, elapsed % 60)
1021 elif 3600 <= elapsed < 86400:
1022 elapsed_str = '%dh%dmn' % (elapsed / 3600, (elapsed % 3600) / 60)
1023 elif elapsed >= 86400:
1024 elapsed_str = '%dd%dh' % (elapsed / 86400, (elapsed % 86400) / 3600)
1025 stats['ElapsedTime'] = elapsed_str
1026
1027 l = []
1028 for ev, value in sorted(stats.items(), key=lambda x: x[0]):
1029 l.append(' %s=%s' % (output_format.field_name(ev),
1030 output_format.field_value(value)))
1031 s = '<%s%s >' % (output_format.class_name(self.__class__.__name__),
1032 ''.join(l))
1033 return s
1034
1035 def dump(self, filename):
1036 """
1037 Dumps statistics.
1038
1039 @param filename: filename where stats will be dumped, filename is
1040 created and must not exist prior to this call.
1041 @type filename: string
1042 """
1043 flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001044 fd = os.open(filename, flags, 0o0600)
1045 os.write(fd, bytes(self.__str__(), locale.getpreferredencoding()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001046 os.close(fd)
1047
1048 def __str__(self, scale=45):
1049 stats = self._stats_copy()
1050 if not stats:
1051 return ''
1052
1053 m = max(stats.values())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001054 unity = scale / m
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001055 fmt = '%%-26s%%-%ds%%s' % (len(output_format.field_value('@' * scale))
1056 + 1)
1057 def func(x):
1058 return fmt % (output_format.field_name(x[0]),
1059 output_format.field_value('@' * int(x[1] * unity)),
1060 output_format.simple('%d' % x[1], 'yellow'))
1061 s = '\n'.join(map(func, sorted(stats.items(), key=lambda x: x[0])))
1062 return s
1063
1064
1065class NotifierError(PyinotifyError):
1066 """
1067 Notifier Exception. Raised on Notifier error.
1068
1069 """
1070 def __init__(self, err):
1071 """
1072 @param err: Exception string's description.
1073 @type err: string
1074 """
1075 PyinotifyError.__init__(self, err)
1076
1077
1078class Notifier:
1079 """
1080 Read notifications, process events.
1081
1082 """
1083 def __init__(self, watch_manager, default_proc_fun=None, read_freq=0,
1084 threshold=0, timeout=None):
1085 """
1086 Initialization. read_freq, threshold and timeout parameters are used
1087 when looping.
1088
1089 @param watch_manager: Watch Manager.
1090 @type watch_manager: WatchManager instance
1091 @param default_proc_fun: Default processing method. If None, a new
1092 instance of PrintAllEvents will be assigned.
1093 @type default_proc_fun: instance of ProcessEvent
1094 @param read_freq: if read_freq == 0, events are read asap,
1095 if read_freq is > 0, this thread sleeps
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001096 max(0, read_freq - (timeout / 1000)) seconds. But if
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001097 timeout is None it may be different because
1098 poll is blocking waiting for something to read.
1099 @type read_freq: int
1100 @param threshold: File descriptor will be read only if the accumulated
1101 size to read becomes >= threshold. If != 0, you likely
1102 want to use it in combination with an appropriate
1103 value for read_freq because without that you would
1104 keep looping without really reading anything and that
1105 until the amount of events to read is >= threshold.
1106 At least with read_freq set you might sleep.
1107 @type threshold: int
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001108 @param timeout: see read_freq above. If provided, it must be set in
1109 milliseconds. See
1110 https://docs.python.org/3/library/select.html#select.poll.poll
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001111 @type timeout: int
1112 """
1113 # Watch Manager instance
1114 self._watch_manager = watch_manager
1115 # File descriptor
1116 self._fd = self._watch_manager.get_fd()
1117 # Poll object and registration
1118 self._pollobj = select.poll()
1119 self._pollobj.register(self._fd, select.POLLIN)
1120 # This pipe is correctely initialized and used by ThreadedNotifier
1121 self._pipe = (-1, -1)
1122 # Event queue
1123 self._eventq = deque()
1124 # System processing functor, common to all events
1125 self._sys_proc_fun = _SysProcessEvent(self._watch_manager, self)
1126 # Default processing method
1127 self._default_proc_fun = default_proc_fun
1128 if default_proc_fun is None:
1129 self._default_proc_fun = PrintAllEvents()
1130 # Loop parameters
1131 self._read_freq = read_freq
1132 self._threshold = threshold
1133 self._timeout = timeout
1134 # Coalesce events option
1135 self._coalesce = False
1136 # set of str(raw_event), only used when coalesce option is True
1137 self._eventset = set()
1138
1139 def append_event(self, event):
1140 """
1141 Append a raw event to the event queue.
1142
1143 @param event: An event.
1144 @type event: _RawEvent instance.
1145 """
1146 self._eventq.append(event)
1147
1148 def proc_fun(self):
1149 return self._default_proc_fun
1150
1151 def coalesce_events(self, coalesce=True):
1152 """
1153 Coalescing events. Events are usually processed by batchs, their size
1154 depend on various factors. Thus, before processing them, events received
1155 from inotify are aggregated in a fifo queue. If this coalescing
1156 option is enabled events are filtered based on their unicity, only
1157 unique events are enqueued, doublons are discarded. An event is unique
1158 when the combination of its fields (wd, mask, cookie, name) is unique
1159 among events of a same batch. After a batch of events is processed any
1160 events is accepted again. By default this option is disabled, you have
1161 to explictly call this function to turn it on.
1162
1163 @param coalesce: Optional new coalescing value. True by default.
1164 @type coalesce: Bool
1165 """
1166 self._coalesce = coalesce
1167 if not coalesce:
1168 self._eventset.clear()
1169
1170 def check_events(self, timeout=None):
1171 """
1172 Check for new events available to read, blocks up to timeout
1173 milliseconds.
1174
1175 @param timeout: If specified it overrides the corresponding instance
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001176 attribute _timeout. timeout must be sepcified in
1177 milliseconds.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001178 @type timeout: int
1179
1180 @return: New events to read.
1181 @rtype: bool
1182 """
1183 while True:
1184 try:
1185 # blocks up to 'timeout' milliseconds
1186 if timeout is None:
1187 timeout = self._timeout
1188 ret = self._pollobj.poll(timeout)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001189 except select.error as err:
1190 if err.args[0] == errno.EINTR:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001191 continue # interrupted, retry
1192 else:
1193 raise
1194 else:
1195 break
1196
1197 if not ret or (self._pipe[0] == ret[0][0]):
1198 return False
1199 # only one fd is polled
1200 return ret[0][1] & select.POLLIN
1201
1202 def read_events(self):
1203 """
1204 Read events from device, build _RawEvents, and enqueue them.
1205 """
1206 buf_ = array.array('i', [0])
1207 # get event queue size
1208 if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1:
1209 return
1210 queue_size = buf_[0]
1211 if queue_size < self._threshold:
1212 log.debug('(fd: %d) %d bytes available to read but threshold is '
1213 'fixed to %d bytes', self._fd, queue_size,
1214 self._threshold)
1215 return
1216
1217 try:
1218 # Read content from file
1219 r = os.read(self._fd, queue_size)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001220 except Exception as msg:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001221 raise NotifierError(msg)
1222 log.debug('Event queue size: %d', queue_size)
1223 rsum = 0 # counter
1224 while rsum < queue_size:
1225 s_size = 16
1226 # Retrieve wd, mask, cookie and fname_len
1227 wd, mask, cookie, fname_len = struct.unpack('iIII',
1228 r[rsum:rsum+s_size])
1229 # Retrieve name
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001230 bname, = struct.unpack('%ds' % fname_len,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001231 r[rsum + s_size:rsum + s_size + fname_len])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001232 # FIXME: should we explictly call sys.getdefaultencoding() here ??
1233 uname = bname.decode()
1234 rawevent = _RawEvent(wd, mask, cookie, uname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001235 if self._coalesce:
1236 # Only enqueue new (unique) events.
1237 raweventstr = str(rawevent)
1238 if raweventstr not in self._eventset:
1239 self._eventset.add(raweventstr)
1240 self._eventq.append(rawevent)
1241 else:
1242 self._eventq.append(rawevent)
1243 rsum += s_size + fname_len
1244
1245 def process_events(self):
1246 """
1247 Routine for processing events from queue by calling their
1248 associated proccessing method (an instance of ProcessEvent).
1249 It also does internal processings, to keep the system updated.
1250 """
1251 while self._eventq:
1252 raw_event = self._eventq.popleft() # pop next event
1253 if self._watch_manager.ignore_events:
1254 log.debug("Event ignored: %s" % repr(raw_event))
1255 continue
1256 watch_ = self._watch_manager.get_watch(raw_event.wd)
1257 if (watch_ is None) and not (raw_event.mask & IN_Q_OVERFLOW):
1258 if not (raw_event.mask & IN_IGNORED):
1259 # Not really sure how we ended up here, nor how we should
1260 # handle these types of events and if it is appropriate to
1261 # completly skip them (like we are doing here).
1262 log.warning("Unable to retrieve Watch object associated to %s",
1263 repr(raw_event))
1264 continue
1265 revent = self._sys_proc_fun(raw_event) # system processings
1266 if watch_ and watch_.proc_fun:
1267 watch_.proc_fun(revent) # user processings
1268 else:
1269 self._default_proc_fun(revent)
1270 self._sys_proc_fun.cleanup() # remove olds MOVED_* events records
1271 if self._coalesce:
1272 self._eventset.clear()
1273
1274 def __daemonize(self, pid_file=None, stdin=os.devnull, stdout=os.devnull,
1275 stderr=os.devnull):
1276 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001277 pid_file: file where the pid will be written. If pid_file=None the pid
1278 is written to /var/run/<sys.argv[0]|pyinotify>.pid, if
1279 pid_file=False no pid_file is written.
1280 stdin, stdout, stderr: files associated to common streams.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001281 """
1282 if pid_file is None:
1283 dirname = '/var/run/'
1284 basename = os.path.basename(sys.argv[0]) or 'pyinotify'
1285 pid_file = os.path.join(dirname, basename + '.pid')
1286
Andrew Geissler82c905d2020-04-13 13:39:40 -05001287 if pid_file and os.path.lexists(pid_file):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001288 err = 'Cannot daemonize: pid file %s already exists.' % pid_file
1289 raise NotifierError(err)
1290
1291 def fork_daemon():
1292 # Adapted from Chad J. Schroeder's recipe
1293 # @see http://code.activestate.com/recipes/278731/
1294 pid = os.fork()
1295 if (pid == 0):
1296 # parent 2
1297 os.setsid()
1298 pid = os.fork()
1299 if (pid == 0):
1300 # child
1301 os.chdir('/')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001302 os.umask(0o022)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001303 else:
1304 # parent 2
1305 os._exit(0)
1306 else:
1307 # parent 1
1308 os._exit(0)
1309
1310 fd_inp = os.open(stdin, os.O_RDONLY)
1311 os.dup2(fd_inp, 0)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001312 fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT, 0o0600)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001313 os.dup2(fd_out, 1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001314 fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT, 0o0600)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001315 os.dup2(fd_err, 2)
1316
1317 # Detach task
1318 fork_daemon()
1319
1320 # Write pid
Andrew Geissler82c905d2020-04-13 13:39:40 -05001321 if pid_file:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001322 flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001323 fd_pid = os.open(pid_file, flags, 0o0600)
1324 os.write(fd_pid, bytes(str(os.getpid()) + '\n',
1325 locale.getpreferredencoding()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001326 os.close(fd_pid)
1327 # Register unlink function
1328 atexit.register(lambda : os.unlink(pid_file))
1329
1330 def _sleep(self, ref_time):
1331 # Only consider sleeping if read_freq is > 0
1332 if self._read_freq > 0:
1333 cur_time = time.time()
1334 sleep_amount = self._read_freq - (cur_time - ref_time)
1335 if sleep_amount > 0:
1336 log.debug('Now sleeping %d seconds', sleep_amount)
1337 time.sleep(sleep_amount)
1338
1339 def loop(self, callback=None, daemonize=False, **args):
1340 """
1341 Events are read only one time every min(read_freq, timeout)
1342 seconds at best and only if the size to read is >= threshold.
1343 After this method returns it must not be called again for the same
1344 instance.
1345
1346 @param callback: Functor called after each event processing iteration.
1347 Expects to receive the notifier object (self) as first
1348 parameter. If this function returns True the loop is
1349 immediately terminated otherwise the loop method keeps
1350 looping.
1351 @type callback: callable object or function
1352 @param daemonize: This thread is daemonized if set to True.
1353 @type daemonize: boolean
1354 @param args: Optional and relevant only if daemonize is True. Remaining
1355 keyworded arguments are directly passed to daemonize see
1356 __daemonize() method. If pid_file=None or is set to a
1357 pathname the caller must ensure the file does not exist
1358 before this method is called otherwise an exception
1359 pyinotify.NotifierError will be raised. If pid_file=False
1360 it is still daemonized but the pid is not written in any
1361 file.
1362 @type args: various
1363 """
1364 if daemonize:
1365 self.__daemonize(**args)
1366
1367 # Read and process events forever
1368 while 1:
1369 try:
1370 self.process_events()
1371 if (callback is not None) and (callback(self) is True):
1372 break
1373 ref_time = time.time()
1374 # check_events is blocking
1375 if self.check_events():
1376 self._sleep(ref_time)
1377 self.read_events()
1378 except KeyboardInterrupt:
1379 # Stop monitoring if sigint is caught (Control-C).
1380 log.debug('Pyinotify stops monitoring.')
1381 break
1382 # Close internals
1383 self.stop()
1384
1385 def stop(self):
1386 """
1387 Close inotify's instance (close its file descriptor).
1388 It destroys all existing watches, pending events,...
1389 This method is automatically called at the end of loop().
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001390 Afterward it is invalid to access this instance.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001391 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001392 if self._fd is not None:
1393 self._pollobj.unregister(self._fd)
1394 os.close(self._fd)
1395 self._fd = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001396 self._sys_proc_fun = None
1397
1398
1399class ThreadedNotifier(threading.Thread, Notifier):
1400 """
1401 This notifier inherits from threading.Thread for instanciating a separate
1402 thread, and also inherits from Notifier, because it is a threaded notifier.
1403
1404 Note that every functionality provided by this class is also provided
1405 through Notifier class. Moreover Notifier should be considered first because
1406 it is not threaded and could be easily daemonized.
1407 """
1408 def __init__(self, watch_manager, default_proc_fun=None, read_freq=0,
1409 threshold=0, timeout=None):
1410 """
1411 Initialization, initialize base classes. read_freq, threshold and
1412 timeout parameters are used when looping.
1413
1414 @param watch_manager: Watch Manager.
1415 @type watch_manager: WatchManager instance
1416 @param default_proc_fun: Default processing method. See base class.
1417 @type default_proc_fun: instance of ProcessEvent
1418 @param read_freq: if read_freq == 0, events are read asap,
1419 if read_freq is > 0, this thread sleeps
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001420 max(0, read_freq - (timeout / 1000)) seconds.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001421 @type read_freq: int
1422 @param threshold: File descriptor will be read only if the accumulated
1423 size to read becomes >= threshold. If != 0, you likely
1424 want to use it in combination with an appropriate
1425 value set for read_freq because without that you would
1426 keep looping without really reading anything and that
1427 until the amount of events to read is >= threshold. At
1428 least with read_freq you might sleep.
1429 @type threshold: int
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001430 @param timeout: see read_freq above. If provided, it must be set in
1431 milliseconds. See
1432 https://docs.python.org/3/library/select.html#select.poll.poll
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001433 @type timeout: int
1434 """
1435 # Init threading base class
1436 threading.Thread.__init__(self)
1437 # Stop condition
1438 self._stop_event = threading.Event()
1439 # Init Notifier base class
1440 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq,
1441 threshold, timeout)
1442 # Create a new pipe used for thread termination
1443 self._pipe = os.pipe()
1444 self._pollobj.register(self._pipe[0], select.POLLIN)
1445
1446 def stop(self):
1447 """
1448 Stop notifier's loop. Stop notification. Join the thread.
1449 """
1450 self._stop_event.set()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001451 os.write(self._pipe[1], b'stop')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001452 threading.Thread.join(self)
1453 Notifier.stop(self)
1454 self._pollobj.unregister(self._pipe[0])
1455 os.close(self._pipe[0])
1456 os.close(self._pipe[1])
1457
1458 def loop(self):
1459 """
1460 Thread's main loop. Don't meant to be called by user directly.
1461 Call inherited start() method instead.
1462
1463 Events are read only once time every min(read_freq, timeout)
1464 seconds at best and only if the size of events to read is >= threshold.
1465 """
1466 # When the loop must be terminated .stop() is called, 'stop'
1467 # is written to pipe fd so poll() returns and .check_events()
1468 # returns False which make evaluate the While's stop condition
1469 # ._stop_event.isSet() wich put an end to the thread's execution.
1470 while not self._stop_event.isSet():
1471 self.process_events()
1472 ref_time = time.time()
1473 if self.check_events():
1474 self._sleep(ref_time)
1475 self.read_events()
1476
1477 def run(self):
1478 """
1479 Start thread's loop: read and process events until the method
1480 stop() is called.
1481 Never call this method directly, instead call the start() method
1482 inherited from threading.Thread, which then will call run() in
1483 its turn.
1484 """
1485 self.loop()
1486
1487
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001488class TornadoAsyncNotifier(Notifier):
1489 """
1490 Tornado ioloop adapter.
1491
1492 """
1493 def __init__(self, watch_manager, ioloop, callback=None,
1494 default_proc_fun=None, read_freq=0, threshold=0, timeout=None,
1495 channel_map=None):
1496 """
1497 Note that if later you must call ioloop.close() be sure to let the
1498 default parameter to all_fds=False.
1499
1500 See example tornado_notifier.py for an example using this notifier.
1501
1502 @param ioloop: Tornado's IO loop.
1503 @type ioloop: tornado.ioloop.IOLoop instance.
1504 @param callback: Functor called at the end of each call to handle_read
1505 (IOLoop's read handler). Expects to receive the
1506 notifier object (self) as single parameter.
1507 @type callback: callable object or function
1508 """
1509 self.io_loop = ioloop
1510 self.handle_read_callback = callback
1511 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq,
1512 threshold, timeout)
1513 ioloop.add_handler(self._fd, self.handle_read, ioloop.READ)
1514
1515 def stop(self):
1516 self.io_loop.remove_handler(self._fd)
1517 Notifier.stop(self)
1518
1519 def handle_read(self, *args, **kwargs):
1520 """
1521 See comment in AsyncNotifier.
1522
1523 """
1524 self.read_events()
1525 self.process_events()
1526 if self.handle_read_callback is not None:
1527 self.handle_read_callback(self)
1528
1529
1530class AsyncioNotifier(Notifier):
1531 """
1532
1533 asyncio/trollius event loop adapter.
1534
1535 """
1536 def __init__(self, watch_manager, loop, callback=None,
1537 default_proc_fun=None, read_freq=0, threshold=0, timeout=None):
1538 """
1539
1540 See examples/asyncio_notifier.py for an example usage.
1541
1542 @param loop: asyncio or trollius event loop instance.
1543 @type loop: asyncio.BaseEventLoop or trollius.BaseEventLoop instance.
1544 @param callback: Functor called at the end of each call to handle_read.
1545 Expects to receive the notifier object (self) as
1546 single parameter.
1547 @type callback: callable object or function
1548
1549 """
1550 self.loop = loop
1551 self.handle_read_callback = callback
1552 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq,
1553 threshold, timeout)
1554 loop.add_reader(self._fd, self.handle_read)
1555
1556 def stop(self):
1557 self.loop.remove_reader(self._fd)
1558 Notifier.stop(self)
1559
1560 def handle_read(self, *args, **kwargs):
1561 self.read_events()
1562 self.process_events()
1563 if self.handle_read_callback is not None:
1564 self.handle_read_callback(self)
1565
1566
1567class Watch:
1568 """
1569 Represent a watch, i.e. a file or directory being watched.
1570
1571 """
1572 __slots__ = ('wd', 'path', 'mask', 'proc_fun', 'auto_add',
1573 'exclude_filter', 'dir')
1574
1575 def __init__(self, wd, path, mask, proc_fun, auto_add, exclude_filter):
1576 """
1577 Initializations.
1578
1579 @param wd: Watch descriptor.
1580 @type wd: int
1581 @param path: Path of the file or directory being watched.
1582 @type path: str
1583 @param mask: Mask.
1584 @type mask: int
1585 @param proc_fun: Processing callable object.
1586 @type proc_fun:
1587 @param auto_add: Automatically add watches on new directories.
1588 @type auto_add: bool
1589 @param exclude_filter: Boolean function, used to exclude new
1590 directories from being automatically watched.
1591 See WatchManager.__init__
1592 @type exclude_filter: callable object
1593 """
1594 self.wd = wd
1595 self.path = path
1596 self.mask = mask
1597 self.proc_fun = proc_fun
1598 self.auto_add = auto_add
1599 self.exclude_filter = exclude_filter
1600 self.dir = os.path.isdir(self.path)
1601
1602 def __repr__(self):
1603 """
1604 @return: String representation.
1605 @rtype: str
1606 """
1607 s = ' '.join(['%s%s%s' % (output_format.field_name(attr),
1608 output_format.punctuation('='),
1609 output_format.field_value(getattr(self,
1610 attr))) \
1611 for attr in self.__slots__ if not attr.startswith('_')])
1612
1613 s = '%s%s %s %s' % (output_format.punctuation('<'),
1614 output_format.class_name(self.__class__.__name__),
1615 s,
1616 output_format.punctuation('>'))
1617 return s
1618
1619
1620class ExcludeFilter:
1621 """
1622 ExcludeFilter is an exclusion filter.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001623 """
1624 def __init__(self, arg_lst):
1625 """
1626 Examples:
1627 ef1 = ExcludeFilter(["/etc/rc.*", "/etc/hostname"])
1628 ef2 = ExcludeFilter("/my/path/exclude.lst")
1629 Where exclude.lst contains:
1630 /etc/rc.*
1631 /etc/hostname
1632
1633 Note: it is not possible to exclude a file if its encapsulating
1634 directory is itself watched. See this issue for more details
1635 https://github.com/seb-m/pyinotify/issues/31
1636
1637 @param arg_lst: is either a list of patterns or a filename from which
1638 patterns will be loaded.
1639 @type arg_lst: list of str or str
1640 """
1641 if isinstance(arg_lst, str):
1642 lst = self._load_patterns_from_file(arg_lst)
1643 elif isinstance(arg_lst, list):
1644 lst = arg_lst
1645 else:
1646 raise TypeError
1647
1648 self._lregex = []
1649 for regex in lst:
1650 self._lregex.append(re.compile(regex, re.UNICODE))
1651
1652 def _load_patterns_from_file(self, filename):
1653 lst = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001654 with open(filename, 'r') as file_obj:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001655 for line in file_obj.readlines():
1656 # Trim leading an trailing whitespaces
1657 pattern = line.strip()
1658 if not pattern or pattern.startswith('#'):
1659 continue
1660 lst.append(pattern)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001661 return lst
1662
1663 def _match(self, regex, path):
1664 return regex.match(path) is not None
1665
1666 def __call__(self, path):
1667 """
1668 @param path: Path to match against provided regexps.
1669 @type path: str
1670 @return: Return True if path has been matched and should
1671 be excluded, False otherwise.
1672 @rtype: bool
1673 """
1674 for regex in self._lregex:
1675 if self._match(regex, path):
1676 return True
1677 return False
1678
1679
1680class WatchManagerError(Exception):
1681 """
1682 WatchManager Exception. Raised on error encountered on watches
1683 operations.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001684 """
1685 def __init__(self, msg, wmd):
1686 """
1687 @param msg: Exception string's description.
1688 @type msg: string
1689 @param wmd: This dictionary contains the wd assigned to paths of the
1690 same call for which watches were successfully added.
1691 @type wmd: dict
1692 """
1693 self.wmd = wmd
1694 Exception.__init__(self, msg)
1695
1696
1697class WatchManager:
1698 """
1699 Provide operations for watching files and directories. Its internal
1700 dictionary is used to reference watched items. When used inside
1701 threaded code, one must instanciate as many WatchManager instances as
1702 there are ThreadedNotifier instances.
1703
1704 """
1705 def __init__(self, exclude_filter=lambda path: False):
1706 """
1707 Initialization: init inotify, init watch manager dictionary.
1708 Raise OSError if initialization fails, raise InotifyBindingNotFoundError
1709 if no inotify binding was found (through ctypes or from direct access to
1710 syscalls).
1711
1712 @param exclude_filter: boolean function, returns True if current
1713 path must be excluded from being watched.
1714 Convenient for providing a common exclusion
1715 filter for every call to add_watch.
1716 @type exclude_filter: callable object
1717 """
1718 self._ignore_events = False
1719 self._exclude_filter = exclude_filter
1720 self._wmd = {} # watch dict key: watch descriptor, value: watch
1721
1722 self._inotify_wrapper = INotifyWrapper.create()
1723 if self._inotify_wrapper is None:
1724 raise InotifyBindingNotFoundError()
1725
1726 self._fd = self._inotify_wrapper.inotify_init() # file descriptor
1727 if self._fd < 0:
1728 err = 'Cannot initialize new instance of inotify, %s'
1729 raise OSError(err % self._inotify_wrapper.str_errno())
1730
1731 def close(self):
1732 """
1733 Close inotify's file descriptor, this action will also automatically
1734 remove (i.e. stop watching) all its associated watch descriptors.
1735 After a call to this method the WatchManager's instance become useless
1736 and cannot be reused, a new instance must then be instanciated. It
1737 makes sense to call this method in few situations for instance if
1738 several independant WatchManager must be instanciated or if all watches
1739 must be removed and no other watches need to be added.
1740 """
1741 os.close(self._fd)
1742
1743 def get_fd(self):
1744 """
1745 Return assigned inotify's file descriptor.
1746
1747 @return: File descriptor.
1748 @rtype: int
1749 """
1750 return self._fd
1751
1752 def get_watch(self, wd):
1753 """
1754 Get watch from provided watch descriptor wd.
1755
1756 @param wd: Watch descriptor.
1757 @type wd: int
1758 """
1759 return self._wmd.get(wd)
1760
1761 def del_watch(self, wd):
1762 """
1763 Remove watch entry associated to watch descriptor wd.
1764
1765 @param wd: Watch descriptor.
1766 @type wd: int
1767 """
1768 try:
1769 del self._wmd[wd]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001770 except KeyError as err:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001771 log.error('Cannot delete unknown watch descriptor %s' % str(err))
1772
1773 @property
1774 def watches(self):
1775 """
1776 Get a reference on the internal watch manager dictionary.
1777
1778 @return: Internal watch manager dictionary.
1779 @rtype: dict
1780 """
1781 return self._wmd
1782
1783 def __format_path(self, path):
1784 """
1785 Format path to its internal (stored in watch manager) representation.
1786 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001787 # path must be a unicode string (str) and is just normalized.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001788 return os.path.normpath(path)
1789
1790 def __add_watch(self, path, mask, proc_fun, auto_add, exclude_filter):
1791 """
1792 Add a watch on path, build a Watch object and insert it in the
1793 watch manager dictionary. Return the wd value.
1794 """
1795 path = self.__format_path(path)
1796 if auto_add and not mask & IN_CREATE:
1797 mask |= IN_CREATE
1798 wd = self._inotify_wrapper.inotify_add_watch(self._fd, path, mask)
1799 if wd < 0:
1800 return wd
1801 watch = Watch(wd=wd, path=path, mask=mask, proc_fun=proc_fun,
1802 auto_add=auto_add, exclude_filter=exclude_filter)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001803 # wd are _always_ indexed with their original unicode paths in wmd.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001804 self._wmd[wd] = watch
1805 log.debug('New %s', watch)
1806 return wd
1807
1808 def __glob(self, path, do_glob):
1809 if do_glob:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001810 return glob.iglob(path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001811 else:
1812 return [path]
1813
1814 def add_watch(self, path, mask, proc_fun=None, rec=False,
1815 auto_add=False, do_glob=False, quiet=True,
1816 exclude_filter=None):
1817 """
1818 Add watch(s) on the provided |path|(s) with associated |mask| flag
1819 value and optionally with a processing |proc_fun| function and
1820 recursive flag |rec| set to True.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001821 All |path| components _must_ be str (i.e. unicode) objects.
1822 If |path| is already watched it is ignored, but if it is called with
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001823 option rec=True a watch is put on each one of its not-watched
1824 subdirectory.
1825
1826 @param path: Path to watch, the path can either be a file or a
1827 directory. Also accepts a sequence (list) of paths.
1828 @type path: string or list of strings
1829 @param mask: Bitmask of events.
1830 @type mask: int
1831 @param proc_fun: Processing object.
1832 @type proc_fun: function or ProcessEvent instance or instance of
1833 one of its subclasses or callable object.
1834 @param rec: Recursively add watches from path on all its
1835 subdirectories, set to False by default (doesn't
1836 follows symlinks in any case).
1837 @type rec: bool
1838 @param auto_add: Automatically add watches on newly created
1839 directories in watched parent |path| directory.
1840 If |auto_add| is True, IN_CREATE is ored with |mask|
1841 when the watch is added.
1842 @type auto_add: bool
1843 @param do_glob: Do globbing on pathname (see standard globbing
1844 module for more informations).
1845 @type do_glob: bool
1846 @param quiet: if False raises a WatchManagerError exception on
1847 error. See example not_quiet.py.
1848 @type quiet: bool
1849 @param exclude_filter: predicate (boolean function), which returns
1850 True if the current path must be excluded
1851 from being watched. This argument has
1852 precedence over exclude_filter passed to
1853 the class' constructor.
1854 @type exclude_filter: callable object
1855 @return: dict of paths associated to watch descriptors. A wd value
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001856 is positive if the watch was added sucessfully, otherwise
1857 the value is negative. If the path was invalid or was already
1858 watched it is not included into this returned dictionary.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001859 @rtype: dict of {str: int}
1860 """
1861 ret_ = {} # return {path: wd, ...}
1862
1863 if exclude_filter is None:
1864 exclude_filter = self._exclude_filter
1865
1866 # normalize args as list elements
1867 for npath in self.__format_param(path):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001868 # Require that path be a unicode string
1869 if not isinstance(npath, str):
1870 ret_[path] = -3
1871 continue
1872
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001873 # unix pathname pattern expansion
1874 for apath in self.__glob(npath, do_glob):
1875 # recursively list subdirs according to rec param
1876 for rpath in self.__walk_rec(apath, rec):
1877 if not exclude_filter(rpath):
1878 wd = ret_[rpath] = self.__add_watch(rpath, mask,
1879 proc_fun,
1880 auto_add,
1881 exclude_filter)
1882 if wd < 0:
1883 err = ('add_watch: cannot watch %s WD=%d, %s' % \
1884 (rpath, wd,
1885 self._inotify_wrapper.str_errno()))
1886 if quiet:
1887 log.error(err)
1888 else:
1889 raise WatchManagerError(err, ret_)
1890 else:
1891 # Let's say -2 means 'explicitely excluded
1892 # from watching'.
1893 ret_[rpath] = -2
1894 return ret_
1895
1896 def __get_sub_rec(self, lpath):
1897 """
1898 Get every wd from self._wmd if its path is under the path of
1899 one (at least) of those in lpath. Doesn't follow symlinks.
1900
1901 @param lpath: list of watch descriptor
1902 @type lpath: list of int
1903 @return: list of watch descriptor
1904 @rtype: list of int
1905 """
1906 for d in lpath:
1907 root = self.get_path(d)
1908 if root is not None:
1909 # always keep root
1910 yield d
1911 else:
1912 # if invalid
1913 continue
1914
1915 # nothing else to expect
1916 if not os.path.isdir(root):
1917 continue
1918
1919 # normalization
1920 root = os.path.normpath(root)
1921 # recursion
1922 lend = len(root)
1923 for iwd in self._wmd.items():
1924 cur = iwd[1].path
1925 pref = os.path.commonprefix([root, cur])
1926 if root == os.sep or (len(pref) == lend and \
1927 len(cur) > lend and \
1928 cur[lend] == os.sep):
1929 yield iwd[1].wd
1930
1931 def update_watch(self, wd, mask=None, proc_fun=None, rec=False,
1932 auto_add=False, quiet=True):
1933 """
1934 Update existing watch descriptors |wd|. The |mask| value, the
1935 processing object |proc_fun|, the recursive param |rec| and the
1936 |auto_add| and |quiet| flags can all be updated.
1937
1938 @param wd: Watch Descriptor to update. Also accepts a list of
1939 watch descriptors.
1940 @type wd: int or list of int
1941 @param mask: Optional new bitmask of events.
1942 @type mask: int
1943 @param proc_fun: Optional new processing function.
1944 @type proc_fun: function or ProcessEvent instance or instance of
1945 one of its subclasses or callable object.
1946 @param rec: Optionally adds watches recursively on all
1947 subdirectories contained into |wd| directory.
1948 @type rec: bool
1949 @param auto_add: Automatically adds watches on newly created
1950 directories in the watch's path corresponding to |wd|.
1951 If |auto_add| is True, IN_CREATE is ored with |mask|
1952 when the watch is updated.
1953 @type auto_add: bool
1954 @param quiet: If False raises a WatchManagerError exception on
1955 error. See example not_quiet.py
1956 @type quiet: bool
1957 @return: dict of watch descriptors associated to booleans values.
1958 True if the corresponding wd has been successfully
1959 updated, False otherwise.
1960 @rtype: dict of {int: bool}
1961 """
1962 lwd = self.__format_param(wd)
1963 if rec:
1964 lwd = self.__get_sub_rec(lwd)
1965
1966 ret_ = {} # return {wd: bool, ...}
1967 for awd in lwd:
1968 apath = self.get_path(awd)
1969 if not apath or awd < 0:
1970 err = 'update_watch: invalid WD=%d' % awd
1971 if quiet:
1972 log.error(err)
1973 continue
1974 raise WatchManagerError(err, ret_)
1975
1976 if mask:
1977 wd_ = self._inotify_wrapper.inotify_add_watch(self._fd, apath,
1978 mask)
1979 if wd_ < 0:
1980 ret_[awd] = False
1981 err = ('update_watch: cannot update %s WD=%d, %s' % \
1982 (apath, wd_, self._inotify_wrapper.str_errno()))
1983 if quiet:
1984 log.error(err)
1985 continue
1986 raise WatchManagerError(err, ret_)
1987
1988 assert(awd == wd_)
1989
1990 if proc_fun or auto_add:
1991 watch_ = self._wmd[awd]
1992
1993 if proc_fun:
1994 watch_.proc_fun = proc_fun
1995
1996 if auto_add:
1997 watch_.auto_add = auto_add
1998
1999 ret_[awd] = True
2000 log.debug('Updated watch - %s', self._wmd[awd])
2001 return ret_
2002
2003 def __format_param(self, param):
2004 """
2005 @param param: Parameter.
2006 @type param: string or int
2007 @return: wrap param.
2008 @rtype: list of type(param)
2009 """
2010 if isinstance(param, list):
2011 for p_ in param:
2012 yield p_
2013 else:
2014 yield param
2015
2016 def get_wd(self, path):
2017 """
2018 Returns the watch descriptor associated to path. This method
2019 presents a prohibitive cost, always prefer to keep the WD
2020 returned by add_watch(). If the path is unknown it returns None.
2021
2022 @param path: Path.
2023 @type path: str
2024 @return: WD or None.
2025 @rtype: int or None
2026 """
2027 path = self.__format_path(path)
2028 for iwd in self._wmd.items():
2029 if iwd[1].path == path:
2030 return iwd[0]
2031
2032 def get_path(self, wd):
2033 """
2034 Returns the path associated to WD, if WD is unknown it returns None.
2035
2036 @param wd: Watch descriptor.
2037 @type wd: int
2038 @return: Path or None.
2039 @rtype: string or None
2040 """
2041 watch_ = self._wmd.get(wd)
2042 if watch_ is not None:
2043 return watch_.path
2044
2045 def __walk_rec(self, top, rec):
2046 """
2047 Yields each subdirectories of top, doesn't follow symlinks.
2048 If rec is false, only yield top.
2049
2050 @param top: root directory.
2051 @type top: string
2052 @param rec: recursive flag.
2053 @type rec: bool
2054 @return: path of one subdirectory.
2055 @rtype: string
2056 """
2057 if not rec or os.path.islink(top) or not os.path.isdir(top):
2058 yield top
2059 else:
2060 for root, dirs, files in os.walk(top):
2061 yield root
2062
2063 def rm_watch(self, wd, rec=False, quiet=True):
2064 """
2065 Removes watch(s).
2066
2067 @param wd: Watch Descriptor of the file or directory to unwatch.
2068 Also accepts a list of WDs.
2069 @type wd: int or list of int.
2070 @param rec: Recursively removes watches on every already watched
2071 subdirectories and subfiles.
2072 @type rec: bool
2073 @param quiet: If False raises a WatchManagerError exception on
2074 error. See example not_quiet.py
2075 @type quiet: bool
2076 @return: dict of watch descriptors associated to booleans values.
2077 True if the corresponding wd has been successfully
2078 removed, False otherwise.
2079 @rtype: dict of {int: bool}
2080 """
2081 lwd = self.__format_param(wd)
2082 if rec:
2083 lwd = self.__get_sub_rec(lwd)
2084
2085 ret_ = {} # return {wd: bool, ...}
2086 for awd in lwd:
2087 # remove watch
2088 wd_ = self._inotify_wrapper.inotify_rm_watch(self._fd, awd)
2089 if wd_ < 0:
2090 ret_[awd] = False
2091 err = ('rm_watch: cannot remove WD=%d, %s' % \
2092 (awd, self._inotify_wrapper.str_errno()))
2093 if quiet:
2094 log.error(err)
2095 continue
2096 raise WatchManagerError(err, ret_)
2097
2098 # Remove watch from our dictionary
2099 if awd in self._wmd:
2100 del self._wmd[awd]
2101 ret_[awd] = True
2102 log.debug('Watch WD=%d (%s) removed', awd, self.get_path(awd))
2103 return ret_
2104
2105
2106 def watch_transient_file(self, filename, mask, proc_class):
2107 """
2108 Watch a transient file, which will be created and deleted frequently
2109 over time (e.g. pid file).
2110
2111 @attention: Currently under the call to this function it is not
2112 possible to correctly watch the events triggered into the same
2113 base directory than the directory where is located this watched
2114 transient file. For instance it would be wrong to make these
2115 two successive calls: wm.watch_transient_file('/var/run/foo.pid', ...)
2116 and wm.add_watch('/var/run/', ...)
2117
2118 @param filename: Filename.
2119 @type filename: string
2120 @param mask: Bitmask of events, should contain IN_CREATE and IN_DELETE.
2121 @type mask: int
2122 @param proc_class: ProcessEvent (or of one of its subclass), beware of
2123 accepting a ProcessEvent's instance as argument into
2124 __init__, see transient_file.py example for more
2125 details.
2126 @type proc_class: ProcessEvent's instance or of one of its subclasses.
2127 @return: Same as add_watch().
2128 @rtype: Same as add_watch().
2129 """
2130 dirname = os.path.dirname(filename)
2131 if dirname == '':
2132 return {} # Maintains coherence with add_watch()
2133 basename = os.path.basename(filename)
2134 # Assuming we are watching at least for IN_CREATE and IN_DELETE
2135 mask |= IN_CREATE | IN_DELETE
2136
2137 def cmp_name(event):
2138 if getattr(event, 'name') is None:
2139 return False
2140 return basename == event.name
2141 return self.add_watch(dirname, mask,
2142 proc_fun=proc_class(ChainIfTrue(func=cmp_name)),
2143 rec=False,
2144 auto_add=False, do_glob=False,
2145 exclude_filter=lambda path: False)
2146
2147 def get_ignore_events(self):
2148 return self._ignore_events
2149
2150 def set_ignore_events(self, nval):
2151 self._ignore_events = nval
2152
2153 ignore_events = property(get_ignore_events, set_ignore_events,
2154 "Make watch manager ignoring new events.")
2155
2156
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002157class RawOutputFormat:
2158 """
2159 Format string representations.
2160 """
2161 def __init__(self, format=None):
2162 self.format = format or {}
2163
2164 def simple(self, s, attribute):
2165 if not isinstance(s, str):
2166 s = str(s)
2167 return (self.format.get(attribute, '') + s +
2168 self.format.get('normal', ''))
2169
2170 def punctuation(self, s):
2171 """Punctuation color."""
2172 return self.simple(s, 'normal')
2173
2174 def field_value(self, s):
2175 """Field value color."""
2176 return self.simple(s, 'purple')
2177
2178 def field_name(self, s):
2179 """Field name color."""
2180 return self.simple(s, 'blue')
2181
2182 def class_name(self, s):
2183 """Class name color."""
2184 return self.format.get('red', '') + self.simple(s, 'bold')
2185
2186output_format = RawOutputFormat()
2187
2188class ColoredOutputFormat(RawOutputFormat):
2189 """
2190 Format colored string representations.
2191 """
2192 def __init__(self):
2193 f = {'normal': '\033[0m',
2194 'black': '\033[30m',
2195 'red': '\033[31m',
2196 'green': '\033[32m',
2197 'yellow': '\033[33m',
2198 'blue': '\033[34m',
2199 'purple': '\033[35m',
2200 'cyan': '\033[36m',
2201 'bold': '\033[1m',
2202 'uline': '\033[4m',
2203 'blink': '\033[5m',
2204 'invert': '\033[7m'}
2205 RawOutputFormat.__init__(self, f)
2206
2207
2208def compatibility_mode():
2209 """
2210 Use this function to turn on the compatibility mode. The compatibility
2211 mode is used to improve compatibility with Pyinotify 0.7.1 (or older)
2212 programs. The compatibility mode provides additional variables 'is_dir',
2213 'event_name', 'EventsCodes.IN_*' and 'EventsCodes.ALL_EVENTS' as
2214 Pyinotify 0.7.1 provided. Do not call this function from new programs!!
2215 Especially if there are developped for Pyinotify >= 0.8.x.
2216 """
2217 setattr(EventsCodes, 'ALL_EVENTS', ALL_EVENTS)
2218 for evname in globals():
2219 if evname.startswith('IN_'):
2220 setattr(EventsCodes, evname, globals()[evname])
2221 global COMPATIBILITY_MODE
2222 COMPATIBILITY_MODE = True
2223
2224
2225def command_line():
2226 """
2227 By default the watched path is '/tmp' and all types of events are
2228 monitored. Events monitoring serves forever, type c^c to stop it.
2229 """
2230 from optparse import OptionParser
2231
2232 usage = "usage: %prog [options] [path1] [path2] [pathn]"
2233
2234 parser = OptionParser(usage=usage)
2235 parser.add_option("-v", "--verbose", action="store_true",
2236 dest="verbose", help="Verbose mode")
2237 parser.add_option("-r", "--recursive", action="store_true",
2238 dest="recursive",
2239 help="Add watches recursively on paths")
2240 parser.add_option("-a", "--auto_add", action="store_true",
2241 dest="auto_add",
2242 help="Automatically add watches on new directories")
2243 parser.add_option("-g", "--glob", action="store_true",
2244 dest="glob",
2245 help="Treat paths as globs")
2246 parser.add_option("-e", "--events-list", metavar="EVENT[,...]",
2247 dest="events_list",
2248 help=("A comma-separated list of events to watch for - "
2249 "see the documentation for valid options (defaults"
2250 " to everything)"))
2251 parser.add_option("-s", "--stats", action="store_true",
2252 dest="stats",
2253 help="Display dummy statistics")
2254 parser.add_option("-V", "--version", action="store_true",
2255 dest="version", help="Pyinotify version")
2256 parser.add_option("-f", "--raw-format", action="store_true",
2257 dest="raw_format",
2258 help="Disable enhanced output format.")
2259 parser.add_option("-c", "--command", action="store",
2260 dest="command",
2261 help="Shell command to run upon event")
2262
2263 (options, args) = parser.parse_args()
2264
2265 if options.verbose:
2266 log.setLevel(10)
2267
2268 if options.version:
2269 print(__version__)
2270
2271 if not options.raw_format:
2272 global output_format
2273 output_format = ColoredOutputFormat()
2274
2275 if len(args) < 1:
2276 path = '/tmp' # default watched path
2277 else:
2278 path = args
2279
2280 # watch manager instance
2281 wm = WatchManager()
2282 # notifier instance and init
2283 if options.stats:
2284 notifier = Notifier(wm, default_proc_fun=Stats(), read_freq=5)
2285 else:
2286 notifier = Notifier(wm, default_proc_fun=PrintAllEvents())
2287
2288 # What mask to apply
2289 mask = 0
2290 if options.events_list:
2291 events_list = options.events_list.split(',')
2292 for ev in events_list:
2293 evcode = EventsCodes.ALL_FLAGS.get(ev, 0)
2294 if evcode:
2295 mask |= evcode
2296 else:
2297 parser.error("The event '%s' specified with option -e"
2298 " is not valid" % ev)
2299 else:
2300 mask = ALL_EVENTS
2301
2302 # stats
2303 cb_fun = None
2304 if options.stats:
2305 def cb(s):
2306 sys.stdout.write(repr(s.proc_fun()))
2307 sys.stdout.write('\n')
2308 sys.stdout.write(str(s.proc_fun()))
2309 sys.stdout.write('\n')
2310 sys.stdout.flush()
2311 cb_fun = cb
2312
2313 # External command
2314 if options.command:
2315 def cb(s):
2316 subprocess.Popen(options.command, shell=True)
2317 cb_fun = cb
2318
2319 log.debug('Start monitoring %s, (press c^c to halt pyinotify)' % path)
2320
2321 wm.add_watch(path, mask, rec=options.recursive, auto_add=options.auto_add, do_glob=options.glob)
2322 # Loop forever (until sigint signal get caught)
2323 notifier.loop(callback=cb_fun)
2324
2325
2326if __name__ == '__main__':
2327 command_line()