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  # Check version 
  30  import sys 
  31  if sys.version < '2.4': 
  32      sys.stderr.write('This module requires at least Python 2.4\n') 
  33      sys.exit(1) 
  34   
  35   
  36  # Import directives 
  37  import threading 
  38  import os 
  39  import select 
  40  import struct 
  41  import fcntl 
  42  import errno 
  43  import termios 
  44  import array 
  45  import logging 
  46  import atexit 
  47  from collections import deque 
  48  from datetime import datetime, timedelta 
  49  import time 
  50  import fnmatch 
  51  import re 
  52  import ctypes 
  53  import ctypes.util 
  54   
  55   
  56  __author__ = "seb@dbzteam.org (Sebastien Martini)" 
  57   
  58  __version__ = "0.8.0t" 
  59   
  60  __metaclass__ = type  # Use new-style classes by default 
  61   
  62   
  63  # load libc 
  64  # ctypes.CDLL("libc.so.6") 
  65  LIBC = ctypes.cdll.LoadLibrary(ctypes.util.find_library('libc')) 
  66   
  67   
  68  # logging 
  69  log = logging.getLogger("pyinotify") 
  70  console_handler = logging.StreamHandler() 
  71  console_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s")) 
  72  log.addHandler(console_handler) 
  73  log.setLevel(20) 
  74   
  75   
  76  # Try to speed-up execution with psyco 
  77  try: 
  78      import psyco 
  79      psyco.full() 
  80  except ImportError: 
  81      log.info('Maybe it could speed-up a little bit' 
  82               ' if you had psyco installed (not required).') 
  83   
  84   
  85   
  86  ### inotify's variables ### 
  87   
  88   
89 -class SysCtlINotify:
90 """ 91 Access (read, write) inotify's variables through sysctl. 92 93 Examples: 94 - Read variable: myvar = max_queued_events.value 95 - Update variable: max_queued_events.value = 42 96 """ 97 98 inotify_attrs = {'max_user_instances': 1, 99 'max_user_watches': 2, 100 'max_queued_events': 3} 101
102 - def __new__(cls, *p, **k):
103 attrname = p[0] 104 if not attrname in globals(): 105 globals()[attrname] = super(SysCtlINotify, cls).__new__(cls, *p, 106 **k) 107 return globals()[attrname]
108
109 - def __init__(self, attrname):
110 sino = ctypes.c_int * 3 111 self._attrname = attrname 112 self._attr = sino(5, 20, SysCtlINotify.inotify_attrs[attrname])
113
114 - def get_val(self):
115 """ 116 @return: stored value. 117 @rtype: int 118 """ 119 oldv = ctypes.c_int(0) 120 size = ctypes.c_int(ctypes.sizeof(oldv)) 121 LIBC.sysctl(self._attr, 3, 122 ctypes.c_voidp(ctypes.addressof(oldv)), 123 ctypes.addressof(size), 124 None, 0) 125 return oldv.value
126
127 - def set_val(self, nval):
128 """ 129 @param nval: set to nval. 130 @type nval: int 131 """ 132 oldv = ctypes.c_int(0) 133 sizeo = ctypes.c_int(ctypes.sizeof(oldv)) 134 newv = ctypes.c_int(nval) 135 sizen = ctypes.c_int(ctypes.sizeof(newv)) 136 LIBC.sysctl(self._attr, 3, 137 ctypes.c_voidp(ctypes.addressof(oldv)), 138 ctypes.addressof(sizeo), 139 ctypes.c_voidp(ctypes.addressof(newv)), 140 ctypes.addressof(sizen))
141 142 value = property(get_val, set_val) 143
144 - def __repr__(self):
145 return '<%s=%d>' % (self._attrname, self.get_val())
146 147 148 # singleton instances 149 # 150 # read int: myvar = max_queued_events.value 151 # update: max_queued_events.value = 42 152 # 153 for i in ('max_queued_events', 'max_user_instances', 'max_user_watches'): 154 SysCtlINotify(i) 155 156 157 # fixme: put those tests elsewhere 158 # 159 # print max_queued_events 160 # print max_queued_events.value 161 # save = max_queued_events.value 162 # print save 163 # max_queued_events.value += 42 164 # print max_queued_events 165 # max_queued_events.value = save 166 # print max_queued_events 167 168 169 ### iglob ### 170 171 172 # Code taken from standart Python Lib, slightly modified in order to work 173 # with pyinotify (don't exclude dotted files/dirs like .foo). 174 # Original version: 175 # http://svn.python.org/projects/python/trunk/Lib/glob.py 176
177 -def iglob(pathname):
178 if not has_magic(pathname): 179 if hasattr(os.path, 'lexists'): 180 if os.path.lexists(pathname): 181 yield pathname 182 else: 183 if os.path.islink(pathname) or os.path.exists(pathname): 184 yield pathname 185 return 186 dirname, basename = os.path.split(pathname) 187 # relative pathname 188 if not dirname: 189 return 190 # absolute pathname 191 if has_magic(dirname): 192 dirs = iglob(dirname) 193 else: 194 dirs = [dirname] 195 if has_magic(basename): 196 glob_in_dir = glob1 197 else: 198 glob_in_dir = glob0 199 for dirname in dirs: 200 for name in glob_in_dir(dirname, basename): 201 yield os.path.join(dirname, name)
202
203 -def glob1(dirname, pattern):
204 if not dirname: 205 dirname = os.curdir 206 try: 207 names = os.listdir(dirname) 208 except os.error: 209 return [] 210 return fnmatch.filter(names, pattern)
211
212 -def glob0(dirname, basename):
213 if basename == '' and os.path.isdir(dirname): 214 # `os.path.split()` returns an empty basename for paths ending with a 215 # directory separator. 'q*x/' should match only directories. 216 return [basename] 217 if hasattr(os.path, 'lexists'): 218 if os.path.lexists(os.path.join(dirname, basename)): 219 return [basename] 220 else: 221 if (os.path.islink(os.path.join(dirname, basename)) or 222 os.path.exists(os.path.join(dirname, basename))): 223 return [basename] 224 return []
225 226 magic_check = re.compile('[*?[]') 227
228 -def has_magic(s):
229 return magic_check.search(s) is not None
230 231 232 233 ### Core ### 234 235
236 -class EventsCodes:
237 """ 238 Set of codes corresponding to each kind of events. 239 Some of these flags are used to communicate with inotify, whereas 240 the others are sent to userspace by inotify notifying some events. 241 242 @cvar IN_ACCESS: File was accessed. 243 @type IN_ACCESS: int 244 @cvar IN_MODIFY: File was modified. 245 @type IN_MODIFY: int 246 @cvar IN_ATTRIB: Metadata changed. 247 @type IN_ATTRIB: int 248 @cvar IN_CLOSE_WRITE: Writtable file was closed. 249 @type IN_CLOSE_WRITE: int 250 @cvar IN_CLOSE_NOWRITE: Unwrittable file closed. 251 @type IN_CLOSE_NOWRITE: int 252 @cvar IN_OPEN: File was opened. 253 @type IN_OPEN: int 254 @cvar IN_MOVED_FROM: File was moved from X. 255 @type IN_MOVED_FROM: int 256 @cvar IN_MOVED_TO: File was moved to Y. 257 @type IN_MOVED_TO: int 258 @cvar IN_CREATE: Subfile was created. 259 @type IN_CREATE: int 260 @cvar IN_DELETE: Subfile was deleted. 261 @type IN_DELETE: int 262 @cvar IN_DELETE_SELF: Self (watched item itself) was deleted. 263 @type IN_DELETE_SELF: int 264 @cvar IN_MOVE_SELF: Self (watched item itself) was moved. 265 @type IN_MOVE_SELF: int 266 @cvar IN_UNMOUNT: Backing fs was unmounted. 267 @type IN_UNMOUNT: int 268 @cvar IN_Q_OVERFLOW: Event queued overflowed. 269 @type IN_Q_OVERFLOW: int 270 @cvar IN_IGNORED: File was ignored. 271 @type IN_IGNORED: int 272 @cvar IN_ONLYDIR: only watch the path if it is a directory (new 273 in kernel 2.6.15). 274 @type IN_ONLYDIR: int 275 @cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15). 276 IN_ONLYDIR we can make sure that we don't watch 277 the target of symlinks. 278 @type IN_DONT_FOLLOW: int 279 @cvar IN_MASK_ADD: add to the mask of an already existing watch (new 280 in kernel 2.6.14). 281 @type IN_MASK_ADD: int 282 @cvar IN_ISDIR: Event occurred against dir. 283 @type IN_ISDIR: int 284 @cvar IN_ONESHOT: Only send event once. 285 @type IN_ONESHOT: int 286 @cvar ALL_EVENTS: Alias for considering all of the events. 287 @type ALL_EVENTS: int 288 """ 289 290 # The idea here is 'configuration-as-code' - this way, we get our nice class 291 # constants, but we also get nice human-friendly text mappings to do lookups 292 # against as well, for free: 293 FLAG_COLLECTIONS = {'OP_FLAGS': { 294 'IN_ACCESS' : 0x00000001, # File was accessed 295 'IN_MODIFY' : 0x00000002, # File was modified 296 'IN_ATTRIB' : 0x00000004, # Metadata changed 297 'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed 298 'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed 299 'IN_OPEN' : 0x00000020, # File was opened 300 'IN_MOVED_FROM' : 0x00000040, # File was moved from X 301 'IN_MOVED_TO' : 0x00000080, # File was moved to Y 302 'IN_CREATE' : 0x00000100, # Subfile was created 303 'IN_DELETE' : 0x00000200, # Subfile was deleted 304 'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself) 305 # was deleted 306 'IN_MOVE_SELF' : 0x00000800, # Self (watched item itself) was moved 307 }, 308 'EVENT_FLAGS': { 309 'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted 310 'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed 311 'IN_IGNORED' : 0x00008000, # File was ignored 312 }, 313 'SPECIAL_FLAGS': { 314 'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a 315 # directory 316 'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink 317 'IN_MASK_ADD' : 0x20000000, # add to the mask of an already 318 # existing watch 319 'IN_ISDIR' : 0x40000000, # event occurred against dir 320 'IN_ONESHOT' : 0x80000000, # only send event once 321 }, 322 } 323
324 - def maskname(mask):
325 """ 326 Return the event name associated to mask. IN_ISDIR is appended when 327 appropriate. Note: only one event is returned, because only one is 328 raised once at a time. 329 330 @param mask: mask. 331 @type mask: int 332 @return: event name. 333 @rtype: str 334 """ 335 ms = mask 336 name = '%s' 337 if mask & IN_ISDIR: 338 ms = mask - IN_ISDIR 339 name = '%s|IN_ISDIR' 340 return name % EventsCodes.ALL_VALUES[ms]
341 342 maskname = staticmethod(maskname)
343 344 345 # So let's now turn the configuration into code 346 EventsCodes.ALL_FLAGS = {} 347 EventsCodes.ALL_VALUES = {} 348 for flagc, valc in EventsCodes.FLAG_COLLECTIONS.iteritems(): 349 # Make the collections' members directly accessible through the 350 # class dictionary 351 setattr(EventsCodes, flagc, valc) 352 353 # Collect all the flags under a common umbrella 354 EventsCodes.ALL_FLAGS.update(valc) 355 356 # Make the individual masks accessible as 'constants' at globals() scope 357 # and masknames accessible by values. 358 for name, val in valc.iteritems(): 359 globals()[name] = val 360 EventsCodes.ALL_VALUES[val] = name 361 362 363 # all 'normal' events 364 ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.itervalues()) 365 EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS 366 EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS' 367 368
369 -class _Event:
370 """ 371 Event structure, represent events raised by the system. This 372 is the base class and should be subclassed. 373 374 """
375 - def __init__(self, dict_):
376 """ 377 Attach attributes (contained in dict_) to self. 378 """ 379 for tpl in dict_.iteritems(): 380 setattr(self, *tpl)
381
382 - def __repr__(self):
383 """ 384 @return: String representation. 385 @rtype: str 386 """ 387 s = '' 388 for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]): 389 if attr.startswith('_'): 390 continue 391 if attr == 'mask': 392 value = hex(getattr(self, attr)) 393 elif isinstance(value, str) and not value: 394 value ="''" 395 s += ' %s%s%s' % (color_theme.field_name(attr), 396 color_theme.punct('='), 397 color_theme.field_value(value)) 398 399 s = '%s%s%s %s' % (color_theme.punct('<'), 400 color_theme.class_name(self.__class__.__name__), 401 s, 402 color_theme.punct('>')) 403 return s
404 405
406 -class _RawEvent(_Event):
407 """ 408 Raw event, it contains only the informations provided by the system. 409 It doesn't infer anything. 410 """
411 - def __init__(self, wd, mask, cookie, name):
412 """ 413 @param wd: Watch Descriptor. 414 @type wd: int 415 @param mask: Bitmask of events. 416 @type mask: int 417 @param cookie: Cookie. 418 @type cookie: int 419 @param name: Basename of the file or directory against which the 420 event was raised, in case where the watched directory 421 is the parent directory. None if the event was raised 422 on the watched item itself. 423 @type name: string or None 424 """ 425 # name: remove trailing '\0' 426 super(_RawEvent, self).__init__({'wd': wd, 427 'mask': mask, 428 'cookie': cookie, 429 'name': name.rstrip('\0')}) 430 log.debug(repr(self))
431 432
433 -class Event(_Event):
434 """ 435 This class contains all the useful informations about the observed 436 event. However, the incorporation of each field is not guaranteed and 437 depends on the type of event. In effect, some fields are irrelevant 438 for some kind of event (for example 'cookie' is meaningless for 439 IN_CREATE whereas it is useful for IN_MOVE_TO). 440 441 The possible fields are: 442 - wd (int): Watch Descriptor. 443 - mask (int): Mask. 444 - maskname (str): Readable event name. 445 - path (str): path of the file or directory being watched. 446 - name (str): Basename of the file or directory against which the 447 event was raised, in case where the watched directory 448 is the parent directory. None if the event was raised 449 on the watched item itself. This field is always provided 450 even if the string is ''. 451 - pathname (str): absolute path of: path + name 452 - cookie (int): Cookie. 453 - dir (bool): is the event raised against directory. 454 455 """
456 - def __init__(self, raw):
457 """ 458 Concretely, this is the raw event plus inferred infos. 459 """ 460 _Event.__init__(self, raw) 461 self.maskname = EventsCodes.maskname(self.mask) 462 try: 463 if self.name: 464 self.pathname = os.path.abspath(os.path.join(self.path, 465 self.name)) 466 else: 467 self.pathname = os.path.abspath(self.path) 468 except AttributeError: 469 pass
470 471
472 -class ProcessEventError(Exception):
473 """ 474 ProcessEventError Exception. Raised on ProcessEvent error. 475 """
476 - def __init__(self, msg):
477 """ 478 @param msg: Exception string's description. 479 @type msg: string 480 """ 481 Exception.__init__(self, msg)
482 483
484 -class _ProcessEvent:
485 """ 486 Abstract processing event class. 487 """
488 - def __call__(self, event):
489 """ 490 To behave like a functor the object must be callable. 491 This method is a dispatch method. Lookup order: 492 1. process_MASKNAME method 493 2. process_FAMILY_NAME method 494 3. otherwise call process_default 495 496 @param event: Event to be processed. 497 @type event: Event object 498 @return: By convention when used from the ProcessEvent class: 499 - Returning False or None (default value) means keep on 500 executing next chained functors (see chain.py example). 501 - Returning True instead means do not execute next 502 processing functions. 503 @rtype: bool 504 @raise ProcessEventError: Event object undispatchable, 505 unknown event. 506 """ 507 stripped_mask = event.mask - (event.mask & IN_ISDIR) 508 maskname = EventsCodes.ALL_VALUES.get(stripped_mask) 509 if maskname is None: 510 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask) 511 512 # 1- look for process_MASKNAME 513 meth = getattr(self, 'process_' + maskname, None) 514 if meth is not None: 515 return meth(event) 516 # 2- look for process_FAMILY_NAME 517 meth = getattr(self, 'process_IN_' + maskname.split(