php IHDR w Q )Ba pHYs sRGB gAMA a IDATxMk\U s&uo,mD )Xw+e?tw.oWp;QHZnw`gaiJ9̟灙a=nl[ ʨ G;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ w@H;@ q$ y H@E7j 1j+OFRg}ܫ;@Ea~ j`u'o> j- $_q?qS XzG'ay

| files >> /var/www/html/sub/images/sym/root/opt/lampp/share/xampp-control-panel/ |
| files >> //var/www/html/sub/images/sym/root/opt/lampp/share/xampp-control-panel/pyinotify.py |
# -*- coding: iso-8859-1 -*-
#
# pyinotify.py - python interface to inotify
# Copyright (C) 2005-2007 Sbastien Martini <sebastien.martini@gmail.com>
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# version 2 as published by the Free Software Foundation; version 2.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
# 02111-1307, USA.
"""
pyinotify
@author: Sebastien Martini
@license: GPL 2
@contact: sebastien.martini@gmail.com
"""
# Current TODO
#
# - be sure that the libc's loading is generic enough
# - include/exclude mechanism
# - hashing Watch ?
# - check exceptions
# - accept a proc_fun as argument ?
#
# Incompatibles Changes :
#
# drop support for python 2.3
# is_dir -> dir
# event_name -> maskname
# inotify.* -> pyinotify.
# -> pathname
# VERBOSE -> log.setLevel(level)
# print_err(msg) -> log.error(msg)
# _get_event_name(mask) -> get_masks(mask)
# EventsCodes.IN_* -> IN_*
#
# check version
import sys
if sys.version < '2.4':
sys.stderr.write('This module requires at least Python 2.4\n')
sys.exit(1)
# import directives
import threading
import os
import select
import struct
import fcntl
import errno
import termios
import array
import logging
from collections import deque
from datetime import datetime, timedelta
import fnmatch
import re
import ctypes
import ctypes.util
__author__ = "Sebastien Martini"
__version__ = "0.8.0d"
__metaclass__ = type # Use new-style classes by default
# load libc
# ctypes.CDLL("libc.so.6")
LIBC = ctypes.cdll.LoadLibrary(ctypes.util.find_library('libc'))
# logging
log = logging.getLogger("pyinotify")
console_handler = logging.StreamHandler()
console_handler.setFormatter(logging.Formatter("%(levelname)s: %(message)s"))
log.addHandler(console_handler)
log.setLevel(20)
# speed-up execution with psyco
try:
import psyco
psyco.full()
except ImportError:
log.info('You could try to speed-up the execution if you had psyco installed.')
### inotify's variables ###
class SysCtlINotify:
"""
Access (read, write) inotify's variables through sysctl.
"""
inotify_attrs = {'max_user_instances': 1,
'max_user_watches': 2,
'max_queued_events': 3}
def __new__(cls, *p, **k):
attrname = p[0]
if not attrname in globals():
globals()[attrname] = super(SysCtlINotify, cls).__new__(cls, *p, **k)
return globals()[attrname]
def __init__(self, attrname):
sino = ctypes.c_int * 3
self._attrname = attrname
self._attr = sino(5, 20, SysCtlINotify.inotify_attrs[attrname])
def get_val(self):
"""
@return: stored value.
@rtype: int
"""
oldv = ctypes.c_int(0)
size = ctypes.c_int(ctypes.sizeof(oldv))
LIBC.sysctl(self._attr, 3,
ctypes.c_voidp(ctypes.addressof(oldv)), ctypes.addressof(size),
None, 0)
return oldv.value
def set_val(self, nval):
"""
@param nval: set to nval.
@type nval: int
"""
oldv = ctypes.c_int(0)
sizeo = ctypes.c_int(ctypes.sizeof(oldv))
newv = ctypes.c_int(nval)
sizen = ctypes.c_int(ctypes.sizeof(newv))
LIBC.sysctl(self._attr, 3,
ctypes.c_voidp(ctypes.addressof(oldv)), ctypes.addressof(sizeo),
ctypes.c_voidp(ctypes.addressof(newv)), ctypes.addressof(sizen))
value = property(get_val, set_val)
def __repr__(self):
return '<%s=%d>' % (self._attrname, self.get_val())
# singleton instances
#
# read int: myvar = max_queued_events.value
# update: max_queued_events.value = 42
#
for i in ('max_queued_events', 'max_user_instances', 'max_user_watches'):
SysCtlINotify(i)
# fixme: put these tests elsewhere
#
# print max_queued_events
# print max_queued_events.value
# save = max_queued_events.value
# print save
# max_queued_events.value += 42
# print max_queued_events
# max_queued_events.value = save
# print max_queued_events
### iglob ###
# Code taken from standart Python Lib, slightly modified in order to work
# with pyinotify (don't exclude dotted files/dirs like .foo).
# Original version:
# http://svn.python.org/projects/python/trunk/Lib/glob.py
def iglob(pathname):
if not has_magic(pathname):
if hasattr(os.path, 'lexists'):
if os.path.lexists(pathname):
yield pathname
else:
if os.path.islink(pathname) or os.path.exists(pathname):
yield pathname
return
dirname, basename = os.path.split(pathname)
# relative pathname
if not dirname:
return
# absolute pathname
if has_magic(dirname):
dirs = iglob(dirname)
else:
dirs = [dirname]
if has_magic(basename):
glob_in_dir = glob1
else:
glob_in_dir = glob0
for dirname in dirs:
for name in glob_in_dir(dirname, basename):
yield os.path.join(dirname, name)
def glob1(dirname, pattern):
if not dirname:
dirname = os.curdir
try:
names = os.listdir(dirname)
except os.error:
return []
return fnmatch.filter(names, pattern)
def glob0(dirname, basename):
if basename == '' and os.path.isdir(dirname):
# `os.path.split()` returns an empty basename for paths ending with a
# directory separator. 'q*x/' should match only directories.
return [basename]
if hasattr(os.path, 'lexists'):
if os.path.lexists(os.path.join(dirname, basename)):
return [basename]
else:
if os.path.islink(os.path.join(dirname, basename)) or os.path.exists(os.path.join(dirname, basename)):
return [basename]
return []
magic_check = re.compile('[*?[]')
def has_magic(s):
return magic_check.search(s) is not None
### Core ###
class EventsCodes:
"""
Set of codes corresponding to each kind of events.
Some of these flags are used to communicate with inotify, whereas
the others are sent to userspace by inotify notifying some events.
@cvar IN_ACCESS: File was accessed.
@type IN_ACCESS: int
@cvar IN_MODIFY: File was modified.
@type IN_MODIFY: int
@cvar IN_ATTRIB: Metadata changed.
@type IN_ATTRIB: int
@cvar IN_CLOSE_WRITE: Writtable file was closed.
@type IN_CLOSE_WRITE: int
@cvar IN_CLOSE_NOWRITE: Unwrittable file closed.
@type IN_CLOSE_NOWRITE: int
@cvar IN_OPEN: File was opened.
@type IN_OPEN: int
@cvar IN_MOVED_FROM: File was moved from X.
@type IN_MOVED_FROM: int
@cvar IN_MOVED_TO: File was moved to Y.
@type IN_MOVED_TO: int
@cvar IN_CREATE: Subfile was created.
@type IN_CREATE: int
@cvar IN_DELETE: Subfile was deleted.
@type IN_DELETE: int
@cvar IN_DELETE_SELF: Self (watched item itself) was deleted.
@type IN_DELETE_SELF: int
@cvar IN_MOVE_SELF: Self (watched item itself) was moved.
@type IN_MOVE_SELF: int
@cvar IN_UNMOUNT: Backing fs was unmounted.
@type IN_UNMOUNT: int
@cvar IN_Q_OVERFLOW: Event queued overflowed.
@type IN_Q_OVERFLOW: int
@cvar IN_IGNORED: File was ignored.
@type IN_IGNORED: int
@cvar IN_ONLYDIR: only watch the path if it is a directory (new
in kernel 2.6.15).
@type IN_ONLYDIR: int
@cvar IN_DONT_FOLLOW: don't follow a symlink (new in kernel 2.6.15).
IN_ONLYDIR we can make sure that we don't watch
the target of symlinks.
@type IN_DONT_FOLLOW: int
@cvar IN_MASK_ADD: add to the mask of an already existing watch (new
in kernel 2.6.14).
@type IN_MASK_ADD: int
@cvar IN_ISDIR: Event occurred against dir.
@type IN_ISDIR: int
@cvar IN_ONESHOT: Only send event once.
@type IN_ONESHOT: int
@cvar ALL_EVENTS: Alias for considering all of the events.
@type ALL_EVENTS: int
"""
# The idea here is 'configuration-as-code' - this way, we get our nice class
# constants, but we also get nice human-friendly text mappings to do lookups
# against as well, for free:
FLAG_COLLECTIONS = {'OP_FLAGS': {
'IN_ACCESS' : 0x00000001, # File was accessed
'IN_MODIFY' : 0x00000002, # File was modified
'IN_ATTRIB' : 0x00000004, # Metadata changed
'IN_CLOSE_WRITE' : 0x00000008, # Writable file was closed
'IN_CLOSE_NOWRITE' : 0x00000010, # Unwritable file closed
'IN_OPEN' : 0x00000020, # File was opened
'IN_MOVED_FROM' : 0x00000040, # File was moved from X
'IN_MOVED_TO' : 0x00000080, # File was moved to Y
'IN_CREATE' : 0x00000100, # Subfile was created
'IN_DELETE' : 0x00000200, # Subfile was deleted
'IN_DELETE_SELF' : 0x00000400, # Self (watched item itself) was deleted
'IN_MOVE_SELF' : 0x00000800, # Self (watched item itself) was moved
},
'EVENT_FLAGS': {
'IN_UNMOUNT' : 0x00002000, # Backing fs was unmounted
'IN_Q_OVERFLOW' : 0x00004000, # Event queued overflowed
'IN_IGNORED' : 0x00008000, # File was ignored
},
'SPECIAL_FLAGS': {
'IN_ONLYDIR' : 0x01000000, # only watch the path if it is a directory
'IN_DONT_FOLLOW' : 0x02000000, # don't follow a symlink
'IN_MASK_ADD' : 0x20000000, # add to the mask of an already existing watch
'IN_ISDIR' : 0x40000000, # event occurred against dir
'IN_ONESHOT' : 0x80000000, # only send event once
},
}
def get_masks(mask):
"""
Return the event name associated to the mask parameter, never
return IN_ISDIR. Only one event is returned, because only one
event is raised once at a time.
@param mask: mask.
@type mask: int
@return: event name.
@rtype: str or None
"""
return reduce(lambda x, y: x + '|' + y,
[ec for ec, val in EventsCodes.ALL_FLAGS.iteritems() \
if val & mask])
get_masks = staticmethod(get_masks)
# So let's now turn the configuration into code
EventsCodes.ALL_FLAGS = {}
for flag, fval in EventsCodes.FLAG_COLLECTIONS.iteritems():
# Make the collections' members directly accessible through the
# class dictionary
setattr(EventsCodes, flag, fval)
# Collect all the flags under a common umbrella
EventsCodes.ALL_FLAGS.update(fval)
# Make the individual masks accessible as 'constants' at globals() scope
for mask, mval in fval.iteritems():
globals()[mask] = mval
# all 'normal' events
ALL_EVENTS = reduce(lambda x, y: x | y, EventsCodes.OP_FLAGS.itervalues())
class _Event:
"""
Event structure, represent events raised by the system. This
is the base class and should be subclassed.
"""
def __init__(self, dict_):
"""
Attach attributes (contained in dict_) to self.
"""
for tpl in dict_.iteritems():
setattr(self, *tpl)
def __repr__(self):
"""
@return: String representation.
@rtype: str
"""
s = ''
for attr, value in sorted(self.__dict__.items(),
lambda x,y: cmp(x[0], y[0])):
if attr.startswith('_'):
continue
if attr == 'mask':
value = hex(getattr(self, attr))
elif isinstance(value, str) and not value:
value ="''"
if attr == 'pathname' and value and self.dir:
value += os.sep
s += ' %s%s%s' % (color_theme.field_name(attr),
color_theme.punct('='),
color_theme.field_value(value))
s = '%s%s%s %s' % (color_theme.punct('<'),
color_theme.class_name(self.__class__.__name__),
s,
color_theme.punct('>'))
return s
class _RawEvent(_Event):
"""
Raw event, it contains only the informations provided by the system.
It doesn't infer anything.
"""
def __init__(self, wd, mask, cookie, name):
"""
@param wd: Watch Descriptor.
@type wd: int
@param mask: Bitmask of events.
@type mask: int
@param cookie: Cookie.
@type cookie: int
@param name: Basename of the file or directory against which the
event was raised, in case where the watched directory
is the parent directory. None if the event was raised
on the watched item itself.
@type name: string or None
"""
# name: remove trailing '\0'
super(_RawEvent, self).__init__({'wd': wd,
'mask': mask,
'cookie': cookie,
'name': name.rstrip('\0')})
log.debug(repr(self))
class Event(_Event):
"""
This class contains all the useful informations about the observed
event. However, the incorporation of each field is not guaranteed and
depends on the type of event. In effect, some fields are irrelevant
for some kind of event (for example 'cookie' is meaningless for
IN_CREATE whereas it is useful for IN_MOVE_TO).
The possible fields are:
- wd (int): Watch Descriptor.
- mask (int): Mask.
- maskname (str): Readable event name.
- path (str): path of the file or directory being watched.
- name (str): Basename of the file or directory against which the
event was raised, in case where the watched directory
is the parent directory. None if the event was raised
on the watched item itself. This field is always provided
even if the string is ''.
- pathname (str): path + name
- cookie (int): Cookie.
- dir (bool): is the event raised against directory.
"""
def __init__(self, raw):
"""
Concretely, this is the raw event plus inferred infos.
"""
_Event.__init__(self, raw)
self.maskname = EventsCodes.get_masks(self.mask)
try:
if self.name:
self.pathname = os.path.join(self.path, self.name)
else:
self.pathname = self.path
except AttributeError:
pass
class ProcessEventError(Exception):
"""
ProcessEventError Exception. Raised on ProcessEvent error.
"""
def __init__(self, msg):
"""
@param msg: Exception string's description.
@type msg: string
"""
Exception.__init__(self, msg)
class _ProcessEvent:
"""
Abstract processing event class.
"""
def __call__(self, event):
"""
To behave like a functor the object must be callable.
This method is a dispatch method. Lookup order:
1. process_MASKNAME method
2. process_FAMILY_NAME method
3. otherwise call process_default
@param event: Event to be processed.
@type event: Event object
@raise ProcessEventError: Event object undispatchable,
unknown event.
"""
mask = event.mask
for ec in EventsCodes.ALL_FLAGS.iterkeys():
if ec != 'IN_ISDIR' and (mask & EventsCodes.ALL_FLAGS[ec]):
# 1- look for process_MASKNAME
meth = getattr(self, 'process_%s' % ec, None)
if meth is not None:
return meth(event)
# 2- look for process_FAMILY_NAME
meth = getattr(self, 'process_IN_%s' % ec.split('_')[1], None)
if meth is not None:
return meth(event)
# 3- default method process_default
return self.process_default(event)
else:
raise ProcessEventError("Unknown mask 0x%08x" % mask)
def __repr__(self):
return '<%s>' % self.__class__.__name__
class _SysProcessEvent(_ProcessEvent):
"""
There is three kind of processing according to each event:
1. special handling (deletion from internal container, bug, ...).
2. default treatment: which is applied to most of events.
4. IN_ISDIR is never sent alone, he is piggybacked with a standart
event, he is not processed as the others events, instead, its
value is captured and appropriately aggregated to dst event.
"""
def __init__(self, wm):
"""
@param wm: Watch Manager.
@type wm: WatchManager instance
"""
self._watch_manager = wm # watch manager
self._mv_cookie = {} # {cookie(int): (src_path(str), date), ...}
self._mv = {} # {src_path(str): (dst_path(str), date), ...}
def cleanup(self):
"""
Cleanup (delete) old (>1mn) records contained in self._mv_cookie
and self._mv.
"""
date_cur_ = datetime.now()
for seq in [self._mv_cookie, self._mv]:
for k in seq.keys():
if (date_cur_ - seq[k][1]) > timedelta(minutes=1):
log.debug('cleanup: deleting entry %s' % seq[k][0])
del seq[k]
def process_IN_CREATE(self, raw_event):
"""
If the event concerns a directory and the auto_add flag of the
targetted watch is set to True, a new watch is added on this
new directory, with the same attributes's values than those of
this watch.
"""
# FIXME -- important problem
# The rec=True is just a trick to try to handle mkdir -p /t1/t2/t3
# Sadly, the underlying problem still remains unsolved. And in this
# case the IN_CREATE events for t2 and t3 will never be caught.
if raw_event.mask & IN_ISDIR:
watch_ = self._watch_manager._wmd.get(raw_event.wd)
if watch_.auto_add:
self._watch_manager.add_watch(os.path.join(watch_.path,
raw_event.name),
watch_.mask,
proc_fun=watch_.proc_fun,
rec=True,
auto_add=watch_.auto_add)
return self.process_default(raw_event)
def process_IN_MOVED_FROM(self, raw_event):
"""
Map the cookie with the source path (+ date for cleaning).
"""
watch_ = self._watch_manager._wmd.get(raw_event.wd)
path_ = watch_.path
src_path = os.path.normpath(os.path.join(path_, raw_event.name))
self._mv_cookie[raw_event.cookie] = (src_path, datetime.now())
return self.process_default(raw_event, {'cookie': raw_event.cookie})
def process_IN_MOVED_TO(self, raw_event):
"""
Map the source path with the destination path (+ date for
cleaning).
"""
watch_ = self._watch_manager._wmd.get(raw_event.wd)
path_ = watch_.path
dst_path = os.path.normpath(os.path.join(path_, raw_event.name))
mv_ = self._mv_cookie.get(raw_event.cookie)
if mv_:
self._mv[mv_[0]] = (dst_path, datetime.now())
return self.process_default(raw_event, {'cookie': raw_event.cookie})
def process_IN_MOVE_SELF(self, raw_event):
"""
status: the following bug has been fixed in the recent kernels
(fixme: which version ?). Now it raises IN_DELETE_SELF instead.
Old kernels are bugged, this event is raised when the watched item
was moved, so we must update its path, but under some circumstances it
can be impossible: if its parent directory and its destination
directory aren't watched. The kernel (see include/linux/fsnotify.h)
doesn't bring us enough informations like the destination path of
moved items.
"""
watch_ = self._watch_manager._wmd.get(raw_event.wd)
src_path = watch_.path
mv_ = self._mv.get(src_path)
if mv_:
watch_.path = mv_[0]
else:
log.error('The path %s of this watch %s must not be trusted anymore' % \
(watch_.path, watch_))
if not watch_.path.endswith('-wrong-path'):
watch_.path += '-wrong-path'
# FIXME: should we pass the cookie even if this is not standart?
return self.process_default(raw_event)
def process_IN_Q_OVERFLOW(self, raw_event):
"""
Only signal overflow, most of the common flags are irrelevant
for this event (path, wd, name).
"""
return Event({'mask': raw_event.mask})
def process_IN_IGNORED(self, raw_event):
"""
The watch descriptor raised by this event is now ignored (forever),
it can be safely deleted from watch manager dictionary.
After this event we can be sure that neither the event queue
neither the system will raise an event associated to this wd.
"""
event_ = self.process_default(raw_event)
try:
del self._watch_manager._wmd[raw_event.wd]
except KeyError, err:
log.error(err)
return event_
def process_default(self, raw_event, to_append={}):
"""
Common handling for the following events:
IN_ACCESS, IN_MODIFY, IN_ATTRIB, IN_CLOSE_WRITE, IN_CLOSE_NOWRITE,
IN_OPEN, IN_DELETE, IN_DELETE_SELF, IN_UNMOUNT.
"""
ret = None
watch_ = self._watch_manager._wmd.get(raw_event.wd)
if raw_event.mask & (IN_DELETE_SELF | IN_MOVE_SELF):
# unfornately information not provided by the kernel
dir_ = watch_.dir
else:
dir_ = bool(raw_event.mask & IN_ISDIR)
dict_ = {'wd': raw_event.wd,
'mask': raw_event.mask,
'path': watch_.path,
'name': raw_event.name,
'dir': dir_}
dict_.update(to_append)
return Event(dict_)
class ProcessEvent(_ProcessEvent):
"""
Process events objects, can be specialized via subclassing,
thus its behavior can be overriden:
Note also: if you define an __init__ method you don't need to explicitely
call its parent class.
1. Define an individual method, e.g. process_IN_DELETE for processing
one single kind of event (IN_DELETE in this case).
2. Process events by 'family', e.g. the process_IN_CLOSE method will
process both IN_CLOSE_WRITE and IN_CLOSE_NOWRITE events (if
process_IN_CLOSE_WRITE and process_IN_CLOSE_NOWRITE aren't
defined).
3. Override process_default for processing the remaining kind of
events.
"""
pevent = None
def __init__(self, pevent=None):
"""
Permit chaining of ProcessEvent instances.
"""
self.pevent = pevent
def __call__(self, event):
if self.pevent is not None:
self.pevent(event)
_ProcessEvent.__call__(self, event)
def process_default(self, event):
"""
Default default processing event method. Print event
on standart output.
@param event: Event to be processed.
@type event: Event instance
"""
print repr(event)
class NotifierError(Exception):
"""
Notifier Exception. Raised on Notifier error.
"""
def __init__(self, msg):
"""
@param msg: Exception string's description.
@type msg: string
"""
Exception.__init__(self, msg)
class Notifier:
"""
Read notifications, process events.
"""
def __init__(self, watch_manager, default_proc_fun=ProcessEvent()):
"""
Initialization.
@param watch_manager: Watch Manager.
@type watch_manager: WatchManager instance
@param default_proc_fun: Default processing method.
@type default_proc_fun: instance of ProcessEvent
"""
# watch manager instance
self._watch_manager = watch_manager
# file descriptor
self._fd = self._watch_manager._fd
# poll object and registration
self._pollobj = select.poll()
self._pollobj.register(self._fd, select.POLLIN)
# event queue
self._eventq = deque()
# system processing functor, common to all events
self._sys_proc_fun = _SysProcessEvent(self._watch_manager)
# default processing method
self._default_proc_fun = default_proc_fun
def check_events(self, timeout=1000):
"""
Check for new events available to read, blocks up to timeout
milliseconds.
@param timeout: The timeout is passed on to select.poll(), thus
timeout=0 means 'return immediately', timeout>0
means 'wait up to timeout milliseconds' and
timeout=None means 'wait eternally', i.e. block.
@type timeout: int
@return: New events to read.
@rtype: bool
"""
while True:
try:
# blocks up to 'timeout' milliseconds
ret = self._pollobj.poll(timeout)
except select.error, err:
if err[0] == errno.EINTR:
continue # interrupted, retry
else:
raise
else:
break
if not ret:
return False
# only one fd is polled
return ret[0][1] & select.POLLIN
def read_events(self):
"""
Read events from device, build _RawEvents, and enqueue them.
"""
buf_ = array.array('i', [0])
# get event queue size
if fcntl.ioctl(self._fd, termios.FIONREAD, buf_, 1) == -1:
return
queue_size = buf_[0]
try:
# read content from file
r = os.read(self._fd, queue_size)
except Exception, msg:
raise NotifierError(msg)
log.debug('event queue size: %d' % queue_size)
rsum = 0 # counter
while rsum < queue_size:
s_size = 16
# retrieve wd, mask, cookie
s_ = struct.unpack('iIII', r[rsum:rsum+s_size])
# length of name
fname_len = s_[3]
# field 'length' useless
s_ = s_[:-1]
# retrieve name
s_ += struct.unpack('%ds' % fname_len,
r[rsum + s_size:rsum + s_size + fname_len])
self._eventq.append(_RawEvent(*s_))
rsum += s_size + fname_len
def process_events(self):
"""
Routine for processing events from queue by calling their
associated proccessing function (instance of ProcessEvent).
It also do internal processings, to keep the system updated.
"""
while self._eventq:
raw_event = self._eventq.popleft() # pop next event
watch_ = self._watch_manager._wmd.get(raw_event.wd)
revent = self._sys_proc_fun(raw_event) # system processings
if watch_ and watch_.proc_fun:
watch_.proc_fun(revent) # user processings
else:
self._default_proc_fun(revent)
self._sys_proc_fun.cleanup() # remove olds MOVED_* events records
def stop(self):
"""
Close the inotify's instance (close its file descriptor).
It destroys all existing watches, pending events,...
"""
self._pollobj.unregister(self._fd)
os.close(self._fd)
class ThreadedNotifier(threading.Thread, Notifier):
"""
This notifier inherits from threading.Thread instantiating a separate
thread, and provide standart Notifier functionality. This is a threaded
version of Notifier.
"""
def __init__(self, watch_manager, default_proc_fun=ProcessEvent()):
"""
Initialization, initialize base classes.
@param watch_manager: Watch Manager.
@type watch_manager: WatchManager instance
@param default_proc_fun: Default processing method.
@type default_proc_fun: instance of ProcessEvent
"""
# init threading base class
threading.Thread.__init__(self)
# stop condition
self._stop_event = threading.Event()
# init Notifier base class
Notifier.__init__(self, watch_manager, default_proc_fun)
def stop(self):
"""
Stop the notifier's thread. Stop notification.
"""
self._stop_event.set()
threading.Thread.join(self)
Notifier.stop(self)
def run(self):
"""
Start the thread's task: Read and process events forever (i.e.
until the method stop() is called).
Don't call this method directly, instead call the start() method
inherited from threading.Thread.
"""
while not self._stop_event.isSet():
self.process_events()
if self.check_events():
self.read_events()
class Watch:
"""
Represent a watch, i.e. a file or directory being watched.
"""
# FIXME: is the locking stuff necessary? most of ops seems to be
# atomic, locking seems to be superflu.
def __init__(self, **keys):
"""
Initializations.
@param wd: Watch descriptor.
@type wd: int
@param path: Path of the file or directory being watched.
@type path: str
@param mask: Mask.
@type mask: int
@param proc_fun: Processing object.
@type proc_fun:
@param auto_add: Automatically add watches on new directories.
@type auto_add: bool
"""
object.__setattr__(self, '_w_lock', threading.RLock())
for k,v in keys.iteritems():
setattr(self, k, v)
self.dir = os.path.isdir(self.path)
def __getattribute__(self, name):
"""
Override default behavior to add locking instructions.
@param name: Attribute name.
@type name: str
@return: Attribute value.
@rtype: undefined
"""
lock_obj = object.__getattribute__(self, '_w_lock')
lock_obj.acquire()
try:
ret = object.__getattribute__(self, name)
finally:
lock_obj.release()
return ret
def __setattr__(self, name, value):
"""
Override default behavior to add locking instructions.
@param name: Attribute name.
@type name: str
@param value: new value.
@type value: undefined
"""
self._w_lock.acquire()
try:
ret = object.__setattr__(self, name, value)
finally:
self._w_lock.release()
return ret
def __repr__(self):
"""
@return: String representation.
@rtype: str
"""
s = ' '.join(['%s%s%s' % (color_theme.field_name(attr),
color_theme.punct('='),
color_theme.field_value(getattr(self, attr))) \
for attr in self.__dict__ if not attr.startswith('_')])
s = '%s%s %s %s' % (color_theme.punct('<'),
color_theme.class_name(self.__class__.__name__),
s,
color_theme.punct('>'))
return s
class WatchManager:
"""
Provide operations for watching files and directories. Integrated
dictionary is used to reference watched items.
"""
def __init__(self):
"""
Initialization: init inotify, init watch manager dictionary.
Raise OSError if initialization fails.
"""
self._wmd = {} # watch dict key: watch descriptor, value: watch
self._fd = LIBC.inotify_init() # inotify's init, file descriptor
if self._fd < 0:
raise OSError
def __add_watch(self, path, mask, proc_fun, auto_add):
"""
Add a watch on path, build a Watch object and insert it in the
watch manager dictionary. Return the wd value.
"""
wd_ = LIBC.inotify_add_watch(self._fd, path, mask)
if wd_ < 0:
log.error('add_watch: cannot watch %s (WD=%d)' % (path, wd_))
return wd_
watch_ = Watch(wd=wd_, path=os.path.normpath(path), mask=mask,
proc_fun=proc_fun, auto_add=auto_add)
self._wmd[wd_] = watch_
log.debug('New %s' % watch_)
return wd_
def __glob(self, path, do_glob):
if do_glob:
return iglob(path)
else:
return [path]
def add_watch(self, path, mask, proc_fun=None, rec=False,
auto_add=False, do_glob=False):
"""
Add watch(s) on given path(s) with the specified mask and
optionnally with a processing function and recursive flag.
@param path: Path to watch, the path can either be a file or a
directory. Also accepts a sequence (list) of paths.
@type path: string or list of string
@param mask: Bitmask of events.
@type mask: int
@param proc_fun: Processing object.
@type proc_fun: function or ProcessEvent instance or instance of
one of its subclasses or callable object.
@param rec: Recursively add watches from path on all its
subdirectories, set to False by default (doesn't
follows symlinks).
@type rec: bool
@param auto_add: Automatically add watches on newly created
directories in the watch's path.
@type auto_add: bool
@param do_glob: Do globbing on pathname.
@type do_glob: bool
@return: dict of paths associated to watch descriptors. A wd value
is positive if the watch has been sucessfully added,
otherwise the value is negative. If the path is invalid
it will be not included in this dict.
@rtype: dict of str: int
"""
ret_ = {} # return {path: wd, ...}
# normalize args as list elements
for npath in self.__format_param(path):
# unix pathname pattern expansion
for apath in self.__glob(npath, do_glob):
# recursively list subdirs according to rec param
for rpath in self.__walk_rec(apath, rec):
ret_[rpath] = self.__add_watch(rpath, mask,
proc_fun, auto_add)
return ret_
def __get_sub_rec(self, lpath):
"""
Get every wd from self._wmd if its path is under the path of
one (at least) of those in lpath. Doesn't follow symlinks.
@param lpath: list of watch descriptor
@type lpath: list of int
@return: list of watch descriptor
@rtype: list of int
"""
for d in lpath:
root = self.get_path(d)
if root:
# always keep root
yield d
else:
# if invalid
continue
# nothing else to expect
if not os.path.isdir(root):
continue
# normalization
root = os.path.normpath(root)
# recursion
lend = len(root)
for iwd in self._wmd.items():
cur = iwd[1].path
pref = os.path.commonprefix([root, cur])
if root == os.sep or (len(pref) == lend and \
len(cur) > lend and \
cur[lend] == os.sep):
yield iwd[1].wd
def update_watch(self, wd, mask=None, proc_fun=None, rec=False,
auto_add=False):
"""
Update existing watch(s). Both the mask and the processing
object can be modified.
@param wd: Watch Descriptor to update. Also accepts a list of
watch descriptors.
@type wd: int or list of int
@param mask: Optional new bitmask of events.
@type mask: int
@param proc_fun: Optional new processing function.
@type proc_fun: function or ProcessEvent instance or instance of
one of its subclasses or callable object.
@param rec: Recursively update watches on every already watched
subdirectories and subfiles.
@type rec: bool
@param auto_add: Automatically add watches on newly created
directories in the watch's path.
@type auto_add: bool
@return: dict of watch descriptors associated to booleans values.
True if the corresponding wd has been successfully
updated, False otherwise.
@rtype: dict of int: bool
"""
lwd = self.__format_param(wd)
if rec:
lwd = self.__get_sub_rec(lwd)
ret_ = {} # return {wd: bool, ...}
for awd in lwd:
apath = self.get_path(awd)
ret_[awd] = False
if not apath or awd < 0:
log.error('update_watch: invalid WD=%d' % awd)
continue
if mask:
wd_ = LIBC.inotify_add_watch(self._fd, apath, mask)
if wd_ < 0:
log.error(('update_watch: cannot update WD=%d (%s)'%
(wd_, apath)))
continue
# fixme: not too strict ?
assert(awd == wd_)
if proc_fun or auto_add:
watch_ = self._wmd[awd]
if proc_fun:
watch_.proc_fun = proc_fun
if auto_add:
watch_.proc_fun = auto_add
ret_[awd] = True
log.debug('Updated watch - %s' % self._wmd[awd])
return ret_
def __format_param(self, param):
"""
@param param: Parameter.
@type param: string or int
@return: wrap param.
@rtype: list of type(param)
"""
if isinstance(param, list):
for p_ in param:
yield p_
else:
yield param
def get_wd(self, path):
"""
Returns the watch descriptor associated to path. This method
has an prohibitive cost, always prefer to keep the WD.
@param path: path.
@type path: str
@return: WD or None.
@rtype: int or None
"""
path = os.path.normpath(path)
for iwd in self._wmd.items():
if iwd[1].path == path:
return iwd[0]
log.error('get_wd: unknown path %s' % path)
def get_path(self, wd):
"""
Returns the path associated to WD, if WD doesn't exist
None is returned.
@param wd: watch descriptor.
@type wd: int
@return: path or None.
@rtype: string or None
"""
watch_ = self._wmd.get(wd)
if watch_:
return watch_.path
log.error('get_path: unknown WD %d' % wd)
def __walk_rec(self, top, rec):
"""
Yields each subdirectories of top, doesn't follow symlinks.
If rec is false, only yield top.
@param top: root directory.
@type top: string
@param rec: recursive flag.
@type rec: bool
@return: path of one subdirectory.
@rtype: string
"""
if not rec or os.path.islink(top) or not os.path.isdir(top):
yield top
else:
for root, dirs, files in os.walk(top):
yield root
def rm_watch(self, wd, rec=False):
"""
Removes watch(s).
@param wd: Watch Descriptor of the file or directory to unwatch.
Also accepts a list of WDs.
@type wd: int or list of int.
@param rec: Recursively removes watches on every already watched
subdirectories and subfiles.
@type rec: bool
@return: dict of watch descriptors associated to booleans values.
True if the corresponding wd has been successfully
removed, False otherwise.
@rtype: dict of int: bool
"""
lwd = self.__format_param(wd)
if rec:
lwd = self.__get_sub_rec(lwd)
ret_ = {} # return {wd: bool, ...}
for awd in lwd:
ret_[awd] = False
apath = self.get_path(awd)
# remove watch
wd_ = LIBC.inotify_rm_watch(self._fd, awd)
if wd_ < 0:
log.error('rm_watch: cannot remove WD=%d' % awd)
continue
ret_[awd] = True
log.debug('watch WD=%d (%s) removed' % (awd, apath))
return ret_
#
# The color mechanism is taken from Scapy:
# http://www.secdev.org/projects/scapy/
# Thanks to Philippe Biondi for his awesome tool and design.
#
class Color:
normal = "\033[0m"
black = "\033[30m"
red = "\033[31m"
green = "\033[32m"
yellow = "\033[33m"
blue = "\033[34m"
purple = "\033[35m"
cyan = "\033[36m"
grey = "\033[37m"
bold = "\033[1m"
uline = "\033[4m"
blink = "\033[5m"
invert = "\033[7m"
class ColorTheme:
def __repr__(self):
return "<%s>" % self.__class__.__name__
def __getattr__(self, attr):
return lambda x:x
class NoTheme(ColorTheme):
pass
class AnsiColorTheme(ColorTheme):
def __getattr__(self, attr):
if attr.startswith("__"):
raise AttributeError(attr)
s = "style_%s" % attr
if s in self.__class__.__dict__:
before = getattr(self, s)
after = self.style_normal
else:
before = after = ""
def do_style(val, fmt=None, before=before, after=after):
if fmt is None:
if type(val) is not str:
val = str(val)
else:
val = fmt % val
return before+val+after
return do_style
style_normal = ""
style_prompt = "" # '>>>'
style_punct = ""
style_id = ""
style_not_printable = ""
style_class_name = ""
style_field_name = ""
style_field_value = ""
style_emph_field_name = ""
style_emph_field_value = ""
style_watchlist_name = ""
style_watchlist_type = ""
style_watchlist_value = ""
style_fail = ""
style_success = ""
style_odd = ""
style_even = ""
style_opening = ""
style_active = ""
style_closed = ""
style_left = ""
style_right = ""
class BlackAndWhite(AnsiColorTheme):
pass
class DefaultTheme(AnsiColorTheme):
style_normal = Color.normal
style_prompt = Color.blue+Color.bold
style_punct = Color.normal
style_id = Color.blue+Color.bold
style_not_printable = Color.grey
style_class_name = Color.red+Color.bold
style_field_name = Color.blue
style_field_value = Color.purple
style_emph_field_name = Color.blue+Color.uline+Color.bold
style_emph_field_value = Color.purple+Color.uline+Color.bold
style_watchlist_type = Color.blue
style_watchlist_value = Color.purple
style_fail = Color.red+Color.bold
style_success = Color.blue+Color.bold
style_even = Color.black+Color.bold
style_odd = Color.black
style_opening = Color.yellow
style_active = Color.black
style_closed = Color.grey
style_left = Color.blue+Color.invert
style_right = Color.red+Color.invert
color_theme = DefaultTheme()
def command_line():
#
# - By default the watched path is '/tmp' for all events.
# - The monitoring execution blocks and serve forever, type c^c
# to stop it.
#
from optparse import OptionParser
usage = "usage: %prog [options] [path1] [path2] [pathn]"
parser = OptionParser(usage=usage)
parser.add_option("-v", "--verbose", action="store_true",
dest="verbose", help="Verbose mode")
parser.add_option("-r", "--recursive", action="store_true",
dest="recursive",
help="Add watches recursively on paths")
parser.add_option("-a", "--auto_add", action="store_true",
dest="auto_add",
help="Automatically add watches on new directories")
parser.add_option("-e", "--events-list", metavar="EVENT[,...]",
dest="events_list",
help="A comma-separated list of events to watch for - see the man page for valid options (defaults to everything)")
(options, args) = parser.parse_args()
if options.verbose:
log.setLevel(10)
if len(args) < 1:
path = '/tmp' # default watched path
else:
path = args
# watch manager instance
wm = WatchManager()
# notifier instance and init
notifier = Notifier(wm)
# What mask to apply
mask = 0
if options.events_list:
events_list = options.events_list.split(',')
for ev in events_list:
evcode = EventsCodes.ALL_FLAGS.get(ev, 0)
if evcode:
mask |= evcode
else:
parser.error("The event '%s' specified with option -e is not valid" % ev)
else:
mask = ALL_EVENTS
wm.add_watch(path, mask, rec=options.recursive, auto_add=options.auto_add)
log.debug('start monitoring %s, (press c^c to halt pyinotify)' % path)
# read and process events
while True:
try:
notifier.process_events()
if notifier.check_events():
notifier.read_events()
except KeyboardInterrupt:
# ...until c^c signal
log.debug('stop monitoring...')
# stop monitoring
notifier.stop()
break
if __name__ == '__main__':
command_line()
y~or5J={Eeu磝Qk ᯘG{?+]ן?wM3X^歌>{7پK>on\jy Rg/=fOroNVv~Y+ NGuÝHWyw[eQʨSb> >}Gmx[o[<{Ϯ_qFvM IENDB`