Module pyinotify
[hide private]
[frames] | no frames]

Source Code for Module pyinotify

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