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