1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 """
22 pyinotify
23
24 @author: Sebastien Martini
25 @license: GPL 2
26 @contact: seb@dbzteam.org
27 """
28
29
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
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.0q"
59
60 __metaclass__ = type
61
62
63
64
65 LIBC = ctypes.cdll.LoadLibrary(ctypes.util.find_library('libc'))
66
67
68
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
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
87
88
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
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
113
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
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
145 return '<%s=%d>' % (self._attrname, self.get_val())
146
147
148
149
150
151
152
153 for i in ('max_queued_events', 'max_user_instances', 'max_user_watches'):
154 SysCtlINotify(i)
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
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
188 if not dirname:
189 return
190
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
215
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
230
231
232
233
234
235
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
291
292
293 FLAG_COLLECTIONS = {'OP_FLAGS': {
294 'IN_ACCESS' : 0x00000001,
295 'IN_MODIFY' : 0x00000002,
296 'IN_ATTRIB' : 0x00000004,
297 'IN_CLOSE_WRITE' : 0x00000008,
298 'IN_CLOSE_NOWRITE' : 0x00000010,
299 'IN_OPEN' : 0x00000020,
300 'IN_MOVED_FROM' : 0x00000040,
301 'IN_MOVED_TO' : 0x00000080,
302 'IN_CREATE' : 0x00000100,
303 'IN_DELETE' : 0x00000200,
304 'IN_DELETE_SELF' : 0x00000400,
305
306 'IN_MOVE_SELF' : 0x00000800,
307 },
308 'EVENT_FLAGS': {
309 'IN_UNMOUNT' : 0x00002000,
310 'IN_Q_OVERFLOW' : 0x00004000,
311 'IN_IGNORED' : 0x00008000,
312 },
313 'SPECIAL_FLAGS': {
314 'IN_ONLYDIR' : 0x01000000,
315
316 'IN_DONT_FOLLOW' : 0x02000000,
317 'IN_MASK_ADD' : 0x20000000,
318
319 'IN_ISDIR' : 0x40000000,
320 'IN_ONESHOT' : 0x80000000,
321 },
322 }
323
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
346 EventsCodes.ALL_FLAGS = {}
347 EventsCodes.ALL_VALUES = {}
348 for flagc, valc in EventsCodes.FLAG_COLLECTIONS.iteritems():
349
350
351 setattr(EventsCodes, flagc, valc)
352
353
354 EventsCodes.ALL_FLAGS.update(valc)
355
356
357
358 for name, val in valc.iteritems():
359 globals()[name] = val
360 EventsCodes.ALL_VALUES[val] = name
361
362
363
364 ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.itervalues())
365
366
368 """
369 Event structure, represent events raised by the system. This
370 is the base class and should be subclassed.
371
372 """
374 """
375 Attach attributes (contained in dict_) to self.
376 """
377 for tpl in dict_.iteritems():
378 setattr(self, *tpl)
379
381 """
382 @return: String representation.
383 @rtype: str
384 """
385 s = ''
386 for attr, value in sorted(self.__dict__.items(), key=lambda x: x[0]):
387 if attr.startswith('_'):
388 continue
389 if attr == 'mask':
390 value = hex(getattr(self, attr))
391 elif isinstance(value, str) and not value:
392 value ="''"
393 if attr == 'pathname' and value and self.dir:
394 value += os.sep
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
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
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
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): path + name
452 - cookie (int): Cookie.
453 - dir (bool): is the event raised against directory.
454
455 """
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.join(self.path, self.name)
465 else:
466 self.pathname = self.path
467 except AttributeError:
468 pass
469
470
472 """
473 ProcessEventError Exception. Raised on ProcessEvent error.
474 """
476 """
477 @param msg: Exception string's description.
478 @type msg: string
479 """
480 Exception.__init__(self, msg)
481
482
484 """
485 Abstract processing event class.
486 """
488 """
489 To behave like a functor the object must be callable.
490 This method is a dispatch method. Lookup order:
491 1. process_MASKNAME method
492 2. process_FAMILY_NAME method
493 3. otherwise call process_default
494
495 @param event: Event to be processed.
496 @type event: Event object
497 @return: By convention when used from the ProcessEvent class:
498 - Returning False or None (default value) means keep on
499 executing next chained functors (see chain.py example).
500 - Returning True instead means do not execute next
501 processing functions.
502 @rtype: bool
503 @raise ProcessEventError: Event object undispatchable,
504 unknown event.
505 """
506 stripped_mask = event.mask - (event.mask & IN_ISDIR)
507 maskname = EventsCodes.ALL_VALUES.get(stripped_mask)
508 if maskname is None:
509 raise ProcessEventError("Unknown mask 0x%08x" % stripped_mask)
510
511
512 meth = getattr(self, 'process_' + maskname, None)
513 if meth is not None:
514 return meth(event)
515
516 meth = getattr(self, 'process_IN_' + maskname.split('_')[1], None)
517 if meth is not None:
518 return meth(event)
519
520 return self.process_default(event)
521
523 return '<%s>' % self.__class__.__name__
524
525
527 """
528 There is three kind of processing according to each event:
529
530 1. special handling (deletion from internal container, bug, ...).
531 2. default treatment: which is applied to most of events.
532 4. IN_ISDIR is never sent alone, he is piggybacked with a standart
533 event, he is not processed as the others events, instead, its
534 value is captured and appropriately aggregated to dst event.
535 """
537 """
538
539 @param wm: Watch Manager.
540 @type wm: WatchManager instance
541 @param notifier: notifier.
542 @type notifier: Instance of Notifier.
543 """
544 self._watch_manager = wm
545 self._notifier = notifier
546 self._mv_cookie = {}
547 self._mv = {}
548
550 """
551 Cleanup (delete) old (>1mn) records contained in self._mv_cookie
552 and self._mv.
553 """
554 date_cur_ = datetime.now()
555 for seq in [self._mv_cookie, self._mv]:
556 for k in seq.keys():
557 if (date_cur_ - seq[k][1]) > timedelta(minutes=1):
558 log.debug('cleanup: deleting entry %s' % seq[k][0])
559 del seq[k]
560
562 """
563 If the event concerns a directory and the auto_add flag of the
564 targetted watch is set to True, a new watch is added on this
565 new directory, with the same attributes's values than those of
566 this watch.
567 """
568 if raw_event.mask & IN_ISDIR:
569 watch_ = self._watch_manager._wmd.get(raw_event.wd)
570 if watch_.auto_add:
571 addw = self._watch_manager.add_watch
572 newwd = addw(os.path.join(watch_.path, raw_event.name),
573 watch_.mask, proc_fun=watch_.proc_fun,
574 rec=False, auto_add=watch_.auto_add)
575
576
577
578
579
580 base = os.path.join(watch_.path, raw_event.name)
581 if newwd[base] > 0:
582 for name in os.listdir(base):
583 inner = os.path.join(base, name)
584 if (os.path.isdir(inner) and
585 self._watch_manager.get_wd(inner) is None):
586
587
588 rawevent = _RawEvent(newwd[base],
589 IN_CREATE | IN_ISDIR,
590 0, name)
591 self._notifier._eventq.append(rawevent)
592 return self.process_default(raw_event)
593
595 """
596 Map the cookie with the source path (+ date for cleaning).
597 """
598 watch_ = self._watch_manager._wmd.get(raw_event.wd)
599 path_ = watch_.path
600 src_path = os.path.normpath(os.path.join(path_, raw_event.name))
601 self._mv_cookie[raw_event.cookie] = (src_path, datetime.now())
602 return self.process_default(raw_event, {'cookie': raw_event.cookie})
603
605 """
606 Map the source path with the destination path (+ date for
607 cleaning).
608 """
609 watch_ = self._watch_manager._wmd.get(raw_event.wd)
610 path_ = watch_.path
611 dst_path = os.path.normpath(os.path.join(path_, raw_event.name))
612 mv_ = self._mv_cookie.get(raw_event.cookie)
613 if mv_:
614 self._mv[mv_[0]] = (dst_path, datetime.now())
615 return self.process_default(raw_event, {'cookie': raw_event.cookie})
616
618 """
619 STATUS: the following bug has been fixed in the recent kernels (fixme:
620 which version ?). Now it raises IN_DELETE_SELF instead.
621
622 Old kernels are bugged, this event is raised when the watched item
623 was moved, so we must update its path, but under some circumstances it
624 can be impossible: if its parent directory and its destination
625 directory aren't watched. The kernel (see include/linux/fsnotify.h)
626 doesn't bring us enough informations like the destination path of
627 moved items.
628 """
629 watch_ = self._watch_manager._wmd.get(raw_event.wd)
630 src_path = watch_.path
631 mv_ = self._mv.get(src_path)
632 if mv_:
633 watch_.path = mv_[0]
634 else:
635 log.error("The path %s of this watch %s must not "
636 "be trusted anymore" % (watch_.path, watch_))
637 if not watch_.path.endswith('-wrong-path'):
638 watch_.path += '-wrong-path'
639
640 return self.process_default(raw_event)
641
643 """
644 Only signal overflow, most of the common flags are irrelevant
645 for this event (path, wd, name).
646 """
647 return Event({'mask': raw_event.mask})
648
650 """
651 The watch descriptor raised by this event is now ignored (forever),
652 it can be safely deleted from watch manager dictionary.
653 After this event we can be sure that neither the event queue
654 neither the system will raise an event associated to this wd.
655 """
656 event_ = self.process_default(raw_event)
657 try:
658 del self._watch_manager._wmd[raw_event.wd]
659 except KeyError, err:
660 log.error(err)
661 return event_
662
664 """
665 Common handling for the following events:
666
667 IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE,
668 IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT.
669 """
670 ret = None
671 watch_ = self._watch_manager._wmd.get(raw_event.wd)
672 if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF):
673
674 dir_ = watch_.dir
675 else:
676 dir_ = bool(raw_event.mask & IN_ISDIR)
677 dict_ = {'wd': raw_event.wd,
678 'mask': raw_event.mask,
679 'path': watch_.path,
680 'name': raw_event.name,
681 'dir': dir_}
682 dict_.update(to_append)
683 return Event(dict_)
684
685
687 """
688 Process events objects, can be specialized via subclassing, thus its
689 behavior can be overriden:
690
691 Note: you should not override __init__ in your subclass instead define
692 a my_init() method, this method will be called from the constructor of
693 this class with optional parameters.
694
695 1. Provide methods, e.g. process_IN_DELETE for processing a given kind
696 of event (eg. IN_DELETE in this case).
697 2. Or/and provide methods for processing events by 'family', e.g.
698 process_IN_CLOSE method will process both IN_CLOSE_WRITE and
699 IN_CLOSE_NOWRITE events (if process_IN_CLOSE_WRITE and
700 process_IN_CLOSE_NOWRITE aren't defined).
701 3. Or/and override process_default for processing the remaining kind of
702 events.
703 """
704 pevent = None
705
706 - def __init__(self, pevent=None, **kargs):
707 """
708 Enable chaining of ProcessEvent instances.
709
710 @param pevent: optional callable object, will be called on event
711 processing (before self).
712 @type pevent: callable
713 @param kargs: optional arguments delagated to template method my_init
714 @type kargs: dict
715 """
716 self.pevent = pevent
717 self.my_init(**kargs)
718
720 """
721 Override this method when subclassing if you want to achieve
722 custom initialization of your subclass' instance. You MUST pass
723 keyword arguments. This method does nothing by default.
724
725 @param kargs: optional arguments delagated to template method my_init
726 @type kargs: dict
727 """
728 pass
729
736
739
741 """
742 Default default processing event method. Print event
743 on standart output.
744
745 @param event: Event to be processed.
746 @type event: Event instance
747 """
748 print(repr(event))
749
750
752 """
753 Makes conditional chaining depending on the result of the nested
754 processing instance.
755 """
758
760 return not self._func(event)
761
762
763 -class Stats(ProcessEvent):
765 self._start_time = time.time()
766 self._stats = {}
767 self._stats_lock = threading.Lock()
768
770 self._stats_lock.acquire()
771 try:
772 events = event.maskname.split('|')
773 for event_name in events:
774 count = self._stats.get(event_name, 0)
775 self._stats[event_name] = count + 1
776 finally:
777 self._stats_lock.release()
778
780 self._stats_lock.acquire()
781 try:
782 return self._stats.copy()
783 finally:
784 self._stats_lock.release()
785
787 stats = self._stats_copy()
788
789 t = int(time.time() - self._start_time)
790 if t < 60:
791 ts = str(t) + 's'
792 elif 60 <= t < 3600:
793 ts = '%.1fmn' % (t / 60.0)
794 elif 3600 <= t < 86400:
795 ts = '%.1fh' % (t / 3600.0)
796 elif t >= 86400:
797 ts = '%.1fd' % (t / 86400.0)
798 stats['ElapsedTime'] = ts
799
800 l = []
801 for ev, value in sorted(stats.items(), key=lambda x: x[0]):
802 l.append(' %s=%s' % (color_theme.field_name(ev),
803 color_theme.field_value(value)))
804 s = '<%s%s >' % (color_theme.class_name(self.__class__.__name__),
805 ''.join(l))
806 return s
807
808 - def dump(self, filename):
809 fo = file(filename, 'wb')
810 try:
811 fo.write(str(self))
812 finally:
813 fo.close()
814
816 stats = self._stats_copy()
817 if not stats:
818 return ''
819
820 m = max(stats.values())
821 unity = int(round(float(m) / scale)) or 1
822 fmt = '%%-26s%%-%ds%%s' % (len(color_theme.field_value('@' * scale))
823 + 1)
824 def func(x):
825 return fmt % (color_theme.field_name(x[0]),
826 color_theme.field_value('@' * (x[1] / unity)),
827 color_theme.yellow('%d' % x[1]))
828 s = '\n'.join(map(func, sorted(stats.items(), key=lambda x: x[0])))
829 return s
830
831
833 """
834 Notifier Exception. Raised on Notifier error.
835
836 """
838 """
839 @param msg: Exception string's description.
840 @type msg: string
841 """
842 Exception.__init__(self, msg)
843
844
846 """
847 Read notifications, process events.
848
849 """
850 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(),
851 read_freq=0, treshold=0, timeout=None):
852 """
853 Initialization. read_freq, treshold and timeout parameters are used
854 when looping.
855
856
857 @param watch_manager: Watch Manager.
858 @type watch_manager: WatchManager instance
859 @param default_proc_fun: Default processing method.
860 @type default_proc_fun: instance of ProcessEvent
861 @param read_freq: if read_freq == 0, events are read asap,
862 if read_freq is > 0, this thread sleeps
863 max(0, read_freq - timeout) seconds. But if
864 timeout is None it can be different because
865 poll is blocking waiting for something to read.
866 @type read_freq: int
867 @param treshold: File descriptor will be read only if its size to
868 read is >= treshold. If != 0, you likely want to
869 use it in combination with read_freq because
870 without that you keep looping without really reading
871 anything and that until the amount to read
872 is >= treshold. At least with read_freq you may sleep.
873 @type treshold: int
874 @param timeout:
875 see http://docs.python.org/lib/poll-objects.html#poll-objects
876 @type timeout: int
877 """
878
879 self._watch_manager = watch_manager
880
881 self._fd = self._watch_manager._fd
882
883 self._pollobj = select.poll()
884 self._pollobj.register(self._fd, select.POLLIN)
885
886 self._eventq = deque()
887
888 self._sys_proc_fun = _SysProcessEvent(self._watch_manager, self)
889
890 self._default_proc_fun = default_proc_fun
891
892 self._read_freq = read_freq
893 self._treshold = treshold
894 self._timeout = timeout
895
897 return self._default_proc_fun
898
900 """
901 Check for new events available to read, blocks up to timeout
902 milliseconds.
903
904 @return: New events to read.
905 @rtype: bool
906 """
907 while True:
908 try:
909
910 ret = self._pollobj.poll(self._timeout)
911 except select.error, err:
912 if err[0] == errno.EINTR:
913 continue
914 else:
915 raise
916 else:
917 break
918
919 if not ret:
920 return False
921
922 return ret[0][1] & select.POLLIN
923
925 """
926 Read events from device, build _RawEvents, and enqueue them.
927 """
928 buf_ = array.array('i', [0])
929
930 if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1:
931 return
932 queue_size = buf_[0]
933 if queue_size < self._treshold:
934 log.debug('(fd: %d) %d bytes available to read but '
935 'treshold is fixed to %d bytes' % (self._fd,
936 queue_size,
937 self._treshold))
938 return
939
940 try:
941
942 r = os.read(self._fd, queue_size)
943 except Exception, msg:
944 raise NotifierError(msg)
945 log.debug('event queue size: %d' % queue_size)
946 rsum = 0
947 while rsum < queue_size:
948 s_size = 16
949
950 s_ = struct.unpack('iIII', r[rsum:rsum+s_size])
951
952 fname_len = s_[3]
953
954 s_ = s_[:-1]
955
956 s_ += struct.unpack('%ds' % fname_len,
957 r[rsum + s_size:rsum + s_size + fname_len])
958 self._eventq.append(_RawEvent(*s_))
959 rsum += s_size + fname_len
960
962 """
963 Routine for processing events from queue by calling their
964 associated proccessing function (instance of ProcessEvent).
965 It also do internal processings, to keep the system updated.
966 """
967 while self._eventq:
968 raw_event = self._eventq.popleft()
969 watch_ = self._watch_manager._wmd.get(raw_event.wd)
970 revent = self._sys_proc_fun(raw_event)
971 if watch_ and watch_.proc_fun:
972 watch_.proc_fun(revent)
973 else:
974 self._default_proc_fun(revent)
975 self._sys_proc_fun.cleanup()
976
977
978 - def __daemonize(self, pid_file=None, force_kill=False, stdin=os.devnull,
979 stdout=os.devnull, stderr=os.devnull):
980 """
981 pid_file: file to which pid will be written.
982 force_kill: if True kill the process associated to pid_file.
983 stdin, stdout, stderr: files associated to common streams.
984 """
985 if pid_file is None:
986 dirname = '/var/run/'
987 basename = sys.argv[0] or 'pyinotify'
988 pid_file = os.path.join(dirname, basename + '.pid')
989
990 if os.path.exists(pid_file):
991 fo = file(pid_file, 'rb')
992 try:
993 try:
994 pid = int(fo.read())
995 except ValueError:
996 pid = None
997 if pid is not None:
998 try:
999 os.kill(pid, 0)
1000 except OSError, err:
1001 pass
1002 else:
1003 if not force_kill:
1004 s = 'There is already a pid file %s with pid %d'
1005 raise NotifierError(s % (pid_file, pid))
1006 else:
1007 os.kill(pid, 9)
1008 finally:
1009 fo.close()
1010
1011
1012 def fork_daemon():
1013
1014 pid = os.fork()
1015 if (pid == 0):
1016
1017 os.setsid()
1018 pid = os.fork()
1019 if (pid == 0):
1020
1021 os.chdir('/')
1022 os.umask(0)
1023 else:
1024
1025 os._exit(0)
1026 else:
1027
1028 os._exit(0)
1029
1030 fd_inp = os.open(stdin, os.O_RDONLY)
1031 os.dup2(fd_inp, 0)
1032 fd_out = os.open(stdout, os.O_WRONLY|os.O_CREAT)
1033 os.dup2(fd_out, 1)
1034 fd_err = os.open(stderr, os.O_WRONLY|os.O_CREAT)
1035 os.dup2(fd_err, 2)
1036
1037
1038 fork_daemon()
1039
1040
1041 fo = file(pid_file, 'wb')
1042 try:
1043 fo.write(str(os.getpid()) + '\n')
1044 finally:
1045 fo.close()
1046
1047 atexit.register(lambda : os.unlink(pid_file))
1048
1049
1051
1052 if self._read_freq > 0:
1053 cur_time = time.time()
1054 sleep_amount = self._read_freq - (cur_time - ref_time)
1055 if sleep_amount > 0:
1056 log.debug('Now sleeping %d seconds' % sleep_amount)
1057 time.sleep(sleep_amount)
1058
1059
1060 - def loop(self, callback=None, daemonize=False, **args):
1061 """
1062 Events are read only once time every min(read_freq, timeout)
1063 seconds at best and only if the size to read is >= treshold.
1064
1065 @param callback: Functor called after each event processing. Expects
1066 to receive notifier object (self) as first parameter.
1067 @type callback: callable
1068 @param daemonize: This thread is daemonized if set to True.
1069 @type daemonize: boolean
1070 """
1071 if daemonize:
1072 self.__daemonize(**args)
1073
1074
1075 while 1:
1076 try:
1077 self.process_events()
1078 if callback is not None:
1079 callback(self)
1080 ref_time = time.time()
1081
1082 if self.check_events():
1083 self._sleep(ref_time)
1084 self.read_events()
1085 except KeyboardInterrupt:
1086
1087 log.debug('stop monitoring...')
1088
1089 self.stop()
1090 break
1091 except Exception, err:
1092 log.error(err)
1093
1095 """
1096 Close the inotify's instance (close its file descriptor).
1097 It destroys all existing watches, pending events,...
1098 """
1099 self._pollobj.unregister(self._fd)
1100 os.close(self._fd)
1101
1102
1104 """
1105 This notifier inherits from threading.Thread for instantiating a separate
1106 thread, and also inherits from Notifier, because it is a threaded notifier.
1107
1108 This class is only maintained for legacy reasons, everything possible with
1109 this class is also possible with Notifier, but Notifier is _better_ under
1110 many aspects (not threaded, can be daemonized, won't unnecessarily read
1111 for events).
1112 """
1113 - def __init__(self, watch_manager, default_proc_fun=ProcessEvent(),
1114 read_freq=0, treshold=0, timeout=10000):
1115 """
1116 Initialization, initialize base classes. read_freq, treshold and
1117 timeout parameters are used when looping.
1118
1119 @param watch_manager: Watch Manager.
1120 @type watch_manager: WatchManager instance
1121 @param default_proc_fun: Default processing method.
1122 @type default_proc_fun: instance of ProcessEvent
1123 @param read_freq: if read_freq == 0, events are read asap,
1124 if read_freq is > 0, this thread sleeps
1125 max(0, read_freq - timeout) seconds.
1126 @type read_freq: int
1127 @param treshold: File descriptor will be read only if its size to
1128 read is >= treshold. If != 0, you likely want to
1129 use it in combination with read_freq because
1130 without that you keep looping without really reading
1131 anything and that until the amount to read
1132 is >= treshold. At least with read_freq you may sleep.
1133 @type treshold: int
1134 @param timeout:
1135 see http://docs.python.org/lib/poll-objects.html#poll-objects
1136 Read the corresponding comment in the source code before changing
1137 it.
1138 @type timeout: int
1139 """
1140
1141 threading.Thread.__init__(self)
1142
1143 self._stop_event = threading.Event()
1144
1145 Notifier.__init__(self, watch_manager, default_proc_fun, read_freq,
1146 treshold, timeout)
1147
1149 """
1150 Stop the notifier's loop. Stop notification. Join the thread.
1151 """
1152 self._stop_event.set()
1153 threading.Thread.join(self)
1154 Notifier.stop(self)
1155
1157 """
1158 Thread's main loop. don't meant to be called by user directly.
1159 Call start() instead.
1160
1161 Events are read only once time every min(read_freq, timeout)
1162 seconds at best and only if the size to read is >= treshold.
1163 """
1164
1165
1166 while not self._stop_event.isSet():
1167 self.process_events()
1168 ref_time = time.time()
1169
1170
1171
1172
1173 if self.check_events():
1174 self._sleep(ref_time)
1175 self.read_events()
1176
1178 """
1179 Start the thread's loop: read and process events until the method
1180 stop() is called.
1181 Never call this method directly, instead call the start() method
1182 inherited from threading.Thread, which then will call run().
1183 """
1184 self.loop()
1185
1186
1188 """
1189 Represent a watch, i.e. a file or directory being watched.
1190
1191 """
1193 """
1194 Initializations.
1195
1196 @param wd: Watch descriptor.
1197 @type wd: int
1198 @param path: Path of the file or directory being watched.
1199 @type path: str
1200 @param mask: Mask.
1201 @type mask: int
1202 @param proc_fun: Processing callable object.
1203 @type proc_fun:
1204 @param auto_add: Automatically add watches on new directories.
1205 @type auto_add: bool
1206 """
1207 for k, v in keys.iteritems():
1208 setattr(self, k, v)
1209 self.dir = os.path.isdir(self.path)
1210
1212 """
1213 @return: String representation.
1214 @rtype: str
1215 """
1216 s = ' '.join(['%s%s%s' % (color_theme.field_name(attr),
1217 color_theme.punct('='),
1218 color_theme.field_value(getattr(self,
1219 attr))) \
1220 for attr in self.__dict__ if not attr.startswith('_')])
1221
1222 s = '%s%s %s %s' % (color_theme.punct('<'),
1223 color_theme.class_name(self.__class__.__name__),
1224 s,
1225 color_theme.punct('>'))
1226 return s
1227
1228
1230 """
1231 WatchManager Exception. Raised on error encountered on watches
1232 operations.
1233
1234 """
1236 """
1237 @param msg: Exception string's description.
1238 @type msg: string
1239 @param wmd: Results of previous operations made by the same function
1240 on previous wd or paths. It also contains the item which
1241 raised this exception.
1242 @type wmd: dict
1243 """
1244 self.wmd = wmd
1245 Exception.__init__(self, msg)
1246
1247
1249 """
1250 Provide operations for watching files and directories. Integrated
1251 dictionary is used to reference watched items.
1252 """
1254 """
1255 Initialization: init inotify, init watch manager dictionary.
1256 Raise OSError if initialization fails.
1257 """
1258 self._wmd = {}
1259 self._fd = LIBC.inotify_init()
1260 if self._fd < 0:
1261 raise OSError()
1262
1263 - def __add_watch(self, path, mask, proc_fun, auto_add):
1264 """
1265 Add a watch on path, build a Watch object and insert it in the
1266 watch manager dictionary. Return the wd value.
1267 """
1268 wd_ = LIBC.inotify_add_watch(self._fd, path, mask)
1269 if wd_ < 0:
1270 return wd_
1271 watch_ = Watch(wd=wd_, path=os.path.normpath(path), mask=mask,
1272 proc_fun=proc_fun, auto_add=auto_add)
1273 self._wmd[wd_] = watch_
1274 log.debug('New %s' % watch_)
1275 return wd_
1276
1277 - def __glob(self, path, do_glob):
1278 if do_glob:
1279 return iglob(path)
1280 else:
1281 return [path]
1282
1283 - def add_watch(self, path, mask, proc_fun=None, rec=False,
1284 auto_add=False, do_glob=False, quiet=True):
1285 """
1286 Add watch(s) on given path(s) with the specified mask and
1287 optionnally with a processing function and recursive flag.
1288
1289 @param path: Path to watch, the path can either be a file or a
1290 directory. Also accepts a sequence (list) of paths.
1291 @type path: string or list of string
1292 @param mask: Bitmask of events.
1293 @type mask: int
1294 @param proc_fun: Processing object.
1295 @type proc_fun: function or ProcessEvent instance or instance of
1296 one of its subclasses or callable object.
1297 @param rec: Recursively add watches from path on all its
1298 subdirectories, set to False by default (doesn't
1299 follows symlinks).
1300 @type rec: bool
1301 @param auto_add: Automatically add watches on newly created
1302 directories in the watch's path.
1303 @type auto_add: bool
1304 @param do_glob: Do globbing on pathname.
1305 @type do_glob: bool
1306 @param quiet: if True raise an WatchManagerError exception on
1307 error. See example not_quiet.py
1308 @type quiet: bool
1309 @return: dict of paths associated to watch descriptors. A wd value
1310 is positive if the watch has been sucessfully added,
1311 otherwise the value is negative. If the path is invalid
1312 it will be not included into this dict.
1313 @rtype: dict of str: int
1314 """
1315 ret_ = {}
1316
1317
1318 for npath in self.__format_param(path):
1319
1320 for apath in self.__glob(npath, do_glob):
1321
1322 for rpath in self.__walk_rec(apath, rec):
1323 wd = ret_[rpath] = self.__add_watch(rpath, mask,
1324 proc_fun, auto_add)
1325 if wd < 0:
1326 err = 'add_watch: cannot watch %s (WD=%d)' % (rpath,
1327 wd)
1328 if quiet:
1329 log.error(err)
1330 else:
1331 raise WatchManagerError(err, ret_)
1332 return ret_
1333
1335 """
1336 Get every wd from self._wmd if its path is under the path of
1337 one (at least) of those in lpath. Doesn't follow symlinks.
1338
1339 @param lpath: list of watch descriptor
1340 @type lpath: list of int
1341 @return: list of watch descriptor
1342 @rtype: list of int
1343 """
1344 for d in lpath:
1345 root = self.get_path(d)
1346 if root:
1347
1348 yield d
1349 else:
1350
1351 continue
1352
1353
1354 if not os.path.isdir(root):
1355 continue
1356
1357
1358 root = os.path.normpath(root)
1359
1360 lend = len(root)
1361 for iwd in self._wmd.items():
1362 cur = iwd[1].path
1363 pref = os.path.commonprefix([root, cur])
1364 if root == os.sep or (len(pref) == lend and \
1365 len(cur) > lend and \
1366 cur[lend] == os.sep):
1367 yield iwd[1].wd
1368
1369 - def update_watch(self, wd, mask=None, proc_fun=None, rec=False,
1370 auto_add=False, quiet=True):
1371 """
1372 Update existing watch(s). Both the mask and the processing
1373 object can be modified.
1374
1375 @param wd: Watch Descriptor to update. Also accepts a list of
1376 watch descriptors.
1377 @type wd: int or list of int
1378 @param mask: Optional new bitmask of events.
1379 @type mask: int
1380 @param proc_fun: Optional new processing function.
1381 @type proc_fun: function or ProcessEvent instance or instance of
1382 one of its subclasses or callable object.
1383 @param rec: Recursively update watches on every already watched
1384 subdirectories and subfiles.
1385 @type rec: bool
1386 @param auto_add: Automatically add watches on newly created
1387 directories in the watch's path.
1388 @type auto_add: bool
1389 @param quiet: if True raise an WatchManagerError exception on
1390 error. See example not_quiet.py
1391 @type quiet: bool
1392 @return: dict of watch descriptors associated to booleans values.
1393 True if the corresponding wd has been successfully
1394 updated, False otherwise.
1395 @rtype: dict of int: bool
1396 """
1397 lwd = self.__format_param(wd)
1398 if rec:
1399 lwd = self.__get_sub_rec(lwd)
1400
1401 ret_ = {}
1402 for awd in lwd:
1403 apath = self.get_path(awd)
1404 if not apath or awd < 0:
1405 err = 'update_watch: invalid WD=%d' % awd
1406 if quiet:
1407 log.error(err)
1408 continue
1409 raise WatchManagerError(err, ret_)
1410
1411 if mask:
1412 wd_ = LIBC.inotify_add_watch(self._fd, apath, mask)
1413 if wd_ < 0:
1414 ret_[awd] = False
1415 err = 'update_watch: cannot update WD=%d (%s)' % (wd_,
1416 apath)
1417 if quiet:
1418 log.error(err)
1419 continue
1420 raise WatchManagerError(err, ret_)
1421
1422 assert(awd == wd_)
1423
1424 if proc_fun or auto_add:
1425 watch_ = self._wmd[awd]
1426
1427 if proc_fun:
1428 watch_.proc_fun = proc_fun
1429
1430 if auto_add:
1431 watch_.proc_fun = auto_add
1432
1433 ret_[awd] = True
1434 log.debug('Updated watch - %s' % self._wmd[awd])
1435 return ret_
1436
1449
1451 """
1452 Returns the watch descriptor associated to path. This method
1453 has an prohibitive cost, always prefer to keep the WD.
1454 If path is unknown None is returned.
1455
1456 @param path: path.
1457 @type path: str
1458 @return: WD or None.
1459 @rtype: int or None
1460 """
1461 path = os.path.normpath(path)
1462 for iwd in self._wmd.iteritems():
1463 if iwd[1].path == path:
1464 return iwd[0]
1465 log.debug('get_wd: unknown path %s' % path)
1466
1468 """
1469 Returns the path associated to WD, if WD is unknown
1470 None is returned.
1471
1472 @param wd: watch descriptor.
1473 @type wd: int
1474 @return: path or None.
1475 @rtype: string or None
1476 """
1477 watch_ = self._wmd.get(wd)
1478 if watch_:
1479 return watch_.path
1480 log.debug('get_path: unknown WD %d' % wd)
1481
1483 """
1484 Yields each subdirectories of top, doesn't follow symlinks.
1485 If rec is false, only yield top.
1486
1487 @param top: root directory.
1488 @type top: string
1489 @param rec: recursive flag.
1490 @type rec: bool
1491 @return: path of one subdirectory.
1492 @rtype: string
1493 """
1494 if not rec or os.path.islink(top) or not os.path.isdir(top):
1495 yield top
1496 else:
1497 for root, dirs, files in os.walk(top):
1498 yield root
1499
1500 - def rm_watch(self, wd, rec=False, quiet=True):
1501 """
1502 Removes watch(s).
1503
1504 @param wd: Watch Descriptor of the file or directory to unwatch.
1505 Also accepts a list of WDs.
1506 @type wd: int or list of int.
1507 @param rec: Recursively removes watches on every already watched
1508 subdirectories and subfiles.
1509 @type rec: bool
1510 @param quiet: if True raise an WatchManagerError exception on
1511 error. See example not_quiet.py
1512 @type quiet: bool
1513 @return: dict of watch descriptors associated to booleans values.
1514 True if the corresponding wd has been successfully
1515 removed, False otherwise.
1516 @rtype: dict of int: bool
1517 """
1518 lwd = self.__format_param(wd)
1519 if rec:
1520 lwd = self.__get_sub_rec(lwd)
1521
1522 ret_ = {}
1523 for awd in lwd:
1524
1525 wd_ = LIBC.inotify_rm_watch(self._fd, awd)
1526 if wd_ < 0:
1527 ret_[awd] = False
1528 err = 'rm_watch: cannot remove WD=%d' % awd
1529 if quiet:
1530 log.error(err)
1531 continue
1532 raise WatchManagerError(err, ret_)
1533
1534 ret_[awd] = True
1535 log.debug('watch WD=%d (%s) removed' % (awd, self.get_path(awd)))
1536 return ret_
1537
1538
1540 """
1541 Watch a transient file, which will be created and deleted frequently
1542 over time (e.g. pid file).
1543
1544 @param filename: Filename.
1545 @type filename: string
1546 @param mask: Bitmask of events, should contain IN_CREATE and IN_DELETE.
1547 @type mask: int
1548 @param proc_class: ProcessEvent (or of one of its subclass), beware of
1549 accepting a ProcessEvent's instance as argument into
1550 __init__, see transient_file.py example for more
1551 details.
1552 @type proc_class: ProcessEvent's instance or of one of its subclasses.
1553 @return: See add_watch().
1554 @rtype: See add_watch().
1555 """
1556 dirname = os.path.dirname(filename)
1557 if dirname == '':
1558 return {}
1559 basename = os.path.basename(filename)
1560
1561 mask |= IN_CREATE | IN_DELETE
1562
1563 def cmp_name(event):
1564 return basename == event.name
1565 return self.add_watch(dirname, mask,
1566 proc_fun=proc_class(ChainIf(func=cmp_name)),
1567 rec=False,
1568 auto_add=False, do_glob=False)
1569
1570
1571
1572
1573
1574
1575
1576
1592
1595 return "<%s>" % self.__class__.__name__
1598
1601
1604 if attr.startswith("__"):
1605 raise AttributeError(attr)
1606 s = "style_%s" % attr
1607 if s in self.__class__.__dict__:
1608 before = getattr(self, s)
1609 after = self.style_normal
1610 else:
1611 before = after = ""
1612
1613 def do_style(val, fmt=None, before=before, after=after):
1614 if fmt is None:
1615 if type(val) is not str:
1616 val = str(val)
1617 else:
1618 val = fmt % val
1619 return before+val+after
1620 return do_style
1621
1622
1623 style_normal = ""
1624 style_prompt = ""
1625 style_punct = ""
1626 style_id = ""
1627 style_not_printable = ""
1628 style_class_name = ""
1629 style_field_name = ""
1630 style_field_value = ""
1631 style_emph_field_name = ""
1632 style_emph_field_value = ""
1633 style_watchlist_name = ""
1634 style_watchlist_type = ""
1635 style_watchlist_value = ""
1636 style_fail = ""
1637 style_success = ""
1638 style_odd = ""
1639 style_even = ""
1640 style_yellow = ""
1641 style_active = ""
1642 style_closed = ""
1643 style_left = ""
1644 style_right = ""
1645
1648
1671
1672 color_theme = DefaultTheme()
1673
1674
1675
1677
1678
1679
1680
1681
1682 from optparse import OptionParser
1683
1684 usage = "usage: %prog [options] [path1] [path2] [pathn]"
1685
1686 parser = OptionParser(usage=usage)
1687 parser.add_option("-v", "--verbose", action="store_true",
1688 dest="verbose", help="Verbose mode")
1689 parser.add_option("-r", "--recursive", action="store_true",
1690 dest="recursive",
1691 help="Add watches recursively on paths")
1692 parser.add_option("-a", "--auto_add", action="store_true",
1693 dest="auto_add",
1694 help="Automatically add watches on new directories")
1695 parser.add_option("-e", "--events-list", metavar="EVENT[,...]",
1696 dest="events_list",
1697 help=("A comma-separated list of events to watch for - "
1698 "see the documentation for valid options (defaults"
1699 " to everything)"))
1700 parser.add_option("-s", "--stats", action="store_true",
1701 dest="stats",
1702 help="Display statistics")
1703
1704 (options, args) = parser.parse_args()
1705
1706 if options.verbose:
1707 log.setLevel(10)
1708
1709 if len(args) < 1:
1710 path = '/tmp'
1711 else:
1712 path = args
1713
1714
1715 wm = WatchManager()
1716
1717 if options.stats:
1718 notifier = Notifier(wm, default_proc_fun=Stats(), read_freq=5)
1719 else:
1720 notifier = Notifier(wm)
1721
1722
1723 mask = 0
1724 if options.events_list:
1725 events_list = options.events_list.split(',')
1726 for ev in events_list:
1727 evcode = EventsCodes.ALL_FLAGS.get(ev, 0)
1728 if evcode:
1729 mask |= evcode
1730 else:
1731 parser.error("The event '%s' specified with option -e"
1732 " is not valid" % ev)
1733 else:
1734 mask = ALL_EVENTS
1735
1736
1737 cb_fun = None
1738 if options.stats:
1739 def cb(s):
1740 print('%s\n%s\n' % (repr(s.proc_fun()),
1741 s.proc_fun()))
1742 cb_fun = cb
1743
1744 wm.add_watch(path, mask, rec=options.recursive, auto_add=options.auto_add)
1745
1746 log.debug('start monitoring %s, (press c^c to halt pyinotify)' % path)
1747
1748 notifier.loop(callback=cb_fun)
1749
1750
1751 if __name__ == '__main__':
1752 command_line()
1753