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