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

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