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