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

Source Code for Module pyinotify

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