blob: 8c94b3e3348ff1649e5ef512f0dca2da10a28b88 [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)
606 maskname = EventsCodes.ALL_VALUES.get(stripped_mask)
607 if maskname is None:
608 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask)
609
610 # 1- look for process_MASKNAME
611 meth = getattr(self, 'process_' + maskname, None)
612 if meth is not None:
613 return meth(event)
614 # 2- look for process_FAMILY_NAME
615 meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None)
616 if meth is not None:
617 return meth(event)
618 # 3- default call method process_default
619 return self.process_default(event)
620
621 def __repr__(self):
622 return '<%s>' % self.__class__.__name__
623
624
625class _SysProcessEvent(_ProcessEvent):
626 """
627 There is three kind of processing according to each event:
628
629 1. special handling (deletion from internal container, bug, ...).
630 2. default treatment: which is applied to the majority of events.
631 3. IN_ISDIR is never sent alone, he is piggybacked with a standard
632 event, he is not processed as the others events, instead, its
633 value is captured and appropriately aggregated to dst event.
634 """
635 def __init__(self, wm, notifier):
636 """
637
638 @param wm: Watch Manager.
639 @type wm: WatchManager instance
640 @param notifier: Notifier.
641 @type notifier: Notifier instance
642 """
643 self._watch_manager = wm # watch manager
644 self._notifier = notifier # notifier
645 self._mv_cookie = {} # {cookie(int): (src_path(str), date), ...}
646 self._mv = {} # {src_path(str): (dst_path(str), date), ...}
647
648 def cleanup(self):
649 """
650 Cleanup (delete) old (>1mn) records contained in self._mv_cookie
651 and self._mv.
652 """
653 date_cur_ = datetime.now()
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600654 for seq in (self._mv_cookie, self._mv):
655 for k in list(seq.keys()):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500656 if (date_cur_ - seq[k][1]) > timedelta(minutes=1):
657 log.debug('Cleanup: deleting entry %s', seq[k][0])
658 del seq[k]
659
660 def process_IN_CREATE(self, raw_event):
661 """
662 If the event affects a directory and the auto_add flag of the
663 targetted watch is set to True, a new watch is added on this
664 new directory, with the same attribute values than those of
665 this watch.
666 """
667 if raw_event.mask & IN_ISDIR:
668 watch_ = self._watch_manager.get_watch(raw_event.wd)
669 created_dir = os.path.join(watch_.path, raw_event.name)
670 if watch_.auto_add and not watch_.exclude_filter(created_dir):
671 addw = self._watch_manager.add_watch
672 # The newly monitored directory inherits attributes from its
673 # parent directory.
674 addw_ret = addw(created_dir, watch_.mask,
675 proc_fun=watch_.proc_fun,
676 rec=False, auto_add=watch_.auto_add,
677 exclude_filter=watch_.exclude_filter)
678
679 # Trick to handle mkdir -p /d1/d2/t3 where d1 is watched and
680 # d2 and t3 (directory or file) are created.
681 # Since the directory d2 is new, then everything inside it must
682 # also be new.
683 created_dir_wd = addw_ret.get(created_dir)
684 if ((created_dir_wd is not None) and (created_dir_wd > 0) and
685 os.path.isdir(created_dir)):
686 try:
687 for name in os.listdir(created_dir):
688 inner = os.path.join(created_dir, name)
689 if self._watch_manager.get_wd(inner) is not None:
690 continue
691 # Generate (simulate) creation events for sub-
692 # directories and files.
693 if os.path.isfile(inner):
694 # symlinks are handled as files.
695 flags = IN_CREATE
696 elif os.path.isdir(inner):
697 flags = IN_CREATE | IN_ISDIR
698 else:
699 # This path should not be taken.
700 continue
701 rawevent = _RawEvent(created_dir_wd, flags, 0, name)
702 self._notifier.append_event(rawevent)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600703 except OSError as err:
704 msg = "process_IN_CREATE, invalid directory: %s"
705 log.debug(msg % str(err))
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500706 return self.process_default(raw_event)
707
708 def process_IN_MOVED_FROM(self, raw_event):
709 """
710 Map the cookie with the source path (+ date for cleaning).
711 """
712 watch_ = self._watch_manager.get_watch(raw_event.wd)
713 path_ = watch_.path
714 src_path = os.path.normpath(os.path.join(path_, raw_event.name))
715 self._mv_cookie[raw_event.cookie] = (src_path, datetime.now())
716 return self.process_default(raw_event, {'cookie': raw_event.cookie})
717
718 def process_IN_MOVED_TO(self, raw_event):
719 """
720 Map the source path with the destination path (+ date for
721 cleaning).
722 """
723 watch_ = self._watch_manager.get_watch(raw_event.wd)
724 path_ = watch_.path
725 dst_path = os.path.normpath(os.path.join(path_, raw_event.name))
726 mv_ = self._mv_cookie.get(raw_event.cookie)
727 to_append = {'cookie': raw_event.cookie}
728 if mv_ is not None:
729 self._mv[mv_[0]] = (dst_path, datetime.now())
730 # Let's assume that IN_MOVED_FROM event is always queued before
731 # that its associated (they share a common cookie) IN_MOVED_TO
732 # event is queued itself. It is then possible in that scenario
733 # to provide as additional information to the IN_MOVED_TO event
734 # the original pathname of the moved file/directory.
735 to_append['src_pathname'] = mv_[0]
736 elif (raw_event.mask & IN_ISDIR and watch_.auto_add and
737 not watch_.exclude_filter(dst_path)):
738 # We got a diretory that's "moved in" from an unknown source and
739 # auto_add is enabled. Manually add watches to the inner subtrees.
740 # The newly monitored directory inherits attributes from its
741 # parent directory.
742 self._watch_manager.add_watch(dst_path, watch_.mask,
743 proc_fun=watch_.proc_fun,
744 rec=True, auto_add=True,
745 exclude_filter=watch_.exclude_filter)
746 return self.process_default(raw_event, to_append)
747
748 def process_IN_MOVE_SELF(self, raw_event):
749 """
750 STATUS: the following bug has been fixed in recent kernels (FIXME:
751 which version ?). Now it raises IN_DELETE_SELF instead.
752
753 Old kernels were bugged, this event raised when the watched item
754 were moved, so we had to update its path, but under some circumstances
755 it was impossible: if its parent directory and its destination
756 directory wasn't watched. The kernel (see include/linux/fsnotify.h)
757 doesn't bring us enough informations like the destination path of
758 moved items.
759 """
760 watch_ = self._watch_manager.get_watch(raw_event.wd)
761 src_path = watch_.path
762 mv_ = self._mv.get(src_path)
763 if mv_:
764 dest_path = mv_[0]
765 watch_.path = dest_path
766 # add the separator to the source path to avoid overlapping
767 # path issue when testing with startswith()
768 src_path += os.path.sep
769 src_path_len = len(src_path)
770 # The next loop renames all watches with src_path as base path.
771 # It seems that IN_MOVE_SELF does not provide IN_ISDIR information
772 # therefore the next loop is iterated even if raw_event is a file.
773 for w in self._watch_manager.watches.values():
774 if w.path.startswith(src_path):
775 # Note that dest_path is a normalized path.
776 w.path = os.path.join(dest_path, w.path[src_path_len:])
777 else:
778 log.error("The pathname '%s' of this watch %s has probably changed "
779 "and couldn't be updated, so it cannot be trusted "
780 "anymore. To fix this error move directories/files only "
781 "between watched parents directories, in this case e.g. "
782 "put a watch on '%s'.",
783 watch_.path, watch_,
784 os.path.normpath(os.path.join(watch_.path,
785 os.path.pardir)))
786 if not watch_.path.endswith('-unknown-path'):
787 watch_.path += '-unknown-path'
788 return self.process_default(raw_event)
789
790 def process_IN_Q_OVERFLOW(self, raw_event):
791 """
792 Only signal an overflow, most of the common flags are irrelevant
793 for this event (path, wd, name).
794 """
795 return Event({'mask': raw_event.mask})
796
797 def process_IN_IGNORED(self, raw_event):
798 """
799 The watch descriptor raised by this event is now ignored (forever),
800 it can be safely deleted from the watch manager dictionary.
801 After this event we can be sure that neither the event queue nor
802 the system will raise an event associated to this wd again.
803 """
804 event_ = self.process_default(raw_event)
805 self._watch_manager.del_watch(raw_event.wd)
806 return event_
807
808 def process_default(self, raw_event, to_append=None):
809 """
810 Commons handling for the followings events:
811
812 IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE,
813 IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT.
814 """
815 watch_ = self._watch_manager.get_watch(raw_event.wd)
816 if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF):
817 # Unfornulately this information is not provided by the kernel
818 dir_ = watch_.dir
819 else:
820 dir_ = bool(raw_event.mask & IN_ISDIR)
821 dict_ = {'wd': raw_event.wd,
822 'mask': raw_event.mask,
823 'path': watch_.path,
824 'name': raw_event.name,
825 'dir': dir_}
826 if COMPATIBILITY_MODE:
827 dict_['is_dir'] = dir_
828 if to_append is not None:
829 dict_.update(to_append)
830 return Event(dict_)
831
832
833class ProcessEvent(_ProcessEvent):
834 """
835 Process events objects, can be specialized via subclassing, thus its
836 behavior can be overriden:
837
838 Note: you should not override __init__ in your subclass instead define
839 a my_init() method, this method will be called automatically from the
840 constructor of this class with its optionals parameters.
841
842 1. Provide specialized individual methods, e.g. process_IN_DELETE for
843 processing a precise type of event (e.g. IN_DELETE in this case).
844 2. Or/and provide methods for processing events by 'family', e.g.
845 process_IN_CLOSE method will process both IN_CLOSE_WRITE and
846 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and
847 process_IN_CLOSE_NOWRITE aren't defined though).
848 3. Or/and override process_default for catching and processing all
849 the remaining types of events.
850 """
851 pevent = None
852
853 def __init__(self, pevent=None, **kargs):
854 """
855 Enable chaining of ProcessEvent instances.
856
857 @param pevent: Optional callable object, will be called on event
858 processing (before self).
859 @type pevent: callable
860 @param kargs: This constructor is implemented as a template method
861 delegating its optionals keyworded arguments to the
862 method my_init().
863 @type kargs: dict
864 """
865 self.pevent = pevent
866 self.my_init(**kargs)
867
868 def my_init(self, **kargs):
869 """
870 This method is called from ProcessEvent.__init__(). This method is
871 empty here and must be redefined to be useful. In effect, if you
872 need to specifically initialize your subclass' instance then you
873 just have to override this method in your subclass. Then all the
874 keyworded arguments passed to ProcessEvent.__init__() will be
875 transmitted as parameters to this method. Beware you MUST pass
876 keyword arguments though.
877
878 @param kargs: optional delegated arguments from __init__().
879 @type kargs: dict
880 """
881 pass
882
883 def __call__(self, event):
884 stop_chaining = False
885 if self.pevent is not None:
886 # By default methods return None so we set as guideline
887 # that methods asking for stop chaining must explicitely
888 # return non None or non False values, otherwise the default
889 # behavior will be to accept chain call to the corresponding
890 # local method.
891 stop_chaining = self.pevent(event)
892 if not stop_chaining:
893 return _ProcessEvent.__call__(self, event)
894
895 def nested_pevent(self):
896 return self.pevent
897
898 def process_IN_Q_OVERFLOW(self, event):
899 """
900 By default this method only reports warning messages, you can overredide
901 it by subclassing ProcessEvent and implement your own
902 process_IN_Q_OVERFLOW method. The actions you can take on receiving this
903 event is either to update the variable max_queued_events in order to
904 handle more simultaneous events or to modify your code in order to
905 accomplish a better filtering diminishing the number of raised events.
906 Because this method is defined, IN_Q_OVERFLOW will never get
907 transmitted as arguments to process_default calls.
908
909 @param event: IN_Q_OVERFLOW event.
910 @type event: dict
911 """
912 log.warning('Event queue overflowed.')
913
914 def process_default(self, event):
915 """
916 Default processing event method. By default does nothing. Subclass
917 ProcessEvent and redefine this method in order to modify its behavior.
918
919 @param event: Event to be processed. Can be of any type of events but
920 IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
921 @type event: Event instance
922 """
923 pass
924
925
926class PrintAllEvents(ProcessEvent):
927 """
928 Dummy class used to print events strings representations. For instance this
929 class is used from command line to print all received events to stdout.
930 """
931 def my_init(self, out=None):
932 """
933 @param out: Where events will be written.
934 @type out: Object providing a valid file object interface.
935 """
936 if out is None:
937 out = sys.stdout
938 self._out = out
939
940 def process_default(self, event):
941 """
942 Writes event string representation to file object provided to
943 my_init().
944
945 @param event: Event to be processed. Can be of any type of events but
946 IN_Q_OVERFLOW events (see method process_IN_Q_OVERFLOW).
947 @type event: Event instance
948 """
949 self._out.write(str(event))
950 self._out.write('\n')
951 self._out.flush()
952
953
954class ChainIfTrue(ProcessEvent):
955 """
956 Makes conditional chaining depending on the result of the nested
957 processing instance.
958 """
959 def my_init(self, func):
960 """
961 Method automatically called from base class constructor.
962 """
963 self._func = func
964
965 def process_default(self, event):
966 return not self._func(event)
967
968
969class Stats(ProcessEvent):
970 """
971 Compute and display trivial statistics about processed events.
972 """
973 def my_init(self):
974 """
975 Method automatically called from base class constructor.
976 """
977 self._start_time = time.time()
978 self._stats = {}
979 self._stats_lock = threading.Lock()
980
981 def process_default(self, event):
982 """
983 Processes |event|.
984 """
985 self._stats_lock.acquire()
986 try:
987 events = event.maskname.split('|')
988 for event_name in events:
989 count = self._stats.get(event_name, 0)
990 self._stats[event_name] = count + 1
991 finally:
992 self._stats_lock.release()
993
994 def _stats_copy(self):
995 self._stats_lock.acquire()
996 try:
997 return self._stats.copy()
998 finally:
999 self._stats_lock.release()
1000
1001 def __repr__(self):
1002 stats = self._stats_copy()
1003
1004 elapsed = int(time.time() - self._start_time)
1005 elapsed_str = ''
1006 if elapsed < 60:
1007 elapsed_str = str(elapsed) + 'sec'
1008 elif 60 <= elapsed < 3600:
1009 elapsed_str = '%dmn%dsec' % (elapsed / 60, elapsed % 60)
1010 elif 3600 <= elapsed < 86400:
1011 elapsed_str = '%dh%dmn' % (elapsed / 3600, (elapsed % 3600) / 60)
1012 elif elapsed >= 86400:
1013 elapsed_str = '%dd%dh' % (elapsed / 86400, (elapsed % 86400) / 3600)
1014 stats['ElapsedTime'] = elapsed_str
1015
1016 l = []
1017 for ev, value in sorted(stats.items(), key=lambda x: x[0]):
1018 l.append(' %s=%s' % (output_format.field_name(ev),
1019 output_format.field_value(value)))
1020 s = '<%s%s >' % (output_format.class_name(self.__class__.__name__),
1021 ''.join(l))
1022 return s
1023
1024 def dump(self, filename):
1025 """
1026 Dumps statistics.
1027
1028 @param filename: filename where stats will be dumped, filename is
1029 created and must not exist prior to this call.
1030 @type filename: string
1031 """
1032 flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001033 fd = os.open(filename, flags, 0o0600)
1034 os.write(fd, bytes(self.__str__(), locale.getpreferredencoding()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001035 os.close(fd)
1036
1037 def __str__(self, scale=45):
1038 stats = self._stats_copy()
1039 if not stats:
1040 return ''
1041
1042 m = max(stats.values())
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001043 unity = scale / m
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001044 fmt = '%%-26s%%-%ds%%s' % (len(output_format.field_value('@' * scale))
1045 + 1)
1046 def func(x):
1047 return fmt % (output_format.field_name(x[0]),
1048 output_format.field_value('@' * int(x[1] * unity)),
1049 output_format.simple('%d' % x[1], 'yellow'))
1050 s = '\n'.join(map(func, sorted(stats.items(), key=lambda x: x[0])))
1051 return s
1052
1053
1054class NotifierError(PyinotifyError):
1055 """
1056 Notifier Exception. Raised on Notifier error.
1057
1058 """
1059 def __init__(self, err):
1060 """
1061 @param err: Exception string's description.
1062 @type err: string
1063 """
1064 PyinotifyError.__init__(self, err)
1065
1066
1067class Notifier:
1068 """
1069 Read notifications, process events.
1070
1071 """
1072 def __init__(self, watch_manager, default_proc_fun=None, read_freq=0,
1073 threshold=0, timeout=None):
1074 """
1075 Initialization. read_freq, threshold and timeout parameters are used
1076 when looping.
1077
1078 @param watch_manager: Watch Manager.
1079 @type watch_manager: WatchManager instance
1080 @param default_proc_fun: Default processing method. If None, a new
1081 instance of PrintAllEvents will be assigned.
1082 @type default_proc_fun: instance of ProcessEvent
1083 @param read_freq: if read_freq == 0, events are read asap,
1084 if read_freq is > 0, this thread sleeps
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001085 max(0, read_freq - (timeout / 1000)) seconds. But if
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001086 timeout is None it may be different because
1087 poll is blocking waiting for something to read.
1088 @type read_freq: int
1089 @param threshold: File descriptor will be read only if the accumulated
1090 size to read becomes >= threshold. If != 0, you likely
1091 want to use it in combination with an appropriate
1092 value for read_freq because without that you would
1093 keep looping without really reading anything and that
1094 until the amount of events to read is >= threshold.
1095 At least with read_freq set you might sleep.
1096 @type threshold: int
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001097 @param timeout: see read_freq above. If provided, it must be set in
1098 milliseconds. See
1099 https://docs.python.org/3/library/select.html#select.poll.poll
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001100 @type timeout: int
1101 """
1102 # Watch Manager instance
1103 self._watch_manager = watch_manager
1104 # File descriptor
1105 self._fd = self._watch_manager.get_fd()
1106 # Poll object and registration
1107 self._pollobj = select.poll()
1108 self._pollobj.register(self._fd, select.POLLIN)
1109 # This pipe is correctely initialized and used by ThreadedNotifier
1110 self._pipe = (-1, -1)
1111 # Event queue
1112 self._eventq = deque()
1113 # System processing functor, common to all events
1114 self._sys_proc_fun = _SysProcessEvent(self._watch_manager, self)
1115 # Default processing method
1116 self._default_proc_fun = default_proc_fun
1117 if default_proc_fun is None:
1118 self._default_proc_fun = PrintAllEvents()
1119 # Loop parameters
1120 self._read_freq = read_freq
1121 self._threshold = threshold
1122 self._timeout = timeout
1123 # Coalesce events option
1124 self._coalesce = False
1125 # set of str(raw_event), only used when coalesce option is True
1126 self._eventset = set()
1127
1128 def append_event(self, event):
1129 """
1130 Append a raw event to the event queue.
1131
1132 @param event: An event.
1133 @type event: _RawEvent instance.
1134 """
1135 self._eventq.append(event)
1136
1137 def proc_fun(self):
1138 return self._default_proc_fun
1139
1140 def coalesce_events(self, coalesce=True):
1141 """
1142 Coalescing events. Events are usually processed by batchs, their size
1143 depend on various factors. Thus, before processing them, events received
1144 from inotify are aggregated in a fifo queue. If this coalescing
1145 option is enabled events are filtered based on their unicity, only
1146 unique events are enqueued, doublons are discarded. An event is unique
1147 when the combination of its fields (wd, mask, cookie, name) is unique
1148 among events of a same batch. After a batch of events is processed any
1149 events is accepted again. By default this option is disabled, you have
1150 to explictly call this function to turn it on.
1151
1152 @param coalesce: Optional new coalescing value. True by default.
1153 @type coalesce: Bool
1154 """
1155 self._coalesce = coalesce
1156 if not coalesce:
1157 self._eventset.clear()
1158
1159 def check_events(self, timeout=None):
1160 """
1161 Check for new events available to read, blocks up to timeout
1162 milliseconds.
1163
1164 @param timeout: If specified it overrides the corresponding instance
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001165 attribute _timeout. timeout must be sepcified in
1166 milliseconds.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001167 @type timeout: int
1168
1169 @return: New events to read.
1170 @rtype: bool
1171 """
1172 while True:
1173 try:
1174 # blocks up to 'timeout' milliseconds
1175 if timeout is None:
1176 timeout = self._timeout
1177 ret = self._pollobj.poll(timeout)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001178 except select.error as err:
1179 if err.args[0] == errno.EINTR:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001180 continue # interrupted, retry
1181 else:
1182 raise
1183 else:
1184 break
1185
1186 if not ret or (self._pipe[0] == ret[0][0]):
1187 return False
1188 # only one fd is polled
1189 return ret[0][1] & select.POLLIN
1190
1191 def read_events(self):
1192 """
1193 Read events from device, build _RawEvents, and enqueue them.
1194 """
1195 buf_ = array.array('i', [0])
1196 # get event queue size
1197 if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1:
1198 return
1199 queue_size = buf_[0]
1200 if queue_size < self._threshold:
1201 log.debug('(fd: %d) %d bytes available to read but threshold is '
1202 'fixed to %d bytes', self._fd, queue_size,
1203 self._threshold)
1204 return
1205
1206 try:
1207 # Read content from file
1208 r = os.read(self._fd, queue_size)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001209 except Exception as msg:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001210 raise NotifierError(msg)
1211 log.debug('Event queue size: %d', queue_size)
1212 rsum = 0 # counter
1213 while rsum < queue_size:
1214 s_size = 16
1215 # Retrieve wd, mask, cookie and fname_len
1216 wd, mask, cookie, fname_len = struct.unpack('iIII',
1217 r[rsum:rsum+s_size])
1218 # Retrieve name
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001219 bname, = struct.unpack('%ds' % fname_len,
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001220 r[rsum + s_size:rsum + s_size + fname_len])
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001221 # FIXME: should we explictly call sys.getdefaultencoding() here ??
1222 uname = bname.decode()
1223 rawevent = _RawEvent(wd, mask, cookie, uname)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001224 if self._coalesce:
1225 # Only enqueue new (unique) events.
1226 raweventstr = str(rawevent)
1227 if raweventstr not in self._eventset:
1228 self._eventset.add(raweventstr)
1229 self._eventq.append(rawevent)
1230 else:
1231 self._eventq.append(rawevent)
1232 rsum += s_size + fname_len
1233
1234 def process_events(self):
1235 """
1236 Routine for processing events from queue by calling their
1237 associated proccessing method (an instance of ProcessEvent).
1238 It also does internal processings, to keep the system updated.
1239 """
1240 while self._eventq:
1241 raw_event = self._eventq.popleft() # pop next event
1242 if self._watch_manager.ignore_events:
1243 log.debug("Event ignored: %s" % repr(raw_event))
1244 continue
1245 watch_ = self._watch_manager.get_watch(raw_event.wd)
1246 if (watch_ is None) and not (raw_event.mask & IN_Q_OVERFLOW):
1247 if not (raw_event.mask & IN_IGNORED):
1248 # Not really sure how we ended up here, nor how we should
1249 # handle these types of events and if it is appropriate to
1250 # completly skip them (like we are doing here).
1251 log.warning("Unable to retrieve Watch object associated to %s",
1252 repr(raw_event))
1253 continue
1254 revent = self._sys_proc_fun(raw_event) # system processings
1255 if watch_ and watch_.proc_fun:
1256 watch_.proc_fun(revent) # user processings
1257 else:
1258 self._default_proc_fun(revent)
1259 self._sys_proc_fun.cleanup() # remove olds MOVED_* events records
1260 if self._coalesce:
1261 self._eventset.clear()
1262
1263 def __daemonize(self, pid_file=None, stdin=os.devnull, stdout=os.devnull,
1264 stderr=os.devnull):
1265 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001266 pid_file: file where the pid will be written. If pid_file=None the pid
1267 is written to /var/run/<sys.argv[0]|pyinotify>.pid, if
1268 pid_file=False no pid_file is written.
1269 stdin, stdout, stderr: files associated to common streams.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001270 """
1271 if pid_file is None:
1272 dirname = '/var/run/'
1273 basename = os.path.basename(sys.argv[0]) or 'pyinotify'
1274 pid_file = os.path.join(dirname, basename + '.pid')
1275
Andrew Geissler82c905d2020-04-13 13:39:40 -05001276 if pid_file and os.path.lexists(pid_file):
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001277 err = 'Cannot daemonize: pid file %s already exists.' % pid_file
1278 raise NotifierError(err)
1279
1280 def fork_daemon():
1281 # Adapted from Chad J. Schroeder's recipe
1282 # @see http://code.activestate.com/recipes/278731/
1283 pid = os.fork()
1284 if (pid == 0):
1285 # parent 2
1286 os.setsid()
1287 pid = os.fork()
1288 if (pid == 0):
1289 # child
1290 os.chdir('/')
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001291 os.umask(0o022)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001292 else:
1293 # parent 2
1294 os._exit(0)
1295 else:
1296 # parent 1
1297 os._exit(0)
1298
1299 fd_inp = os.open(stdin, os.O_RDONLY)
1300 os.dup2(fd_inp, 0)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001301 fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT, 0o0600)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001302 os.dup2(fd_out, 1)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001303 fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT, 0o0600)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001304 os.dup2(fd_err, 2)
1305
1306 # Detach task
1307 fork_daemon()
1308
1309 # Write pid
Andrew Geissler82c905d2020-04-13 13:39:40 -05001310 if pid_file:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001311 flags = os.O_WRONLY|os.O_CREAT|os.O_NOFOLLOW|os.O_EXCL
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001312 fd_pid = os.open(pid_file, flags, 0o0600)
1313 os.write(fd_pid, bytes(str(os.getpid()) + '\n',
1314 locale.getpreferredencoding()))
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001315 os.close(fd_pid)
1316 # Register unlink function
1317 atexit.register(lambda : os.unlink(pid_file))
1318
1319 def _sleep(self, ref_time):
1320 # Only consider sleeping if read_freq is > 0
1321 if self._read_freq > 0:
1322 cur_time = time.time()
1323 sleep_amount = self._read_freq - (cur_time - ref_time)
1324 if sleep_amount > 0:
1325 log.debug('Now sleeping %d seconds', sleep_amount)
1326 time.sleep(sleep_amount)
1327
1328 def loop(self, callback=None, daemonize=False, **args):
1329 """
1330 Events are read only one time every min(read_freq, timeout)
1331 seconds at best and only if the size to read is >= threshold.
1332 After this method returns it must not be called again for the same
1333 instance.
1334
1335 @param callback: Functor called after each event processing iteration.
1336 Expects to receive the notifier object (self) as first
1337 parameter. If this function returns True the loop is
1338 immediately terminated otherwise the loop method keeps
1339 looping.
1340 @type callback: callable object or function
1341 @param daemonize: This thread is daemonized if set to True.
1342 @type daemonize: boolean
1343 @param args: Optional and relevant only if daemonize is True. Remaining
1344 keyworded arguments are directly passed to daemonize see
1345 __daemonize() method. If pid_file=None or is set to a
1346 pathname the caller must ensure the file does not exist
1347 before this method is called otherwise an exception
1348 pyinotify.NotifierError will be raised. If pid_file=False
1349 it is still daemonized but the pid is not written in any
1350 file.
1351 @type args: various
1352 """
1353 if daemonize:
1354 self.__daemonize(**args)
1355
1356 # Read and process events forever
1357 while 1:
1358 try:
1359 self.process_events()
1360 if (callback is not None) and (callback(self) is True):
1361 break
1362 ref_time = time.time()
1363 # check_events is blocking
1364 if self.check_events():
1365 self._sleep(ref_time)
1366 self.read_events()
1367 except KeyboardInterrupt:
1368 # Stop monitoring if sigint is caught (Control-C).
1369 log.debug('Pyinotify stops monitoring.')
1370 break
1371 # Close internals
1372 self.stop()
1373
1374 def stop(self):
1375 """
1376 Close inotify's instance (close its file descriptor).
1377 It destroys all existing watches, pending events,...
1378 This method is automatically called at the end of loop().
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001379 Afterward it is invalid to access this instance.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001380 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001381 if self._fd is not None:
1382 self._pollobj.unregister(self._fd)
1383 os.close(self._fd)
1384 self._fd = None
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001385 self._sys_proc_fun = None
1386
1387
1388class ThreadedNotifier(threading.Thread, Notifier):
1389 """
1390 This notifier inherits from threading.Thread for instanciating a separate
1391 thread, and also inherits from Notifier, because it is a threaded notifier.
1392
1393 Note that every functionality provided by this class is also provided
1394 through Notifier class. Moreover Notifier should be considered first because
1395 it is not threaded and could be easily daemonized.
1396 """
1397 def __init__(self, watch_manager, default_proc_fun=None, read_freq=0,
1398 threshold=0, timeout=None):
1399 """
1400 Initialization, initialize base classes. read_freq, threshold and
1401 timeout parameters are used when looping.
1402
1403 @param watch_manager: Watch Manager.
1404 @type watch_manager: WatchManager instance
1405 @param default_proc_fun: Default processing method. See base class.
1406 @type default_proc_fun: instance of ProcessEvent
1407 @param read_freq: if read_freq == 0, events are read asap,
1408 if read_freq is > 0, this thread sleeps
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001409 max(0, read_freq - (timeout / 1000)) seconds.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001410 @type read_freq: int
1411 @param threshold: File descriptor will be read only if the accumulated
1412 size to read becomes >= threshold. If != 0, you likely
1413 want to use it in combination with an appropriate
1414 value set for read_freq because without that you would
1415 keep looping without really reading anything and that
1416 until the amount of events to read is >= threshold. At
1417 least with read_freq you might sleep.
1418 @type threshold: int
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001419 @param timeout: see read_freq above. If provided, it must be set in
1420 milliseconds. See
1421 https://docs.python.org/3/library/select.html#select.poll.poll
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001422 @type timeout: int
1423 """
1424 # Init threading base class
1425 threading.Thread.__init__(self)
1426 # Stop condition
1427 self._stop_event = threading.Event()
1428 # Init Notifier base class
1429 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq,
1430 threshold, timeout)
1431 # Create a new pipe used for thread termination
1432 self._pipe = os.pipe()
1433 self._pollobj.register(self._pipe[0], select.POLLIN)
1434
1435 def stop(self):
1436 """
1437 Stop notifier's loop. Stop notification. Join the thread.
1438 """
1439 self._stop_event.set()
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001440 os.write(self._pipe[1], b'stop')
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001441 threading.Thread.join(self)
1442 Notifier.stop(self)
1443 self._pollobj.unregister(self._pipe[0])
1444 os.close(self._pipe[0])
1445 os.close(self._pipe[1])
1446
1447 def loop(self):
1448 """
1449 Thread's main loop. Don't meant to be called by user directly.
1450 Call inherited start() method instead.
1451
1452 Events are read only once time every min(read_freq, timeout)
1453 seconds at best and only if the size of events to read is >= threshold.
1454 """
1455 # When the loop must be terminated .stop() is called, 'stop'
1456 # is written to pipe fd so poll() returns and .check_events()
1457 # returns False which make evaluate the While's stop condition
1458 # ._stop_event.isSet() wich put an end to the thread's execution.
1459 while not self._stop_event.isSet():
1460 self.process_events()
1461 ref_time = time.time()
1462 if self.check_events():
1463 self._sleep(ref_time)
1464 self.read_events()
1465
1466 def run(self):
1467 """
1468 Start thread's loop: read and process events until the method
1469 stop() is called.
1470 Never call this method directly, instead call the start() method
1471 inherited from threading.Thread, which then will call run() in
1472 its turn.
1473 """
1474 self.loop()
1475
1476
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001477class TornadoAsyncNotifier(Notifier):
1478 """
1479 Tornado ioloop adapter.
1480
1481 """
1482 def __init__(self, watch_manager, ioloop, callback=None,
1483 default_proc_fun=None, read_freq=0, threshold=0, timeout=None,
1484 channel_map=None):
1485 """
1486 Note that if later you must call ioloop.close() be sure to let the
1487 default parameter to all_fds=False.
1488
1489 See example tornado_notifier.py for an example using this notifier.
1490
1491 @param ioloop: Tornado's IO loop.
1492 @type ioloop: tornado.ioloop.IOLoop instance.
1493 @param callback: Functor called at the end of each call to handle_read
1494 (IOLoop's read handler). Expects to receive the
1495 notifier object (self) as single parameter.
1496 @type callback: callable object or function
1497 """
1498 self.io_loop = ioloop
1499 self.handle_read_callback = callback
1500 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq,
1501 threshold, timeout)
1502 ioloop.add_handler(self._fd, self.handle_read, ioloop.READ)
1503
1504 def stop(self):
1505 self.io_loop.remove_handler(self._fd)
1506 Notifier.stop(self)
1507
1508 def handle_read(self, *args, **kwargs):
1509 """
1510 See comment in AsyncNotifier.
1511
1512 """
1513 self.read_events()
1514 self.process_events()
1515 if self.handle_read_callback is not None:
1516 self.handle_read_callback(self)
1517
1518
1519class AsyncioNotifier(Notifier):
1520 """
1521
1522 asyncio/trollius event loop adapter.
1523
1524 """
1525 def __init__(self, watch_manager, loop, callback=None,
1526 default_proc_fun=None, read_freq=0, threshold=0, timeout=None):
1527 """
1528
1529 See examples/asyncio_notifier.py for an example usage.
1530
1531 @param loop: asyncio or trollius event loop instance.
1532 @type loop: asyncio.BaseEventLoop or trollius.BaseEventLoop instance.
1533 @param callback: Functor called at the end of each call to handle_read.
1534 Expects to receive the notifier object (self) as
1535 single parameter.
1536 @type callback: callable object or function
1537
1538 """
1539 self.loop = loop
1540 self.handle_read_callback = callback
1541 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq,
1542 threshold, timeout)
1543 loop.add_reader(self._fd, self.handle_read)
1544
1545 def stop(self):
1546 self.loop.remove_reader(self._fd)
1547 Notifier.stop(self)
1548
1549 def handle_read(self, *args, **kwargs):
1550 self.read_events()
1551 self.process_events()
1552 if self.handle_read_callback is not None:
1553 self.handle_read_callback(self)
1554
1555
1556class Watch:
1557 """
1558 Represent a watch, i.e. a file or directory being watched.
1559
1560 """
1561 __slots__ = ('wd', 'path', 'mask', 'proc_fun', 'auto_add',
1562 'exclude_filter', 'dir')
1563
1564 def __init__(self, wd, path, mask, proc_fun, auto_add, exclude_filter):
1565 """
1566 Initializations.
1567
1568 @param wd: Watch descriptor.
1569 @type wd: int
1570 @param path: Path of the file or directory being watched.
1571 @type path: str
1572 @param mask: Mask.
1573 @type mask: int
1574 @param proc_fun: Processing callable object.
1575 @type proc_fun:
1576 @param auto_add: Automatically add watches on new directories.
1577 @type auto_add: bool
1578 @param exclude_filter: Boolean function, used to exclude new
1579 directories from being automatically watched.
1580 See WatchManager.__init__
1581 @type exclude_filter: callable object
1582 """
1583 self.wd = wd
1584 self.path = path
1585 self.mask = mask
1586 self.proc_fun = proc_fun
1587 self.auto_add = auto_add
1588 self.exclude_filter = exclude_filter
1589 self.dir = os.path.isdir(self.path)
1590
1591 def __repr__(self):
1592 """
1593 @return: String representation.
1594 @rtype: str
1595 """
1596 s = ' '.join(['%s%s%s' % (output_format.field_name(attr),
1597 output_format.punctuation('='),
1598 output_format.field_value(getattr(self,
1599 attr))) \
1600 for attr in self.__slots__ if not attr.startswith('_')])
1601
1602 s = '%s%s %s %s' % (output_format.punctuation('<'),
1603 output_format.class_name(self.__class__.__name__),
1604 s,
1605 output_format.punctuation('>'))
1606 return s
1607
1608
1609class ExcludeFilter:
1610 """
1611 ExcludeFilter is an exclusion filter.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001612 """
1613 def __init__(self, arg_lst):
1614 """
1615 Examples:
1616 ef1 = ExcludeFilter(["/etc/rc.*", "/etc/hostname"])
1617 ef2 = ExcludeFilter("/my/path/exclude.lst")
1618 Where exclude.lst contains:
1619 /etc/rc.*
1620 /etc/hostname
1621
1622 Note: it is not possible to exclude a file if its encapsulating
1623 directory is itself watched. See this issue for more details
1624 https://github.com/seb-m/pyinotify/issues/31
1625
1626 @param arg_lst: is either a list of patterns or a filename from which
1627 patterns will be loaded.
1628 @type arg_lst: list of str or str
1629 """
1630 if isinstance(arg_lst, str):
1631 lst = self._load_patterns_from_file(arg_lst)
1632 elif isinstance(arg_lst, list):
1633 lst = arg_lst
1634 else:
1635 raise TypeError
1636
1637 self._lregex = []
1638 for regex in lst:
1639 self._lregex.append(re.compile(regex, re.UNICODE))
1640
1641 def _load_patterns_from_file(self, filename):
1642 lst = []
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001643 with open(filename, 'r') as file_obj:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001644 for line in file_obj.readlines():
1645 # Trim leading an trailing whitespaces
1646 pattern = line.strip()
1647 if not pattern or pattern.startswith('#'):
1648 continue
1649 lst.append(pattern)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001650 return lst
1651
1652 def _match(self, regex, path):
1653 return regex.match(path) is not None
1654
1655 def __call__(self, path):
1656 """
1657 @param path: Path to match against provided regexps.
1658 @type path: str
1659 @return: Return True if path has been matched and should
1660 be excluded, False otherwise.
1661 @rtype: bool
1662 """
1663 for regex in self._lregex:
1664 if self._match(regex, path):
1665 return True
1666 return False
1667
1668
1669class WatchManagerError(Exception):
1670 """
1671 WatchManager Exception. Raised on error encountered on watches
1672 operations.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001673 """
1674 def __init__(self, msg, wmd):
1675 """
1676 @param msg: Exception string's description.
1677 @type msg: string
1678 @param wmd: This dictionary contains the wd assigned to paths of the
1679 same call for which watches were successfully added.
1680 @type wmd: dict
1681 """
1682 self.wmd = wmd
1683 Exception.__init__(self, msg)
1684
1685
1686class WatchManager:
1687 """
1688 Provide operations for watching files and directories. Its internal
1689 dictionary is used to reference watched items. When used inside
1690 threaded code, one must instanciate as many WatchManager instances as
1691 there are ThreadedNotifier instances.
1692
1693 """
1694 def __init__(self, exclude_filter=lambda path: False):
1695 """
1696 Initialization: init inotify, init watch manager dictionary.
1697 Raise OSError if initialization fails, raise InotifyBindingNotFoundError
1698 if no inotify binding was found (through ctypes or from direct access to
1699 syscalls).
1700
1701 @param exclude_filter: boolean function, returns True if current
1702 path must be excluded from being watched.
1703 Convenient for providing a common exclusion
1704 filter for every call to add_watch.
1705 @type exclude_filter: callable object
1706 """
1707 self._ignore_events = False
1708 self._exclude_filter = exclude_filter
1709 self._wmd = {} # watch dict key: watch descriptor, value: watch
1710
1711 self._inotify_wrapper = INotifyWrapper.create()
1712 if self._inotify_wrapper is None:
1713 raise InotifyBindingNotFoundError()
1714
1715 self._fd = self._inotify_wrapper.inotify_init() # file descriptor
1716 if self._fd < 0:
1717 err = 'Cannot initialize new instance of inotify, %s'
1718 raise OSError(err % self._inotify_wrapper.str_errno())
1719
1720 def close(self):
1721 """
1722 Close inotify's file descriptor, this action will also automatically
1723 remove (i.e. stop watching) all its associated watch descriptors.
1724 After a call to this method the WatchManager's instance become useless
1725 and cannot be reused, a new instance must then be instanciated. It
1726 makes sense to call this method in few situations for instance if
1727 several independant WatchManager must be instanciated or if all watches
1728 must be removed and no other watches need to be added.
1729 """
1730 os.close(self._fd)
1731
1732 def get_fd(self):
1733 """
1734 Return assigned inotify's file descriptor.
1735
1736 @return: File descriptor.
1737 @rtype: int
1738 """
1739 return self._fd
1740
1741 def get_watch(self, wd):
1742 """
1743 Get watch from provided watch descriptor wd.
1744
1745 @param wd: Watch descriptor.
1746 @type wd: int
1747 """
1748 return self._wmd.get(wd)
1749
1750 def del_watch(self, wd):
1751 """
1752 Remove watch entry associated to watch descriptor wd.
1753
1754 @param wd: Watch descriptor.
1755 @type wd: int
1756 """
1757 try:
1758 del self._wmd[wd]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001759 except KeyError as err:
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001760 log.error('Cannot delete unknown watch descriptor %s' % str(err))
1761
1762 @property
1763 def watches(self):
1764 """
1765 Get a reference on the internal watch manager dictionary.
1766
1767 @return: Internal watch manager dictionary.
1768 @rtype: dict
1769 """
1770 return self._wmd
1771
1772 def __format_path(self, path):
1773 """
1774 Format path to its internal (stored in watch manager) representation.
1775 """
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001776 # path must be a unicode string (str) and is just normalized.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001777 return os.path.normpath(path)
1778
1779 def __add_watch(self, path, mask, proc_fun, auto_add, exclude_filter):
1780 """
1781 Add a watch on path, build a Watch object and insert it in the
1782 watch manager dictionary. Return the wd value.
1783 """
1784 path = self.__format_path(path)
1785 if auto_add and not mask & IN_CREATE:
1786 mask |= IN_CREATE
1787 wd = self._inotify_wrapper.inotify_add_watch(self._fd, path, mask)
1788 if wd < 0:
1789 return wd
1790 watch = Watch(wd=wd, path=path, mask=mask, proc_fun=proc_fun,
1791 auto_add=auto_add, exclude_filter=exclude_filter)
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001792 # wd are _always_ indexed with their original unicode paths in wmd.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001793 self._wmd[wd] = watch
1794 log.debug('New %s', watch)
1795 return wd
1796
1797 def __glob(self, path, do_glob):
1798 if do_glob:
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001799 return glob.iglob(path)
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001800 else:
1801 return [path]
1802
1803 def add_watch(self, path, mask, proc_fun=None, rec=False,
1804 auto_add=False, do_glob=False, quiet=True,
1805 exclude_filter=None):
1806 """
1807 Add watch(s) on the provided |path|(s) with associated |mask| flag
1808 value and optionally with a processing |proc_fun| function and
1809 recursive flag |rec| set to True.
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001810 All |path| components _must_ be str (i.e. unicode) objects.
1811 If |path| is already watched it is ignored, but if it is called with
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001812 option rec=True a watch is put on each one of its not-watched
1813 subdirectory.
1814
1815 @param path: Path to watch, the path can either be a file or a
1816 directory. Also accepts a sequence (list) of paths.
1817 @type path: string or list of strings
1818 @param mask: Bitmask of events.
1819 @type mask: int
1820 @param proc_fun: Processing object.
1821 @type proc_fun: function or ProcessEvent instance or instance of
1822 one of its subclasses or callable object.
1823 @param rec: Recursively add watches from path on all its
1824 subdirectories, set to False by default (doesn't
1825 follows symlinks in any case).
1826 @type rec: bool
1827 @param auto_add: Automatically add watches on newly created
1828 directories in watched parent |path| directory.
1829 If |auto_add| is True, IN_CREATE is ored with |mask|
1830 when the watch is added.
1831 @type auto_add: bool
1832 @param do_glob: Do globbing on pathname (see standard globbing
1833 module for more informations).
1834 @type do_glob: bool
1835 @param quiet: if False raises a WatchManagerError exception on
1836 error. See example not_quiet.py.
1837 @type quiet: bool
1838 @param exclude_filter: predicate (boolean function), which returns
1839 True if the current path must be excluded
1840 from being watched. This argument has
1841 precedence over exclude_filter passed to
1842 the class' constructor.
1843 @type exclude_filter: callable object
1844 @return: dict of paths associated to watch descriptors. A wd value
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001845 is positive if the watch was added sucessfully, otherwise
1846 the value is negative. If the path was invalid or was already
1847 watched it is not included into this returned dictionary.
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001848 @rtype: dict of {str: int}
1849 """
1850 ret_ = {} # return {path: wd, ...}
1851
1852 if exclude_filter is None:
1853 exclude_filter = self._exclude_filter
1854
1855 # normalize args as list elements
1856 for npath in self.__format_param(path):
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001857 # Require that path be a unicode string
1858 if not isinstance(npath, str):
1859 ret_[path] = -3
1860 continue
1861
Patrick Williamsc124f4f2015-09-15 14:41:29 -05001862 # unix pathname pattern expansion
1863 for apath in self.__glob(npath, do_glob):
1864 # recursively list subdirs according to rec param
1865 for rpath in self.__walk_rec(apath, rec):
1866 if not exclude_filter(rpath):
1867 wd = ret_[rpath] = self.__add_watch(rpath, mask,
1868 proc_fun,
1869 auto_add,
1870 exclude_filter)
1871 if wd < 0:
1872 err = ('add_watch: cannot watch %s WD=%d, %s' % \
1873 (rpath, wd,
1874 self._inotify_wrapper.str_errno()))
1875 if quiet:
1876 log.error(err)
1877 else:
1878 raise WatchManagerError(err, ret_)
1879 else:
1880 # Let's say -2 means 'explicitely excluded
1881 # from watching'.
1882 ret_[rpath] = -2
1883 return ret_
1884
1885 def __get_sub_rec(self, lpath):
1886 """
1887 Get every wd from self._wmd if its path is under the path of
1888 one (at least) of those in lpath. Doesn't follow symlinks.
1889
1890 @param lpath: list of watch descriptor
1891 @type lpath: list of int
1892 @return: list of watch descriptor
1893 @rtype: list of int
1894 """
1895 for d in lpath:
1896 root = self.get_path(d)
1897 if root is not None:
1898 # always keep root
1899 yield d
1900 else:
1901 # if invalid
1902 continue
1903
1904 # nothing else to expect
1905 if not os.path.isdir(root):
1906 continue
1907
1908 # normalization
1909 root = os.path.normpath(root)
1910 # recursion
1911 lend = len(root)
1912 for iwd in self._wmd.items():
1913 cur = iwd[1].path
1914 pref = os.path.commonprefix([root, cur])
1915 if root == os.sep or (len(pref) == lend and \
1916 len(cur) > lend and \
1917 cur[lend] == os.sep):
1918 yield iwd[1].wd
1919
1920 def update_watch(self, wd, mask=None, proc_fun=None, rec=False,
1921 auto_add=False, quiet=True):
1922 """
1923 Update existing watch descriptors |wd|. The |mask| value, the
1924 processing object |proc_fun|, the recursive param |rec| and the
1925 |auto_add| and |quiet| flags can all be updated.
1926
1927 @param wd: Watch Descriptor to update. Also accepts a list of
1928 watch descriptors.
1929 @type wd: int or list of int
1930 @param mask: Optional new bitmask of events.
1931 @type mask: int
1932 @param proc_fun: Optional new processing function.
1933 @type proc_fun: function or ProcessEvent instance or instance of
1934 one of its subclasses or callable object.
1935 @param rec: Optionally adds watches recursively on all
1936 subdirectories contained into |wd| directory.
1937 @type rec: bool
1938 @param auto_add: Automatically adds watches on newly created
1939 directories in the watch's path corresponding to |wd|.
1940 If |auto_add| is True, IN_CREATE is ored with |mask|
1941 when the watch is updated.
1942 @type auto_add: bool
1943 @param quiet: If False raises a WatchManagerError exception on
1944 error. See example not_quiet.py
1945 @type quiet: bool
1946 @return: dict of watch descriptors associated to booleans values.
1947 True if the corresponding wd has been successfully
1948 updated, False otherwise.
1949 @rtype: dict of {int: bool}
1950 """
1951 lwd = self.__format_param(wd)
1952 if rec:
1953 lwd = self.__get_sub_rec(lwd)
1954
1955 ret_ = {} # return {wd: bool, ...}
1956 for awd in lwd:
1957 apath = self.get_path(awd)
1958 if not apath or awd < 0:
1959 err = 'update_watch: invalid WD=%d' % awd
1960 if quiet:
1961 log.error(err)
1962 continue
1963 raise WatchManagerError(err, ret_)
1964
1965 if mask:
1966 wd_ = self._inotify_wrapper.inotify_add_watch(self._fd, apath,
1967 mask)
1968 if wd_ < 0:
1969 ret_[awd] = False
1970 err = ('update_watch: cannot update %s WD=%d, %s' % \
1971 (apath, wd_, self._inotify_wrapper.str_errno()))
1972 if quiet:
1973 log.error(err)
1974 continue
1975 raise WatchManagerError(err, ret_)
1976
1977 assert(awd == wd_)
1978
1979 if proc_fun or auto_add:
1980 watch_ = self._wmd[awd]
1981
1982 if proc_fun:
1983 watch_.proc_fun = proc_fun
1984
1985 if auto_add:
1986 watch_.auto_add = auto_add
1987
1988 ret_[awd] = True
1989 log.debug('Updated watch - %s', self._wmd[awd])
1990 return ret_
1991
1992 def __format_param(self, param):
1993 """
1994 @param param: Parameter.
1995 @type param: string or int
1996 @return: wrap param.
1997 @rtype: list of type(param)
1998 """
1999 if isinstance(param, list):
2000 for p_ in param:
2001 yield p_
2002 else:
2003 yield param
2004
2005 def get_wd(self, path):
2006 """
2007 Returns the watch descriptor associated to path. This method
2008 presents a prohibitive cost, always prefer to keep the WD
2009 returned by add_watch(). If the path is unknown it returns None.
2010
2011 @param path: Path.
2012 @type path: str
2013 @return: WD or None.
2014 @rtype: int or None
2015 """
2016 path = self.__format_path(path)
2017 for iwd in self._wmd.items():
2018 if iwd[1].path == path:
2019 return iwd[0]
2020
2021 def get_path(self, wd):
2022 """
2023 Returns the path associated to WD, if WD is unknown it returns None.
2024
2025 @param wd: Watch descriptor.
2026 @type wd: int
2027 @return: Path or None.
2028 @rtype: string or None
2029 """
2030 watch_ = self._wmd.get(wd)
2031 if watch_ is not None:
2032 return watch_.path
2033
2034 def __walk_rec(self, top, rec):
2035 """
2036 Yields each subdirectories of top, doesn't follow symlinks.
2037 If rec is false, only yield top.
2038
2039 @param top: root directory.
2040 @type top: string
2041 @param rec: recursive flag.
2042 @type rec: bool
2043 @return: path of one subdirectory.
2044 @rtype: string
2045 """
2046 if not rec or os.path.islink(top) or not os.path.isdir(top):
2047 yield top
2048 else:
2049 for root, dirs, files in os.walk(top):
2050 yield root
2051
2052 def rm_watch(self, wd, rec=False, quiet=True):
2053 """
2054 Removes watch(s).
2055
2056 @param wd: Watch Descriptor of the file or directory to unwatch.
2057 Also accepts a list of WDs.
2058 @type wd: int or list of int.
2059 @param rec: Recursively removes watches on every already watched
2060 subdirectories and subfiles.
2061 @type rec: bool
2062 @param quiet: If False raises a WatchManagerError exception on
2063 error. See example not_quiet.py
2064 @type quiet: bool
2065 @return: dict of watch descriptors associated to booleans values.
2066 True if the corresponding wd has been successfully
2067 removed, False otherwise.
2068 @rtype: dict of {int: bool}
2069 """
2070 lwd = self.__format_param(wd)
2071 if rec:
2072 lwd = self.__get_sub_rec(lwd)
2073
2074 ret_ = {} # return {wd: bool, ...}
2075 for awd in lwd:
2076 # remove watch
2077 wd_ = self._inotify_wrapper.inotify_rm_watch(self._fd, awd)
2078 if wd_ < 0:
2079 ret_[awd] = False
2080 err = ('rm_watch: cannot remove WD=%d, %s' % \
2081 (awd, self._inotify_wrapper.str_errno()))
2082 if quiet:
2083 log.error(err)
2084 continue
2085 raise WatchManagerError(err, ret_)
2086
2087 # Remove watch from our dictionary
2088 if awd in self._wmd:
2089 del self._wmd[awd]
2090 ret_[awd] = True
2091 log.debug('Watch WD=%d (%s) removed', awd, self.get_path(awd))
2092 return ret_
2093
2094
2095 def watch_transient_file(self, filename, mask, proc_class):
2096 """
2097 Watch a transient file, which will be created and deleted frequently
2098 over time (e.g. pid file).
2099
2100 @attention: Currently under the call to this function it is not
2101 possible to correctly watch the events triggered into the same
2102 base directory than the directory where is located this watched
2103 transient file. For instance it would be wrong to make these
2104 two successive calls: wm.watch_transient_file('/var/run/foo.pid', ...)
2105 and wm.add_watch('/var/run/', ...)
2106
2107 @param filename: Filename.
2108 @type filename: string
2109 @param mask: Bitmask of events, should contain IN_CREATE and IN_DELETE.
2110 @type mask: int
2111 @param proc_class: ProcessEvent (or of one of its subclass), beware of
2112 accepting a ProcessEvent's instance as argument into
2113 __init__, see transient_file.py example for more
2114 details.
2115 @type proc_class: ProcessEvent's instance or of one of its subclasses.
2116 @return: Same as add_watch().
2117 @rtype: Same as add_watch().
2118 """
2119 dirname = os.path.dirname(filename)
2120 if dirname == '':
2121 return {} # Maintains coherence with add_watch()
2122 basename = os.path.basename(filename)
2123 # Assuming we are watching at least for IN_CREATE and IN_DELETE
2124 mask |= IN_CREATE | IN_DELETE
2125
2126 def cmp_name(event):
2127 if getattr(event, 'name') is None:
2128 return False
2129 return basename == event.name
2130 return self.add_watch(dirname, mask,
2131 proc_fun=proc_class(ChainIfTrue(func=cmp_name)),
2132 rec=False,
2133 auto_add=False, do_glob=False,
2134 exclude_filter=lambda path: False)
2135
2136 def get_ignore_events(self):
2137 return self._ignore_events
2138
2139 def set_ignore_events(self, nval):
2140 self._ignore_events = nval
2141
2142 ignore_events = property(get_ignore_events, set_ignore_events,
2143 "Make watch manager ignoring new events.")
2144
2145
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002146class RawOutputFormat:
2147 """
2148 Format string representations.
2149 """
2150 def __init__(self, format=None):
2151 self.format = format or {}
2152
2153 def simple(self, s, attribute):
2154 if not isinstance(s, str):
2155 s = str(s)
2156 return (self.format.get(attribute, '') + s +
2157 self.format.get('normal', ''))
2158
2159 def punctuation(self, s):
2160 """Punctuation color."""
2161 return self.simple(s, 'normal')
2162
2163 def field_value(self, s):
2164 """Field value color."""
2165 return self.simple(s, 'purple')
2166
2167 def field_name(self, s):
2168 """Field name color."""
2169 return self.simple(s, 'blue')
2170
2171 def class_name(self, s):
2172 """Class name color."""
2173 return self.format.get('red', '') + self.simple(s, 'bold')
2174
2175output_format = RawOutputFormat()
2176
2177class ColoredOutputFormat(RawOutputFormat):
2178 """
2179 Format colored string representations.
2180 """
2181 def __init__(self):
2182 f = {'normal': '\033[0m',
2183 'black': '\033[30m',
2184 'red': '\033[31m',
2185 'green': '\033[32m',
2186 'yellow': '\033[33m',
2187 'blue': '\033[34m',
2188 'purple': '\033[35m',
2189 'cyan': '\033[36m',
2190 'bold': '\033[1m',
2191 'uline': '\033[4m',
2192 'blink': '\033[5m',
2193 'invert': '\033[7m'}
2194 RawOutputFormat.__init__(self, f)
2195
2196
2197def compatibility_mode():
2198 """
2199 Use this function to turn on the compatibility mode. The compatibility
2200 mode is used to improve compatibility with Pyinotify 0.7.1 (or older)
2201 programs. The compatibility mode provides additional variables 'is_dir',
2202 'event_name', 'EventsCodes.IN_*' and 'EventsCodes.ALL_EVENTS' as
2203 Pyinotify 0.7.1 provided. Do not call this function from new programs!!
2204 Especially if there are developped for Pyinotify >= 0.8.x.
2205 """
2206 setattr(EventsCodes, 'ALL_EVENTS', ALL_EVENTS)
2207 for evname in globals():
2208 if evname.startswith('IN_'):
2209 setattr(EventsCodes, evname, globals()[evname])
2210 global COMPATIBILITY_MODE
2211 COMPATIBILITY_MODE = True
2212
2213
2214def command_line():
2215 """
2216 By default the watched path is '/tmp' and all types of events are
2217 monitored. Events monitoring serves forever, type c^c to stop it.
2218 """
2219 from optparse import OptionParser
2220
2221 usage = "usage: %prog [options] [path1] [path2] [pathn]"
2222
2223 parser = OptionParser(usage=usage)
2224 parser.add_option("-v", "--verbose", action="store_true",
2225 dest="verbose", help="Verbose mode")
2226 parser.add_option("-r", "--recursive", action="store_true",
2227 dest="recursive",
2228 help="Add watches recursively on paths")
2229 parser.add_option("-a", "--auto_add", action="store_true",
2230 dest="auto_add",
2231 help="Automatically add watches on new directories")
2232 parser.add_option("-g", "--glob", action="store_true",
2233 dest="glob",
2234 help="Treat paths as globs")
2235 parser.add_option("-e", "--events-list", metavar="EVENT[,...]",
2236 dest="events_list",
2237 help=("A comma-separated list of events to watch for - "
2238 "see the documentation for valid options (defaults"
2239 " to everything)"))
2240 parser.add_option("-s", "--stats", action="store_true",
2241 dest="stats",
2242 help="Display dummy statistics")
2243 parser.add_option("-V", "--version", action="store_true",
2244 dest="version", help="Pyinotify version")
2245 parser.add_option("-f", "--raw-format", action="store_true",
2246 dest="raw_format",
2247 help="Disable enhanced output format.")
2248 parser.add_option("-c", "--command", action="store",
2249 dest="command",
2250 help="Shell command to run upon event")
2251
2252 (options, args) = parser.parse_args()
2253
2254 if options.verbose:
2255 log.setLevel(10)
2256
2257 if options.version:
2258 print(__version__)
2259
2260 if not options.raw_format:
2261 global output_format
2262 output_format = ColoredOutputFormat()
2263
2264 if len(args) < 1:
2265 path = '/tmp' # default watched path
2266 else:
2267 path = args
2268
2269 # watch manager instance
2270 wm = WatchManager()
2271 # notifier instance and init
2272 if options.stats:
2273 notifier = Notifier(wm, default_proc_fun=Stats(), read_freq=5)
2274 else:
2275 notifier = Notifier(wm, default_proc_fun=PrintAllEvents())
2276
2277 # What mask to apply
2278 mask = 0
2279 if options.events_list:
2280 events_list = options.events_list.split(',')
2281 for ev in events_list:
2282 evcode = EventsCodes.ALL_FLAGS.get(ev, 0)
2283 if evcode:
2284 mask |= evcode
2285 else:
2286 parser.error("The event '%s' specified with option -e"
2287 " is not valid" % ev)
2288 else:
2289 mask = ALL_EVENTS
2290
2291 # stats
2292 cb_fun = None
2293 if options.stats:
2294 def cb(s):
2295 sys.stdout.write(repr(s.proc_fun()))
2296 sys.stdout.write('\n')
2297 sys.stdout.write(str(s.proc_fun()))
2298 sys.stdout.write('\n')
2299 sys.stdout.flush()
2300 cb_fun = cb
2301
2302 # External command
2303 if options.command:
2304 def cb(s):
2305 subprocess.Popen(options.command, shell=True)
2306 cb_fun = cb
2307
2308 log.debug('Start monitoring %s, (press c^c to halt pyinotify)' % path)
2309
2310 wm.add_watch(path, mask, rec=options.recursive, auto_add=options.auto_add, do_glob=options.glob)
2311 # Loop forever (until sigint signal get caught)
2312 notifier.loop(callback=cb_fun)
2313
2314
2315if __name__ == '__main__':
2316 command_line()