diff options
Diffstat (limited to 'src/lib/Bcfg2/Server/FileMonitor/Inotify.py')
-rw-r--r-- | src/lib/Bcfg2/Server/FileMonitor/Inotify.py | 126 |
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() |