Blame SOURCES/rsyslog-8.24.0-rhbz1531295-imfile-rewrite-with-symlink.patch

c17bfd
From: Jiri Vymazal <jvymazal@redhat.com>
c17bfd
Date: Mon, 28 Jun 2018 15:07:55 +0100
c17bfd
Subject: Imfile rewrite with symlink support
c17bfd
c17bfd
This commit greatly refactors imfile internal workings. It changes the
c17bfd
handling of inotify, FEN, and polling modes. Mostly unchanged is the
c17bfd
processing of the way a file is read and state files are kept.
c17bfd
c17bfd
This is about a 50% rewrite of the module.
c17bfd
c17bfd
Polling, inotify, and FEN modes now use greatly unified code. Some
c17bfd
differences still exists and may be changed with further commits. The
c17bfd
internal handling of wildcards and file detection has been completely
c17bfd
re-written from scratch. For example, previously when multi-level
c17bfd
wildcards were used these were not reliably detected. The code also
c17bfd
now provides much of the same functionality in all modes, most importantly
c17bfd
wildcards are now also supported in polling mode.
c17bfd
c17bfd
The refactoring sets ground for further enhancements and smaller
c17bfd
refactorings. This commit provides the same feature set that imfile
c17bfd
had previously.
c17bfd
c17bfd
Some specific changes:
c17bfd
bugfix: imfile did not pick up all files when not present
c17bfd
at startup
c17bfd
c17bfd
bugfix: directories only support "*" wildcard, no others
c17bfd
c17bfd
bugfix: parameter "sortfiles" did only work in FEN mode
c17bfd
c17bfd
provides the ability to dynamically add and remove files via
c17bfd
multi-level wildcards
c17bfd
c17bfd
the state file name currently has been changed to inode number
c17bfd
c17bfd
We change it to json and also change the way it is stored and loaded.
c17bfd
This sets base to additional improvements in imfile.
c17bfd
c17bfd
When imfile rewrites state files, it does not truncate previous
c17bfd
content. If the new content is smaller than the existing one, the
c17bfd
existing part will not be overwritten, resulting in invalid json.
c17bfd
That in turn can lead to some other failures.
c17bfd
c17bfd
This introduces symlink detection and following as well
c17bfd
as monitoring changes on them.
c17bfd
c17bfd
stream/bugfix: memory leak on stream open if filename as already generated - 
c17bfd
this can happen if imfile reads a state file. On each open, memory for the
c17bfd
file name can be lost.
c17bfd
c17bfd
(cherry picked from commit a03dccf8484d621fe06cb2d11816fbe921751e54 - https://gitlab.cee.redhat.com/rsyslog/rsyslog)
c17bfd
---
c17bfd
 plugins/imfile/imfile.c                          | 2264 ++++++++++++++++++++++---------------------
c17bfd
 runtime/msg.c                                    |   22 ++++++++++++++++++++++
c17bfd
 runtime/msg.h                                    |    1 +
c17bfd
 runtime/stream.c                                 |  136 ++++++++++++++++++++-------
c17bfd
 runtime/stream.h                                 |   17 ++++++++++++++---
c17bfd
 5 files changed, 1303 insertions(+), 1137 deletitions(-)
c17bfd
c17bfd
diff --git a/plugins/imfile/imfile.c b/plugins/imfile/imfile.c
c17bfd
index b0bc860bcd16beaecd67ce1b7c61991356ea5471..f8225d7068d8fc98edde7bbed194be1105b1696b 100644
c17bfd
--- a/plugins/imfile/imfile.c
c17bfd
+++ b/plugins/imfile/imfile.c
c17bfd
@@ -35,6 +35,7 @@
c17bfd
 #include <unistd.h>
c17bfd
 #include <glob.h>
c17bfd
 #include <poll.h>
c17bfd
+#include <json.h>
c17bfd
 #include <fnmatch.h>
c17bfd
 #ifdef HAVE_SYS_INOTIFY_H
c17bfd
 #include <sys/inotify.h>
c17bfd
@@ -56,6 +57,7 @@
c17bfd
 #include "stringbuf.h"
c17bfd
 #include "ruleset.h"
c17bfd
 #include "ratelimit.h"
c17bfd
+#include "parserif.h"
c17bfd
 
c17bfd
 #include <regex.h> // TODO: fix via own module
c17bfd
 
c17bfd
@@ -77,50 +81,19 @@ static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config para
c17bfd
 
c17bfd
 #define NUM_MULTISUB 1024 /* default max number of submits */
c17bfd
 #define DFLT_PollInterval 10
c17bfd
-
c17bfd
-#define INIT_FILE_TAB_SIZE 4 /* default file table size - is extended as needed, use 2^x value */
c17bfd
-#define INIT_FILE_IN_DIR_TAB_SIZE 1 /* initial size for "associated files tab" in directory table */
c17bfd
 #define INIT_WDMAP_TAB_SIZE 1 /* default wdMap table size - is extended as needed, use 2^x value */
c17bfd
-
c17bfd
 #define ADD_METADATA_UNSPECIFIED -1
c17bfd
+#define CONST_LEN_CEE_COOKIE 5
c17bfd
+#define CONST_CEE_COOKIE "@cee:"
c17bfd
+
c17bfd
+/* If set to 1, fileTableDisplay will be compiled and used for debugging */
c17bfd
+#define ULTRA_DEBUG 0
c17bfd
+
c17bfd
+/* Setting GLOB_BRACE to ZERO which disables support for GLOB_BRACE if not available on current platform */
c17bfd
+#ifndef GLOB_BRACE
c17bfd
+	#define GLOB_BRACE 0
c17bfd
+#endif
c17bfd
 
c17bfd
-/* this structure is used in pure polling mode as well one of the support
c17bfd
- * structures for inotify.
c17bfd
- */
c17bfd
-typedef struct lstn_s {
c17bfd
-	struct lstn_s *next, *prev;
c17bfd
-	struct lstn_s *masterLstn;/* if dynamic file (via wildcard), this points to the configured
c17bfd
-				 * master entry. For master entries, it is always NULL. Only
c17bfd
-				 * dynamic files can be deleted from the "files" list. */
c17bfd
-	uchar *pszFileName;
c17bfd
-	uchar *pszDirName;
c17bfd
-	uchar *pszBaseName;
c17bfd
-	uchar *pszTag;
c17bfd
-	size_t lenTag;
c17bfd
-	uchar *pszStateFile; /* file in which state between runs is to be stored (dynamic if NULL) */
c17bfd
-	int readTimeout;
c17bfd
-	int iFacility;
c17bfd
-	int iSeverity;
c17bfd
-	int maxLinesAtOnce;
c17bfd
-	uint32_t trimLineOverBytes;
c17bfd
-	int nRecords; /**< How many records did we process before persisting the stream? */
c17bfd
-	int iPersistStateInterval; /**< how often should state be persisted? (0=on close only) */
c17bfd
-	strm_t *pStrm;	/* its stream (NULL if not assigned) */
c17bfd
-	sbool bRMStateOnDel;
c17bfd
-	sbool hasWildcard;
c17bfd
-	uint8_t readMode;	/* which mode to use in ReadMulteLine call? */
c17bfd
-	uchar *startRegex;	/* regex that signifies end of message (NULL if unset) */
c17bfd
-	regex_t end_preg;	/* compiled version of startRegex */
c17bfd
-	uchar *prevLineSegment;	/* previous line segment (in regex mode) */
c17bfd
-	sbool escapeLF;	/* escape LF inside the MSG content? */
c17bfd
-	sbool reopenOnTruncate;
c17bfd
-	sbool addMetadata;
c17bfd
-	sbool addCeeTag;
c17bfd
-	sbool freshStartTail; /* read from tail of file on fresh start? */
c17bfd
-	ruleset_t *pRuleset;	/* ruleset to bind listener to (use system default if unspecified) */
c17bfd
-	ratelimit_t *ratelimiter;
c17bfd
-	multi_submit_t multiSub;
c17bfd
-} lstn_t;
c17bfd
 
c17bfd
 static struct configSettings_s {
c17bfd
 	uchar *pszFileName;
c17bfd
@@ -138,9 +111,11 @@ static struct configSettings_s {
c17bfd
 
c17bfd
 struct instanceConf_s {
c17bfd
 	uchar *pszFileName;
c17bfd
+	uchar *pszFileName_forOldStateFile; /* we unfortunately needs this to read old state files */
c17bfd
 	uchar *pszDirName;
c17bfd
 	uchar *pszFileBaseName;
c17bfd
 	uchar *pszTag;
c17bfd
+	size_t lenTag;
c17bfd
 	uchar *pszStateFile;
c17bfd
 	uchar *pszBindRuleset;
c17bfd
 	int nMultiSub;
c17bfd
@@ -151,11 +126,15 @@ struct instanceConf_s {
c17bfd
 	sbool bRMStateOnDel;
c17bfd
 	uint8_t readMode;
c17bfd
 	uchar *startRegex;
c17bfd
+	regex_t end_preg;	/* compiled version of startRegex */
c17bfd
+	sbool discardTruncatedMsg;
c17bfd
+	sbool msgDiscardingError;
c17bfd
 	sbool escapeLF;
c17bfd
 	sbool reopenOnTruncate;
c17bfd
 	sbool addCeeTag;
c17bfd
 	sbool addMetadata;
c17bfd
 	sbool freshStartTail;
c17bfd
+	sbool fileNotFoundError;
c17bfd
 	int maxLinesAtOnce;
c17bfd
 	uint32_t trimLineOverBytes;
c17bfd
 	ruleset_t *pBindRuleset;	/* ruleset to bind listener to (use system default if unspecified) */
c17bfd
@@ -163,9 +142,54 @@ struct instanceConf_s {
c17bfd
 };
c17bfd
 
c17bfd
 
c17bfd
+/* file system objects */
c17bfd
+typedef struct fs_edge_s fs_edge_t;
c17bfd
+typedef struct fs_node_s fs_node_t;
c17bfd
+typedef struct act_obj_s act_obj_t;
c17bfd
+struct act_obj_s {
c17bfd
+	act_obj_t *prev;
c17bfd
+	act_obj_t *next;
c17bfd
+	fs_edge_t *edge;	/* edge which this object belongs to */
c17bfd
+	char *name;		/* full path name of active object */
c17bfd
+	char *basename;		/* only basename */ //TODO: remove when refactoring rename support
c17bfd
+	char *source_name;  /* if this object is target of a symlink, source_name is its name (else NULL) */
c17bfd
+	//char *statefile;	/* base name of state file (for move operations) */
c17bfd
+	int wd;
c17bfd
+	time_t timeoutBase; /* what time to calculate the timeout against? */
c17bfd
+	/* file dynamic data */
c17bfd
+	int in_move;	/* workaround for inotify move: if set, state file must not be deleted */
c17bfd
+	ino_t ino;	/* current inode nbr */
c17bfd
+	strm_t *pStrm;	/* its stream (NULL if not assigned) */
c17bfd
+	int nRecords; /**< How many records did we process before persisting the stream? */
c17bfd
+	ratelimit_t *ratelimiter;
c17bfd
+	multi_submit_t multiSub;
c17bfd
+	int is_symlink;
c17bfd
+};
c17bfd
+struct fs_edge_s {
c17bfd
+	fs_node_t *parent;
c17bfd
+	fs_node_t *node;	/* node this edge points to */
c17bfd
+	fs_edge_t *next;
c17bfd
+	uchar *name;
c17bfd
+	uchar *path;
c17bfd
+	act_obj_t *active;
c17bfd
+	int is_file;
c17bfd
+	int ninst;	/* nbr of instances in instarr */
c17bfd
+	instanceConf_t **instarr;
c17bfd
+};
c17bfd
+struct fs_node_s {
c17bfd
+	fs_edge_t *edges;
c17bfd
+	fs_node_t *root;
c17bfd
+};
c17bfd
+
c17bfd
+
c17bfd
 /* forward definitions */
c17bfd
-static rsRetVal persistStrmState(lstn_t *pInfo);
c17bfd
+static rsRetVal persistStrmState(act_obj_t *);
c17bfd
 static rsRetVal resetConfigVariables(uchar __attribute__((unused)) *pp, void __attribute__((unused)) *pVal);
c17bfd
+static rsRetVal pollFile(act_obj_t *act);
c17bfd
+static int getBasename(uchar *const __restrict__ basen, uchar *const __restrict__ path);
c17bfd
+static void act_obj_unlink(act_obj_t *act);
c17bfd
+static uchar * getStateFileName(const act_obj_t *, uchar *, const size_t);
c17bfd
+static int getFullStateFileName(const uchar *const, uchar *const pszout, const size_t ilenout);
c17bfd
 
c17bfd
 
c17bfd
 #define OPMODE_POLLING 0
c17bfd
@@ -178,57 +200,23 @@ struct modConfData_s {
c17bfd
 	int readTimeout;
c17bfd
 	int timeoutGranularity;		/* value in ms */
c17bfd
 	instanceConf_t *root, *tail;
c17bfd
-	lstn_t *pRootLstn;
c17bfd
-	lstn_t *pTailLstn;
c17bfd
+	fs_node_t *conf_tree;
c17bfd
 	uint8_t opMode;
c17bfd
 	sbool configSetViaV2Method;
c17bfd
+	sbool sortFiles;
c17bfd
+	sbool normalizePath;	/* normalize file system pathes (all start with root dir) */
c17bfd
 	sbool haveReadTimeouts;	/* use special processing if read timeouts exist */
c17bfd
+	sbool bHadFileData;	/* actually a global variable:
c17bfd
+				   1 - last call to pollFile() had data
c17bfd
+				   0 - last call to pollFile() had NO data
c17bfd
+				   Must be manually reset to 0 if desired. Helper for
c17bfd
+				   polling mode.
c17bfd
+				 */
c17bfd
 };
c17bfd
 static modConfData_t *loadModConf = NULL;/* modConf ptr to use for the current load process */
c17bfd
 static modConfData_t *runModConf = NULL;/* modConf ptr to use for the current load process */
c17bfd
 
c17bfd
 #ifdef HAVE_INOTIFY_INIT
c17bfd
-/* support for inotify mode */
c17bfd
-
c17bfd
-/* we need to track directories */
c17bfd
-struct dirInfoFiles_s { /* associated files */
c17bfd
-	lstn_t *pLstn;
c17bfd
-	int refcnt;	/* due to inotify's async nature, we may have multiple
c17bfd
-			 * references to a single file inside our cache - e.g. when
c17bfd
-			 * inodes are removed, and the file name is re-created BUT another
c17bfd
-			 * process (like rsyslogd ;)) holds open the old inode.
c17bfd
-			 */
c17bfd
-};
c17bfd
-typedef struct dirInfoFiles_s dirInfoFiles_t;
c17bfd
-
c17bfd
-/* This structure is a dynamic table to track file entries */
c17bfd
-struct fileTable_s {
c17bfd
-	dirInfoFiles_t *listeners;
c17bfd
-	int currMax;
c17bfd
-	int allocMax;
c17bfd
-};
c17bfd
-typedef struct fileTable_s fileTable_t;
c17bfd
-
c17bfd
-/* The dirs table (defined below) contains one entry for each directory that
c17bfd
- * is to be monitored. For each directory, it contains array which point to
c17bfd
- * the associated *active* files as well as *configured* files. Note that
c17bfd
- * the configured files may currently not exist, but will be processed
c17bfd
- * when they are created.
c17bfd
- */
c17bfd
-struct dirInfo_s {
c17bfd
-	uchar *dirName;
c17bfd
-	fileTable_t active; /* associated active files */
c17bfd
-	fileTable_t configured; /* associated configured files */
c17bfd
-};
c17bfd
-typedef struct dirInfo_s dirInfo_t;
c17bfd
-static dirInfo_t *dirs = NULL;
c17bfd
-static int allocMaxDirs;
c17bfd
-static int currMaxDirs;
c17bfd
-/* the following two macros are used to select the correct file table */
c17bfd
-#define ACTIVE_FILE 1
c17bfd
-#define CONFIGURED_FILE 0
c17bfd
-
c17bfd
-
c17bfd
 /* We need to map watch descriptors to our actual objects. Unfortunately, the
c17bfd
  * inotify API does not provide us with any cookie, so a simple O(1) algorithm
c17bfd
  * cannot be done (what a shame...). We assume that maintaining the array is much
c17bfd
@@ -238,9 +226,7 @@ static int currMaxDirs;
c17bfd
  */
c17bfd
 struct wd_map_s {
c17bfd
 	int wd;		/* ascending sort key */
c17bfd
-	lstn_t *pLstn;	/* NULL, if this is a dir entry, otherwise pointer into listener(file) table */
c17bfd
-	int dirIdx;	/* index into dirs table, undefined if pLstn == NULL */
c17bfd
-	time_t timeoutBase; /* what time to calculate the timeout against? */
c17bfd
+	act_obj_t *act; /* point to related active object */
c17bfd
 };
c17bfd
 typedef struct wd_map_s wd_map_t;
c17bfd
 static wd_map_t *wdmap = NULL;
c17bfd
@@ -257,6 +243,8 @@ static struct cnfparamdescr modpdescr[] = {
c17bfd
 	{ "pollinginterval", eCmdHdlrPositiveInt, 0 },
c17bfd
 	{ "readtimeout", eCmdHdlrPositiveInt, 0 },
c17bfd
 	{ "timeoutgranularity", eCmdHdlrPositiveInt, 0 },
c17bfd
+	{ "sortfiles", eCmdHdlrBinary, 0 },
c17bfd
+	{ "normalizepath", eCmdHdlrBinary, 0 },
c17bfd
 	{ "mode", eCmdHdlrGetWord, 0 }
c17bfd
 };
c17bfd
 static struct cnfparamblk modpblk =
c17bfd
@@ -286,7 +274,8 @@ static struct cnfparamdescr inppdescr[] = {
c17bfd
 	{ "addceetag", eCmdHdlrBinary, 0 },
c17bfd
 	{ "statefile", eCmdHdlrString, CNFPARAM_DEPRECATED },
c17bfd
 	{ "readtimeout", eCmdHdlrPositiveInt, 0 },
c17bfd
-	{ "freshstarttail", eCmdHdlrBinary, 0}
c17bfd
+	{ "freshstarttail", eCmdHdlrBinary, 0},
c17bfd
+	{ "filenotfounderror", eCmdHdlrBinary, 0}
c17bfd
 };
c17bfd
 static struct cnfparamblk inppblk =
c17bfd
 	{ CNFPARAMBLK_VERSION,
c17bfd
@@ -297,18 +286,106 @@ static struct cnfparamblk inppblk =
c17bfd
 #include "im-helper.h" /* must be included AFTER the type definitions! */
c17bfd
 
c17bfd
 
c17bfd
-#ifdef HAVE_INOTIFY_INIT
c17bfd
-/* support for inotify mode */
c17bfd
+/* Support for "old cruft" state files will potentially become optional in the
c17bfd
+ * future (hopefully). To prepare so, we use conditional compilation with a
c17bfd
+ * fixed-true condition ;-) -- rgerhards, 2018-03-28
c17bfd
+ * reason: https://github.com/rsyslog/rsyslog/issues/2231#issuecomment-376862280
c17bfd
+ */
c17bfd
+#define ENABLE_V1_STATE_FILE_FORMAT_SUPPORT 1
c17bfd
+#ifdef ENABLE_V1_STATE_FILE_FORMAT_SUPPORT
c17bfd
+static uchar *
c17bfd
+OLD_getStateFileName(const instanceConf_t *const inst,
c17bfd
+	 uchar *const __restrict__ buf,
c17bfd
+	 const size_t lenbuf)
c17bfd
+{
c17bfd
+	DBGPRINTF("OLD_getStateFileName trying '%s'\n", inst->pszFileName_forOldStateFile);
c17bfd
+	snprintf((char*)buf, lenbuf - 1, "imfile-state:%s", inst->pszFileName_forOldStateFile);
c17bfd
+	buf[lenbuf-1] = '\0'; /* be on the safe side... */
c17bfd
+	uchar *p = buf;
c17bfd
+	for( ; *p ; ++p) {
c17bfd
+		if(*p == '/')
c17bfd
+			*p = '-';
c17bfd
+	}
c17bfd
+	return buf;
c17bfd
+}
c17bfd
 
c17bfd
-#if 0 /* enable if you need this for debugging */
c17bfd
+/* try to open an old-style state file for given file. If the state file does not
c17bfd
+ * exist or cannot be read, an error is returned.
c17bfd
+ */
c17bfd
+static rsRetVal
c17bfd
+OLD_openFileWithStateFile(act_obj_t *const act)
c17bfd
+{
c17bfd
+	DEFiRet;
c17bfd
+	strm_t *psSF = NULL;
c17bfd
+	uchar pszSFNam[MAXFNAME];
c17bfd
+	size_t lenSFNam;
c17bfd
+	struct stat stat_buf;
c17bfd
+	uchar statefile[MAXFNAME];
c17bfd
+	const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
c17bfd
+
c17bfd
+	uchar *const statefn = OLD_getStateFileName(inst, statefile, sizeof(statefile));
c17bfd
+	DBGPRINTF("OLD_openFileWithStateFile: trying to open state for '%s', state file '%s'\n",
c17bfd
+		  act->name, statefn);
c17bfd
+
c17bfd
+	/* Get full path and file name */
c17bfd
+	lenSFNam = getFullStateFileName(statefn, pszSFNam, sizeof(pszSFNam));
c17bfd
+
c17bfd
+	/* check if the file exists */
c17bfd
+	if(stat((char*) pszSFNam, &stat_buf) == -1) {
c17bfd
+		if(errno == ENOENT) {
c17bfd
+			DBGPRINTF("OLD_openFileWithStateFile: NO state file (%s) exists for '%s'\n",
c17bfd
+				pszSFNam, act->name);
c17bfd
+			ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
c17bfd
+		} else {
c17bfd
+			char errStr[1024];
c17bfd
+			rs_strerror_r(errno, errStr, sizeof(errStr));
c17bfd
+			DBGPRINTF("OLD_openFileWithStateFile: error trying to access state "
c17bfd
+				"file for '%s':%s\n", act->name, errStr);
c17bfd
+			ABORT_FINALIZE(RS_RET_IO_ERROR);
c17bfd
+		}
c17bfd
+	}
c17bfd
+
c17bfd
+	/* If we reach this point, we have a state file */
c17bfd
+
c17bfd
+	DBGPRINTF("old state file found - instantiating from it\n");
c17bfd
+	CHKiRet(strm.Construct(&psSF));
c17bfd
+	CHKiRet(strm.SettOperationsMode(psSF, STREAMMODE_READ));
c17bfd
+	CHKiRet(strm.SetsType(psSF, STREAMTYPE_FILE_SINGLE));
c17bfd
+	CHKiRet(strm.SetFName(psSF, pszSFNam, lenSFNam));
c17bfd
+	CHKiRet(strm.SetFileNotFoundError(psSF, inst->fileNotFoundError));
c17bfd
+	CHKiRet(strm.ConstructFinalize(psSF));
c17bfd
+
c17bfd
+	/* read back in the object */
c17bfd
+	CHKiRet(obj.Deserialize(&act->pStrm, (uchar*) "strm", psSF, NULL, act));
c17bfd
+	free(act->pStrm->pszFName);
c17bfd
+	CHKmalloc(act->pStrm->pszFName = ustrdup(act->name));
c17bfd
+
c17bfd
+	strm.CheckFileChange(act->pStrm);
c17bfd
+	CHKiRet(strm.SeekCurrOffs(act->pStrm));
c17bfd
+
c17bfd
+	/* we now persist the new state file and delete the old one, so we will
c17bfd
+	 * never have to deal with the old one. */
c17bfd
+	persistStrmState(act);
c17bfd
+	unlink((char*)pszSFNam);
c17bfd
+
c17bfd
+finalize_it:
c17bfd
+	if(psSF != NULL)
c17bfd
+		strm.Destruct(&psSF);
c17bfd
+	RETiRet;
c17bfd
+}
c17bfd
+#endif /* #ifdef ENABLE_V1_STATE_FILE_FORMAT_SUPPORT */
c17bfd
+
c17bfd
+
c17bfd
+#ifdef HAVE_INOTIFY_INIT
c17bfd
+#if ULTRA_DEBUG == 1
c17bfd
 static void
c17bfd
-dbg_wdmapPrint(char *msg)
c17bfd
+dbg_wdmapPrint(const char *msg)
c17bfd
 {
c17bfd
 	int i;
c17bfd
 	DBGPRINTF("%s\n", msg);
c17bfd
 	for(i = 0 ; i < nWdmap ; ++i)
c17bfd
-		DBGPRINTF("wdmap[%d]: wd: %d, file %d, dir %d\n", i,
c17bfd
-			  wdmap[i].wd, wdmap[i].fIdx, wdmap[i].dirIdx);
c17bfd
+		DBGPRINTF("wdmap[%d]: wd: %d, act %p, name: %s\n",
c17bfd
+			i, wdmap[i].wd, wdmap[i].act, wdmap[i].act->name);
c17bfd
 }
c17bfd
 #endif
c17bfd
 
c17bfd
@@ -324,48 +401,10 @@ finalize_it:
c17bfd
 	RETiRet;
c17bfd
 }
c17bfd
 
c17bfd
-/* looks up a wdmap entry by dirIdx and returns it's index if found
c17bfd
- * or -1 if not found.
c17bfd
- */
c17bfd
-static int
c17bfd
-wdmapLookupListner(lstn_t* pLstn)
c17bfd
-{
c17bfd
-	int i = 0;
c17bfd
-	int wd = -1;
c17bfd
-	/* Loop through */
c17bfd
-	for(i = 0 ; i < nWdmap; ++i) {
c17bfd
-		if (wdmap[i].pLstn == pLstn)
c17bfd
-			wd = wdmap[i].wd;
c17bfd
-	}
c17bfd
-
c17bfd
-	return wd;
c17bfd
-}
c17bfd
-
c17bfd
-/* compare function for bsearch() */
c17bfd
-static int
c17bfd
-wdmap_cmp(const void *k, const void *a)
c17bfd
-{
c17bfd
-	int key = *((int*) k);
c17bfd
-	wd_map_t *etry = (wd_map_t*) a;
c17bfd
-	if(key < etry->wd)
c17bfd
-		return -1;
c17bfd
-	else if(key > etry->wd)
c17bfd
-		return 1;
c17bfd
-	else
c17bfd
-		return 0;
c17bfd
-}
c17bfd
-/* looks up a wdmap entry and returns it's index if found
c17bfd
- * or -1 if not found.
c17bfd
- */
c17bfd
-static wd_map_t *
c17bfd
-wdmapLookup(int wd)
c17bfd
-{
c17bfd
-	return bsearch(&wd, wdmap, nWdmap, sizeof(wd_map_t), wdmap_cmp);
c17bfd
-}
c17bfd
 
c17bfd
 /* note: we search backwards, as inotify tends to return increasing wd's */
c17bfd
 static rsRetVal
c17bfd
-wdmapAdd(int wd, const int dirIdx, lstn_t *const pLstn)
c17bfd
+wdmapAdd(int wd, act_obj_t *const act)
c17bfd
 {
c17bfd
 	wd_map_t *newmap;
c17bfd
 	int newmapsize;
c17bfd
@@ -375,7 +414,7 @@ wdmapAdd(int wd, const int dirIdx, lstn_t *const pLstn)
c17bfd
 	for(i = nWdmap-1 ; i >= 0 && wdmap[i].wd > wd ; --i)
c17bfd
 		; 	/* just scan */
c17bfd
 	if(i >= 0 && wdmap[i].wd == wd) {
c17bfd
-		DBGPRINTF("imfile: wd %d already in wdmap!\n", wd);
c17bfd
+		LogError(0, RS_RET_INTERNAL_ERROR, "imfile: wd %d already in wdmap!", wd);
c17bfd
 		ABORT_FINALIZE(RS_RET_FILE_ALREADY_IN_TABLE);
c17bfd
 	}
c17bfd
 	++i;
c17bfd
@@ -392,17 +431,59 @@ wdmapAdd(int wd, const int dirIdx, lstn_t *const pLstn)
c17bfd
 		memmove(wdmap + i + 1, wdmap + i, sizeof(wd_map_t) * (nWdmap - i));
c17bfd
 	}
c17bfd
 	wdmap[i].wd = wd;
c17bfd
-	wdmap[i].dirIdx = dirIdx;
c17bfd
-	wdmap[i].pLstn = pLstn;
c17bfd
+	wdmap[i].act = act;
c17bfd
 	++nWdmap;
c17bfd
-	DBGPRINTF("imfile: enter into wdmap[%d]: wd %d, dir %d, lstn %s:%s\n",i,wd,dirIdx,
c17bfd
-		  (pLstn == NULL) ? "DIRECTORY" : "FILE",
c17bfd
-	          (pLstn == NULL) ? dirs[dirIdx].dirName : pLstn->pszFileName);
c17bfd
+	DBGPRINTF("add wdmap[%d]: wd %d, act obj %p, path %s\n", i, wd, act, act->name);
c17bfd
 
c17bfd
 finalize_it:
c17bfd
 	RETiRet;
c17bfd
 }
c17bfd
 
c17bfd
+/* return wd or -1 on error */
c17bfd
+static int
c17bfd
+in_setupWatch(act_obj_t *const act, const int is_file)
c17bfd
+{
c17bfd
+	int wd = -1;
c17bfd
+	if(runModConf->opMode != OPMODE_INOTIFY)
c17bfd
+		goto done;
c17bfd
+
c17bfd
+	wd = inotify_add_watch(ino_fd, act->name,
c17bfd
+		(is_file) ? IN_MODIFY|IN_DONT_FOLLOW : IN_CREATE|IN_DELETE|IN_MOVED_FROM|IN_MOVED_TO);
c17bfd
+	if(wd < 0) { /* There is high probability of selinux denial on top-level paths */
c17bfd
+		if (errno != EACCES)
c17bfd
+			LogError(errno, RS_RET_IO_ERROR, "imfile: cannot watch object '%s'", act->name);
c17bfd
+		else
c17bfd
+			DBGPRINTF("Access denied when creating watch on '%s'\n", act->name);
c17bfd
+		goto done;
c17bfd
+	}
c17bfd
+	wdmapAdd(wd, act);
c17bfd
+	DBGPRINTF("in_setupWatch: watch %d added for %s(object %p)\n", wd, act->name, act);
c17bfd
+done:	return wd;
c17bfd
+}
c17bfd
+
c17bfd
+/* compare function for bsearch() */
c17bfd
+static int
c17bfd
+wdmap_cmp(const void *k, const void *a)
c17bfd
+{
c17bfd
+	int key = *((int*) k);
c17bfd
+	wd_map_t *etry = (wd_map_t*) a;
c17bfd
+	if(key < etry->wd)
c17bfd
+		return -1;
c17bfd
+	else if(key > etry->wd)
c17bfd
+		return 1;
c17bfd
+	else
c17bfd
+		return 0;
c17bfd
+}
c17bfd
+/* looks up a wdmap entry and returns it's index if found
c17bfd
+ * or -1 if not found.
c17bfd
+ */
c17bfd
+static wd_map_t *
c17bfd
+wdmapLookup(int wd)
c17bfd
+{
c17bfd
+	return bsearch(&wd, wdmap, nWdmap, sizeof(wd_map_t), wdmap_cmp);
c17bfd
+}
c17bfd
+
c17bfd
+
c17bfd
 static rsRetVal
c17bfd
 wdmapDel(const int wd)
c17bfd
 {
c17bfd
@@ -427,46 +506,570 @@ finalize_it:
c17bfd
 	RETiRet;
c17bfd
 }
c17bfd
 
c17bfd
-#endif /* #if HAVE_INOTIFY_INIT */
c17bfd
+#endif // #ifdef HAVE_INOTIFY_INIT
c17bfd
+
c17bfd
+static void
c17bfd
+fen_setupWatch(act_obj_t *const __attribute__((unused)) act)
c17bfd
+{
c17bfd
+	DBGPRINTF("fen_setupWatch: DUMMY CALLED - not on Solaris?\n");
c17bfd
+}
c17bfd
+
c17bfd
+static void
c17bfd
+fs_node_print(const fs_node_t *const node, const int level)
c17bfd
+{
c17bfd
+	fs_edge_t *chld;
c17bfd
+	act_obj_t *act;
c17bfd
+	dbgprintf("node print[%2.2d]: %p edges:\n", level, node);
c17bfd
+
c17bfd
+	for(chld = node->edges ; chld != NULL ; chld = chld->next) {
c17bfd
+		dbgprintf("node print[%2.2d]:     child %p '%s' isFile %d, path: '%s'\n",
c17bfd
+			level, chld->node, chld->name, chld->is_file, chld->path);
c17bfd
+		for(int i = 0 ; i < chld->ninst ; ++i) {
c17bfd
+			dbgprintf("\tinst: %p\n", chld->instarr[i]);
c17bfd
+		}
c17bfd
+		for(act = chld->active ; act != NULL ; act = act->next) {
c17bfd
+			dbgprintf("\tact : %p\n", act);
c17bfd
+			dbgprintf("\tact : %p: name '%s', wd: %d\n",
c17bfd
+				act, act->name, act->wd);
c17bfd
+		}
c17bfd
+	}
c17bfd
+	for(chld = node->edges ; chld != NULL ; chld = chld->next) {
c17bfd
+		fs_node_print(chld->node, level+1);
c17bfd
+	}
c17bfd
+}
c17bfd
+
c17bfd
+/* add a new file system object if it not yet exists, ignore call
c17bfd
+ * if it already does.
c17bfd
+ */
c17bfd
+static rsRetVal
c17bfd
+act_obj_add(fs_edge_t *const edge, const char *const name, const int is_file,
c17bfd
+	const ino_t ino, const int is_symlink, const char *const source)
c17bfd
+{
c17bfd
+	act_obj_t *act;
c17bfd
+	char basename[MAXFNAME];
c17bfd
+	DEFiRet;
c17bfd
+	
c17bfd
+	DBGPRINTF("act_obj_add: edge %p, name '%s' (source '%s')\n", edge, name, source? source : "---");
c17bfd
+	for(act = edge->active ; act != NULL ; act = act->next) {
c17bfd
+		if(!strcmp(act->name, name)) {
c17bfd
+                       if (!source || !act->source_name || !strcmp(act->source_name, source)) {
c17bfd
+                               DBGPRINTF("active object '%s' already exists in '%s' - no need to add\n",
c17bfd
+                                       name, edge->path);
c17bfd
+                               FINALIZE;
c17bfd
+                       }
c17bfd
+		}
c17bfd
+	}
c17bfd
+	DBGPRINTF("add new active object '%s' in '%s'\n", name, edge->path);
c17bfd
+	CHKmalloc(act = calloc(sizeof(act_obj_t), 1));
c17bfd
+	CHKmalloc(act->name = strdup(name));
c17bfd
+       if (-1 == getBasename((uchar*)basename, (uchar*)name)) {
c17bfd
+               CHKmalloc(act->basename = strdup(name)); /* assume basename is same as name */
c17bfd
+       } else {
c17bfd
+               CHKmalloc(act->basename = strdup(basename));
c17bfd
+       }
c17bfd
+	act->edge = edge;
c17bfd
+	act->ino = ino;
c17bfd
+	act->is_symlink = is_symlink;
c17bfd
+       if (source) { /* we are target of symlink */
c17bfd
+               CHKmalloc(act->source_name = strdup(source));
c17bfd
+       } else {
c17bfd
+               act->source_name = NULL;
c17bfd
+       }
c17bfd
+	#ifdef HAVE_INOTIFY_INIT
c17bfd
+	act->wd = in_setupWatch(act, is_file);
c17bfd
+	#endif
c17bfd
+	fen_setupWatch(act);
c17bfd
+	if(is_file && !is_symlink) {
c17bfd
+		const instanceConf_t *const inst = edge->instarr[0];// TODO: same file, multiple instances?
c17bfd
+		CHKiRet(ratelimitNew(&act->ratelimiter, "imfile", name));
c17bfd
+		CHKmalloc(act->multiSub.ppMsgs = MALLOC(inst->nMultiSub * sizeof(smsg_t *)));
c17bfd
+		act->multiSub.maxElem = inst->nMultiSub;
c17bfd
+		act->multiSub.nElem = 0;
c17bfd
+		pollFile(act);
c17bfd
+	}
c17bfd
+
c17bfd
+	/* all well, add to active list */
c17bfd
+	if(edge->active != NULL) {
c17bfd
+		edge->active->prev = act;
c17bfd
+	}
c17bfd
+	act->next = edge->active;
c17bfd
+	edge->active = act;
c17bfd
+//dbgprintf("printout of fs tree after act_obj_add for '%s'\n", name);
c17bfd
+//fs_node_print(runModConf->conf_tree, 0);
c17bfd
+//dbg_wdmapPrint("wdmap after act_obj_add");
c17bfd
+finalize_it:
c17bfd
+	if(iRet != RS_RET_OK) {
c17bfd
+		if(act != NULL) {
c17bfd
+			free(act->name);
c17bfd
+			free(act);
c17bfd
+		}
c17bfd
+	}
c17bfd
+	RETiRet;
c17bfd
+}
c17bfd
+
c17bfd
+
c17bfd
+/* this walks an edges active list and detects and acts on any changes
c17bfd
+ * seen there. It does NOT detect newly appeared files, as they are not
c17bfd
+ * inside the active list!
c17bfd
+ */
c17bfd
+static void
c17bfd
+detect_updates(fs_edge_t *const edge)
c17bfd
+{
c17bfd
+	act_obj_t *act;
c17bfd
+	struct stat fileInfo;
c17bfd
+	int restart = 0;
c17bfd
+
c17bfd
+	for(act = edge->active ; act != NULL ; ) {
c17bfd
+		DBGPRINTF("detect_updates checking active obj '%s'\n", act->name);
c17bfd
+		const int r = lstat(act->name, &fileInfo);
c17bfd
+		if(r == -1) { /* object gone away? */
c17bfd
+			DBGPRINTF("object gone away, unlinking: '%s'\n", act->name);
c17bfd
+			act_obj_unlink(act);
c17bfd
+			restart = 1;
c17bfd
+			break;
c17bfd
+		}
c17bfd
+		// TODO: add inode check for change notification!
c17bfd
+
c17bfd
+		/* Note: active nodes may get deleted, so we need to do the
c17bfd
+		 * pointer advancement at the end of the for loop!
c17bfd
+		 */
c17bfd
+		act = act->next;
c17bfd
+	}
c17bfd
+	if (restart)
c17bfd
+		detect_updates(edge);
c17bfd
+}
c17bfd
+
c17bfd
+
c17bfd
+/* check if active files need to be processed. This is only needed in
c17bfd
+ * polling mode.
c17bfd
+ */
c17bfd
+static void
c17bfd
+poll_active_files(fs_edge_t *const edge)
c17bfd
+{
c17bfd
+	if(   runModConf->opMode != OPMODE_POLLING
c17bfd
+	   || !edge->is_file
c17bfd
+	   || glbl.GetGlobalInputTermState() != 0) {
c17bfd
+		return;
c17bfd
+	}
c17bfd
+
c17bfd
+	act_obj_t *act;
c17bfd
+	for(act = edge->active ; act != NULL ; act = act->next) {
c17bfd
+		fen_setupWatch(act);
c17bfd
+		DBGPRINTF("poll_active_files: polling '%s'\n", act->name);
c17bfd
+		pollFile(act);
c17bfd
+	}
c17bfd
+}
c17bfd
+
c17bfd
+static rsRetVal
c17bfd
+process_symlink(fs_edge_t *const chld, const char *symlink)
c17bfd
+{
c17bfd
+	DEFiRet;
c17bfd
+	char *target = NULL;
c17bfd
+	CHKmalloc(target = realpath(symlink, target));
c17bfd
+       struct stat fileInfo;
c17bfd
+       if(lstat(target, &fileInfo) != 0) {
c17bfd
+               LogError(errno, RS_RET_ERR,     "imfile: process_symlink cannot stat file '%s' - ignored", target);
c17bfd
+               FINALIZE;
c17bfd
+       }
c17bfd
+       const int is_file = (S_ISREG(fileInfo.st_mode));
c17bfd
+       DBGPRINTF("process_symlink:  found '%s', File: %d (config file: %d), symlink: %d\n",
c17bfd
+               target, is_file, chld->is_file, 0);
c17bfd
+	if (act_obj_add(chld, target, is_file, fileInfo.st_ino, 0, symlink) == RS_RET_OK) {
c17bfd
+		/* need to watch parent target as well for proper rotation support */
c17bfd
+		uint idx = ustrlen(chld->active->name) - ustrlen(chld->active->basename);
c17bfd
+		if (idx) { /* basename is different from name */
c17bfd
+			char parent[MAXFNAME];
c17bfd
+			memcpy(parent, chld->active->name, idx-1);
c17bfd
+			parent[idx-1] = '\0';
c17bfd
+			if(lstat(parent, &fileInfo) != 0) {
c17bfd
+				LogError(errno, RS_RET_ERR,
c17bfd
+						 "imfile: process_symlink: cannot stat directory '%s' - ignored", parent);
c17bfd
+				FINALIZE;
c17bfd
+			}
c17bfd
+			DBGPRINTF("process_symlink:	adding parent '%s' of target '%s'\n", parent, target);
c17bfd
+			act_obj_add(chld->parent->root->edges, parent, 0, fileInfo.st_ino, 0, NULL);
c17bfd
+		}
c17bfd
+	}
c17bfd
+
c17bfd
+finalize_it:
c17bfd
+	free(target);
c17bfd
+	RETiRet;
c17bfd
+}
c17bfd
+
c17bfd
+static void
c17bfd
+poll_tree(fs_edge_t *const chld)
c17bfd
+{
c17bfd
+	struct stat fileInfo;
c17bfd
+	glob_t files;
c17bfd
+	int issymlink;
c17bfd
+	DBGPRINTF("poll_tree: chld %p, name '%s', path: %s\n", chld, chld->name, chld->path);
c17bfd
+	detect_updates(chld);
c17bfd
+	const int ret = glob((char*)chld->path, runModConf->sortFiles|GLOB_BRACE, NULL, &files);
c17bfd
+	DBGPRINTF("poll_tree: glob returned %d\n", ret);
c17bfd
+	if(ret == 0) {
c17bfd
+		DBGPRINTF("poll_tree: processing %d files\n", (int) files.gl_pathc);
c17bfd
+		for(unsigned i = 0 ; i < files.gl_pathc ; i++) {
c17bfd
+			if(glbl.GetGlobalInputTermState() != 0) {
c17bfd
+				goto done;
c17bfd
+			}
c17bfd
+			char *const file = files.gl_pathv[i];
c17bfd
+			if(lstat(file, &fileInfo) != 0) {
c17bfd
+				LogError(errno, RS_RET_ERR,
c17bfd
+					"imfile: poll_tree cannot stat file '%s' - ignored", file);
c17bfd
+				continue;
c17bfd
+			}
c17bfd
+
c17bfd
+			if (S_ISLNK(fileInfo.st_mode)) {
c17bfd
+				rsRetVal slink_ret = process_symlink(chld, file);
c17bfd
+				if (slink_ret != RS_RET_OK) {
c17bfd
+					continue;
c17bfd
+				}
c17bfd
+				issymlink = 1;
c17bfd
+			} else {
c17bfd
+				issymlink = 0;
c17bfd
+			}
c17bfd
+			const int is_file = (S_ISREG(fileInfo.st_mode) || issymlink);
c17bfd
+			DBGPRINTF("poll_tree:  found '%s', File: %d (config file: %d), symlink: %d\n",
c17bfd
+				file, is_file, chld->is_file, issymlink);
c17bfd
+			if(!is_file && S_ISREG(fileInfo.st_mode)) {
c17bfd
+				LogMsg(0, RS_RET_ERR, LOG_WARNING,
c17bfd
+					"imfile: '%s' is neither a regular file, symlink, nor a "
c17bfd
+					"directory - ignored", file);
c17bfd
+				continue;
c17bfd
+			}
c17bfd
+			if(chld->is_file != is_file) {
c17bfd
+				LogMsg(0, RS_RET_ERR, LOG_WARNING,
c17bfd
+					"imfile: '%s' is %s but %s expected - ignored",
c17bfd
+					file, (is_file) ? "FILE" : "DIRECTORY",
c17bfd
+					(chld->is_file) ? "FILE" : "DIRECTORY");
c17bfd
+				continue;
c17bfd
+			}
c17bfd
+			act_obj_add(chld, file, is_file, fileInfo.st_ino, issymlink, NULL);
c17bfd
+		}
c17bfd
+		globfree(&files);
c17bfd
+	}
c17bfd
+
c17bfd
+	poll_active_files(chld);
c17bfd
+
c17bfd
+done:	return;
c17bfd
+}
c17bfd
+
c17bfd
+#ifdef HAVE_INOTIFY_INIT // TODO: shouldn't we use that in polling as well?
c17bfd
+static void
c17bfd
+poll_timeouts(fs_edge_t *const edge)
c17bfd
+{
c17bfd
+	if(edge->is_file) {
c17bfd
+		act_obj_t *act;
c17bfd
+		for(act = edge->active ; act != NULL ; act = act->next) {
c17bfd
+			if(strmReadMultiLine_isTimedOut(act->pStrm)) {
c17bfd
+				DBGPRINTF("timeout occured on %s\n", act->name);
c17bfd
+				pollFile(act);
c17bfd
+			}
c17bfd
+		}
c17bfd
+	}
c17bfd
+}
c17bfd
+#endif
c17bfd
+
c17bfd
+
c17bfd
+/* destruct a single act_obj object */
c17bfd
+static void
c17bfd
+act_obj_destroy(act_obj_t *const act, const int is_deleted)
c17bfd
+{
c17bfd
+	uchar *statefn;
c17bfd
+	uchar statefile[MAXFNAME];
c17bfd
+	uchar toDel[MAXFNAME];
c17bfd
+
c17bfd
+	if(act == NULL)
c17bfd
+		return;
c17bfd
+
c17bfd
+	DBGPRINTF("act_obj_destroy: act %p '%s', (source '%s'), wd %d, pStrm %p, is_deleted %d, in_move %d\n",
c17bfd
+		act, act->name, act->source_name? act->source_name : "---", act->wd, act->pStrm, is_deleted, act->in_move);
c17bfd
+       if(act->is_symlink && is_deleted) {
c17bfd
+		act_obj_t *target_act;
c17bfd
+		for(target_act = act->edge->active ; target_act != NULL ; target_act = target_act->next) {
c17bfd
+			if(target_act->source_name && !strcmp(target_act->source_name, act->name)) {
c17bfd
+				DBGPRINTF("act_obj_destroy: unlinking slink target %s of %s "
c17bfd
+						"symlink\n", target_act->name, act->name);
c17bfd
+				act_obj_unlink(target_act);
c17bfd
+				break;
c17bfd
+			}
c17bfd
+		}
c17bfd
+	}
c17bfd
+	if(act->ratelimiter != NULL) {
c17bfd
+		ratelimitDestruct(act->ratelimiter);
c17bfd
+	}
c17bfd
+	if(act->pStrm != NULL) {
c17bfd
+		const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
c17bfd
+		pollFile(act); /* get any left-over data */
c17bfd
+		if(inst->bRMStateOnDel) {
c17bfd
+			statefn = getStateFileName(act, statefile, sizeof(statefile));
c17bfd
+			getFullStateFileName(statefn, toDel, sizeof(toDel));
c17bfd
+			statefn = toDel;
c17bfd
+		}
c17bfd
+		persistStrmState(act);
c17bfd
+		strm.Destruct(&act->pStrm);
c17bfd
+		/* we delete state file after destruct in case strm obj initiated a write */
c17bfd
+		if(is_deleted && !act->in_move && inst->bRMStateOnDel) {
c17bfd
+			DBGPRINTF("act_obj_destroy: deleting state file %s\n", statefn);
c17bfd
+			unlink((char*)statefn);
c17bfd
+		}
c17bfd
+	}
c17bfd
+	#ifdef HAVE_INOTIFY_INIT
c17bfd
+	if(act->wd != -1) {
c17bfd
+		wdmapDel(act->wd);
c17bfd
+	}
c17bfd
+	#endif
c17bfd
+	#if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE)
c17bfd
+	if(act->pfinf != NULL) {
c17bfd
+		free(act->pfinf->fobj.fo_name);
c17bfd
+		free(act->pfinf);
c17bfd
+	}
c17bfd
+	#endif
c17bfd
+	free(act->basename);
c17bfd
+	free(act->source_name);
c17bfd
+	//free(act->statefile);
c17bfd
+	free(act->multiSub.ppMsgs);
c17bfd
+	#if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE)
c17bfd
+		act->is_deleted = 1;
c17bfd
+	#else
c17bfd
+		free(act->name);
c17bfd
+		free(act);
c17bfd
+	#endif
c17bfd
+}
c17bfd
+
c17bfd
 
c17bfd
+/* destroy complete act list starting at given node */
c17bfd
+static void
c17bfd
+act_obj_destroy_all(act_obj_t *act)
c17bfd
+{
c17bfd
+	if(act == NULL)
c17bfd
+		return;
c17bfd
+
c17bfd
+	DBGPRINTF("act_obj_destroy_all: act %p '%s', wd %d, pStrm %p\n", act, act->name, act->wd, act->pStrm);
c17bfd
+	while(act != NULL) {
c17bfd
+		act_obj_t *const toDel = act;
c17bfd
+		act = act->next;
c17bfd
+		act_obj_destroy(toDel, 0);
c17bfd
+	}
c17bfd
+}
c17bfd
+
c17bfd
+#if 0
c17bfd
+/* debug: find if ptr is still present in list */
c17bfd
+static void
c17bfd
+chk_active(const act_obj_t *act, const act_obj_t *const deleted)
c17bfd
+{
c17bfd
+	while(act != NULL) {
c17bfd
+		DBGPRINTF("chk_active %p vs %p\n", act, deleted);
c17bfd
+		if(act->prev == deleted)
c17bfd
+			DBGPRINTF("chk_active %p prev points to %p\n", act, deleted);
c17bfd
+		if(act->next == deleted)
c17bfd
+			DBGPRINTF("chk_active %p next points to %p\n", act, deleted);
c17bfd
+		act = act->next;
c17bfd
+		DBGPRINTF("chk_active next %p\n", act);
c17bfd
+	}
c17bfd
+}
c17bfd
+#endif
c17bfd
+
c17bfd
+/* unlink act object from linked list and then
c17bfd
+ * destruct it.
c17bfd
+ */
c17bfd
+static void
c17bfd
+act_obj_unlink(act_obj_t *act)
c17bfd
+{
c17bfd
+	DBGPRINTF("act_obj_unlink %p: %s\n", act, act->name);
c17bfd
+	if(act->prev == NULL) {
c17bfd
+		act->edge->active = act->next;
c17bfd
+	} else {
c17bfd
+		act->prev->next = act->next;
c17bfd
+	}
c17bfd
+	if(act->next != NULL) {
c17bfd
+		act->next->prev = act->prev;
c17bfd
+	}
c17bfd
+	act_obj_destroy(act, 1);
c17bfd
+	act = NULL;
c17bfd
+//dbgprintf("printout of fs tree post unlink\n");
c17bfd
+//fs_node_print(runModConf->conf_tree, 0);
c17bfd
+//dbg_wdmapPrint("wdmap after");
c17bfd
+}
c17bfd
+
c17bfd
+static void
c17bfd
+fs_node_destroy(fs_node_t *const node)
c17bfd
+{
c17bfd
+	fs_edge_t *edge;
c17bfd
+	DBGPRINTF("node destroy: %p edges:\n", node);
c17bfd
+
c17bfd
+	for(edge = node->edges ; edge != NULL ; ) {
c17bfd
+		fs_node_destroy(edge->node);
c17bfd
+		fs_edge_t *const toDel = edge;
c17bfd
+		edge = edge->next;
c17bfd
+		act_obj_destroy_all(toDel->active);
c17bfd
+		free(toDel->name);
c17bfd
+		free(toDel->path);
c17bfd
+		free(toDel->instarr);
c17bfd
+		free(toDel);
c17bfd
+	}
c17bfd
+	free(node);
c17bfd
+}
c17bfd
+
c17bfd
+static void
c17bfd
+fs_node_walk(fs_node_t *const node,
c17bfd
+	void (*f_usr)(fs_edge_t*const))
c17bfd
+{
c17bfd
+	DBGPRINTF("node walk: %p edges:\n", node);
c17bfd
+
c17bfd
+	fs_edge_t *edge;
c17bfd
+	for(edge = node->edges ; edge != NULL ; edge = edge->next) {
c17bfd
+		DBGPRINTF("node walk: child %p '%s'\n", edge->node, edge->name);
c17bfd
+		f_usr(edge);
c17bfd
+		fs_node_walk(edge->node, f_usr);
c17bfd
+	}
c17bfd
+}
c17bfd
+
c17bfd
+
c17bfd
+
c17bfd
+/* add a file system object to config tree (or update existing node with new monitor)
c17bfd
+ */
c17bfd
+static rsRetVal
c17bfd
+fs_node_add(fs_node_t *const node, fs_node_t *const source,
c17bfd
+	const uchar *const toFind,
c17bfd
+	const size_t pathIdx,
c17bfd
+	instanceConf_t *const inst)
c17bfd
+{
c17bfd
+	DEFiRet;
c17bfd
+	fs_edge_t *newchld = NULL;
c17bfd
+	int i;
c17bfd
+
c17bfd
+	DBGPRINTF("fs_node_add(%p, '%s') enter, idx %zd\n",
c17bfd
+		node, toFind+pathIdx, pathIdx);
c17bfd
+	assert(toFind[0] != '\0');
c17bfd
+	for(i = pathIdx ; (toFind[i] != '\0') && (toFind[i] != '/') ; ++i)
c17bfd
+		/*JUST SKIP*/;
c17bfd
+	const int isFile = (toFind[i] == '\0') ? 1 : 0;
c17bfd
+	uchar ourPath[PATH_MAX];
c17bfd
+	if(i == 0) {
c17bfd
+		ourPath[0] = '/';
c17bfd
+		ourPath[1] = '\0';
c17bfd
+	} else {
c17bfd
+		memcpy(ourPath, toFind, i);
c17bfd
+		ourPath[i] = '\0';
c17bfd
+	}
c17bfd
+	const size_t nextPathIdx = i+1;
c17bfd
+	const size_t len = i - pathIdx;
c17bfd
+	uchar name[PATH_MAX];
c17bfd
+	memcpy(name, toFind+pathIdx, len);
c17bfd
+	name[len] = '\0';
c17bfd
+	DBGPRINTF("fs_node_add: name '%s'\n", name); node->root = source;
c17bfd
+
c17bfd
+	fs_edge_t *chld;
c17bfd
+	for(chld = node->edges ; chld != NULL ; chld = chld->next) {
c17bfd
+		if(!ustrcmp(chld->name, name)) {
c17bfd
+			DBGPRINTF("fs_node_add(%p, '%s') found '%s'\n", chld->node, toFind, name);
c17bfd
+			/* add new instance */
c17bfd
+			chld->ninst++;
c17bfd
+			CHKmalloc(chld->instarr = realloc(chld->instarr, sizeof(instanceConf_t*) * chld->ninst));
c17bfd
+			chld->instarr[chld->ninst-1] = inst;
c17bfd
+			/* recurse */
c17bfd
+			if(!isFile) {
c17bfd
+				CHKiRet(fs_node_add(chld->node, node, toFind, nextPathIdx, inst));
c17bfd
+			}
c17bfd
+			FINALIZE;
c17bfd
+		}
c17bfd
+	}
c17bfd
+
c17bfd
+	/* could not find node --> add it */
c17bfd
+	DBGPRINTF("fs_node_add(%p, '%s') did not find '%s' - adding it\n",
c17bfd
+		node, toFind, name);
c17bfd
+	CHKmalloc(newchld = calloc(sizeof(fs_edge_t), 1));
c17bfd
+	CHKmalloc(newchld->name = ustrdup(name));
c17bfd
+	CHKmalloc(newchld->node = calloc(sizeof(fs_node_t), 1));
c17bfd
+	CHKmalloc(newchld->path = ustrdup(ourPath));
c17bfd
+	CHKmalloc(newchld->instarr = calloc(sizeof(instanceConf_t*), 1));
c17bfd
+	newchld->instarr[0] = inst;
c17bfd
+	newchld->is_file = isFile;
c17bfd
+	newchld->ninst = 1;
c17bfd
+	newchld->parent = node;
c17bfd
+
c17bfd
+	DBGPRINTF("fs_node_add(%p, '%s') returns %p\n", node, toFind, newchld->node);
c17bfd
+
c17bfd
+	if(!isFile) {
c17bfd
+		CHKiRet(fs_node_add(newchld->node, node, toFind, nextPathIdx, inst));
c17bfd
+	}
c17bfd
+
c17bfd
+	/* link to list */
c17bfd
+	newchld->next = node->edges;
c17bfd
+	node->edges = newchld;
c17bfd
+finalize_it:
c17bfd
+	if(iRet != RS_RET_OK) {
c17bfd
+		if(newchld != NULL) {
c17bfd
+		free(newchld->name);
c17bfd
+		free(newchld->node);
c17bfd
+		free(newchld->path);
c17bfd
+		free(newchld->instarr);
c17bfd
+		free(newchld);
c17bfd
+		}
c17bfd
+	}
c17bfd
+	RETiRet;
c17bfd
+}
c17bfd
+
c17bfd
+/* Helper function to combine statefile and workdir
c17bfd
+ * This function is guranteed to work only on config data and DOES NOT
c17bfd
+ * open or otherwise modify disk file state.
c17bfd
+ */
c17bfd
+static int
c17bfd
+getFullStateFileName(const uchar *const pszstatefile, uchar *const pszout, const size_t ilenout)
c17bfd
+{
c17bfd
+	int lenout;
c17bfd
+	const uchar* pszworkdir;
c17bfd
 
c17bfd
-/* this generates a state file name suitable for the current file. To avoid
c17bfd
+	/* Get Raw Workdir, if it is NULL we need to propper handle it */
c17bfd
+	pszworkdir = glblGetWorkDirRaw();
c17bfd
+
c17bfd
+	/* Construct file name */
c17bfd
+	lenout = snprintf((char*)pszout, ilenout, "%s/%s",
c17bfd
+			     (char*) (pszworkdir == NULL ? "." : (char*) pszworkdir), (char*)pszstatefile);
c17bfd
+
c17bfd
+	/* return out length */
c17bfd
+	return lenout;
c17bfd
+}
c17bfd
+
c17bfd
+
c17bfd
+/* this generates a state file name suitable for the given file. To avoid
c17bfd
  * malloc calls, it must be passed a buffer which should be MAXFNAME large.
c17bfd
  * Note: the buffer is not necessarily populated ... always ONLY use the
c17bfd
  * RETURN VALUE!
c17bfd
+ * This function is guranteed to work only on config data and DOES NOT
c17bfd
+ * open or otherwise modify disk file state.
c17bfd
  */
c17bfd
 static uchar *
c17bfd
-getStateFileName(lstn_t *const __restrict__ pLstn,
c17bfd
+getStateFileName(const act_obj_t *const act,
c17bfd
 	 	 uchar *const __restrict__ buf,
c17bfd
 		 const size_t lenbuf)
c17bfd
 {
c17bfd
-	uchar *ret;
c17bfd
-	if(pLstn->pszStateFile == NULL) {
c17bfd
-		snprintf((char*)buf, lenbuf - 1, "imfile-state:%s", pLstn->pszFileName);
c17bfd
-		buf[lenbuf-1] = '\0'; /* be on the safe side... */
c17bfd
-		uchar *p = buf;
c17bfd
-		for( ; *p ; ++p) {
c17bfd
-			if(*p == '/')
c17bfd
-				*p = '-';
c17bfd
-		}
c17bfd
-		ret = buf;
c17bfd
-	} else {
c17bfd
-		ret = pLstn->pszStateFile;
c17bfd
-	}
c17bfd
-	return ret;
c17bfd
+	DBGPRINTF("getStateFileName for '%s'\n", act->name);
c17bfd
+	snprintf((char*)buf, lenbuf - 1, "imfile-state:%lld", (long long) act->ino);
c17bfd
+	DBGPRINTF("getStateFileName:  stat file name now is %s\n", buf);
c17bfd
+	return buf;
c17bfd
 }
c17bfd
 
c17bfd
 
c17bfd
 /* enqueue the read file line as a message. The provided string is
c17bfd
- * not freed - thuis must be done by the caller.
c17bfd
+ * not freed - this must be done by the caller.
c17bfd
  */
c17bfd
-static rsRetVal enqLine(lstn_t *const __restrict__ pLstn,
c17bfd
-                        cstr_t *const __restrict__ cstrLine)
c17bfd
+#define MAX_OFFSET_REPRESENTATION_NUM_BYTES 20
c17bfd
+static rsRetVal
c17bfd
+enqLine(act_obj_t *const act,
c17bfd
+	cstr_t *const __restrict__ cstrLine,
c17bfd
+	const int64 strtOffs)
c17bfd
 {
c17bfd
 	DEFiRet;
c17bfd
+	const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
c17bfd
 	smsg_t *pMsg;
c17bfd
+	uchar file_offset[MAX_OFFSET_REPRESENTATION_NUM_BYTES+1];
c17bfd
+	const uchar *metadata_names[2] = {(uchar *)"filename",(uchar *)"fileoffset"} ;
c17bfd
+	const uchar *metadata_values[2] ;
c17bfd
+	const size_t msgLen = cstrLen(cstrLine);
c17bfd
 
c17bfd
-	if(rsCStrLen(cstrLine) == 0) {
c17bfd
+	if(msgLen == 0) {
c17bfd
 		/* we do not process empty lines */
c17bfd
 		FINALIZE;
c17bfd
 	}
c17bfd
@@ -474,27 +1180,34 @@ static rsRetVal enqLine(lstn_t *const __restrict__ pLstn,
c17bfd
 	CHKiRet(msgConstruct(&pMsg));
c17bfd
 	MsgSetFlowControlType(pMsg, eFLOWCTL_FULL_DELAY);
c17bfd
 	MsgSetInputName(pMsg, pInputName);
c17bfd
-	if (pLstn->addCeeTag) {
c17bfd
-		size_t msgLen = cstrLen(cstrLine);
c17bfd
-		const char *const ceeToken = "@cee:";
c17bfd
-		size_t ceeMsgSize = msgLen + strlen(ceeToken) +1;
c17bfd
+	if(inst->addCeeTag) {
c17bfd
+		/* Make sure we account for terminating null byte */
c17bfd
+		size_t ceeMsgSize = msgLen + CONST_LEN_CEE_COOKIE + 1;
c17bfd
 		char *ceeMsg;
c17bfd
 		CHKmalloc(ceeMsg = MALLOC(ceeMsgSize));
c17bfd
-		strcpy(ceeMsg, ceeToken);
c17bfd
+		strcpy(ceeMsg, CONST_CEE_COOKIE);
c17bfd
 		strcat(ceeMsg, (char*)rsCStrGetSzStrNoNULL(cstrLine));
c17bfd
 		MsgSetRawMsg(pMsg, ceeMsg, ceeMsgSize);
c17bfd
 		free(ceeMsg);
c17bfd
 	} else {
c17bfd
-		MsgSetRawMsg(pMsg, (char*)rsCStrGetSzStrNoNULL(cstrLine), cstrLen(cstrLine));
c17bfd
+		MsgSetRawMsg(pMsg, (char*)rsCStrGetSzStrNoNULL(cstrLine), msgLen);
c17bfd
 	}
c17bfd
 	MsgSetMSGoffs(pMsg, 0);	/* we do not have a header... */
c17bfd
 	MsgSetHOSTNAME(pMsg, glbl.GetLocalHostName(), ustrlen(glbl.GetLocalHostName()));
c17bfd
-	MsgSetTAG(pMsg, pLstn->pszTag, pLstn->lenTag);
c17bfd
-	msgSetPRI(pMsg, pLstn->iFacility | pLstn->iSeverity);
c17bfd
-	MsgSetRuleset(pMsg, pLstn->pRuleset);
c17bfd
-	if(pLstn->addMetadata)
c17bfd
-		msgAddMetadata(pMsg, (uchar*)"filename", pLstn->pszFileName);
c17bfd
-	ratelimitAddMsg(pLstn->ratelimiter, &pLstn->multiSub, pMsg);
c17bfd
+	MsgSetTAG(pMsg, inst->pszTag, inst->lenTag);
c17bfd
+	msgSetPRI(pMsg, inst->iFacility | inst->iSeverity);
c17bfd
+	MsgSetRuleset(pMsg, inst->pBindRuleset);
c17bfd
+	if(inst->addMetadata) {
c17bfd
+		if (act->source_name) {
c17bfd
+			metadata_values[0] = (const uchar*)act->source_name;
c17bfd
+		} else {
c17bfd
+			metadata_values[0] = (const uchar*)act->name;
c17bfd
+		}
c17bfd
+		snprintf((char *)file_offset, MAX_OFFSET_REPRESENTATION_NUM_BYTES+1, "%lld", strtOffs);
c17bfd
+		metadata_values[1] = file_offset;
c17bfd
+		msgAddMultiMetadata(pMsg, metadata_names, metadata_values, 2);
c17bfd
+	}
c17bfd
+	ratelimitAddMsg(act->ratelimiter, &act->multiSub, pMsg);
c17bfd
 finalize_it:
c17bfd
 	RETiRet;
c17bfd
 }
c17bfd
@@ -504,70 +1213,89 @@ finalize_it:
c17bfd
  * exist or cannot be read, an error is returned.
c17bfd
  */
c17bfd
 static rsRetVal
c17bfd
-openFileWithStateFile(lstn_t *const __restrict__ pLstn)
c17bfd
+openFileWithStateFile(act_obj_t *const act)
c17bfd
 {
c17bfd
 	DEFiRet;
c17bfd
-	strm_t *psSF = NULL;
c17bfd
 	uchar pszSFNam[MAXFNAME];
c17bfd
-	size_t lenSFNam;
c17bfd
-	struct stat stat_buf;
c17bfd
 	uchar statefile[MAXFNAME];
c17bfd
+	int fd = -1;
c17bfd
+	const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
c17bfd
 
c17bfd
-	uchar *const statefn = getStateFileName(pLstn, statefile, sizeof(statefile));
c17bfd
-	DBGPRINTF("imfile: trying to open state for '%s', state file '%s'\n",
c17bfd
-		  pLstn->pszFileName, statefn);
c17bfd
-	/* Construct file name */
c17bfd
-	lenSFNam = snprintf((char*)pszSFNam, sizeof(pszSFNam), "%s/%s",
c17bfd
-			     (char*) glbl.GetWorkDir(), (char*)statefn);
c17bfd
+	uchar *const statefn = getStateFileName(act, statefile, sizeof(statefile));
c17bfd
+
c17bfd
+	getFullStateFileName(statefn, pszSFNam, sizeof(pszSFNam));
c17bfd
+	DBGPRINTF("trying to open state for '%s', state file '%s'\n", act->name, pszSFNam);
c17bfd
 
c17bfd
 	/* check if the file exists */
c17bfd
-	if(stat((char*) pszSFNam, &stat_buf) == -1) {
c17bfd
+	fd = open((char*)pszSFNam, O_CLOEXEC | O_NOCTTY | O_RDONLY, 0600);
c17bfd
+	if(fd < 0) {
c17bfd
 		if(errno == ENOENT) {
c17bfd
-			DBGPRINTF("imfile: NO state file exists for '%s'\n", pLstn->pszFileName);
c17bfd
-			ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
c17bfd
+			DBGPRINTF("NO state file (%s) exists for '%s' - trying to see if "
c17bfd
+				"old-style file exists\n", pszSFNam, act->name);
c17bfd
+			CHKiRet(OLD_openFileWithStateFile(act));
c17bfd
+			FINALIZE;
c17bfd
 		} else {
c17bfd
-			char errStr[1024];
c17bfd
-			rs_strerror_r(errno, errStr, sizeof(errStr));
c17bfd
-			DBGPRINTF("imfile: error trying to access state file for '%s':%s\n",
c17bfd
-			          pLstn->pszFileName, errStr);
c17bfd
+			LogError(errno, RS_RET_IO_ERROR,
c17bfd
+				"imfile error trying to access state file for '%s'",
c17bfd
+			        act->name);
c17bfd
 			ABORT_FINALIZE(RS_RET_IO_ERROR);
c17bfd
 		}
c17bfd
 	}
c17bfd
 
c17bfd
-	/* If we reach this point, we have a state file */
c17bfd
+	CHKiRet(strm.Construct(&act->pStrm));
c17bfd
 
c17bfd
-	CHKiRet(strm.Construct(&psSF));
c17bfd
-	CHKiRet(strm.SettOperationsMode(psSF, STREAMMODE_READ));
c17bfd
-	CHKiRet(strm.SetsType(psSF, STREAMTYPE_FILE_SINGLE));
c17bfd
-	CHKiRet(strm.SetFName(psSF, pszSFNam, lenSFNam));
c17bfd
-	CHKiRet(strm.ConstructFinalize(psSF));
c17bfd
+	struct json_object *jval;
c17bfd
+	struct json_object *json = fjson_object_from_fd(fd);
c17bfd
+	if(json == NULL) {
c17bfd
+		LogError(0, RS_RET_ERR, "imfile: error reading state file for '%s'", act->name);
c17bfd
+	}
c17bfd
 
c17bfd
-	/* read back in the object */
c17bfd
-	CHKiRet(obj.Deserialize(&pLstn->pStrm, (uchar*) "strm", psSF, NULL, pLstn));
c17bfd
-	DBGPRINTF("imfile: deserialized state file, state file base name '%s', "
c17bfd
-		  "configured base name '%s'\n", pLstn->pStrm->pszFName,
c17bfd
-		  pLstn->pszFileName);
c17bfd
-	if(ustrcmp(pLstn->pStrm->pszFName, pLstn->pszFileName)) {
c17bfd
-		errmsg.LogError(0, RS_RET_STATEFILE_WRONG_FNAME, "imfile: state file '%s' "
c17bfd
-				"contains file name '%s', but is used for file '%s'. State "
c17bfd
-				"file deleted, starting from begin of file.",
c17bfd
-				pszSFNam, pLstn->pStrm->pszFName, pLstn->pszFileName);
c17bfd
+	/* we access some data items a bit dirty, as we need to refactor the whole
c17bfd
+	 * thing in any case - TODO
c17bfd
+	 */
c17bfd
+	/* Note: we ignore filname property - it is just an aid to the user. Most
c17bfd
+	 * importantly it *is wrong* after a file move!
c17bfd
+	 */
c17bfd
+	fjson_object_object_get_ex(json, "prev_was_nl", &jval);
c17bfd
+	act->pStrm->bPrevWasNL = fjson_object_get_int(jval);
c17bfd
 
c17bfd
-		unlink((char*)pszSFNam);
c17bfd
-		ABORT_FINALIZE(RS_RET_STATEFILE_WRONG_FNAME);
c17bfd
+	fjson_object_object_get_ex(json, "curr_offs", &jval);
c17bfd
+	act->pStrm->iCurrOffs = fjson_object_get_int64(jval);
c17bfd
+
c17bfd
+	fjson_object_object_get_ex(json, "strt_offs", &jval);
c17bfd
+	act->pStrm->strtOffs = fjson_object_get_int64(jval);
c17bfd
+
c17bfd
+	fjson_object_object_get_ex(json, "prev_line_segment", &jval);
c17bfd
+	const uchar *const prev_line_segment = (const uchar*)fjson_object_get_string(jval);
c17bfd
+	if(jval != NULL) {
c17bfd
+		CHKiRet(rsCStrConstructFromszStr(&act->pStrm->prevLineSegment, prev_line_segment));
c17bfd
+		cstrFinalize(act->pStrm->prevLineSegment);
c17bfd
+		uchar *ret = rsCStrGetSzStrNoNULL(act->pStrm->prevLineSegment);
c17bfd
+		DBGPRINTF("prev_line_segment present in state file 2, is: %s\n", ret);
c17bfd
 	}
c17bfd
 
c17bfd
-	strm.CheckFileChange(pLstn->pStrm);
c17bfd
-	CHKiRet(strm.SeekCurrOffs(pLstn->pStrm));
c17bfd
+	fjson_object_object_get_ex(json, "prev_msg_segment", &jval);
c17bfd
+	const uchar *const prev_msg_segment = (const uchar*)fjson_object_get_string(jval);
c17bfd
+	if(jval != NULL) {
c17bfd
+		CHKiRet(rsCStrConstructFromszStr(&act->pStrm->prevMsgSegment, prev_msg_segment));
c17bfd
+		cstrFinalize(act->pStrm->prevMsgSegment);
c17bfd
+		uchar *ret = rsCStrGetSzStrNoNULL(act->pStrm->prevMsgSegment);
c17bfd
+		DBGPRINTF("prev_msg_segment present in state file 2, is: %s\n", ret);
c17bfd
+	}
c17bfd
+	fjson_object_put(json);
c17bfd
 
c17bfd
-	/* note: we do not delete the state file, so that the last position remains
c17bfd
-	 * known even in the case that rsyslogd aborts for some reason (like powerfail)
c17bfd
-	 */
c17bfd
+	CHKiRet(strm.SetFName(act->pStrm, (uchar*)act->name, strlen(act->name)));
c17bfd
+	CHKiRet(strm.SettOperationsMode(act->pStrm, STREAMMODE_READ));
c17bfd
+	CHKiRet(strm.SetsType(act->pStrm, STREAMTYPE_FILE_MONITOR));
c17bfd
+	CHKiRet(strm.SetFileNotFoundError(act->pStrm, inst->fileNotFoundError));
c17bfd
+	CHKiRet(strm.ConstructFinalize(act->pStrm));
c17bfd
 
c17bfd
-finalize_it:
c17bfd
-	if(psSF != NULL)
c17bfd
-		strm.Destruct(&psSF);
c17bfd
+	CHKiRet(strm.SeekCurrOffs(act->pStrm));
c17bfd
 
c17bfd
+finalize_it:
c17bfd
+	if(fd >= 0) {
c17bfd
+		close(fd);
c17bfd
+	}
c17bfd
 	RETiRet;
c17bfd
 }
c17bfd
 
c17bfd
@@ -576,30 +1304,32 @@ finalize_it:
c17bfd
  * checked before calling it.
c17bfd
  */
c17bfd
 static rsRetVal
c17bfd
-openFileWithoutStateFile(lstn_t *const __restrict__ pLstn)
c17bfd
+openFileWithoutStateFile(act_obj_t *const act)
c17bfd
 {
c17bfd
 	DEFiRet;
c17bfd
 	struct stat stat_buf;
c17bfd
 
c17bfd
-	DBGPRINTF("imfile: clean startup withOUT state file for '%s'\n", pLstn->pszFileName);
c17bfd
-	if(pLstn->pStrm != NULL)
c17bfd
-		strm.Destruct(&pLstn->pStrm);
c17bfd
-	CHKiRet(strm.Construct(&pLstn->pStrm));
c17bfd
-	CHKiRet(strm.SettOperationsMode(pLstn->pStrm, STREAMMODE_READ));
c17bfd
-	CHKiRet(strm.SetsType(pLstn->pStrm, STREAMTYPE_FILE_MONITOR));
c17bfd
-	CHKiRet(strm.SetFName(pLstn->pStrm, pLstn->pszFileName, strlen((char*) pLstn->pszFileName)));
c17bfd
-	CHKiRet(strm.ConstructFinalize(pLstn->pStrm));
c17bfd
+	const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
c17bfd
+
c17bfd
+	DBGPRINTF("clean startup withOUT state file for '%s'\n", act->name);
c17bfd
+	if(act->pStrm != NULL)
c17bfd
+		strm.Destruct(&act->pStrm);
c17bfd
+	CHKiRet(strm.Construct(&act->pStrm));
c17bfd
+	CHKiRet(strm.SettOperationsMode(act->pStrm, STREAMMODE_READ));
c17bfd
+	CHKiRet(strm.SetsType(act->pStrm, STREAMTYPE_FILE_MONITOR));
c17bfd
+	CHKiRet(strm.SetFName(act->pStrm, (uchar*)act->name, strlen(act->name)));
c17bfd
+	CHKiRet(strm.SetFileNotFoundError(act->pStrm, inst->fileNotFoundError));
c17bfd
+	CHKiRet(strm.ConstructFinalize(act->pStrm));
c17bfd
 
c17bfd
 	/* As a state file not exist, this is a fresh start. seek to file end
c17bfd
 	 * when freshStartTail is on.
c17bfd
 	 */
c17bfd
-	if(pLstn->freshStartTail){
c17bfd
-		if(stat((char*) pLstn->pszFileName, &stat_buf) != -1) {
c17bfd
-			pLstn->pStrm->iCurrOffs = stat_buf.st_size;
c17bfd
-			CHKiRet(strm.SeekCurrOffs(pLstn->pStrm));
c17bfd
+	if(inst->freshStartTail){
c17bfd
+		if(stat((char*) act->name, &stat_buf) != -1) {
c17bfd
+			act->pStrm->iCurrOffs = stat_buf.st_size;
c17bfd
+			CHKiRet(strm.SeekCurrOffs(act->pStrm));
c17bfd
 		}
c17bfd
 	}
c17bfd
-	strmSetReadTimeout(pLstn->pStrm, pLstn->readTimeout);
c17bfd
 
c17bfd
 finalize_it:
c17bfd
 	RETiRet;
c17bfd
@@ -608,17 +1338,18 @@ finalize_it:
c17bfd
  * if so, reading it in. Processing continues from the last know location.
c17bfd
  */
c17bfd
 static rsRetVal
c17bfd
-openFile(lstn_t *const __restrict__ pLstn)
c17bfd
+openFile(act_obj_t *const act)
c17bfd
 {
c17bfd
 	DEFiRet;
c17bfd
+	const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
c17bfd
 
c17bfd
-	CHKiRet_Hdlr(openFileWithStateFile(pLstn)) {
c17bfd
-		CHKiRet(openFileWithoutStateFile(pLstn));
c17bfd
+	CHKiRet_Hdlr(openFileWithStateFile(act)) {
c17bfd
+		CHKiRet(openFileWithoutStateFile(act));
c17bfd
 	}
c17bfd
 
c17bfd
-	DBGPRINTF("imfile: breopenOnTruncate %d for '%s'\n",
c17bfd
-		pLstn->reopenOnTruncate, pLstn->pszFileName);
c17bfd
-	CHKiRet(strm.SetbReopenOnTruncate(pLstn->pStrm, pLstn->reopenOnTruncate));
c17bfd
+	DBGPRINTF("breopenOnTruncate %d for '%s'\n", inst->reopenOnTruncate, act->name);
c17bfd
+	CHKiRet(strm.SetbReopenOnTruncate(act->pStrm, inst->reopenOnTruncate));
c17bfd
+	strmSetReadTimeout(act->pStrm, inst->readTimeout);
c17bfd
 
c17bfd
 finalize_it:
c17bfd
 	RETiRet;
c17bfd
@@ -638,58 +1369,72 @@ static void pollFileCancelCleanup(void *pArg)
c17bfd
 }
c17bfd
 
c17bfd
 
c17bfd
-/* poll a file, need to check file rollover etc. open file if not open */
c17bfd
-#if !defined(_AIX)
c17bfd
-#pragma GCC diagnostic ignored "-Wempty-body"
c17bfd
-#endif
c17bfd
+/* pollFile needs to be split due to the unfortunate pthread_cancel_push() macros. */
c17bfd
 static rsRetVal
c17bfd
-pollFile(lstn_t *pLstn, int *pbHadFileData)
c17bfd
+pollFileReal(act_obj_t *act, cstr_t **pCStr)
c17bfd
 {
c17bfd
-	cstr_t *pCStr = NULL;
c17bfd
+	int64 strtOffs;
c17bfd
 	DEFiRet;
c17bfd
-
c17bfd
-	/* Note: we must do pthread_cleanup_push() immediately, because the POXIS macros
c17bfd
-	 * otherwise do not work if I include the _cleanup_pop() inside an if... -- rgerhards, 2008-08-14
c17bfd
-	 */
c17bfd
-	pthread_cleanup_push(pollFileCancelCleanup, &pCStr);
c17bfd
 	int nProcessed = 0;
c17bfd
-	if(pLstn->pStrm == NULL) {
c17bfd
-		CHKiRet(openFile(pLstn)); /* open file */
c17bfd
+
c17bfd
+	DBGPRINTF("pollFileReal enter, pStrm %p, name '%s'\n", act->pStrm, act->name);
c17bfd
+	DBGPRINTF("pollFileReal enter, edge %p\n", act->edge);
c17bfd
+	DBGPRINTF("pollFileReal enter, edge->instarr %p\n", act->edge->instarr);
c17bfd
+
c17bfd
+	instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
c17bfd
+
c17bfd
+	if(act->pStrm == NULL) {
c17bfd
+		CHKiRet(openFile(act)); /* open file */
c17bfd
 	}
c17bfd
 
c17bfd
 	/* loop below will be exited when strmReadLine() returns EOF */
c17bfd
 	while(glbl.GetGlobalInputTermState() == 0) {
c17bfd
-		if(pLstn->maxLinesAtOnce != 0 && nProcessed >= pLstn->maxLinesAtOnce)
c17bfd
+		if(inst->maxLinesAtOnce != 0 && nProcessed >= inst->maxLinesAtOnce)
c17bfd
 			break;
c17bfd
-		if(pLstn->startRegex == NULL) {
c17bfd
-			CHKiRet(strm.ReadLine(pLstn->pStrm, &pCStr, pLstn->readMode, pLstn->escapeLF, pLstn->trimLineOverBytes));
c17bfd
+		if(inst->startRegex == NULL) {
c17bfd
+			CHKiRet(strm.ReadLine(act->pStrm, pCStr, inst->readMode, inst->escapeLF,
c17bfd
+				inst->trimLineOverBytes, &strtOffs));
c17bfd
 		} else {
c17bfd
-			CHKiRet(strmReadMultiLine(pLstn->pStrm, &pCStr, &pLstn->end_preg, pLstn->escapeLF));
c17bfd
+			CHKiRet(strmReadMultiLine(act->pStrm, pCStr, &inst->end_preg,
c17bfd
+				inst->escapeLF, &strtOffs));
c17bfd
 		}
c17bfd
 		++nProcessed;
c17bfd
-		if(pbHadFileData != NULL)
c17bfd
-			*pbHadFileData = 1; /* this is just a flag, so set it and forget it */
c17bfd
-		CHKiRet(enqLine(pLstn, pCStr)); /* process line */
c17bfd
-		rsCStrDestruct(&pCStr); /* discard string (must be done by us!) */
c17bfd
-		if(pLstn->iPersistStateInterval > 0 && pLstn->nRecords++ >= pLstn->iPersistStateInterval) {
c17bfd
-			persistStrmState(pLstn);
c17bfd
-			pLstn->nRecords = 0;
c17bfd
+		runModConf->bHadFileData = 1; /* this is just a flag, so set it and forget it */
c17bfd
+		CHKiRet(enqLine(act, *pCStr, strtOffs)); /* process line */
c17bfd
+		rsCStrDestruct(pCStr); /* discard string (must be done by us!) */
c17bfd
+		if(inst->iPersistStateInterval > 0 && ++act->nRecords >= inst->iPersistStateInterval) {
c17bfd
+			persistStrmState(act);
c17bfd
+			act->nRecords = 0;
c17bfd
 		}
c17bfd
 	}
c17bfd
 
c17bfd
 finalize_it:
c17bfd
-	multiSubmitFlush(&pLstn->multiSub);
c17bfd
-	pthread_cleanup_pop(0);
c17bfd
+	multiSubmitFlush(&act->multiSub);
c17bfd
 
c17bfd
-	if(pCStr != NULL) {
c17bfd
-		rsCStrDestruct(&pCStr);
c17bfd
+	if(*pCStr != NULL) {
c17bfd
+		rsCStrDestruct(pCStr);
c17bfd
 	}
c17bfd
 
c17bfd
 	RETiRet;
c17bfd
 }
c17bfd
-#if !defined(_AIX)
c17bfd
-#pragma GCC diagnostic warning "-Wempty-body"
c17bfd
-#endif
c17bfd
+
c17bfd
+/* poll a file, need to check file rollover etc. open file if not open */
c17bfd
+static rsRetVal
c17bfd
+pollFile(act_obj_t *const act)
c17bfd
+{
c17bfd
+	cstr_t *pCStr = NULL;
c17bfd
+	DEFiRet;
c17bfd
+	if (act->is_symlink) {
c17bfd
+		FINALIZE;    /* no reason to poll symlink file */
c17bfd
+	}
c17bfd
+	/* Note: we must do pthread_cleanup_push() immediately, because the POSIX macros
c17bfd
+	 * otherwise do not work if I include the _cleanup_pop() inside an if... -- rgerhards, 2008-08-14
c17bfd
+	 */
c17bfd
+	pthread_cleanup_push(pollFileCancelCleanup, &pCStr);
c17bfd
+	iRet = pollFileReal(act, &pCStr);
c17bfd
+	pthread_cleanup_pop(0);
c17bfd
+finalize_it: RETiRet;
c17bfd
+}
c17bfd
 
c17bfd
 
c17bfd
 /* create input instance, set default parameters, and
c17bfd
@@ -722,6 +1467,7 @@ createInstance(instanceConf_t **pinst)
c17bfd
 	inst->addMetadata = ADD_METADATA_UNSPECIFIED;
c17bfd
 	inst->addCeeTag = 0;
c17bfd
 	inst->freshStartTail = 0;
c17bfd
+	inst->fileNotFoundError = 1;
c17bfd
 	inst->readTimeout = loadModConf->readTimeout;
c17bfd
 
c17bfd
 	/* node created, let's add to config */
c17bfd
@@ -767,19 +1513,11 @@ getBasename(uchar *const __restrict__ basen, uchar *const __restrict__ path)
c17bfd
 }
c17bfd
 
c17bfd
 /* this function checks instance parameters and does some required pre-processing
c17bfd
- * (e.g. split filename in path and actual name)
c17bfd
- * Note: we do NOT use dirname()/basename() as they have portability problems.
c17bfd
  */
c17bfd
 static rsRetVal
c17bfd
-checkInstance(instanceConf_t *inst)
c17bfd
+checkInstance(instanceConf_t *const inst)
c17bfd
 {
c17bfd
-	char dirn[MAXFNAME];
c17bfd
-	uchar basen[MAXFNAME];
c17bfd
-	int i;
c17bfd
-	struct stat sb;
c17bfd
-	int r;
c17bfd
-	int eno;
c17bfd
-	char errStr[512];
c17bfd
+	uchar curr_wd[MAXFNAME];
c17bfd
 	DEFiRet;
c17bfd
 
c17bfd
 	/* this is primarily for the clang static analyzer, but also
c17bfd
@@ -788,36 +1526,37 @@ checkInstance(instanceConf_t *inst)
c17bfd
 	if(inst->pszFileName == NULL)
c17bfd
 		ABORT_FINALIZE(RS_RET_INTERNAL_ERROR);
c17bfd
 
c17bfd
-	i = getBasename(basen, inst->pszFileName);
c17bfd
-	if (i == -1) {
c17bfd
-		errmsg.LogError(0, RS_RET_CONFIG_ERROR, "imfile: file path '%s' does not include a basename component",
c17bfd
-			inst->pszFileName);
c17bfd
-		ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
c17bfd
-	}
c17bfd
-	
c17bfd
-	memcpy(dirn, inst->pszFileName, i); /* do not copy slash */
c17bfd
-	dirn[i] = '\0';
c17bfd
-	CHKmalloc(inst->pszFileBaseName = (uchar*) strdup((char*)basen));
c17bfd
-	CHKmalloc(inst->pszDirName = (uchar*) strdup(dirn));
c17bfd
-
c17bfd
-	if(dirn[0] == '\0') {
c17bfd
-		dirn[0] = '/';
c17bfd
-		dirn[1] = '\0';
c17bfd
-	}
c17bfd
-	r = stat(dirn, &sb);
c17bfd
-	if(r != 0)  {
c17bfd
-		eno = errno;
c17bfd
-		rs_strerror_r(eno, errStr, sizeof(errStr));
c17bfd
-		errmsg.LogError(0, RS_RET_CONFIG_ERROR, "imfile warning: directory '%s': %s",
c17bfd
-				dirn, errStr);
c17bfd
-		ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
c17bfd
-	}
c17bfd
-	if(!S_ISDIR(sb.st_mode)) {
c17bfd
-		errmsg.LogError(0, RS_RET_CONFIG_ERROR, "imfile warning: configured directory "
c17bfd
-				"'%s' is NOT a directory", dirn);
c17bfd
-		ABORT_FINALIZE(RS_RET_CONFIG_ERROR);
c17bfd
+	CHKmalloc(inst->pszFileName_forOldStateFile = ustrdup(inst->pszFileName));
c17bfd
+	if(loadModConf->normalizePath) {
c17bfd
+		if(inst->pszFileName[0] == '.' && inst->pszFileName[1] == '/') {
c17bfd
+			DBGPRINTF("imfile: removing heading './' from name '%s'\n", inst->pszFileName);
c17bfd
+			memmove(inst->pszFileName, inst->pszFileName+2, ustrlen(inst->pszFileName) - 1);
c17bfd
+		}
c17bfd
+
c17bfd
+		if(inst->pszFileName[0] != '/') {
c17bfd
+			if(getcwd((char*)curr_wd, MAXFNAME) == NULL || curr_wd[0] != '/') {
c17bfd
+				LogError(errno, RS_RET_ERR, "imfile: error querying current working "
c17bfd
+					"directory - can not continue with %s", inst->pszFileName);
c17bfd
+				ABORT_FINALIZE(RS_RET_ERR);
c17bfd
+			}
c17bfd
+			const size_t len_curr_wd = ustrlen(curr_wd);
c17bfd
+			if(len_curr_wd + ustrlen(inst->pszFileName) + 1 >= MAXFNAME) {
c17bfd
+				LogError(0, RS_RET_ERR, "imfile: length of configured file and current "
c17bfd
+					"working directory exceeds permitted size - ignoring %s",
c17bfd
+					inst->pszFileName);
c17bfd
+				ABORT_FINALIZE(RS_RET_ERR);
c17bfd
+			}
c17bfd
+			curr_wd[len_curr_wd] = '/';
c17bfd
+			strcpy((char*)curr_wd+len_curr_wd+1, (char*)inst->pszFileName);
c17bfd
+			free(inst->pszFileName);
c17bfd
+			CHKmalloc(inst->pszFileName = ustrdup(curr_wd));
c17bfd
+		}
c17bfd
 	}
c17bfd
+	dbgprintf("imfile: adding file monitor for '%s'\n", inst->pszFileName);
c17bfd
 
c17bfd
+	if(inst->pszTag != NULL) {
c17bfd
+		inst->lenTag = ustrlen(inst->pszTag);
c17bfd
+	}
c17bfd
 finalize_it:
c17bfd
 	RETiRet;
c17bfd
 }
c17bfd
@@ -869,140 +1608,14 @@ addInstance(void __attribute__((unused)) *pVal, uchar *pNewVal)
c17bfd
 	inst->bRMStateOnDel = 0;
c17bfd
 	inst->readTimeout = loadModConf->readTimeout;
c17bfd
 
c17bfd
-	CHKiRet(checkInstance(inst));
c17bfd
-
c17bfd
-	/* reset legacy system */
c17bfd
-	cs.iPersistStateInterval = 0;
c17bfd
-	resetConfigVariables(NULL, NULL); /* values are both dummies */
c17bfd
-
c17bfd
-finalize_it:
c17bfd
-	free(pNewVal); /* we do not need it, but we must free it! */
c17bfd
-	RETiRet;
c17bfd
-}
c17bfd
-
c17bfd
-
c17bfd
-/* This adds a new listener object to the bottom of the list, but
c17bfd
- * it does NOT initialize any data members except for the list
c17bfd
- * pointers themselves.
c17bfd
- */
c17bfd
-static rsRetVal
c17bfd
-lstnAdd(lstn_t **newLstn)
c17bfd
-{
c17bfd
-	lstn_t *pLstn;
c17bfd
-	DEFiRet;
c17bfd
-
c17bfd
-	CHKmalloc(pLstn = (lstn_t*) MALLOC(sizeof(lstn_t)));
c17bfd
-	if(runModConf->pRootLstn == NULL) {
c17bfd
-		runModConf->pRootLstn = pLstn;
c17bfd
-		pLstn->prev = NULL;
c17bfd
-	} else {
c17bfd
-		runModConf->pTailLstn->next = pLstn;
c17bfd
-		pLstn->prev = runModConf->pTailLstn;
c17bfd
-	}
c17bfd
-	runModConf->pTailLstn = pLstn;
c17bfd
-	pLstn->next = NULL;
c17bfd
-	*newLstn = pLstn;
c17bfd
-
c17bfd
-finalize_it:
c17bfd
-	RETiRet;
c17bfd
-}
c17bfd
-
c17bfd
-/* delete a listener object */
c17bfd
-static void
c17bfd
-lstnDel(lstn_t *pLstn)
c17bfd
-{
c17bfd
-	DBGPRINTF("imfile: lstnDel called for %s\n", pLstn->pszFileName);
c17bfd
-	if(pLstn->pStrm != NULL) { /* stream open? */
c17bfd
-		persistStrmState(pLstn);
c17bfd
-		strm.Destruct(&(pLstn->pStrm));
c17bfd
-	}
c17bfd
-	ratelimitDestruct(pLstn->ratelimiter);
c17bfd
-	free(pLstn->multiSub.ppMsgs);
c17bfd
-	free(pLstn->pszFileName);
c17bfd
-	free(pLstn->pszTag);
c17bfd
-	free(pLstn->pszStateFile);
c17bfd
-	free(pLstn->pszBaseName);
c17bfd
-	if(pLstn->startRegex != NULL)
c17bfd
-		regfree(&pLstn->end_preg);
c17bfd
-
c17bfd
-	if(pLstn == runModConf->pRootLstn)
c17bfd
-		runModConf->pRootLstn = pLstn->next;
c17bfd
-	if(pLstn == runModConf->pTailLstn)
c17bfd
-		runModConf->pTailLstn = pLstn->prev;
c17bfd
-	if(pLstn->next != NULL)
c17bfd
-		pLstn->next->prev = pLstn->prev;
c17bfd
-	if(pLstn->prev != NULL)
c17bfd
-		pLstn->prev->next = pLstn->next;
c17bfd
-	free(pLstn);
c17bfd
-}
c17bfd
-
c17bfd
-/* This function is called when a new listener shall be added.
c17bfd
- * It also does some late stage error checking on the config
c17bfd
- * and reports issues it finds.
c17bfd
- */
c17bfd
-static rsRetVal
c17bfd
-addListner(instanceConf_t *inst)
c17bfd
-{
c17bfd
-	DEFiRet;
c17bfd
-	lstn_t *pThis;
c17bfd
-	sbool hasWildcard;
c17bfd
-
c17bfd
-	hasWildcard = containsGlobWildcard((char*)inst->pszFileBaseName);
c17bfd
-	if(hasWildcard) {
c17bfd
-		if(runModConf->opMode == OPMODE_POLLING) {
c17bfd
-			errmsg.LogError(0, RS_RET_IMFILE_WILDCARD,
c17bfd
-				"imfile: The to-be-monitored file \"%s\" contains "
c17bfd
-				"wildcards. This is not supported in "
c17bfd
-				"polling mode.", inst->pszFileName);
c17bfd
-			ABORT_FINALIZE(RS_RET_IMFILE_WILDCARD);
c17bfd
-		} else if(inst->pszStateFile != NULL) {
c17bfd
-			errmsg.LogError(0, RS_RET_IMFILE_WILDCARD,
c17bfd
-				"imfile: warning: it looks like to-be-monitored "
c17bfd
-				"file \"%s\" contains wildcards. This usually "
c17bfd
-				"does not work well with specifying a state file.",
c17bfd
-				inst->pszFileName);
c17bfd
-		}
c17bfd
-	}
c17bfd
+	CHKiRet(checkInstance(inst));
c17bfd
+
c17bfd
+	/* reset legacy system */
c17bfd
+	cs.iPersistStateInterval = 0;
c17bfd
+	resetConfigVariables(NULL, NULL); /* values are both dummies */
c17bfd
 
c17bfd
-	CHKiRet(lstnAdd(&pThis));
c17bfd
-	pThis->hasWildcard = hasWildcard;
c17bfd
-	pThis->pszFileName = (uchar*) strdup((char*) inst->pszFileName);
c17bfd
-	pThis->pszDirName = inst->pszDirName; /* use memory from inst! */
c17bfd
-	pThis->pszBaseName = (uchar*)strdup((char*)inst->pszFileBaseName); /* be consistent with expanded wildcards! */
c17bfd
-	pThis->pszTag = (uchar*) strdup((char*) inst->pszTag);
c17bfd
-	pThis->lenTag = ustrlen(pThis->pszTag);
c17bfd
-	pThis->pszStateFile = inst->pszStateFile == NULL ? NULL : (uchar*) strdup((char*) inst->pszStateFile);
c17bfd
-
c17bfd
-	CHKiRet(ratelimitNew(&pThis->ratelimiter, "imfile", (char*)inst->pszFileName));
c17bfd
-	CHKmalloc(pThis->multiSub.ppMsgs = MALLOC(inst->nMultiSub * sizeof(smsg_t *)));
c17bfd
-	pThis->multiSub.maxElem = inst->nMultiSub;
c17bfd
-	pThis->multiSub.nElem = 0;
c17bfd
-	pThis->iSeverity = inst->iSeverity;
c17bfd
-	pThis->iFacility = inst->iFacility;
c17bfd
-	pThis->maxLinesAtOnce = inst->maxLinesAtOnce;
c17bfd
-	pThis->trimLineOverBytes = inst->trimLineOverBytes;
c17bfd
-	pThis->iPersistStateInterval = inst->iPersistStateInterval;
c17bfd
-	pThis->readMode = inst->readMode;
c17bfd
-	pThis->startRegex = inst->startRegex; /* no strdup, as it is read-only */
c17bfd
-	if(pThis->startRegex != NULL)
c17bfd
-		if(regcomp(&pThis->end_preg, (char*)pThis->startRegex, REG_EXTENDED)) {
c17bfd
-			DBGPRINTF("imfile: error regex compile\n");
c17bfd
-			ABORT_FINALIZE(RS_RET_ERR);
c17bfd
-		}
c17bfd
-	pThis->bRMStateOnDel = inst->bRMStateOnDel;
c17bfd
-	pThis->escapeLF = inst->escapeLF;
c17bfd
-	pThis->reopenOnTruncate = inst->reopenOnTruncate;
c17bfd
-	pThis->addMetadata = (inst->addMetadata == ADD_METADATA_UNSPECIFIED) ?
c17bfd
-			       hasWildcard : inst->addMetadata;
c17bfd
-	pThis->addCeeTag = inst->addCeeTag;
c17bfd
-	pThis->readTimeout = inst->readTimeout;
c17bfd
-	pThis->freshStartTail = inst->freshStartTail;
c17bfd
-	pThis->pRuleset = inst->pBindRuleset;
c17bfd
-	pThis->nRecords = 0;
c17bfd
-	pThis->pStrm = NULL;
c17bfd
-	pThis->prevLineSegment = NULL;
c17bfd
-	pThis->masterLstn = NULL; /* we *are* a master! */
c17bfd
 finalize_it:
c17bfd
+	free(pNewVal); /* we do not need it, but we must free it! */
c17bfd
 	RETiRet;
c17bfd
 }
c17bfd
 
c17bfd
@@ -1055,6 +1668,8 @@ CODESTARTnewInpInst
c17bfd
 			inst->addCeeTag = (sbool) pvals[i].val.d.n;
c17bfd
 		} else if(!strcmp(inppblk.descr[i].name, "freshstarttail")) {
c17bfd
 			inst->freshStartTail = (sbool) pvals[i].val.d.n;
c17bfd
+		} else if(!strcmp(inppblk.descr[i].name, "filenotfounderror")) {
c17bfd
+			inst->fileNotFoundError = (sbool) pvals[i].val.d.n;
c17bfd
 		} else if(!strcmp(inppblk.descr[i].name, "escapelf")) {
c17bfd
 			inst->escapeLF = (sbool) pvals[i].val.d.n;
c17bfd
 		} else if(!strcmp(inppblk.descr[i].name, "reopenontruncate")) {
c17bfd
@@ -1087,6 +1702,16 @@ CODESTARTnewInpInst
c17bfd
 			"at the same time --- remove one of them");
c17bfd
 			ABORT_FINALIZE(RS_RET_PARAM_NOT_PERMITTED);
c17bfd
 	}
c17bfd
+
c17bfd
+	if(inst->startRegex != NULL) {
c17bfd
+		const int errcode = regcomp(&inst->end_preg, (char*)inst->startRegex, REG_EXTENDED);
c17bfd
+		if(errcode != 0) {
c17bfd
+			char errbuff[512];
c17bfd
+			regerror(errcode, &inst->end_preg, errbuff, sizeof(errbuff));
c17bfd
+			parser_errmsg("imfile: error in regex expansion: %s", errbuff);
c17bfd
+			ABORT_FINALIZE(RS_RET_ERR);
c17bfd
+		}
c17bfd
+	}
c17bfd
 	if(inst->readTimeout != 0)
c17bfd
 		loadModConf->haveReadTimeouts = 1;
c17bfd
 	CHKiRet(checkInstance(inst));
c17bfd
@@ -1106,6 +1731,10 @@ CODESTARTbeginCnfLoad
c17bfd
 	loadModConf->readTimeout = 0; /* default: no timeout */
c17bfd
 	loadModConf->timeoutGranularity = 1000; /* default: 1 second */
c17bfd
 	loadModConf->haveReadTimeouts = 0; /* default: no timeout */
c17bfd
+	loadModConf->normalizePath = 1;
c17bfd
+	loadModConf->sortFiles = GLOB_NOSORT;
c17bfd
+	loadModConf->conf_tree = calloc(sizeof(fs_node_t), 1);
c17bfd
+	loadModConf->conf_tree->edges = NULL;
c17bfd
 	bLegacyCnfModGlobalsPermitted = 1;
c17bfd
 	/* init legacy config vars */
c17bfd
 	cs.pszFileName = NULL;
c17bfd
@@ -1148,6 +1777,10 @@ CODESTARTsetModCnf
c17bfd
 		} else if(!strcmp(modpblk.descr[i].name, "timeoutgranularity")) {
c17bfd
 			/* note: we need ms, thus "* 1000" */
c17bfd
 			loadModConf->timeoutGranularity = (int) pvals[i].val.d.n * 1000;
c17bfd
+		} else if(!strcmp(modpblk.descr[i].name, "sortfiles")) {
c17bfd
+			loadModConf->sortFiles = ((sbool) pvals[i].val.d.n) ? 0 : GLOB_NOSORT;
c17bfd
+		} else if(!strcmp(modpblk.descr[i].name, "normalizepath")) {
c17bfd
+			loadModConf->normalizePath = (sbool) pvals[i].val.d.n;
c17bfd
 		} else if(!strcmp(modpblk.descr[i].name, "mode")) {
c17bfd
 			if(!es_strconstcmp(pvals[i].val.d.estr, "polling"))
c17bfd
 				loadModConf->opMode = OPMODE_POLLING;
c17bfd
@@ -1217,19 +1850,31 @@ BEGINactivateCnf
c17bfd
 	instanceConf_t *inst;
c17bfd
 CODESTARTactivateCnf
c17bfd
 	runModConf = pModConf;
c17bfd
-	runModConf->pRootLstn = NULL,
c17bfd
-	runModConf->pTailLstn = NULL;
c17bfd
+	if(runModConf->root == NULL) {
c17bfd
+		LogError(0, NO_ERRCODE, "imfile: no file monitors configured, "
c17bfd
+				"input not activated.\n");
c17bfd
+		ABORT_FINALIZE(RS_RET_NO_RUN);
c17bfd
+	}
c17bfd
 
c17bfd
 	for(inst = runModConf->root ; inst != NULL ; inst = inst->next) {
c17bfd
-		addListner(inst);
c17bfd
+		// TODO: provide switch to turn off this warning?
c17bfd
+		if(!containsGlobWildcard((char*)inst->pszFileName)) {
c17bfd
+			if(access((char*)inst->pszFileName, R_OK) != 0) {
c17bfd
+				LogError(errno, RS_RET_ERR,
c17bfd
+					"imfile: on startup file '%s' does not exist "
c17bfd
+					"but is configured in static file monitor - this "
c17bfd
+					"may indicate a misconfiguration. If the file "
c17bfd
+					"appears at a later time, it will automatically "
c17bfd
+					"be processed. Reason", inst->pszFileName);
c17bfd
+			}
c17bfd
+		}
c17bfd
+		fs_node_add(runModConf->conf_tree, NULL, inst->pszFileName, 0, inst);
c17bfd
 	}
c17bfd
 
c17bfd
-	/* if we could not set up any listeners, there is no point in running... */
c17bfd
-	if(runModConf->pRootLstn == 0) {
c17bfd
-		errmsg.LogError(0, NO_ERRCODE, "imfile: no file monitors could be started, "
c17bfd
-				"input not activated.\n");
c17bfd
-		ABORT_FINALIZE(RS_RET_NO_RUN);
c17bfd
+	if(Debug) {
c17bfd
+		fs_node_print(runModConf->conf_tree, 0);
c17bfd
 	}
c17bfd
+
c17bfd
 finalize_it:
c17bfd
 ENDactivateCnf
c17bfd
 
c17bfd
@@ -1237,14 +1882,20 @@ ENDactivateCnf
c17bfd
 BEGINfreeCnf
c17bfd
 	instanceConf_t *inst, *del;
c17bfd
 CODESTARTfreeCnf
c17bfd
+	fs_node_destroy(pModConf->conf_tree);
c17bfd
+	//move_list_destruct(pModConf);
c17bfd
 	for(inst = pModConf->root ; inst != NULL ; ) {
c17bfd
+		if(inst->startRegex != NULL)
c17bfd
+			regfree(&inst->end_preg);
c17bfd
 		free(inst->pszBindRuleset);
c17bfd
 		free(inst->pszFileName);
c17bfd
-		free(inst->pszDirName);
c17bfd
-		free(inst->pszFileBaseName);
c17bfd
 		free(inst->pszTag);
c17bfd
 		free(inst->pszStateFile);
c17bfd
-		free(inst->startRegex);
c17bfd
+		free(inst->pszFileName_forOldStateFile);
c17bfd
+		if(inst->startRegex != NULL) {
c17bfd
+			regfree(&inst->end_preg);
c17bfd
+			free(inst->startRegex);
c17bfd
+		}
c17bfd
 		del = inst;
c17bfd
 		inst = inst->next;
c17bfd
 		free(del);
c17bfd
@@ -1252,45 +1903,25 @@ CODESTARTfreeCnf
c17bfd
 ENDfreeCnf
c17bfd
 
c17bfd
 
c17bfd
-/* Monitor files in traditional polling mode.
c17bfd
- *
c17bfd
- * We go through all files and remember if at least one had data. If so, we do
c17bfd
- * another run (until no data was present in any file). Then we sleep for
c17bfd
- * PollInterval seconds and restart the whole process. This ensures that as
c17bfd
- * long as there is some data present, it will be processed at the fastest
c17bfd
- * possible pace - probably important for busy systmes. If we monitor just a
c17bfd
- * single file, the algorithm is slightly modified. In that case, the sleep
c17bfd
- * hapens immediately. The idea here is that if we have just one file, we
c17bfd
- * returned from the file processer because that file had no additional data.
c17bfd
- * So even if we found some lines, it is highly unlikely to find a new one
c17bfd
- * just now. Trying it would result in a performance-costly additional try
c17bfd
- * which in the very, very vast majority of cases will never find any new
c17bfd
- * lines.
c17bfd
- * On spamming the main queue: keep in mind that it will automatically rate-limit
c17bfd
- * ourselfes if we begin to overrun it. So we really do not need to care here.
c17bfd
- */
c17bfd
+/* Monitor files in polling mode. */
c17bfd
 static rsRetVal
c17bfd
 doPolling(void)
c17bfd
 {
c17bfd
-	int bHadFileData; /* were there at least one file with data during this run? */
c17bfd
 	DEFiRet;
c17bfd
 	while(glbl.GetGlobalInputTermState() == 0) {
c17bfd
+		DBGPRINTF("doPolling: new poll run\n");
c17bfd
 		do {
c17bfd
-			lstn_t *pLstn;
c17bfd
-			bHadFileData = 0;
c17bfd
-			for(pLstn = runModConf->pRootLstn ; pLstn != NULL ; pLstn = pLstn->next) {
c17bfd
-				if(glbl.GetGlobalInputTermState() == 1)
c17bfd
-					break; /* terminate input! */
c17bfd
-				pollFile(pLstn, &bHadFileData);
c17bfd
-			}
c17bfd
-		} while(bHadFileData == 1 && glbl.GetGlobalInputTermState() == 0);
c17bfd
-		  /* warning: do...while()! */
c17bfd
+			runModConf->bHadFileData = 0;
c17bfd
+			fs_node_walk(runModConf->conf_tree, poll_tree);
c17bfd
+			DBGPRINTF("doPolling: end poll walk, hadData %d\n", runModConf->bHadFileData);
c17bfd
+		} while(runModConf->bHadFileData); /* warning: do...while()! */
c17bfd
 
c17bfd
 		/* Note: the additional 10ns wait is vitally important. It guards rsyslog
c17bfd
 		 * against totally hogging the CPU if the users selects a polling interval
c17bfd
 		 * of 0 seconds. It doesn't hurt any other valid scenario. So do not remove.
c17bfd
 		 * rgerhards, 2008-02-14
c17bfd
 		 */
c17bfd
+		DBGPRINTF("doPolling: poll going to sleep\n");
c17bfd
 		if(glbl.GetGlobalInputTermState() == 0)
c17bfd
 			srSleep(runModConf->iPollInterval, 10);
c17bfd
 	}
c17bfd
@@ -1298,631 +1929,122 @@ doPolling(void)
c17bfd
 	RETiRet;
c17bfd
 }
c17bfd
 
c17bfd
+#if defined(HAVE_INOTIFY_INIT)
c17bfd
 
c17bfd
-#ifdef HAVE_INOTIFY_INIT
c17bfd
-static rsRetVal
c17bfd
-fileTableInit(fileTable_t *const __restrict__ tab, const int nelem)
c17bfd
-{
c17bfd
-	DEFiRet;
c17bfd
-	CHKmalloc(tab->listeners = malloc(sizeof(dirInfoFiles_t) * nelem));
c17bfd
-	tab->allocMax = nelem;
c17bfd
-	tab->currMax = 0;
c17bfd
-finalize_it:
c17bfd
-	RETiRet;
c17bfd
-}
c17bfd
-/* uncomment if needed
c17bfd
 static void
c17bfd
-fileTableDisplay(fileTable_t *tab)
c17bfd
+in_dbg_showEv(const struct inotify_event *ev)
c17bfd
 {
c17bfd
-	int f;
c17bfd
-	uchar *baseName;
c17bfd
-	DBGPRINTF("imfile: dirs.currMaxfiles %d\n", tab->currMax);
c17bfd
-	for(f = 0 ; f < tab->currMax ; ++f) {
c17bfd
-		baseName = tab->listeners[f].pLstn->pszBaseName;
c17bfd
-		DBGPRINTF("imfile: TABLE %p CONTENTS, %d->%p:'%s'\n", tab, f, tab->listeners[f].pLstn, (char*)baseName);
c17bfd
-	}
c17bfd
-}
c17bfd
-*/
c17bfd
-
c17bfd
-static int
c17bfd
-fileTableSearch(fileTable_t *const __restrict__ tab, uchar *const __restrict__ fn)
c17bfd
-{
c17bfd
-	int f;
c17bfd
-	uchar *baseName = NULL;
c17bfd
-	/* UNCOMMENT FOR DEBUG fileTableDisplay(tab); */
c17bfd
-	for(f = 0 ; f < tab->currMax ; ++f) {
c17bfd
-		baseName = tab->listeners[f].pLstn->pszBaseName;
c17bfd
-		if(!fnmatch((char*)baseName, (char*)fn, FNM_PATHNAME | FNM_PERIOD))
c17bfd
-			break; /* found */
c17bfd
-	}
c17bfd
-	if(f == tab->currMax)
c17bfd
-		f = -1;
c17bfd
-	DBGPRINTF("imfile: fileTableSearch file '%s' - '%s', found:%d\n", fn, baseName, f);
c17bfd
-	return f;
c17bfd
-}
c17bfd
-
c17bfd
-static int
c17bfd
-fileTableSearchNoWildcard(fileTable_t *const __restrict__ tab, uchar *const __restrict__ fn)
c17bfd
-{
c17bfd
-	int f;
c17bfd
-	uchar *baseName = NULL;
c17bfd
-	/* UNCOMMENT FOR DEBUG fileTableDisplay(tab); */
c17bfd
-	for(f = 0 ; f < tab->currMax ; ++f) {
c17bfd
-		baseName = tab->listeners[f].pLstn->pszBaseName;
c17bfd
-		if (strcmp((const char*)baseName, (const char*)fn) == 0)
c17bfd
-			break; /* found */
c17bfd
-	}
c17bfd
-	if(f == tab->currMax)
c17bfd
-		f = -1;
c17bfd
-	DBGPRINTF("imfile: fileTableSearchNoWildcard file '%s' - '%s', found:%d\n", fn, baseName, f);
c17bfd
-	return f;
c17bfd
-}
c17bfd
-
c17bfd
-/* add file to file table */
c17bfd
-static rsRetVal
c17bfd
-fileTableAddFile(fileTable_t *const __restrict__ tab, lstn_t *const __restrict__ pLstn)
c17bfd
-{
c17bfd
-	int j;
c17bfd
-	DEFiRet;
c17bfd
-	/* UNCOMMENT FOR DEBUG fileTableDisplay(tab); */
c17bfd
-	for(j = 0 ; j < tab->currMax && tab->listeners[j].pLstn != pLstn ; ++j)
c17bfd
-		; /* just scan */
c17bfd
-	if(j < tab->currMax) {
c17bfd
-		++tab->listeners[j].refcnt;
c17bfd
-		DBGPRINTF("imfile: file '%s' already registered, refcnt now %d\n",
c17bfd
-			pLstn->pszFileName, tab->listeners[j].refcnt);
c17bfd
-		FINALIZE;
c17bfd
+	if(ev->mask & IN_IGNORED) {
c17bfd
+		dbgprintf("INOTIFY event: watch was REMOVED\n");
c17bfd
 	}
c17bfd
-
c17bfd
-	if(tab->currMax == tab->allocMax) {
c17bfd
-		const int newMax = 2 * tab->allocMax;
c17bfd
-		dirInfoFiles_t *newListenerTab = realloc(tab->listeners, newMax * sizeof(dirInfoFiles_t));
c17bfd
-		if(newListenerTab == NULL) {
c17bfd
-			errmsg.LogError(0, RS_RET_OUT_OF_MEMORY,
c17bfd
-					"cannot alloc memory to map directory/file relationship "
c17bfd
-					"for '%s' - ignoring", pLstn->pszFileName);
c17bfd
-			ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
c17bfd
-		}
c17bfd
-		tab->listeners = newListenerTab;
c17bfd
-		tab->allocMax = newMax;
c17bfd
-		DBGPRINTF("imfile: increased dir table to %d entries\n", allocMaxDirs);
c17bfd
+	if(ev->mask & IN_MODIFY) {
c17bfd
+		dbgprintf("INOTIFY event: watch was MODIFID\n");
c17bfd
 	}
c17bfd
-
c17bfd
-	tab->listeners[tab->currMax].pLstn = pLstn;
c17bfd
-	tab->listeners[tab->currMax].refcnt = 1;
c17bfd
-	tab->currMax++;
c17bfd
-finalize_it:
c17bfd
-	RETiRet;
c17bfd
-}
c17bfd
-
c17bfd
-/* delete a file from file table */
c17bfd
-static rsRetVal
c17bfd
-fileTableDelFile(fileTable_t *const __restrict__ tab, lstn_t *const __restrict__ pLstn)
c17bfd
-{
c17bfd
-	int j;
c17bfd
-	DEFiRet;
c17bfd
-
c17bfd
-	for(j = 0 ; j < tab->currMax && tab->listeners[j].pLstn != pLstn ; ++j)
c17bfd
-		; /* just scan */
c17bfd
-	if(j == tab->currMax) {
c17bfd
-		DBGPRINTF("imfile: no association for file '%s'\n", pLstn->pszFileName);
c17bfd
-		FINALIZE;
c17bfd
+	if(ev->mask & IN_ACCESS) {
c17bfd
+		dbgprintf("INOTIFY event: watch IN_ACCESS\n");
c17bfd
 	}
c17bfd
-	tab->listeners[j].refcnt--;
c17bfd
-	if(tab->listeners[j].refcnt == 0) {
c17bfd
-		/* we remove that entry (but we never shrink the table) */
c17bfd
-		if(j < tab->currMax - 1) {
c17bfd
-			/* entry in middle - need to move others */
c17bfd
-			memmove(tab->listeners+j, tab->listeners+j+1,
c17bfd
-				(tab->currMax -j-1) * sizeof(dirInfoFiles_t));
c17bfd
-		}
c17bfd
-		--tab->currMax;
c17bfd
+	if(ev->mask & IN_ATTRIB) {
c17bfd
+		dbgprintf("INOTIFY event: watch IN_ATTRIB\n");
c17bfd
 	}
c17bfd
-finalize_it:
c17bfd
-	RETiRet;
c17bfd
-}
c17bfd
-/* add entry to dirs array */
c17bfd
-static rsRetVal
c17bfd
-dirsAdd(uchar *dirName)
c17bfd
-{
c17bfd
-	int newMax;
c17bfd
-	dirInfo_t *newDirTab;
c17bfd
-	DEFiRet;
c17bfd
-
c17bfd
-	if(currMaxDirs == allocMaxDirs) {
c17bfd
-		newMax = 2 * allocMaxDirs;
c17bfd
-		newDirTab = realloc(dirs, newMax * sizeof(dirInfo_t));
c17bfd
-		if(newDirTab == NULL) {
c17bfd
-			errmsg.LogError(0, RS_RET_OUT_OF_MEMORY,
c17bfd
-					"cannot alloc memory to monitor directory '%s' - ignoring",
c17bfd
-					dirName);
c17bfd
-			ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
c17bfd
-		}
c17bfd
-		dirs = newDirTab;
c17bfd
-		allocMaxDirs = newMax;
c17bfd
-		DBGPRINTF("imfile: increased dir table to %d entries\n", allocMaxDirs);
c17bfd
+	if(ev->mask & IN_CLOSE_WRITE) {
c17bfd
+		dbgprintf("INOTIFY event: watch IN_CLOSE_WRITE\n");
c17bfd
 	}
c17bfd
-
c17bfd
-	/* if we reach this point, there is space in the file table for the new entry */
c17bfd
-	dirs[currMaxDirs].dirName = dirName;
c17bfd
-	CHKiRet(fileTableInit(&dirs[currMaxDirs].active, INIT_FILE_IN_DIR_TAB_SIZE));
c17bfd
-	CHKiRet(fileTableInit(&dirs[currMaxDirs].configured, INIT_FILE_IN_DIR_TAB_SIZE));
c17bfd
-
c17bfd
-	++currMaxDirs;
c17bfd
-	DBGPRINTF("imfile: added to dirs table: '%s'\n", dirName);
c17bfd
-finalize_it:
c17bfd
-	RETiRet;
c17bfd
-}
c17bfd
-
c17bfd
-
c17bfd
-/* checks if a dir name is already inside the dirs array. If so, returns
c17bfd
- * its index. If not present, -1 is returned.
c17bfd
- */
c17bfd
-static int
c17bfd
-dirsFindDir(uchar *dir)
c17bfd
-{
c17bfd
-	int i;
c17bfd
-
c17bfd
-	for(i = 0 ; i < currMaxDirs && ustrcmp(dir, dirs[i].dirName) ; ++i)
c17bfd
-		; /* just scan, all done in for() */
c17bfd
-	if(i == currMaxDirs)
c17bfd
-		i = -1;
c17bfd
-	return i;
c17bfd
-}
c17bfd
-
c17bfd
-static rsRetVal
c17bfd
-dirsInit(void)
c17bfd
-{
c17bfd
-	instanceConf_t *inst;
c17bfd
-	DEFiRet;
c17bfd
-
c17bfd
-	free(dirs);
c17bfd
-	CHKmalloc(dirs = malloc(sizeof(dirInfo_t) * INIT_FILE_TAB_SIZE));
c17bfd
-	allocMaxDirs = INIT_FILE_TAB_SIZE;
c17bfd
-	currMaxDirs = 0;
c17bfd
-
c17bfd
-	for(inst = runModConf->root ; inst != NULL ; inst = inst->next) {
c17bfd
-		if(dirsFindDir(inst->pszDirName) == -1)
c17bfd
-			dirsAdd(inst->pszDirName);
c17bfd
+	if(ev->mask & IN_CLOSE_NOWRITE) {
c17bfd
+		dbgprintf("INOTIFY event: watch IN_CLOSE_NOWRITE\n");
c17bfd
 	}
c17bfd
-
c17bfd
-finalize_it:
c17bfd
-	RETiRet;
c17bfd
-}
c17bfd
-
c17bfd
-/* add file to directory (create association)
c17bfd
- * fIdx is index into file table, all other information is pulled from that table.
c17bfd
- * bActive is 1 if the file is to be added to active set, else zero
c17bfd
- */
c17bfd
-static rsRetVal
c17bfd
-dirsAddFile(lstn_t *__restrict__ pLstn, const int bActive)
c17bfd
-{
c17bfd
-	int dirIdx;
c17bfd
-	dirInfo_t *dir;
c17bfd
-	DEFiRet;
c17bfd
-
c17bfd
-	dirIdx = dirsFindDir(pLstn->pszDirName);
c17bfd
-	if(dirIdx == -1) {
c17bfd
-		errmsg.LogError(0, RS_RET_INTERNAL_ERROR, "imfile: could not find "
c17bfd
-			"directory '%s' in dirs array - ignoring",
c17bfd
-			pLstn->pszDirName);
c17bfd
-		FINALIZE;
c17bfd
+	if(ev->mask & IN_CREATE) {
c17bfd
+		dbgprintf("INOTIFY event: file was CREATED: %s\n", ev->name);
c17bfd
 	}
c17bfd
-
c17bfd
-	dir = dirs + dirIdx;
c17bfd
-	CHKiRet(fileTableAddFile((bActive ? &dir->active : &dir->configured), pLstn));
c17bfd
-	DBGPRINTF("imfile: associated file [%s] to directory %d[%s], Active = %d\n",
c17bfd
-		pLstn->pszFileName, dirIdx, dir->dirName, bActive);
c17bfd
-	/* UNCOMMENT FOR DEBUG fileTableDisplay(bActive ? &dir->active : &dir->configured); */
c17bfd
-finalize_it:
c17bfd
-	RETiRet;
c17bfd
-}
c17bfd
-
c17bfd
-
c17bfd
-static void
c17bfd
-in_setupDirWatch(const int dirIdx)
c17bfd
-{
c17bfd
-	int wd;
c17bfd
-	wd = inotify_add_watch(ino_fd, (char*)dirs[dirIdx].dirName, IN_CREATE|IN_DELETE|IN_MOVED_FROM);
c17bfd
-	if(wd < 0) {
c17bfd
-		DBGPRINTF("imfile: could not create dir watch for '%s'\n",
c17bfd
-			dirs[dirIdx].dirName);
c17bfd
-		goto done;
c17bfd
+	if(ev->mask & IN_DELETE) {
c17bfd
+		dbgprintf("INOTIFY event: watch IN_DELETE\n");
c17bfd
 	}
c17bfd
-	wdmapAdd(wd, dirIdx, NULL);
c17bfd
-	DBGPRINTF("imfile: watch %d added for dir %s\n", wd, dirs[dirIdx].dirName);
c17bfd
-done:	return;
c17bfd
-}
c17bfd
-
c17bfd
-/* Setup a new file watch for a known active file. It must already have
c17bfd
- * been entered into the correct tables.
c17bfd
- * Note: we need to try to read this file, as it may already contain data this
c17bfd
- * needs to be processed, and we won't get an event for that as notifications
c17bfd
- * happen only for things after the watch has been activated.
c17bfd
- * Note: newFileName is NULL for configured files, and non-NULL for dynamically
c17bfd
- * detected files (e.g. wildcards!)
c17bfd
- */
c17bfd
-static void
c17bfd
-startLstnFile(lstn_t *const __restrict__ pLstn)
c17bfd
-{
c17bfd
-	rsRetVal localRet;
c17bfd
-	const int wd = inotify_add_watch(ino_fd, (char*)pLstn->pszFileName, IN_MODIFY);
c17bfd
-	if(wd < 0) {
c17bfd
-		char errStr[512];
c17bfd
-		rs_strerror_r(errno, errStr, sizeof(errStr));
c17bfd
-		DBGPRINTF("imfile: could not create file table entry for '%s' - "
c17bfd
-			  "not processing it now: %s\n",
c17bfd
-			  pLstn->pszFileName, errStr);
c17bfd
-		goto done;
c17bfd
+	if(ev->mask & IN_DELETE_SELF) {
c17bfd
+		dbgprintf("INOTIFY event: watch IN_DELETE_SELF\n");
c17bfd
 	}
c17bfd
-	if((localRet = wdmapAdd(wd, -1, pLstn)) != RS_RET_OK) {
c17bfd
-		DBGPRINTF("imfile: error %d adding file to wdmap, ignoring\n", localRet);
c17bfd
-		goto done;
c17bfd
+	if(ev->mask & IN_MOVE_SELF) {
c17bfd
+		dbgprintf("INOTIFY event: watch IN_MOVE_SELF\n");
c17bfd
 	}
c17bfd
-	DBGPRINTF("imfile: watch %d added for file %s\n", wd, pLstn->pszFileName);
c17bfd
-	dirsAddFile(pLstn, ACTIVE_FILE);
c17bfd
-	pollFile(pLstn, NULL);
c17bfd
-done:	return;
c17bfd
-}
c17bfd
-
c17bfd
-/* Duplicate an existing listener. This is called when a new file is to
c17bfd
- * be monitored due to wildcard detection. Returns the new pLstn in
c17bfd
- * the ppExisting parameter.
c17bfd
- */
c17bfd
-static rsRetVal
c17bfd
-lstnDup(lstn_t **ppExisting, uchar *const __restrict__ newname)
c17bfd
-{
c17bfd
-	DEFiRet;
c17bfd
-	lstn_t *const existing = *ppExisting;
c17bfd
-	lstn_t *pThis;
c17bfd
-
c17bfd
-	CHKiRet(lstnAdd(&pThis));
c17bfd
-	pThis->pszDirName = existing->pszDirName; /* read-only */
c17bfd
-	pThis->pszBaseName = (uchar*)strdup((char*)newname);
c17bfd
-	if(asprintf((char**)&pThis->pszFileName, "%s/%s", (char*)pThis->pszDirName, (char*)newname) == -1) {
c17bfd
-		DBGPRINTF("imfile/lstnDup: asprintf failed, malfunction can happen\n");
c17bfd
-		ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
c17bfd
-	}
c17bfd
-	pThis->pszTag = (uchar*) strdup((char*) existing->pszTag);
c17bfd
-	pThis->lenTag = ustrlen(pThis->pszTag);
c17bfd
-	pThis->pszStateFile = existing->pszStateFile == NULL ? NULL : (uchar*) strdup((char*) existing->pszStateFile);
c17bfd
-
c17bfd
-	CHKiRet(ratelimitNew(&pThis->ratelimiter, "imfile", (char*)pThis->pszFileName));
c17bfd
-	pThis->multiSub.maxElem = existing->multiSub.maxElem;
c17bfd
-	pThis->multiSub.nElem = 0;
c17bfd
-	CHKmalloc(pThis->multiSub.ppMsgs = MALLOC(pThis->multiSub.maxElem * sizeof(smsg_t*)));
c17bfd
-	pThis->iSeverity = existing->iSeverity;
c17bfd
-	pThis->iFacility = existing->iFacility;
c17bfd
-	pThis->maxLinesAtOnce = existing->maxLinesAtOnce;
c17bfd
-	pThis->trimLineOverBytes = existing->trimLineOverBytes;
c17bfd
-	pThis->iPersistStateInterval = existing->iPersistStateInterval;
c17bfd
-	pThis->readMode = existing->readMode;
c17bfd
-	pThis->startRegex = existing->startRegex; /* no strdup, as it is read-only */
c17bfd
-	if(pThis->startRegex != NULL) // TODO: make this a single function with better error handling
c17bfd
-		if(regcomp(&pThis->end_preg, (char*)pThis->startRegex, REG_EXTENDED)) {
c17bfd
-			DBGPRINTF("imfile: error regex compile\n");
c17bfd
-			ABORT_FINALIZE(RS_RET_ERR);
c17bfd
-		}
c17bfd
-	pThis->bRMStateOnDel = existing->bRMStateOnDel;
c17bfd
-	pThis->hasWildcard = existing->hasWildcard;
c17bfd
-	pThis->escapeLF = existing->escapeLF;
c17bfd
-	pThis->reopenOnTruncate = existing->reopenOnTruncate;
c17bfd
-	pThis->addMetadata = existing->addMetadata;
c17bfd
-	pThis->addCeeTag = existing->addCeeTag;
c17bfd
-	pThis->readTimeout = existing->readTimeout;
c17bfd
-	pThis->freshStartTail = existing->freshStartTail;
c17bfd
-	pThis->pRuleset = existing->pRuleset;
c17bfd
-	pThis->nRecords = 0;
c17bfd
-	pThis->pStrm = NULL;
c17bfd
-	pThis->prevLineSegment = NULL;
c17bfd
-	pThis->masterLstn = existing;
c17bfd
-	*ppExisting = pThis;
c17bfd
-finalize_it:
c17bfd
-	RETiRet;
c17bfd
-}
c17bfd
-
c17bfd
-/* Setup a new file watch for dynamically discovered files (via wildcards).
c17bfd
- * Note: we need to try to read this file, as it may already contain data this
c17bfd
- * needs to be processed, and we won't get an event for that as notifications
c17bfd
- * happen only for things after the watch has been activated.
c17bfd
- */
c17bfd
-static void
c17bfd
-in_setupFileWatchDynamic(lstn_t *pLstn, uchar *const __restrict__ newBaseName)
c17bfd
-{
c17bfd
-	char fullfn[MAXFNAME];
c17bfd
-	struct stat fileInfo;
c17bfd
-	snprintf(fullfn, MAXFNAME, "%s/%s", pLstn->pszDirName, newBaseName);
c17bfd
-	if(stat(fullfn, &fileInfo) != 0) {
c17bfd
-		char errStr[1024];
c17bfd
-		rs_strerror_r(errno, errStr, sizeof(errStr));
c17bfd
-		DBGPRINTF("imfile: ignoring file '%s' cannot stat(): %s\n",
c17bfd
-			fullfn, errStr);
c17bfd
-		goto done;
c17bfd
+	if(ev->mask & IN_MOVED_FROM) {
c17bfd
+		dbgprintf("INOTIFY event: watch IN_MOVED_FROM, cookie %u, name '%s'\n", ev->cookie, ev->name);
c17bfd
 	}
c17bfd
-
c17bfd
-	if(S_ISDIR(fileInfo.st_mode)) {
c17bfd
-		DBGPRINTF("imfile: ignoring directory '%s'\n", fullfn);
c17bfd
-		goto done;
c17bfd
+	if(ev->mask & IN_MOVED_TO) {
c17bfd
+		dbgprintf("INOTIFY event: watch IN_MOVED_TO, cookie %u, name '%s'\n", ev->cookie, ev->name);
c17bfd
 	}
c17bfd
-
c17bfd
-	if(lstnDup(&pLstn, newBaseName) != RS_RET_OK)
c17bfd
-		goto done;
c17bfd
-
c17bfd
-	startLstnFile(pLstn);
c17bfd
-done:	return;
c17bfd
-}
c17bfd
-
c17bfd
-/* Setup a new file watch for static (configured) files.
c17bfd
- * Note: we need to try to read this file, as it may already contain data this
c17bfd
- * needs to be processed, and we won't get an event for that as notifications
c17bfd
- * happen only for things after the watch has been activated.
c17bfd
- */
c17bfd
-static void
c17bfd
-in_setupFileWatchStatic(lstn_t *pLstn)
c17bfd
-{
c17bfd
-	DBGPRINTF("imfile: adding file '%s' to configured table\n",
c17bfd
-		  pLstn->pszFileName);
c17bfd
-	dirsAddFile(pLstn, CONFIGURED_FILE);
c17bfd
-
c17bfd
-	if(pLstn->hasWildcard) {
c17bfd
-		DBGPRINTF("imfile: file '%s' has wildcard, doing initial "
c17bfd
-			  "expansion\n", pLstn->pszFileName);
c17bfd
-		glob_t files;
c17bfd
-		const int ret = glob((char*)pLstn->pszFileName,
c17bfd
-					GLOB_MARK|GLOB_NOSORT|GLOB_BRACE, NULL, &files);
c17bfd
-		if(ret == 0) {
c17bfd
-			for(unsigned i = 0 ; i < files.gl_pathc ; i++) {
c17bfd
-				uchar basen[MAXFNAME];
c17bfd
-				uchar *const file = (uchar*)files.gl_pathv[i];
c17bfd
-				if(file[strlen((char*)file)-1] == '/')
c17bfd
-					continue;/* we cannot process subdirs! */
c17bfd
-				getBasename(basen, file);
c17bfd
-				in_setupFileWatchDynamic(pLstn, basen);
c17bfd
-			}
c17bfd
-			globfree(&files);
c17bfd
-		}
c17bfd
-	} else {
c17bfd
-		/* Duplicate static object as well, otherwise the configobject could be deleted later! */
c17bfd
-		if(lstnDup(&pLstn, pLstn->pszBaseName) != RS_RET_OK) {
c17bfd
-			DBGPRINTF("imfile: in_setupFileWatchStatic failed to duplicate listener for '%s'\n", pLstn->pszFileName);
c17bfd
-			goto done;
c17bfd
-		}
c17bfd
-		startLstnFile(pLstn);
c17bfd
+	if(ev->mask & IN_OPEN) {
c17bfd
+		dbgprintf("INOTIFY event: watch IN_OPEN\n");
c17bfd
 	}
c17bfd
-done:	return;
c17bfd
-}
c17bfd
-
c17bfd
-/* setup our initial set of watches, based on user config */
c17bfd
-static void
c17bfd
-in_setupInitialWatches(void)
c17bfd
-{
c17bfd
-	int i;
c17bfd
-	for(i = 0 ; i < currMaxDirs ; ++i) {
c17bfd
-		in_setupDirWatch(i);
c17bfd
-	}
c17bfd
-	lstn_t *pLstn;
c17bfd
-	for(pLstn = runModConf->pRootLstn ; pLstn != NULL ; pLstn = pLstn->next) {
c17bfd
-		if(pLstn->masterLstn == NULL) {
c17bfd
-			/* we process only static (master) entries */
c17bfd
-			in_setupFileWatchStatic(pLstn);
c17bfd
-		}
c17bfd
+	if(ev->mask & IN_ISDIR) {
c17bfd
+		dbgprintf("INOTIFY event: watch IN_ISDIR\n");
c17bfd
 	}
c17bfd
 }
c17bfd
 
c17bfd
-static void
c17bfd
-in_dbg_showEv(struct inotify_event *ev)
c17bfd
-{
c17bfd
-	if(ev->mask & IN_IGNORED) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch was REMOVED\n");
c17bfd
-	} else if(ev->mask & IN_MODIFY) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch was MODIFID\n");
c17bfd
-	} else if(ev->mask & IN_ACCESS) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch IN_ACCESS\n");
c17bfd
-	} else if(ev->mask & IN_ATTRIB) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch IN_ATTRIB\n");
c17bfd
-	} else if(ev->mask & IN_CLOSE_WRITE) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch IN_CLOSE_WRITE\n");
c17bfd
-	} else if(ev->mask & IN_CLOSE_NOWRITE) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch IN_CLOSE_NOWRITE\n");
c17bfd
-	} else if(ev->mask & IN_CREATE) {
c17bfd
-		DBGPRINTF("INOTIFY event: file was CREATED: %s\n", ev->name);
c17bfd
-	} else if(ev->mask & IN_DELETE) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch IN_DELETE\n");
c17bfd
-	} else if(ev->mask & IN_DELETE_SELF) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch IN_DELETE_SELF\n");
c17bfd
-	} else if(ev->mask & IN_MOVE_SELF) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch IN_MOVE_SELF\n");
c17bfd
-	} else if(ev->mask & IN_MOVED_FROM) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch IN_MOVED_FROM\n");
c17bfd
-	} else if(ev->mask & IN_MOVED_TO) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch IN_MOVED_TO\n");
c17bfd
-	} else if(ev->mask & IN_OPEN) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch IN_OPEN\n");
c17bfd
-	} else if(ev->mask & IN_ISDIR) {
c17bfd
-		DBGPRINTF("INOTIFY event: watch IN_ISDIR\n");
c17bfd
-	} else {
c17bfd
-		DBGPRINTF("INOTIFY event: unknown mask code %8.8x\n", ev->mask);
c17bfd
-	 }
c17bfd
-}
c17bfd
-
c17bfd
 
c17bfd
-/* inotify told us that a file's wd was closed. We now need to remove
c17bfd
- * the file from our internal structures. Remember that a different inode
c17bfd
- * with the same name may already be in processing.
c17bfd
- */
c17bfd
 static void
c17bfd
-in_removeFile(const int dirIdx,
c17bfd
-	      lstn_t *const __restrict__ pLstn)
c17bfd
+in_handleFileEvent(struct inotify_event *ev, const wd_map_t *const etry)
c17bfd
 {
c17bfd
-	uchar statefile[MAXFNAME];
c17bfd
-	uchar toDel[MAXFNAME];
c17bfd
-	int bDoRMState;
c17bfd
-        int wd;
c17bfd
-	uchar *statefn;
c17bfd
-	DBGPRINTF("imfile: remove listener '%s', dirIdx %d\n",
c17bfd
-	          pLstn->pszFileName, dirIdx);
c17bfd
-	if(pLstn->bRMStateOnDel) {
c17bfd
-		statefn = getStateFileName(pLstn, statefile, sizeof(statefile));
c17bfd
-		snprintf((char*)toDel, sizeof(toDel), "%s/%s",
c17bfd
-				     glbl.GetWorkDir(), (char*)statefn);
c17bfd
-		bDoRMState = 1;
c17bfd
+	if(ev->mask & IN_MODIFY) {
c17bfd
+		DBGPRINTF("fs_node_notify_file_update: act->name '%s'\n", etry->act->name);
c17bfd
+		pollFile(etry->act);
c17bfd
 	} else {
c17bfd
-		bDoRMState = 0;
c17bfd
-	}
c17bfd
-	pollFile(pLstn, NULL); /* one final try to gather data */
c17bfd
-	/*	delete listener data */
c17bfd
-	DBGPRINTF("imfile: DELETING listener data for '%s' - '%s'\n", pLstn->pszBaseName, pLstn->pszFileName);
c17bfd
-	lstnDel(pLstn);
c17bfd
-	fileTableDelFile(&dirs[dirIdx].active, pLstn);
c17bfd
-	if(bDoRMState) {
c17bfd
-		DBGPRINTF("imfile: unlinking '%s'\n", toDel);
c17bfd
-		if(unlink((char*)toDel) != 0) {
c17bfd
-			char errStr[1024];
c17bfd
-			rs_strerror_r(errno, errStr, sizeof(errStr));
c17bfd
-			errmsg.LogError(0, RS_RET_ERR, "imfile: could not remove state "
c17bfd
-				"file \"%s\": %s", toDel, errStr);
c17bfd
-		}
c17bfd
+		DBGPRINTF("got non-expected inotify event:\n");
c17bfd
+		in_dbg_showEv(ev);
c17bfd
 	}
c17bfd
-        wd = wdmapLookupListner(pLstn);
c17bfd
-        wdmapDel(wd);
c17bfd
 }
c17bfd
 
c17bfd
-static void
c17bfd
-in_handleDirEventCREATE(struct inotify_event *ev, const int dirIdx)
c17bfd
-{
c17bfd
-	lstn_t *pLstn;
c17bfd
-	int ftIdx;
c17bfd
-	ftIdx = fileTableSearch(&dirs[dirIdx].active, (uchar*)ev->name);
c17bfd
-	if(ftIdx >= 0) {
c17bfd
-		pLstn = dirs[dirIdx].active.listeners[ftIdx].pLstn;
c17bfd
-	} else {
c17bfd
-		DBGPRINTF("imfile: file '%s' not active in dir '%s'\n",
c17bfd
-			ev->name, dirs[dirIdx].dirName);
c17bfd
-		ftIdx = fileTableSearch(&dirs[dirIdx].configured, (uchar*)ev->name);
c17bfd
-		if(ftIdx == -1) {
c17bfd
-			DBGPRINTF("imfile: file '%s' not associated with dir '%s'\n",
c17bfd
-				ev->name, dirs[dirIdx].dirName);
c17bfd
-			goto done;
c17bfd
-		}
c17bfd
-		pLstn = dirs[dirIdx].configured.listeners[ftIdx].pLstn;
c17bfd
-	}
c17bfd
-	DBGPRINTF("imfile: file '%s' associated with dir '%s'\n", ev->name, dirs[dirIdx].dirName);
c17bfd
-	in_setupFileWatchDynamic(pLstn, (uchar*)ev->name);
c17bfd
-done:	return;
c17bfd
-}
c17bfd
 
c17bfd
-/* note: we need to care only for active files in the DELETE case.
c17bfd
- * Two reasons: a) if this is a configured file, it should be active
c17bfd
- * b) if not for some reason, there still is nothing we can do against
c17bfd
- * it, and trying to process a *deleted* file really makes no sense
c17bfd
- * (remeber we don't have it open, so it actually *is gone*).
c17bfd
+/* workaround for IN_MOVED: walk active list and prevent state file deletion of
c17bfd
+ * IN_MOVED_IN active object
c17bfd
+ * TODO: replace by a more generic solution.
c17bfd
  */
c17bfd
 static void
c17bfd
-in_handleDirEventDELETE(struct inotify_event *const ev, const int dirIdx)
c17bfd
-{
c17bfd
-	const int ftIdx = fileTableSearch(&dirs[dirIdx].active, (uchar*)ev->name);
c17bfd
-	if(ftIdx == -1) {
c17bfd
-		DBGPRINTF("imfile: deleted file '%s' not active in dir '%s'\n",
c17bfd
-			ev->name, dirs[dirIdx].dirName);
c17bfd
-		goto done;
c17bfd
-	}
c17bfd
-	DBGPRINTF("imfile: imfile delete processing for '%s'\n",
c17bfd
-	          dirs[dirIdx].active.listeners[ftIdx].pLstn->pszFileName);
c17bfd
-	in_removeFile(dirIdx, dirs[dirIdx].active.listeners[ftIdx].pLstn);
c17bfd
-done:	return;
c17bfd
-}
c17bfd
-
c17bfd
-static void
c17bfd
-in_handleDirEvent(struct inotify_event *const ev, const int dirIdx)
c17bfd
+flag_in_move(fs_edge_t *const edge, const char *name_moved)
c17bfd
 {
c17bfd
-	DBGPRINTF("imfile: handle dir event for %s\n", dirs[dirIdx].dirName);
c17bfd
-	if((ev->mask & IN_CREATE)) {
c17bfd
-		in_handleDirEventCREATE(ev, dirIdx);
c17bfd
-	} else if((ev->mask & IN_DELETE)) {
c17bfd
-		in_handleDirEventDELETE(ev, dirIdx);
c17bfd
-	} else {
c17bfd
-		DBGPRINTF("imfile: got non-expected inotify event:\n");
c17bfd
-		in_dbg_showEv(ev);
c17bfd
-	}
c17bfd
-}
c17bfd
+	act_obj_t *act;
c17bfd
 
c17bfd
-
c17bfd
-static void
c17bfd
-in_handleFileEvent(struct inotify_event *ev, const wd_map_t *const etry)
c17bfd
-{
c17bfd
-	if(ev->mask & IN_MODIFY) {
c17bfd
-		pollFile(etry->pLstn, NULL);
c17bfd
-	} else {
c17bfd
-		DBGPRINTF("imfile: got non-expected inotify event:\n");
c17bfd
-		in_dbg_showEv(ev);
c17bfd
+	for(act = edge->active ; act != NULL ; act = act->next) {
c17bfd
+		DBGPRINTF("checking active object %s\n", act->basename);
c17bfd
+		if(!strcmp(act->basename, name_moved)){
c17bfd
+			DBGPRINTF("found file\n");
c17bfd
+			act->in_move = 1;
c17bfd
+			break;
c17bfd
+		} else {
c17bfd
+			DBGPRINTF("name check fails, '%s' != '%s'\n", act->basename, name_moved);
c17bfd
+		}
c17bfd
 	}
c17bfd
 }
c17bfd
 
c17bfd
 static void
c17bfd
 in_processEvent(struct inotify_event *ev)
c17bfd
 {
c17bfd
-	wd_map_t *etry;
c17bfd
-	lstn_t *pLstn;
c17bfd
-	int iRet;
c17bfd
-	int ftIdx;
c17bfd
-	int wd;
c17bfd
-
c17bfd
 	if(ev->mask & IN_IGNORED) {
c17bfd
-		goto done;
c17bfd
-	} else if(ev->mask & IN_MOVED_FROM) {
c17bfd
-		/* Find wd entry and remove it */
c17bfd
-		etry =  wdmapLookup(ev->wd);
c17bfd
-		if(etry != NULL) {
c17bfd
-			ftIdx = fileTableSearchNoWildcard(&dirs[etry->dirIdx].active, (uchar*)ev->name);
c17bfd
-			DBGPRINTF("imfile: IN_MOVED_FROM Event (ftIdx=%d, name=%s)\n", ftIdx, ev->name);
c17bfd
-			if(ftIdx >= 0) {
c17bfd
-				/* Find listener and wd table index*/
c17bfd
-				pLstn = dirs[etry->dirIdx].active.listeners[ftIdx].pLstn;
c17bfd
-				wd = wdmapLookupListner(pLstn);
c17bfd
-
c17bfd
-				/* Remove file from inotify watch */
c17bfd
-				iRet = inotify_rm_watch(ino_fd, wd); /* Note this will TRIGGER IN_IGNORED Event! */
c17bfd
-				if (iRet != 0) {
c17bfd
-					DBGPRINTF("imfile: inotify_rm_watch error %d (ftIdx=%d, wd=%d, name=%s)\n", errno, ftIdx, wd, ev->name);
c17bfd
-				} else {
c17bfd
-					DBGPRINTF("imfile: inotify_rm_watch successfully removed file from watch (ftIdx=%d, wd=%d, name=%s)\n", ftIdx, wd, ev->name);
c17bfd
-				}
c17bfd
-				in_removeFile(etry->dirIdx, pLstn);
c17bfd
-				DBGPRINTF("imfile: IN_MOVED_FROM Event file removed file (wd=%d, name=%s)\n", wd, ev->name);
c17bfd
-			}
c17bfd
-		}
c17bfd
+		DBGPRINTF("imfile: got IN_IGNORED event\n");
c17bfd
 		goto done;
c17bfd
 	}
c17bfd
-	etry =  wdmapLookup(ev->wd);
c17bfd
+
c17bfd
+	DBGPRINTF("in_processEvent process Event %x for %s\n", ev->mask, ev->name);
c17bfd
+	const wd_map_t *const etry =  wdmapLookup(ev->wd);
c17bfd
 	if(etry == NULL) {
c17bfd
-		DBGPRINTF("imfile: could not lookup wd %d\n", ev->wd);
c17bfd
+		LogMsg(0, RS_RET_INTERNAL_ERROR, LOG_WARNING, "imfile: internal error? "
c17bfd
+			"inotify provided watch descriptor %d which we could not find "
c17bfd
+			"in our tables - ignored", ev->wd);
c17bfd
 		goto done;
c17bfd
 	}
c17bfd
-	if(etry->pLstn == NULL) { /* directory? */
c17bfd
-		in_handleDirEvent(ev, etry->dirIdx);
c17bfd
+	DBGPRINTF("in_processEvent process Event %x is_file %d, act->name '%s'\n",
c17bfd
+		ev->mask, etry->act->edge->is_file, etry->act->name);
c17bfd
+
c17bfd
+	if((ev->mask & IN_MOVED_FROM)) {
c17bfd
+		flag_in_move(etry->act->edge->node->edges, ev->name);
c17bfd
+	}
c17bfd
+	if(ev->mask & (IN_MOVED_FROM | IN_MOVED_TO))  {
c17bfd
+		fs_node_walk(etry->act->edge->node, poll_tree);
c17bfd
+	} else if(etry->act->edge->is_file && !(etry->act->is_symlink)) {
c17bfd
+		in_handleFileEvent(ev, etry); // esentially poll_file()!
c17bfd
 	} else {
c17bfd
-		in_handleFileEvent(ev, etry);
c17bfd
+		fs_node_walk(etry->act->edge->node, poll_tree);
c17bfd
 	}
c17bfd
 done:	return;
c17bfd
 }
c17bfd
 
c17bfd
-static void
c17bfd
-in_do_timeout_processing(void)
c17bfd
-{
c17bfd
-	int i;
c17bfd
-	DBGPRINTF("imfile: readTimeouts are configured, checking if some apply\n");
c17bfd
-
c17bfd
-	for(i = 0 ; i < nWdmap ; ++i) {
c17bfd
-		dbgprintf("imfile: wdmap %d, plstn %p\n", i, wdmap[i].pLstn);
c17bfd
-		lstn_t *const pLstn = wdmap[i].pLstn;
c17bfd
-		if(pLstn != NULL && strmReadMultiLine_isTimedOut(pLstn->pStrm)) {
c17bfd
-			dbgprintf("imfile: wdmap %d, timeout occured\n", i);
c17bfd
-			pollFile(pLstn, NULL);
c17bfd
-		}
c17bfd
-	}
c17bfd
-
c17bfd
-}
c17bfd
-
c17bfd
 
c17bfd
 /* Monitor files in inotify mode */
c17bfd
 #if !defined(_AIX)
c17bfd
@@ -1940,14 +2062,16 @@ do_inotify(void)
c17bfd
 	DEFiRet;
c17bfd
 
c17bfd
 	CHKiRet(wdmapInit());
c17bfd
-	CHKiRet(dirsInit());
c17bfd
 	ino_fd = inotify_init();
c17bfd
-        if(ino_fd < 0) {
c17bfd
-            errmsg.LogError(1, RS_RET_INOTIFY_INIT_FAILED, "imfile: Init inotify instance failed ");
c17bfd
-            return RS_RET_INOTIFY_INIT_FAILED;
c17bfd
-        }
c17bfd
-	DBGPRINTF("imfile: inotify fd %d\n", ino_fd);
c17bfd
-	in_setupInitialWatches();
c17bfd
+	if(ino_fd < 0) {
c17bfd
+		LogError(errno, RS_RET_INOTIFY_INIT_FAILED, "imfile: Init inotify "
c17bfd
+			"instance failed ");
c17bfd
+		return RS_RET_INOTIFY_INIT_FAILED;
c17bfd
+	}
c17bfd
+	DBGPRINTF("inotify fd %d\n", ino_fd);
c17bfd
+
c17bfd
+	/* do watch initialization */
c17bfd
+	fs_node_walk(runModConf->conf_tree, poll_tree);
c17bfd
 
c17bfd
 	while(glbl.GetGlobalInputTermState() == 0) {
c17bfd
 		if(runModConf->haveReadTimeouts) {
c17bfd
@@ -1959,7 +2083,8 @@ do_inotify(void)
c17bfd
 				r = poll(&pollfd, 1, runModConf->timeoutGranularity);
c17bfd
 			} while(r  == -1 && errno == EINTR);
c17bfd
 			if(r == 0) {
c17bfd
-				in_do_timeout_processing();
c17bfd
+				DBGPRINTF("readTimeouts are configured, checking if some apply\n");
c17bfd
+				fs_node_walk(runModConf->conf_tree, poll_timeouts);
c17bfd
 				continue;
c17bfd
 			} else if (r == -1) {
c17bfd
 				char errStr[1024];
c17bfd
@@ -2035,49 +2160,96 @@ CODESTARTwillRun
c17bfd
 	CHKiRet(prop.Construct(&pInputName));
c17bfd
 	CHKiRet(prop.SetString(pInputName, UCHAR_CONSTANT("imfile"), sizeof("imfile") - 1));
c17bfd
 	CHKiRet(prop.ConstructFinalize(pInputName));
c17bfd
-
c17bfd
 finalize_it:
c17bfd
 ENDwillRun
c17bfd
 
c17bfd
+// TODO: refactor this into a generically-usable "atomic file creation" utility for
c17bfd
+// all kinds of "state files"
c17bfd
+static rsRetVal
c17bfd
+atomicWriteStateFile(const char *fn, const char *content)
c17bfd
+{
c17bfd
+	DEFiRet;
c17bfd
+	const int fd = open(fn, O_CLOEXEC | O_NOCTTY | O_WRONLY | O_CREAT | O_TRUNC, 0600);
c17bfd
+	if(fd < 0) {
c17bfd
+		LogError(errno, RS_RET_IO_ERROR, "imfile: cannot open state file '%s' for "
c17bfd
+			"persisting file state - some data will probably be duplicated "
c17bfd
+			"on next startup", fn);
c17bfd
+		ABORT_FINALIZE(RS_RET_IO_ERROR);
c17bfd
+	}
c17bfd
+
c17bfd
+	const size_t toWrite = strlen(content);
c17bfd
+	const ssize_t w = write(fd, content, toWrite);
c17bfd
+	if(w != (ssize_t) toWrite) {
c17bfd
+		LogError(errno, RS_RET_IO_ERROR, "imfile: partial write to state file '%s' "
c17bfd
+			"this may cause trouble in the future. We will try to delete the "
c17bfd
+			"state file, as this provides most consistent state", fn);
c17bfd
+		unlink(fn);
c17bfd
+		ABORT_FINALIZE(RS_RET_IO_ERROR);
c17bfd
+	}
c17bfd
+
c17bfd
+finalize_it:
c17bfd
+	if(fd >= 0) {
c17bfd
+		close(fd);
c17bfd
+	}
c17bfd
+	RETiRet;
c17bfd
+}
c17bfd
+
c17bfd
+
c17bfd
 /* This function persists information for a specific file being monitored.
c17bfd
  * To do so, it simply persists the stream object. We do NOT abort on error
c17bfd
  * iRet as that makes matters worse (at least we can try persisting the others...).
c17bfd
  * rgerhards, 2008-02-13
c17bfd
  */
c17bfd
 static rsRetVal
c17bfd
-persistStrmState(lstn_t *pLstn)
c17bfd
+persistStrmState(act_obj_t *const act)
c17bfd
 {
c17bfd
 	DEFiRet;
c17bfd
-	strm_t *psSF = NULL; /* state file (stream) */
c17bfd
-	size_t lenDir;
c17bfd
 	uchar statefile[MAXFNAME];
c17bfd
+	uchar statefname[MAXFNAME];
c17bfd
+
c17bfd
+	uchar *const statefn = getStateFileName(act, statefile, sizeof(statefile));
c17bfd
+	getFullStateFileName(statefn, statefname, sizeof(statefname));
c17bfd
+	DBGPRINTF("persisting state for '%s', state file '%s'\n", act->name, statefname);
c17bfd
+
c17bfd
+	struct json_object *jval = NULL;
c17bfd
+	struct json_object *json = NULL;
c17bfd
+	CHKmalloc(json = json_object_new_object());
c17bfd
+	jval = json_object_new_string((char*) act->name);
c17bfd
+	json_object_object_add(json, "filename", jval);
c17bfd
+	jval = json_object_new_int(strmGetPrevWasNL(act->pStrm));
c17bfd
+	json_object_object_add(json, "prev_was_nl", jval);
c17bfd
+
c17bfd
+	/* we access some data items a bit dirty, as we need to refactor the whole
c17bfd
+	 * thing in any case - TODO
c17bfd
+	 */
c17bfd
+	jval = json_object_new_int64(act->pStrm->iCurrOffs);
c17bfd
+	json_object_object_add(json, "curr_offs", jval);
c17bfd
+	jval = json_object_new_int64(act->pStrm->strtOffs);
c17bfd
+	json_object_object_add(json, "strt_offs", jval);
c17bfd
 
c17bfd
-	uchar *const statefn = getStateFileName(pLstn, statefile, sizeof(statefile));
c17bfd
-	DBGPRINTF("imfile: persisting state for '%s' to file '%s'\n",
c17bfd
-		  pLstn->pszFileName, statefn);
c17bfd
-	CHKiRet(strm.Construct(&psSF));
c17bfd
-	lenDir = ustrlen(glbl.GetWorkDir());
c17bfd
-	if(lenDir > 0)
c17bfd
-		CHKiRet(strm.SetDir(psSF, glbl.GetWorkDir(), lenDir));
c17bfd
-	CHKiRet(strm.SettOperationsMode(psSF, STREAMMODE_WRITE_TRUNC));
c17bfd
-	CHKiRet(strm.SetsType(psSF, STREAMTYPE_FILE_SINGLE));
c17bfd
-	CHKiRet(strm.SetFName(psSF, statefn, strlen((char*) statefn)));
c17bfd
-	CHKiRet(strm.ConstructFinalize(psSF));
c17bfd
+	const uchar *const prevLineSegment = strmGetPrevLineSegment(act->pStrm);
c17bfd
+	if(prevLineSegment != NULL) {
c17bfd
+		jval = json_object_new_string((const char*) prevLineSegment);
c17bfd
+		json_object_object_add(json, "prev_line_segment", jval);
c17bfd
+	}
c17bfd
 
c17bfd
-	CHKiRet(strm.Serialize(pLstn->pStrm, psSF));
c17bfd
-	CHKiRet(strm.Flush(psSF));
c17bfd
+	const uchar *const prevMsgSegment = strmGetPrevMsgSegment(act->pStrm);
c17bfd
+	if(prevMsgSegment != NULL) {
c17bfd
+		jval = json_object_new_string((const char*) prevMsgSegment);
c17bfd
+		json_object_object_add(json, "prev_msg_segment", jval);
c17bfd
+	}
c17bfd
 
c17bfd
-	CHKiRet(strm.Destruct(&psSF));
c17bfd
+	const char *jstr =  json_object_to_json_string_ext(json, JSON_C_TO_STRING_SPACED);
c17bfd
 
c17bfd
-finalize_it:
c17bfd
-	if(psSF != NULL)
c17bfd
-		strm.Destruct(&psSF);
c17bfd
+	CHKiRet(atomicWriteStateFile((const char*)statefname, jstr));
c17bfd
+	json_object_put(json);
c17bfd
 
c17bfd
+finalize_it:
c17bfd
 	if(iRet != RS_RET_OK) {
c17bfd
 		errmsg.LogError(0, iRet, "imfile: could not persist state "
c17bfd
 				"file %s - data may be repeated on next "
c17bfd
 				"startup. Is WorkDirectory set?",
c17bfd
-				statefn);
c17bfd
+				statefname);
c17bfd
 	}
c17bfd
 
c17bfd
 	RETiRet;
c17bfd
@@ -2089,11 +2261,6 @@ finalize_it:
c17bfd
  */
c17bfd
 BEGINafterRun
c17bfd
 CODESTARTafterRun
c17bfd
-	while(runModConf->pRootLstn != NULL) {
c17bfd
-		/* Note: lstnDel() reasociates root! */
c17bfd
-		lstnDel(runModConf->pRootLstn);
c17bfd
-	}
c17bfd
-
c17bfd
 	if(pInputName != NULL)
c17bfd
 		prop.Destruct(&pInputName);
c17bfd
 ENDafterRun
c17bfd
@@ -2118,12 +2285,6 @@ CODESTARTmodExit
c17bfd
 	objRelease(prop, CORE_COMPONENT);
c17bfd
 	objRelease(ruleset, CORE_COMPONENT);
c17bfd
 #ifdef HAVE_INOTIFY_INIT
c17bfd
-	/* we use these vars only in inotify mode */
c17bfd
-	if(dirs != NULL) {
c17bfd
-		free(dirs->active.listeners);
c17bfd
-		free(dirs->configured.listeners);
c17bfd
-		free(dirs);
c17bfd
-	}
c17bfd
 	free(wdmap);
c17bfd
 #endif
c17bfd
 ENDmodExit
c17bfd
diff --git a/runtime/msg.c b/runtime/msg.c
c17bfd
index a885d2368bbaeea90a6e92dc0d569d169b1dd2e5..f45d6175283097974023905fc072508a18a8270a 100644
c17bfd
--- a/runtime/msg.c
c17bfd
+++ b/runtime/msg.c
c17bfd
@@ -4890,6 +4890,28 @@ finalize_it:
c17bfd
 	RETiRet;
c17bfd
 }
c17bfd
 
c17bfd
+rsRetVal
c17bfd
+msgAddMultiMetadata(smsg_t *const __restrict__ pMsg,
c17bfd
+	       const uchar ** __restrict__ metaname,
c17bfd
+	       const uchar ** __restrict__ metaval,
c17bfd
+	       const int count)
c17bfd
+{
c17bfd
+	DEFiRet;
c17bfd
+	int i = 0 ;
c17bfd
+	struct json_object *const json = json_object_new_object();
c17bfd
+	CHKmalloc(json);
c17bfd
+	for ( i = 0 ; i < count ; i++ ) {
c17bfd
+		struct json_object *const jval = json_object_new_string((char*)metaval[i]);
c17bfd
+		if(jval == NULL) {
c17bfd
+			json_object_put(json);
c17bfd
+			ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
c17bfd
+ 		}
c17bfd
+		json_object_object_add(json, (const char *const)metaname[i], jval);
c17bfd
+	}
c17bfd
+	iRet = msgAddJSON(pMsg, (uchar*)"!metadata", json, 0, 0);
c17bfd
+finalize_it:
c17bfd
+	RETiRet;
c17bfd
+}
c17bfd
 
c17bfd
 static struct json_object *
c17bfd
 jsonDeepCopy(struct json_object *src)
c17bfd
diff --git a/runtime/msg.h b/runtime/msg.h
c17bfd
index 6521e19b28b013f0d06e357bdb0f33a94dab638b..0e92da43398156f4871b2e567a242cb089f67a08 100644
c17bfd
--- a/runtime/msg.h
c17bfd
+++ b/runtime/msg.h
c17bfd
@@ -195,6 +195,7 @@ int getPRIi(const smsg_t * const pM);
c17bfd
 void getRawMsg(smsg_t *pM, uchar **pBuf, int *piLen);
c17bfd
 rsRetVal msgAddJSON(smsg_t *pM, uchar *name, struct json_object *json, int force_reset, int sharedReference);
c17bfd
 rsRetVal msgAddMetadata(smsg_t *msg, uchar *metaname, uchar *metaval);
c17bfd
+rsRetVal msgAddMultiMetadata(smsg_t *msg, const uchar **metaname, const uchar **metaval, const int count);
c17bfd
 rsRetVal MsgGetSeverity(smsg_t *pThis, int *piSeverity);
c17bfd
 rsRetVal MsgDeserialize(smsg_t *pMsg, strm_t *pStrm);
c17bfd
 rsRetVal MsgSetPropsViaJSON(smsg_t *__restrict__ const pMsg, const uchar *__restrict__ const json);
c17bfd
diff --git a/runtime/stream.c b/runtime/stream.c
c17bfd
index 701144c0e39d6fbcf9dd63fe60421e1dcd6f01c6..fb1ff11d1890bbaee107658dd3568c2bc67c223d 100644
c17bfd
--- a/runtime/stream.c
c17bfd
+++ b/runtime/stream.c
c17bfd
@@ -91,6 +91,41 @@ static rsRetVal strmSeekCurrOffs(strm_t *pThis);
c17bfd
 
c17bfd
 /* methods */
c17bfd
 
c17bfd
+/* note: this may return NULL if not line segment is currently set  */
c17bfd
+// TODO: due to the cstrFinalize() this is not totally clean, albeit for our
c17bfd
+// current use case it does not hurt -- refactor! rgerhards, 2018-03-27
c17bfd
+const uchar *
c17bfd
+strmGetPrevLineSegment(strm_t *const pThis)
c17bfd
+{
c17bfd
+	const uchar *ret = NULL;
c17bfd
+	if(pThis->prevLineSegment != NULL) {
c17bfd
+		cstrFinalize(pThis->prevLineSegment);
c17bfd
+		ret = rsCStrGetSzStrNoNULL(pThis->prevLineSegment);
c17bfd
+	}
c17bfd
+	return ret;
c17bfd
+}
c17bfd
+/* note: this may return NULL if not line segment is currently set  */
c17bfd
+// TODO: due to the cstrFinalize() this is not totally clean, albeit for our
c17bfd
+// current use case it does not hurt -- refactor! rgerhards, 2018-03-27
c17bfd
+const uchar *
c17bfd
+strmGetPrevMsgSegment(strm_t *const pThis)
c17bfd
+{
c17bfd
+	const uchar *ret = NULL;
c17bfd
+	if(pThis->prevMsgSegment != NULL) {
c17bfd
+		cstrFinalize(pThis->prevMsgSegment);
c17bfd
+		ret = rsCStrGetSzStrNoNULL(pThis->prevMsgSegment);
c17bfd
+	}
c17bfd
+	return ret;
c17bfd
+}
c17bfd
+
c17bfd
+
c17bfd
+int
c17bfd
+strmGetPrevWasNL(const strm_t *const pThis)
c17bfd
+{
c17bfd
+	return pThis->bPrevWasNL;
c17bfd
+}
c17bfd
+
c17bfd
+
c17bfd
 /* output (current) file name for debug log purposes. Falls back to various
c17bfd
  * levels of impreciseness if more precise name is not known.
c17bfd
  */
c17bfd
@@ -242,17 +277,18 @@ doPhysOpen(strm_t *pThis)
c17bfd
 	}
c17bfd
 
c17bfd
 	pThis->fd = open((char*)pThis->pszCurrFName, iFlags | O_LARGEFILE, pThis->tOpenMode);
c17bfd
+	const int errno_save = errno; /* dbgprintf can mangle it! */
c17bfd
 	DBGPRINTF("file '%s' opened as #%d with mode %d\n", pThis->pszCurrFName,
c17bfd
 		  pThis->fd, (int) pThis->tOpenMode);
c17bfd
 	if(pThis->fd == -1) {
c17bfd
-		char errStr[1024];
c17bfd
-		int err = errno;
c17bfd
-		rs_strerror_r(err, errStr, sizeof(errStr));
c17bfd
-		DBGOPRINT((obj_t*) pThis, "open error %d, file '%s': %s\n", errno, pThis->pszCurrFName, errStr);
c17bfd
-		if(err == ENOENT)
c17bfd
-			ABORT_FINALIZE(RS_RET_FILE_NOT_FOUND);
c17bfd
-		else
c17bfd
-			ABORT_FINALIZE(RS_RET_FILE_OPEN_ERROR);
c17bfd
+		const rsRetVal errcode = (errno_save == ENOENT)
c17bfd
+			? RS_RET_FILE_NOT_FOUND : RS_RET_FILE_OPEN_ERROR;
c17bfd
+		if(pThis->fileNotFoundError) {
c17bfd
+			LogError(errno_save, errcode, "file '%s': open error", pThis->pszCurrFName);
c17bfd
+		} else {
c17bfd
+			DBGPRINTF("file '%s': open error", pThis->pszCurrFName);
c17bfd
+		}
c17bfd
+		ABORT_FINALIZE(errcode);
c17bfd
 	}
c17bfd
 
c17bfd
 	if(pThis->tOperationsMode == STREAMMODE_READ) {
c17bfd
@@ -344,6 +380,8 @@ static rsRetVal strmOpenFile(strm_t *pThis)
c17bfd
 
c17bfd
 	if(pThis->fd != -1)
c17bfd
 		ABORT_FINALIZE(RS_RET_OK);
c17bfd
+
c17bfd
+	free(pThis->pszCurrFName);
c17bfd
 	pThis->pszCurrFName = NULL; /* used to prevent mem leak in case of error */
c17bfd
 
c17bfd
 	if(pThis->pszFName == NULL)
c17bfd
@@ -733,11 +771,11 @@ static rsRetVal strmUnreadChar(strm_t *pThis, uchar c)
c17bfd
  * a line, but following lines that are indented are part of the same log entry
c17bfd
  */
c17bfd
 static rsRetVal
c17bfd
-strmReadLine(strm_t *pThis, cstr_t **ppCStr, uint8_t mode, sbool bEscapeLF, uint32_t trimLineOverBytes)
c17bfd
+strmReadLine(strm_t *pThis, cstr_t **ppCStr, uint8_t mode, sbool bEscapeLF,
c17bfd
+	uint32_t trimLineOverBytes, int64 *const strtOffs)
c17bfd
 {
c17bfd
         uchar c;
c17bfd
 	uchar finished;
c17bfd
-	rsRetVal readCharRet;
c17bfd
         DEFiRet;
c17bfd
 
c17bfd
         ASSERT(pThis != NULL);
c17bfd
@@ -756,12 +794,7 @@ strmReadLine(strm_t *pThis, cstr_t **ppCStr, uint8_t mode, sbool bEscapeLF, uint
c17bfd
         if(mode == 0) {
c17bfd
 		while(c != '\n') {
c17bfd
 			CHKiRet(cstrAppendChar(*ppCStr, c));
c17bfd
-			readCharRet = strmReadChar(pThis, &c);
c17bfd
-			if((readCharRet == RS_RET_TIMED_OUT) ||
c17bfd
-			   (readCharRet == RS_RET_EOF) ) { /* end reached without \n? */
c17bfd
-				CHKiRet(rsCStrConstructFromCStr(&pThis->prevLineSegment, *ppCStr));
c17bfd
-                	}
c17bfd
-                	CHKiRet(readCharRet);
c17bfd
+			CHKiRet(strmReadChar(pThis, &c);;
c17bfd
         	}
c17bfd
 		if (trimLineOverBytes > 0 && (uint32_t) cstrLen(*ppCStr) > trimLineOverBytes) {
c17bfd
 			/* Truncate long line at trimLineOverBytes position */
c17bfd
@@ -850,12 +883,19 @@ strmReadLine(strm_t *pThis, cstr_t **ppCStr, uint8_t mode, sbool bEscapeLF, uint
c17bfd
 	}
c17bfd
 
c17bfd
 finalize_it:
c17bfd
-        if(iRet != RS_RET_OK && *ppCStr != NULL) {
c17bfd
-		if(cstrLen(*ppCStr) > 0) {
c17bfd
-		/* we may have an empty string in an unsuccsfull poll or after restart! */
c17bfd
-			rsCStrConstructFromCStr(&pThis->prevLineSegment, *ppCStr);
c17bfd
+        if(iRet == RS_RET_OK) {
c17bfd
+		if(strtOffs != NULL) {
c17bfd
+			*strtOffs = pThis->strtOffs;
c17bfd
+		}
c17bfd
+		pThis->strtOffs = pThis->iCurrOffs; /* we are at begin of next line */
c17bfd
+	} else {
c17bfd
+		if(*ppCStr != NULL) {
c17bfd
+			if(cstrLen(*ppCStr) > 0) {
c17bfd
+			/* we may have an empty string in an unsuccesfull poll or after restart! */
c17bfd
+				rsCStrConstructFromCStr(&pThis->prevLineSegment, *ppCStr);
c17bfd
+			}
c17bfd
+			cstrDestruct(ppCStr);
c17bfd
 		}
c17bfd
-                cstrDestruct(ppCStr);
c17bfd
 	}
c17bfd
 
c17bfd
         RETiRet;
c17bfd
@@ -882,7 +922,8 @@ strmReadMultiLine_isTimedOut(const strm_t *const __restrict__ pThis)
c17bfd
  * added 2015-05-12 rgerhards
c17bfd
  */
c17bfd
 rsRetVal
c17bfd
-strmReadMultiLine(strm_t *pThis, cstr_t **ppCStr, regex_t *preg, const sbool bEscapeLF)
c17bfd
+strmReadMultiLine(strm_t *pThis, cstr_t **ppCStr, regex_t *preg, const sbool bEscapeLF,
c17bfd
+	int64 *const strtOffs)
c17bfd
 {
c17bfd
         uchar c;
c17bfd
 	uchar finished = 0;
c17bfd
@@ -946,16 +987,24 @@ strmReadMultiLine(strm_t *pThis, cstr_t **ppCStr, regex_t *preg, const sbool bEs
c17bfd
 	} while(finished == 0);
c17bfd
 
c17bfd
 finalize_it:
c17bfd
-	if(   pThis->readTimeout
c17bfd
-	   && (iRet != RS_RET_OK)
c17bfd
-	   && (pThis->prevMsgSegment != NULL)
c17bfd
-	   && (tCurr > pThis->lastRead + pThis->readTimeout)) {
c17bfd
-		CHKiRet(rsCStrConstructFromCStr(ppCStr, pThis->prevMsgSegment));
c17bfd
-		cstrDestruct(&pThis->prevMsgSegment);
c17bfd
-		pThis->lastRead = tCurr;
c17bfd
-		dbgprintf("stream: generated msg based on timeout: %s\n", cstrGetSzStrNoNULL(*ppCStr));
c17bfd
-			FINALIZE;
c17bfd
-		iRet = RS_RET_OK;
c17bfd
+	*strtOffs = pThis->strtOffs;
c17bfd
+	if(thisLine != NULL) {
c17bfd
+		cstrDestruct(&thisLine);
c17bfd
+	}
c17bfd
+	if(iRet == RS_RET_OK) {
c17bfd
+		pThis->strtOffs = pThis->iCurrOffs; /* we are at begin of next line */
c17bfd
+	} else {
c17bfd
+		if(   pThis->readTimeout
c17bfd
+		   && (pThis->prevMsgSegment != NULL)
c17bfd
+		   && (tCurr > pThis->lastRead + pThis->readTimeout)) {
c17bfd
+			CHKiRet(rsCStrConstructFromCStr(ppCStr, pThis->prevMsgSegment));
c17bfd
+			cstrDestruct(&pThis->prevMsgSegment);
c17bfd
+			pThis->lastRead = tCurr;
c17bfd
+			pThis->strtOffs = pThis->iCurrOffs; /* we are at begin of next line */
c17bfd
+			dbgprintf("stream: generated msg based on timeout: %s\n", cstrGetSzStrNoNULL(*ppCStr));
c17bfd
+				FINALIZE;
c17bfd
+			iRet = RS_RET_OK;
c17bfd
+		}
c17bfd
 	}
c17bfd
         RETiRet;
c17bfd
 }
c17bfd
@@ -974,7 +1023,10 @@ BEGINobjConstruct(strm) /* be sure to specify the object type also in END macro!
c17bfd
 	pThis->pszSizeLimitCmd = NULL;
c17bfd
 	pThis->prevLineSegment = NULL;
c17bfd
 	pThis->prevMsgSegment = NULL;
c17bfd
+	pThis->strtOffs = 0;
c17bfd
+	pThis->ignoringMsg = 0;
c17bfd
 	pThis->bPrevWasNL = 0;
c17bfd
+	pThis->fileNotFoundError = 1;
c17bfd
 ENDobjConstruct(strm)
c17bfd
 
c17bfd
 
c17bfd
@@ -1686,7 +1738,7 @@ static rsRetVal strmSeek(strm_t *pThis, off64_t offs)
c17bfd
 		DBGPRINTF("strmSeek: error %lld seeking to offset %lld\n", i, (long long) offs);
c17bfd
 		ABORT_FINALIZE(RS_RET_IO_ERROR);
c17bfd
 	}
c17bfd
-	pThis->iCurrOffs = offs; /* we are now at *this* offset */
c17bfd
+	pThis->strtOffs = pThis->iCurrOffs = offs; /* we are now at *this* offset */
c17bfd
 	pThis->iBufPtr = 0; /* buffer invalidated */
c17bfd
 
c17bfd
 finalize_it:
c17bfd
@@ -1738,7 +1790,7 @@ strmMultiFileSeek(strm_t *pThis, unsigned int FNum, off64_t offs, off64_t *bytes
c17bfd
 	} else {
c17bfd
 		*bytesDel = 0;
c17bfd
 	}
c17bfd
-	pThis->iCurrOffs = offs;
c17bfd
+	pThis->strtOffs = pThis->iCurrOffs = offs;
c17bfd
 
c17bfd
 finalize_it:
c17bfd
 	RETiRet;
c17bfd
@@ -1763,7 +1815,7 @@ static rsRetVal strmSeekCurrOffs(strm_t *pThis)
c17bfd
 
c17bfd
 	/* As the cryprov may use CBC or similiar things, we need to read skip data */
c17bfd
 	targetOffs = pThis->iCurrOffs;
c17bfd
-	pThis->iCurrOffs = 0;
c17bfd
+	pThis->strtOffs = pThis->iCurrOffs = 0;
c17bfd
 	DBGOPRINT((obj_t*) pThis, "encrypted, doing skip read of %lld bytes\n",
c17bfd
 		(long long) targetOffs);
c17bfd
 	while(targetOffs != pThis->iCurrOffs) {
c17bfd
@@ -1935,6 +1987,12 @@ static rsRetVal strmSetiMaxFiles(strm_t *pThis, int iNewVal)
c17bfd
 	return RS_RET_OK;
c17bfd
 }
c17bfd
 
c17bfd
+static rsRetVal strmSetFileNotFoundError(strm_t *pThis, int pFileNotFoundError)
c17bfd
+{
c17bfd
+	pThis->fileNotFoundError = pFileNotFoundError;
c17bfd
+	return RS_RET_OK;
c17bfd
+}
c17bfd
+
c17bfd
 
c17bfd
 /* set the stream's file prefix
c17bfd
  * The passed-in string is duplicated. So if the caller does not need
c17bfd
@@ -2076,6 +2134,9 @@ static rsRetVal strmSerialize(strm_t *pThis, strm_t *pStrm)
c17bfd
 	l = pThis->inode;
c17bfd
 	objSerializeSCALAR_VAR(pStrm, inode, INT64, l);
c17bfd
 
c17bfd
+	l = pThis->strtOffs;
c17bfd
+	objSerializeSCALAR_VAR(pStrm, strtOffs, INT64, l);
c17bfd
+
c17bfd
 	if(pThis->prevLineSegment != NULL) {
c17bfd
 		cstrFinalize(pThis->prevLineSegment);
c17bfd
 		objSerializePTR(pStrm, prevLineSegment, CSTR);
c17bfd
@@ -2188,8 +2249,12 @@ static rsRetVal strmSetProperty(strm_t *pThis, var_t *pProp)
c17bfd
 		pThis->iCurrOffs = pProp->val.num;
c17bfd
  	} else if(isProp("inode")) {
c17bfd
 		pThis->inode = (ino_t) pProp->val.num;
c17bfd
+ 	} else if(isProp("strtOffs")) {
c17bfd
+		pThis->strtOffs = pProp->val.num;
c17bfd
  	} else if(isProp("iMaxFileSize")) {
c17bfd
 		CHKiRet(strmSetiMaxFileSize(pThis, pProp->val.num));
c17bfd
+ 	} else if(isProp("fileNotFoundError")) {
c17bfd
+		CHKiRet(strmSetFileNotFoundError(pThis, pProp->val.num));
c17bfd
  	} else if(isProp("iMaxFiles")) {
c17bfd
 		CHKiRet(strmSetiMaxFiles(pThis, pProp->val.num));
c17bfd
  	} else if(isProp("iFileNumDigits")) {
c17bfd
@@ -2253,6 +2318,7 @@ CODESTARTobjQueryInterface(strm)
c17bfd
 	pIf->WriteChar = strmWriteChar;
c17bfd
 	pIf->WriteLong = strmWriteLong;
c17bfd
 	pIf->SetFName = strmSetFName;
c17bfd
+	pIf->SetFileNotFoundError = strmSetFileNotFoundError;
c17bfd
 	pIf->SetDir = strmSetDir;
c17bfd
 	pIf->Flush = strmFlush;
c17bfd
 	pIf->RecordBegin = strmRecordBegin;
c17bfd
diff --git a/runtime/stream.h b/runtime/stream.h
c17bfd
index 1eee34979db34620b82e6351111864645187b035..bcb81a14f60f9effa52fffa42d18d66c484ae86d 100644
c17bfd
--- a/runtime/stream.h
c17bfd
+++ b/runtime/stream.h
c17bfd
@@ -159,6 +159,10 @@ typedef struct strm_s {
c17bfd
 	sbool	bIsTTY;		/* is this a tty file? */
c17bfd
 	cstr_t *prevLineSegment; /* for ReadLine, previous, unprocessed part of file */
c17bfd
 	cstr_t *prevMsgSegment; /* for ReadMultiLine, previous, yet unprocessed part of msg */
c17bfd
+	int64 strtOffs;		/* start offset in file for current line/msg */
c17bfd
+	int fileNotFoundError;
c17bfd
+	int noRepeatedErrorOutput; /* if a file is missing the Error is only given once */
c17bfd
+	int ignoringMsg;
c17bfd
 } strm_t;
c17bfd
 
c17bfd
 
c17bfd
@@ -174,6 +178,7 @@ BEGINinterface(strm) /* name must also be changed in ENDinterface macro! */
c17bfd
 	rsRetVal (*Write)(strm_t *const pThis, const uchar *const pBuf, size_t lenBuf);
c17bfd
 	rsRetVal (*WriteChar)(strm_t *pThis, uchar c);
c17bfd
 	rsRetVal (*WriteLong)(strm_t *pThis, long i);
c17bfd
+	rsRetVal (*SetFileNotFoundError)(strm_t *pThis, int pFileNotFoundError);
c17bfd
 	rsRetVal (*SetFName)(strm_t *pThis, uchar *pszPrefix, size_t iLenPrefix);
c17bfd
 	rsRetVal (*SetDir)(strm_t *pThis, uchar *pszDir, size_t iLenDir);
c17bfd
 	rsRetVal (*Flush)(strm_t *pThis);
c17bfd
@@ -198,7 +203,8 @@ BEGINinterface(strm) /* name must also be changed in ENDinterface macro! */
c17bfd
 	INTERFACEpropSetMeth(strm, iFlushInterval, int);
c17bfd
 	INTERFACEpropSetMeth(strm, pszSizeLimitCmd, uchar*);
c17bfd
 	/* v6 added */
c17bfd
-	rsRetVal (*ReadLine)(strm_t *pThis, cstr_t **ppCStr, uint8_t mode, sbool bEscapeLF, uint32_t trimLineOverBytes);
c17bfd
+	rsRetVal (*ReadLine)(strm_t *pThis, cstr_t **ppCStr, uint8_t mode, sbool bEscapeLF,
c17bfd
+		uint32_t trimLineOverBytes, int64 *const strtOffs);
c17bfd
 	/* v7 added  2012-09-14 */
c17bfd
 	INTERFACEpropSetMeth(strm, bVeryReliableZip, int);
c17bfd
 	/* v8 added  2013-03-21 */
c17bfd
@@ -207,19 +213,24 @@ BEGINinterface(strm) /* name must also be changed in ENDinterface macro! */
c17bfd
 	INTERFACEpropSetMeth(strm, cryprov, cryprov_if_t*);
c17bfd
 	INTERFACEpropSetMeth(strm, cryprovData, void*);
c17bfd
 ENDinterface(strm)
c17bfd
-#define strmCURR_IF_VERSION 12 /* increment whenever you change the interface structure! */
c17bfd
+#define strmCURR_IF_VERSION 13 /* increment whenever you change the interface structure! */
c17bfd
 /* V10, 2013-09-10: added new parameter bEscapeLF, changed mode to uint8_t (rgerhards) */
c17bfd
 /* V11, 2015-12-03: added new parameter bReopenOnTruncate */
c17bfd
 /* V12, 2015-12-11: added new parameter trimLineOverBytes, changed mode to uint32_t */
c17bfd
+/* V13, 2017-09-06: added new parameter strtoffs to ReadLine() */
c17bfd
 
c17bfd
 #define strmGetCurrFileNum(pStrm) ((pStrm)->iCurrFNum)
c17bfd
 
c17bfd
 /* prototypes */
c17bfd
 PROTOTYPEObjClassInit(strm);
c17bfd
 rsRetVal strmMultiFileSeek(strm_t *pThis, unsigned int fileNum, off64_t offs, off64_t *bytesDel);
c17bfd
-rsRetVal strmReadMultiLine(strm_t *pThis, cstr_t **ppCStr, regex_t *preg, sbool bEscapeLF);
c17bfd
+rsRetVal strmReadMultiLine(strm_t *pThis, cstr_t **ppCStr, regex_t *preg,
c17bfd
+	sbool bEscapeLF, int64 *const strtOffs);
c17bfd
 int strmReadMultiLine_isTimedOut(const strm_t *const __restrict__ pThis);
c17bfd
 void strmDebugOutBuf(const strm_t *const pThis);
c17bfd
 void strmSetReadTimeout(strm_t *const __restrict__ pThis, const int val);
c17bfd
+const uchar * strmGetPrevLineSegment(strm_t *const pThis);
c17bfd
+const uchar * strmGetPrevMsgSegment(strm_t *const pThis);
c17bfd
+int strmGetPrevWasNL(const strm_t *const pThis);
c17bfd
 
c17bfd
 #endif /* #ifndef STREAM_H_INCLUDED */
c17bfd