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