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.0t"
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 EventsCodes.ALL_FLAGS['ALL_EVENTS'] = ALL_EVENTS
366 EventsCodes.ALL_VALUES[ALL_EVENTS] = 'ALL_EVENTS'
367
368
370 """
371 Event structure, represent events raised by the system. This
372 is the base class and should be subclassed.
373
374 """
376 """
377 Attach attributes (contained in dict_) to self.
378 """
379 for tpl in dict_.iteritems():
380 setattr(self, *tpl)
381
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
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): absolute path of: 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.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
473 """
474 ProcessEventError Exception. Raised on ProcessEvent error.
475 """
477 """
478 @param msg: Exception string's description.
479 @type msg: string
480 """
481 Exception.__init__(self, msg)
482
483
485 """
486 Abstract processing event class.
487 """
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
513 meth = getattr(self, 'process_' + maskname, None)
514 if meth is not None:
515 return meth(event)
516
517 meth = getattr(self, 'process_IN_' + maskname.split(