summaryrefslogtreecommitdiffstats
path: root/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/Bcfg2/Server/FileMonitor/Inotify.py')
-rw-r--r--src/lib/Bcfg2/Server/FileMonitor/Inotify.py126
1 files changed, 126 insertions, 0 deletions
diff --git a/src/lib/Bcfg2/Server/FileMonitor/Inotify.py b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
new file mode 100644
index 000000000..880ac7e8d
--- /dev/null
+++ b/src/lib/Bcfg2/Server/FileMonitor/Inotify.py
@@ -0,0 +1,126 @@
+""" Inotify driver for file alteration events """
+
+import logging
+import operator
+import os
+import pyinotify
+import sys
+from Bcfg2.Bcfg2Py3k import reduce
+from Bcfg2.Server.FileMonitor import Event
+from Bcfg2.Server.FileMonitor.Pseudo import Pseudo
+
+logger = logging.getLogger(__name__)
+
+class Inotify(Pseudo, pyinotify.ProcessEvent):
+ __priority__ = 1
+ action_map = {pyinotify.IN_CREATE: 'created',
+ pyinotify.IN_DELETE: 'deleted',
+ pyinotify.IN_MODIFY: 'changed',
+ pyinotify.IN_MOVED_FROM: 'deleted',
+ pyinotify.IN_MOVED_TO: 'created'}
+ mask = reduce(lambda x, y: x | y, action_map.keys())
+
+ def __init__(self, ignore=None, debug=False):
+ Pseudo.__init__(self, ignore=ignore, debug=debug)
+ self.wm = pyinotify.WatchManager()
+ self.notifier = pyinotify.ThreadedNotifier(self.wm, self)
+ self.notifier.start()
+ self.event_filter = dict()
+ self.watches_by_path = dict()
+
+ def fileno(self):
+ return self.wm.get_fd()
+
+ def process_default(self, ievent):
+ action = ievent.maskname
+ for amask, aname in self.action_map.items():
+ if ievent.mask & amask:
+ action = aname
+ break
+ try:
+ watch = self.wm.watches[ievent.wd]
+ except KeyError:
+ err = sys.exc_info()[1]
+ logger.error("Error handling event for %s: Watch %s not found" %
+ (ievent.pathname, ievent.wd))
+ return
+ # FAM-style file monitors return the full path to the parent
+ # directory that is being watched, relative paths to anything
+ # contained within the directory. since we can't use inotify
+ # to watch files directly, we have to sort of guess at whether
+ # this watch was actually added on a file (and thus is in
+ # self.event_filter because we're filtering out other events
+ # on the directory) or was added directly on a directory.
+ if (watch.path == ievent.pathname or ievent.wd in self.event_filter):
+ path = ievent.pathname
+ else:
+ # relative path
+ path = os.path.basename(ievent.pathname)
+ # figure out the handleID. start with the path of the event;
+ # that should catch events on files that are watched directly.
+ # (we have to watch the directory that a file is in, so this
+ # lets us handle events on different files in the same
+ # directory -- and thus under the same watch -- with different
+ # objects.) If the path to the event doesn't have a handler,
+ # use the path of the watch itself.
+ handleID = ievent.pathname
+ if handleID not in self.handles:
+ handleID = watch.path
+ evt = Event(handleID, path, action)
+
+ if (ievent.wd not in self.event_filter or
+ ievent.pathname in self.event_filter[ievent.wd]):
+ self.events.append(evt)
+
+ def AddMonitor(self, path, obj):
+ # strip trailing slashes
+ path = path.rstrip("/")
+ if not os.path.isdir(path):
+ # inotify is a little wonky about watching files. for
+ # instance, if you watch /tmp/foo, and then do 'mv
+ # /tmp/bar /tmp/foo', it processes that as a deletion of
+ # /tmp/foo (which it technically _is_, but that's rather
+ # useless -- we care that /tmp/foo changed, not that it
+ # was first deleted and then created). In order to
+ # effectively watch a file, we have to watch the directory
+ # it's in, and filter out events for other files in the
+ # same directory that are not similarly watched.
+ # watch_transient_file requires a Processor _class_, not
+ # an object, so we can't have this object handle events,
+ # which is Wrong, so we can't use that function.
+ watch_path = os.path.dirname(path)
+ is_dir = False
+ else:
+ watch_path = path
+ is_dir = True
+
+ # see if this path is already being watched
+ try:
+ wd = self.watches_by_path[watch_path]
+ except KeyError:
+ wd = self.wm.add_watch(watch_path, self.mask,
+ quiet=False)[watch_path]
+ self.watches_by_path[watch_path] = wd
+
+ produce_exists = True
+ if not is_dir:
+ if wd not in self.event_filter:
+ self.event_filter[wd] = [path]
+ elif path not in self.event_filter[wd]:
+ self.event_filter[wd].append(path)
+ else:
+ # we've been asked to watch a file that we're already
+ # watching, so we don't need to produce 'exists'
+ # events
+ produce_exists = False
+
+ # inotify doesn't produce initial 'exists' events, so we
+ # inherit from Pseudo to produce those
+ if produce_exists:
+ return Pseudo.AddMonitor(self, path, obj, handleID=path)
+ else:
+ self.handles[path] = obj
+ return path
+
+ def shutdown(self):
+ self.notifier.stop()