mirror of https://github.com/fail2ban/fail2ban
differentiate between watched directories and files (refreshing monitoring of files/dirs expected different flags for watcher)
parent
e340d0d2b2
commit
7b614a7a15
|
@ -78,7 +78,8 @@ class FilterPyinotify(FileFilter):
|
|||
self.__modified = False
|
||||
# Pyinotify watch manager
|
||||
self.__monitor = pyinotify.WatchManager()
|
||||
self.__watches = dict()
|
||||
self.__watchFiles = dict()
|
||||
self.__watchDirs = dict()
|
||||
self.__pending = dict()
|
||||
self.__pendingChkTime = 0
|
||||
self.__pendingNextTime = 0
|
||||
|
@ -87,45 +88,56 @@ class FilterPyinotify(FileFilter):
|
|||
def callback(self, event, origin=''):
|
||||
logSys.log(7, "[%s] %sCallback for Event: %s", self.jailName, origin, event)
|
||||
path = event.pathname
|
||||
# check watching of this path:
|
||||
isWF = isWD = False
|
||||
if path in self.__watchDirs:
|
||||
isWD = True
|
||||
elif path in self.__watchFiles:
|
||||
isWF = True
|
||||
# fix pyinotify behavior with '-unknown-path' (if target not watched also):
|
||||
if (event.mask & pyinotify.IN_MOVE_SELF and
|
||||
path.endswith('-unknown-path') and not isWF and not isWD
|
||||
):
|
||||
path = path[:-len('-unknown-path')]
|
||||
isWD = path in self.__watchDirs
|
||||
assumeNoDir = False
|
||||
if event.mask & ( pyinotify.IN_CREATE | pyinotify.IN_MOVED_TO ):
|
||||
# refresh watched dir (may be expected):
|
||||
if isWD:
|
||||
self._refreshWatcher(path, isDir=True)
|
||||
return
|
||||
# skip directories altogether
|
||||
if event.mask & pyinotify.IN_ISDIR:
|
||||
logSys.debug("Ignoring creation of directory %s", path)
|
||||
return
|
||||
# check if that is a file we care about
|
||||
if path not in self.__watches:
|
||||
if not isWF:
|
||||
logSys.debug("Ignoring creation of %s we do not monitor", path)
|
||||
return
|
||||
self._refreshFileWatcher(path)
|
||||
self._refreshWatcher(path)
|
||||
elif event.mask & (pyinotify.IN_IGNORED | pyinotify.IN_MOVE_SELF | pyinotify.IN_DELETE_SELF):
|
||||
# fix pyinotify behavior with '-unknown-path' (if target not watched also):
|
||||
if (event.mask & pyinotify.IN_MOVE_SELF and path not in self.__watches and
|
||||
path.endswith('-unknown-path')
|
||||
):
|
||||
path = path[:-len('-unknown-path')]
|
||||
# watch was removed for some reasons (log-rotate?):
|
||||
if not os.path.isfile(path):
|
||||
for log in self.getLogs():
|
||||
logpath = log.getFileName()
|
||||
if logpath.startswith(path):
|
||||
# check exists (rotated):
|
||||
if event.mask & pyinotify.IN_MOVE_SELF or not os.path.isfile(logpath):
|
||||
self._addPendingFile(logpath, event)
|
||||
else:
|
||||
path = logpath
|
||||
break
|
||||
if path not in self.__watches:
|
||||
logSys.debug("Ignoring event of %s we do not monitor", path)
|
||||
return
|
||||
if not os.path.isfile(path):
|
||||
if self.containsLogPath(path):
|
||||
self._addPendingFile(path, event)
|
||||
logSys.debug("Ignoring watching/rotation event (%s) for %s", event.maskname, path)
|
||||
return
|
||||
self._refreshFileWatcher(path)
|
||||
assumeNoDir = event.mask & (pyinotify.IN_MOVE_SELF | pyinotify.IN_DELETE_SELF)
|
||||
if isWD and (assumeNoDir or not os.path.isdir(path)):
|
||||
self._addPending(path, event, isDir=True)
|
||||
elif not isWF:
|
||||
for logpath in self.__watchDirs:
|
||||
if logpath.startswith(path + pathsep) and (assumeNoDir or not os.path.isdir(logpath)):
|
||||
self._addPending(logpath, event, isDir=True)
|
||||
# pending file:
|
||||
for logpath in self.__watchFiles:
|
||||
if logpath.startswith(path + pathsep) and (assumeNoDir or not os.path.isfile(logpath)):
|
||||
self._addPending(logpath, event)
|
||||
if isWF and not os.path.isfile(path):
|
||||
self._addPending(path, event)
|
||||
return
|
||||
# do nothing if idle:
|
||||
if self.idle:
|
||||
return
|
||||
# be sure we process a file:
|
||||
if not isWF:
|
||||
logSys.debug("Ignoring event (%s) of %s we do not monitor", event.maskname, path)
|
||||
return
|
||||
self._process_file(path)
|
||||
|
||||
def _process_file(self, path):
|
||||
|
@ -143,27 +155,36 @@ class FilterPyinotify(FileFilter):
|
|||
self.failManager.cleanup(MyTime.time())
|
||||
self.__modified = False
|
||||
|
||||
def _addPendingFile(self, path, event):
|
||||
def _addPending(self, path, event, isDir=False):
|
||||
if path not in self.__pending:
|
||||
self.__pending[path] = self.sleeptime / 10;
|
||||
self.__pending[path] = [self.sleeptime / 10, isDir];
|
||||
self.__pendingNextTime = 0
|
||||
logSys.log(logging.MSG, "Log absence detected (possibly rotation) for %s, reason: %s of %s",
|
||||
path, event.maskname, event.pathname)
|
||||
|
||||
def _checkPendingFiles(self):
|
||||
def _delPending(self, path):
|
||||
try:
|
||||
del self.__pending[path]
|
||||
except KeyError: pass
|
||||
|
||||
def _checkPending(self):
|
||||
if self.__pending:
|
||||
ntm = time.time()
|
||||
if ntm > self.__pendingNextTime:
|
||||
found = {}
|
||||
minTime = 60
|
||||
for path, retardTM in self.__pending.iteritems():
|
||||
for path, (retardTM, isDir) in self.__pending.iteritems():
|
||||
if ntm - self.__pendingChkTime > retardTM:
|
||||
if not os.path.isfile(path): # not found - prolong for next time
|
||||
chkpath = os.path.isdir if isDir else os.path.isfile
|
||||
if not chkpath(path): # not found - prolong for next time
|
||||
if retardTM < 60: retardTM *= 2
|
||||
if minTime > retardTM: minTime = retardTM
|
||||
self.__pending[path] = retardTM
|
||||
self.__pending[path][0] = retardTM
|
||||
continue
|
||||
found[path] = 1
|
||||
self._refreshFileWatcher(path)
|
||||
logSys.log(logging.MSG, "Log presence detected for %s %s",
|
||||
"directory" if isDir else "file", path)
|
||||
found[path] = isDir
|
||||
self._refreshWatcher(path, isDir=isDir)
|
||||
for path in found:
|
||||
try:
|
||||
del self.__pending[path]
|
||||
|
@ -171,24 +192,32 @@ class FilterPyinotify(FileFilter):
|
|||
self.__pendingChkTime = time.time()
|
||||
self.__pendingNextTime = self.__pendingChkTime + minTime
|
||||
# process now because we'he missed it in monitoring:
|
||||
for path in found:
|
||||
self._process_file(path)
|
||||
for path, isDir in found.iteritems():
|
||||
if not isDir:
|
||||
self._process_file(path)
|
||||
|
||||
def _refreshFileWatcher(self, oldPath, newPath=None):
|
||||
def _refreshWatcher(self, oldPath, newPath=None, isDir=False):
|
||||
if not newPath: newPath = oldPath
|
||||
# we need to substitute the watcher with a new one, so first
|
||||
# remove old one
|
||||
self._delFileWatcher(oldPath)
|
||||
# place a new one
|
||||
self._addFileWatcher(newPath or oldPath)
|
||||
# remove old one and then place a new one
|
||||
if not isDir:
|
||||
self._delFileWatcher(oldPath)
|
||||
self._addFileWatcher(newPath)
|
||||
else:
|
||||
self._delDirWatcher(oldPath)
|
||||
self._addDirWatcher(newPath)
|
||||
|
||||
def _addFileWatcher(self, path):
|
||||
# we need to watch also the directory for IN_CREATE
|
||||
self._addDirWatcher(dirname(path))
|
||||
# add file watcher:
|
||||
wd = self.__monitor.add_watch(path, pyinotify.IN_MODIFY)
|
||||
self.__watches.update(wd)
|
||||
self.__watchFiles.update(wd)
|
||||
logSys.debug("Added file watcher for %s", path)
|
||||
|
||||
def _delFileWatcher(self, path):
|
||||
try:
|
||||
wdInt = self.__watches.pop(path)
|
||||
wdInt = self.__watchFiles.pop(path)
|
||||
wd = self.__monitor.rm_watch(wdInt)
|
||||
if wd[wdInt]:
|
||||
logSys.debug("Removed file watcher for %s", path)
|
||||
|
@ -197,21 +226,30 @@ class FilterPyinotify(FileFilter):
|
|||
pass
|
||||
return False
|
||||
|
||||
def _addDirWatcher(self, path_dir):
|
||||
# Add watch for the directory:
|
||||
if path_dir not in self.__watchDirs:
|
||||
self.__watchDirs.update(
|
||||
self.__monitor.add_watch(path_dir, pyinotify.IN_CREATE |
|
||||
pyinotify.IN_MOVED_TO | pyinotify.IN_MOVE_SELF |
|
||||
pyinotify.IN_DELETE_SELF | pyinotify.IN_ISDIR))
|
||||
logSys.debug("Added monitor for the parent directory %s", path_dir)
|
||||
|
||||
def _delDirWatcher(self, path_dir):
|
||||
# Remove watches for the directory:
|
||||
try:
|
||||
wdInt = self.__watchDirs.pop(path_dir)
|
||||
self.__monitor.rm_watch(wdInt)
|
||||
except KeyError: # pragma: no cover
|
||||
pass
|
||||
logSys.debug("Removed monitor for the parent directory %s", path_dir)
|
||||
|
||||
##
|
||||
# Add a log file path
|
||||
#
|
||||
# @param path log file path
|
||||
|
||||
def _addLogPath(self, path):
|
||||
path_dir = dirname(path)
|
||||
if not (path_dir in self.__watches):
|
||||
# we need to watch also the directory for IN_CREATE
|
||||
self.__watches.update(
|
||||
self.__monitor.add_watch(path_dir, pyinotify.IN_CREATE |
|
||||
pyinotify.IN_MOVED_TO | pyinotify.IN_MOVE_SELF |
|
||||
pyinotify.IN_DELETE_SELF | pyinotify.IN_ISDIR))
|
||||
logSys.debug("Added monitor for the parent directory %s", path_dir)
|
||||
|
||||
self._addFileWatcher(path)
|
||||
self._process_file(path)
|
||||
|
||||
|
@ -223,18 +261,18 @@ class FilterPyinotify(FileFilter):
|
|||
def _delLogPath(self, path):
|
||||
if not self._delFileWatcher(path):
|
||||
logSys.error("Failed to remove watch on path: %s", path)
|
||||
self._delPending(path)
|
||||
|
||||
path_dir = dirname(path)
|
||||
if not len([k for k in self.__watches
|
||||
if k.startswith(path_dir + pathsep)]):
|
||||
for k in self.__watchFiles:
|
||||
if k.startswith(path_dir + pathsep):
|
||||
path_dir = None
|
||||
break
|
||||
if path_dir:
|
||||
# Remove watches for the directory
|
||||
# since there is no other monitored file under this directory
|
||||
try:
|
||||
wdInt = self.__watches.pop(path_dir)
|
||||
self.__monitor.rm_watch(wdInt)
|
||||
except KeyError: # pragma: no cover
|
||||
pass
|
||||
logSys.debug("Removed monitor for the parent directory %s", path_dir)
|
||||
self._delDirWatcher(path_dir)
|
||||
self._delPending(path_dir)
|
||||
|
||||
# pyinotify.ProcessEvent default handler:
|
||||
def __process_default(self, event):
|
||||
|
@ -247,14 +285,16 @@ class FilterPyinotify(FileFilter):
|
|||
|
||||
# slow check events while idle:
|
||||
def __check_events(self, *args, **kwargs):
|
||||
# check pending files (logrotate ready):
|
||||
self._checkPendingFiles()
|
||||
|
||||
if self.idle:
|
||||
if Utils.wait_for(lambda: not self.active or not self.idle,
|
||||
self.sleeptime * 10, self.sleeptime
|
||||
):
|
||||
pass
|
||||
|
||||
# check pending files/dirs (logrotate ready):
|
||||
if not self.idle:
|
||||
self._checkPending()
|
||||
|
||||
self.ticks += 1
|
||||
return pyinotify.ThreadedNotifier.check_events(self.__notifier, *args, **kwargs)
|
||||
|
||||
|
|
Loading…
Reference in New Issue