/usr/lib/python2.7/dist-packages/Bcfg2/Server/FileMonitor/Inotify.py is in bcfg2-server 1.4.0~pre2+git141-g6d40dace6358-1.
This file is owned by root:root, with mode 0o644.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | """File monitor backend with `inotify <http://inotify.aiken.cz/>`_
support. """
import os
import errno
import pyinotify
from Bcfg2.Compat import reduce # pylint: disable=W0622
from Bcfg2.Server.FileMonitor import Event
from Bcfg2.Server.FileMonitor.Pseudo import Pseudo
class Inotify(Pseudo, pyinotify.ProcessEvent):
""" File monitor backend with `inotify
<http://inotify.aiken.cz/>`_ support. """
__rmi__ = Pseudo.__rmi__ + ["list_watches", "list_paths"]
#: Inotify is the best FAM backend, so it gets a very high
#: priority
__priority__ = 99
# pylint: disable=E1101
#: Map pyinotify event constants to FAM :ref:`event codes
#: <development-fam-event-codes>`. The mapping is not
#: terrifically exact.
action_map = {pyinotify.IN_CREATE: 'created',
pyinotify.IN_DELETE: 'deleted',
pyinotify.IN_MODIFY: 'changed',
pyinotify.IN_MOVED_FROM: 'deleted',
pyinotify.IN_MOVED_TO: 'created'}
# pylint: enable=E1101
#: The pyinotify event mask. We only ask for events that are
#: listed in :attr:`action_map`
mask = reduce(lambda x, y: x | y, action_map.keys())
def __init__(self):
Pseudo.__init__(self)
pyinotify.ProcessEvent.__init__(self)
#: inotify can't set useful monitors directly on files, only
#: on directories, so when a monitor is added on a file we add
#: its parent directory to ``event_filter`` and then only
#: produce events on a file in that directory if the file is
#: listed in ``event_filter``. Keys are directories -- the
#: parent directories of individual files that are monitored
#: -- and values are lists of full paths to files in each
#: directory that events *should* be produced for. An event
#: on a file whose parent directory is in ``event_filter`` but
#: which is not itself listed will be silently suppressed.
self.event_filter = dict()
#: inotify doesn't like monitoring a path twice, so we keep a
#: dict of :class:`pyinotify.Watch` objects, keyed by monitor
#: path, to avoid trying to create duplicate monitors.
#: (Duplicates can happen if an object accidentally requests
#: duplicate monitors, or if two files in a single directory
#: are both individually monitored, since inotify can't set
#: monitors on the files but only on the parent directories.)
self.watches_by_path = dict()
#: The :class:`pyinotify.ThreadedNotifier` object. This is
#: created in :func:`start` after the server is done
#: daemonizing.
self.notifier = None
#: The :class:`pyinotify.WatchManager` object. This is created
#: in :func:`start` after the server is done daemonizing.
self.watchmgr = None
#: The queue used to record monitors that are added before
#: :func:`start` has been called and :attr:`notifier` and
#: :attr:`watchmgr` are created.
self.add_q = []
def start(self):
""" The inotify notifier and manager objects in
:attr:`notifier` and :attr:`watchmgr` must be created by the
daemonized process, so they are created in ``start()``. Before
those objects are created, monitors are added to
:attr:`add_q`, and are created once the
:class:`pyinotify.ThreadedNotifier` and
:class:`pyinotify.WatchManager` objects are created."""
Pseudo.start(self)
self.watchmgr = pyinotify.WatchManager()
self.notifier = pyinotify.ThreadedNotifier(self.watchmgr, self)
self.notifier.start()
for monitor in self.add_q:
self.AddMonitor(*monitor)
self.add_q = []
def fileno(self):
if self.started:
return self.watchmgr.get_fd()
else:
return None
fileno.__doc__ = Pseudo.fileno.__doc__
def process_default(self, ievent):
""" Process all inotify events received. This process a
:class:`pyinotify._Event` object, creates a
:class:`Bcfg2.Server.FileMonitor.Event` object from it, and
adds that event to :attr:`events`.
:param ievent: Event to be processed
:type ievent: pyinotify._Event
"""
action = ievent.maskname
for amask, aname in self.action_map.items():
if ievent.mask & amask:
action = aname
break
else:
# event action is not in the mask, and thus is not
# something we care about
self.debug_log("Ignoring event %s for %s" % (action,
ievent.pathname))
return
try:
watch = self.watchmgr.watches[ievent.wd]
except KeyError:
self.logger.error("Error handling event %s for %s: "
"Watch %s not found" %
(action, 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, handleID=None):
# strip trailing slashes
path = path.rstrip("/")
if not self.started:
self.add_q.append((path, obj))
return path
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:
watchdir = self.watches_by_path[watch_path]
except KeyError:
if not os.path.exists(watch_path):
raise OSError(errno.ENOENT,
"No such file or directory: '%s'" % path)
watchdir = self.watchmgr.add_watch(watch_path, self.mask,
quiet=False)[watch_path]
self.watches_by_path[watch_path] = watchdir
produce_exists = True
if not is_dir:
if watchdir not in self.event_filter:
self.event_filter[watchdir] = [path]
elif path not in self.event_filter[watchdir]:
self.event_filter[watchdir].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
AddMonitor.__doc__ = Pseudo.AddMonitor.__doc__
def shutdown(self):
if self.started and self.notifier:
self.notifier.stop()
Pseudo.shutdown(self)
shutdown.__doc__ = Pseudo.shutdown.__doc__
def list_watches(self):
""" XML-RPC that returns a list of current inotify watches for
debugging purposes. """
return list(self.watches_by_path.keys())
def list_paths(self):
""" XML-RPC that returns a list of paths that are handled for
debugging purposes. Because inotify doesn't like watching
files, but prefers to watch directories, this will be
different from
:func:`Bcfg2.Server.FileMonitor.Inotify.Inotify.ListWatches`. For
instance, if a plugin adds a monitor to
``/var/lib/bcfg2/Plugin/foo.xml``, :func:`ListPaths` will
return ``/var/lib/bcfg2/Plugin/foo.xml``, while
:func:`ListWatches` will return ``/var/lib/bcfg2/Plugin``. """
return list(self.handles.keys())
|