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