Blame SOURCES/rsyslog-8.24.0-rhbz1763746-file-id.patch

c750b0
From 70ea76962bfe9ffed2bd604898e43e0e3b17645d Mon Sep 17 00:00:00 2001
c750b0
From: Rainer Gerhards <rgerhards@adiscon.com>
c750b0
Date: Thu, 23 Aug 2018 10:15:21 +0200
c750b0
Subject: [PATCH] omfile: implement file-id, used in state file
c750b0
c750b0
This ensures that files with the same inodes are not accidently treated
c750b0
as equal, at least within the limits of the file id hash (see doc for
c750b0
details).
c750b0
c750b0
We use the siphash reference implementation to generate our non-cryptographic
c750b0
hash.
c750b0
c750b0
State file handling was invalid. When a file was moved and re-created
c750b0
rsyslog could use the file_id if the new file to write the old files'
c750b0
state file. This could make the file reader stuck until it reached the
c750b0
previous offset. Depending on file sizes this could never happen AND
c750b0
would cause large message loss. This situation was timing dependent
c750b0
(a race) and most frequently occurred under log rotation. In polling
c750b0
mode the bug was less likely, but could also occur.
c750b0
---
c750b0
 plugins/imfile/Makefile.am      |   2 +-
c750b0
 plugins/imfile/imfile.c         | 190 ++++++++++++++++++-----
c750b0
 plugins/imfile/siphash.c        | 185 ++++++++++++++++++++++++++++++++
c750b0
 3 files changed, 381 insertions(+), 19 deletions(-)
c750b0
 create mode 100644 plugins/imfile/siphash.c
c750b0
c750b0
diff --git a/plugins/imfile/Makefile.am b/plugins/imfile/Makefile.am
c750b0
index f4df0ed687..9e137efdc8 100644
c750b0
--- a/plugins/imfile/Makefile.am
c750b0
+++ b/plugins/imfile/Makefile.am
c750b0
@@ -1,6 +1,6 @@
c750b0
 pkglib_LTLIBRARIES = imfile.la
c750b0
 
c750b0
-imfile_la_SOURCES = imfile.c
c750b0
+imfile_la_SOURCES = imfile.c siphash.c
c750b0
 imfile_la_CPPFLAGS = -I$(top_srcdir) $(PTHREADS_CFLAGS) $(RSRT_CFLAGS)
c750b0
 imfile_la_LDFLAGS = -module -avoid-version
c750b0
 imfile_la_LIBADD = 
c750b0
diff --git a/plugins/imfile/imfile.c b/plugins/imfile/imfile.c
c750b0
index 4bc6078bda..14f4f1f495 100644
c750b0
--- a/plugins/imfile/imfile.c
c750b0
+++ b/plugins/imfile/imfile.c
c750b0
@@ -66,6 +66,8 @@
c750b0
 MODULE_CNFNAME("imfile")
c750b0
 
c750b0
 /* defines */
c750b0
+#define FILE_ID_HASH_SIZE 20	/* max size of a file_id hash */
c750b0
+#define FILE_ID_SIZE	512	/* how many bytes are used for file-id? */
c750b0
 
c750b0
 /* Module static data */
c750b0
 DEF_IMOD_STATIC_DATA	/* must be present, starts static data */
c750b0
@@ -75,6 +77,9 @@
c750b0
 DEFobjCurrIf(prop)
c750b0
 DEFobjCurrIf(ruleset)
c750b0
 
c750b0
+extern int rs_siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
c750b0
+	uint8_t *out, const size_t outlen); /* see siphash.c */
c750b0
+
c750b0
 static int bLegacyCnfModGlobalsPermitted;/* are legacy module-global config parameters permitted? */
c750b0
 
c750b0
 #define NUM_MULTISUB 1024 /* default max number of submits */
c750b0
@@ -155,8 +160,10 @@
c750b0
 	int wd;
c750b0
 	time_t timeoutBase; /* what time to calculate the timeout against? */
c750b0
 	/* file dynamic data */
c750b0
+	char file_id[FILE_ID_HASH_SIZE]; /* file id for this entry, once we could obtain it */
c750b0
 	int in_move;	/* workaround for inotify move: if set, state file must not be deleted */
c750b0
 	ino_t ino;	/* current inode nbr */
c750b0
+	int fd;		/* fd to file in order to obtain file_id (needs to be preserved across move) */
c750b0
 	strm_t *pStrm;	/* its stream (NULL if not assigned) */
c750b0
 	int nRecords; /**< How many records did we process before persisting the stream? */
c750b0
 	ratelimit_t *ratelimiter;
c750b0
@@ -187,7 +194,7 @@
c750b0
 static int getBasename(uchar *const __restrict__ basen, uchar *const __restrict__ path);
c750b0
 static void act_obj_unlink(act_obj_t *act);
c750b0
 static uchar * getStateFileName(const act_obj_t *, uchar *, const size_t);
c750b0
-static int getFullStateFileName(const uchar *const, uchar *const pszout, const size_t ilenout);
c750b0
+static int getFullStateFileName(const uchar *const, const char *const, uchar *const pszout, const size_t ilenout);
c750b0
 
c750b0
 
c750b0
 #define OPMODE_POLLING 0
c750b0
@@ -328,7 +335,7 @@
c750b0
 		  act->name, statefn);
c750b0
 
c750b0
 	/* Get full path and file name */
c750b0
-	lenSFNam = getFullStateFileName(statefn, pszSFNam, sizeof(pszSFNam));
c750b0
+	lenSFNam = getFullStateFileName(statefn, "", pszSFNam, sizeof(pszSFNam));
c750b0
 
c750b0
 	/* check if the file exists */
c750b0
 	if(stat((char*) pszSFNam, &stat_buf) == -1) {
c750b0
@@ -561,16 +568,25 @@
c750b0
                        }
c750b0
 		}
c750b0
 	}
c750b0
+	DBGPRINTF("need to add new active object '%s' in '%s' - checking if accessible\n", name, edge->path);
c750b0
+	const int fd = open(name, O_RDONLY | O_CLOEXEC);
c750b0
+	if(fd < 0) {
c750b0
+		if (is_file) { LogMsg(errno, RS_RET_ERR, LOG_WARNING, "imfile: error accessing file '%s'", name);
c750b0
+		} else { DBGPRINTF("imfile: error accessing file '%s'", name); }
c750b0
+		FINALIZE;
c750b0
+	}
c750b0
 	DBGPRINTF("add new active object '%s' in '%s'\n", name, edge->path);
c750b0
 	CHKmalloc(act = calloc(sizeof(act_obj_t), 1));
c750b0
 	CHKmalloc(act->name = strdup(name));
c750b0
-       if (-1 == getBasename((uchar*)basename, (uchar*)name)) {
c750b0
-               CHKmalloc(act->basename = strdup(name)); /* assume basename is same as name */
c750b0
-       } else {
c750b0
-               CHKmalloc(act->basename = strdup(basename));
c750b0
-       }
c750b0
+	if (-1 == getBasename((uchar*)basename, (uchar*)name)) {
c750b0
+		CHKmalloc(act->basename = strdup(name)); /* assume basename is same as name */
c750b0
+	} else {
c750b0
+		CHKmalloc(act->basename = strdup(basename));
c750b0
+	}
c750b0
 	act->edge = edge;
c750b0
 	act->ino = ino;
c750b0
+	act->fd = fd;
c750b0
+	act->file_id[0] = '\0';
c750b0
 	act->is_symlink = is_symlink;
c750b0
        if (source) { /* we are target of symlink */
c750b0
                CHKmalloc(act->source_name = strdup(source));
c750b0
@@ -813,7 +828,7 @@
c750b0
 		pollFile(act); /* get any left-over data */
c750b0
 		if(inst->bRMStateOnDel) {
c750b0
 			statefn = getStateFileName(act, statefile, sizeof(statefile));
c750b0
-			getFullStateFileName(statefn, toDel, sizeof(toDel));
c750b0
+			getFullStateFileName(statefn, "", toDel, sizeof(toDel)); // TODO: check!
c750b0
 			statefn = toDel;
c750b0
 		}
c750b0
 		persistStrmState(act);
c750b0
@@ -832,6 +847,9 @@
c750b0
 		wdmapDel(act->wd);
c750b0
 	}
c750b0
 	#endif
c750b0
+	if(act->fd >= 0) {
c750b0
+		close(act->fd);
c750b0
+	}
c750b0
 	#if defined(OS_SOLARIS) && defined (HAVE_PORT_SOURCE_FILE)
c750b0
 	if(act->pfinf != NULL) {
c750b0
 		free(act->pfinf->fobj.fo_name);
c750b0
@@ -1029,7 +1047,7 @@
c750b0
  * open or otherwise modify disk file state.
c750b0
  */
c750b0
 static int
c750b0
-getFullStateFileName(const uchar *const pszstatefile, uchar *const pszout, const size_t ilenout)
c750b0
+getFullStateFileName(const uchar *const pszstatefile, const char *const file_id, uchar *const pszout, const size_t ilenout)
c750b0
 {
c750b0
 	int lenout;
c750b0
 	const uchar* pszworkdir;
ff08e8
@@ -1038,14 +1056,69 @@
c750b0
 	pszworkdir = glblGetWorkDirRaw();
c750b0
 
c750b0
 	/* Construct file name */
c750b0
-	lenout = snprintf((char*)pszout, ilenout, "%s/%s",
c750b0
-			     (char*) (pszworkdir == NULL ? "." : (char*) pszworkdir), (char*)pszstatefile);
c750b0
+	lenout = snprintf((char*)pszout, ilenout, "%s/%s%s%s",
c750b0
+		(char*) (pszworkdir == NULL ? "." : (char*) pszworkdir), (char*)pszstatefile,
c750b0
+		(*file_id == '\0') ? "" : ":", file_id);
c750b0
 
c750b0
 	/* return out length */
c750b0
 	return lenout;
c750b0
 }
c750b0
 
c750b0
 
c750b0
+/* hash function for file-id
c750b0
+ * Takes a block of data and returns a string with the hash value.
c750b0
+ *
c750b0
+ * Currently one provided by Aaaron Wiebe based on perl's hashing algorithm
c750b0
+ * (so probably pretty generic). Not for excessively large strings!
c750b0
+ * TODO: re-think the hash function!
c750b0
+ */
c750b0
+#if defined(__clang__)
c750b0
+#pragma GCC diagnostic ignored "-Wunknown-attributes"
c750b0
+#endif
c750b0
+static void __attribute__((nonnull(1,3)))
c750b0
+#if defined(__clang__)
c750b0
+__attribute__((no_sanitize("unsigned-integer-overflow")))
c750b0
+#endif
c750b0
+get_file_id_hash(const char *data, size_t lendata,
c750b0
+	char *const hash_str, const size_t len_hash_str)
c750b0
+{
c750b0
+	assert(len_hash_str >= 17); /* we always generate 8-byte strings */
c750b0
+
c750b0
+	size_t i;
c750b0
+	uint8_t out[8], k[16];
c750b0
+	for (i = 0; i < 16; ++i)
c750b0
+		k[i] = i;
c750b0
+	memset(out, 0, sizeof(out));
c750b0
+	rs_siphash((const uint8_t *)data, lendata, k, out, 8);
c750b0
+
c750b0
+	for(i = 0 ; i < 8 ; ++i) {
c750b0
+		if(2 * i+1 >= len_hash_str)
c750b0
+			break;
c750b0
+		snprintf(hash_str+(2*i), 3, "%2.2x", out[i]);
c750b0
+	}
c750b0
+}
c750b0
+
c750b0
+
c750b0
+
c750b0
+/* this returns the file-id for a given file
c750b0
+ */
c750b0
+static void getFileID(act_obj_t *const act)
c750b0
+{
c750b0
+	/* save the old id for cleaning purposes */
c750b0
+	strncpy(act->file_id_prev, (const char*)act->file_id, FILE_ID_HASH_SIZE);
c750b0
+	act->file_id[0] = '\0';
c750b0
+	assert(act->fd >= 0); /* fd must have been opened at act_obj_t creation! */
c750b0
+	char filedata[FILE_ID_SIZE];
ff08e8
+	lseek(act->fd, 0, SEEK_SET); /* Seek to beginning of file so we have correct id */
c750b0
+	const int r = read(act->fd, filedata, FILE_ID_SIZE);
c750b0
+	if(r == FILE_ID_SIZE) {
c750b0
+		get_file_id_hash(filedata, sizeof(filedata), act->file_id, sizeof(act->file_id));
c750b0
+	} else {
c750b0
+		DBGPRINTF("getFileID partial or error read, ret %d\n", r);
c750b0
+	}
c750b0
+	DBGPRINTF("getFileID for '%s', file_id_hash '%s'\n", act->name, act->file_id);
c750b0
+}
c750b0
+
c750b0
 /* this generates a state file name suitable for the given file. To avoid
c750b0
  * malloc calls, it must be passed a buffer which should be MAXFNAME large.
c750b0
  * Note: the buffer is not necessarily populated ... always ONLY use the
c750b0
@@ -1060,7 +1135,7 @@
c750b0
 {
c750b0
 	DBGPRINTF("getStateFileName for '%s'\n", act->name);
c750b0
 	snprintf((char*)buf, lenbuf - 1, "imfile-state:%lld", (long long) act->ino);
c750b0
-	DBGPRINTF("getStateFileName:  stat file name now is %s\n", buf);
c750b0
+	DBGPRINTF("getStateFileName:  state file name now is %s\n", buf);
c750b0
 	return buf;
c750b0
 }
c750b0
 
c750b0
@@ -1136,18 +1209,45 @@
c750b0
 	const instanceConf_t *const inst = act->edge->instarr[0];// TODO: same file, multiple instances?
c750b0
 
c750b0
 	uchar *const statefn = getStateFileName(act, statefile, sizeof(statefile));
c750b0
+	getFileID(act);
c750b0
 
c750b0
-	getFullStateFileName(statefn, pszSFNam, sizeof(pszSFNam));
c750b0
+	getFullStateFileName(statefn, act->file_id, pszSFNam, sizeof(pszSFNam));
c750b0
 	DBGPRINTF("trying to open state for '%s', state file '%s'\n", act->name, pszSFNam);
c750b0
 
c750b0
 	/* check if the file exists */
c750b0
 	fd = open((char*)pszSFNam, O_CLOEXEC | O_NOCTTY | O_RDONLY, 0600);
c750b0
 	if(fd < 0) {
c750b0
 		if(errno == ENOENT) {
c750b0
-			DBGPRINTF("NO state file (%s) exists for '%s' - trying to see if "
c750b0
-				"old-style file exists\n", pszSFNam, act->name);
c750b0
-			CHKiRet(OLD_openFileWithStateFile(act));
c750b0
-			FINALIZE;
c750b0
+			if(act->file_id[0] != '\0') {
c750b0
+				const char *pszSFNamHash = strdup((const char*)pszSFNam);
c750b0
+				CHKmalloc(pszSFNamHash);
c750b0
+				DBGPRINTF("state file %s for %s does not exist - trying to see if "
c750b0
+					"inode-only file exists\n", pszSFNam, act->name);
c750b0
+				getFullStateFileName(statefn, "", pszSFNam, sizeof(pszSFNam));
c750b0
+				fd = open((char*)pszSFNam, O_CLOEXEC | O_NOCTTY | O_RDONLY, 0600);
c750b0
+				if(fd >= 0) {
c750b0
+					dbgprintf("found inode-only state file, renaming it now that we "
c750b0
+						"know the file_id, new name: %s\n", pszSFNamHash);
c750b0
+					/* we now can use identify the file, so let's rename it */
c750b0
+					if(rename((const char*)pszSFNam, pszSFNamHash) != 0) {
c750b0
+						LogError(errno, RS_RET_IO_ERROR,
c750b0
+							"imfile error trying to rename state file for '%s' - "
c750b0
+							"ignoring this error, usually this means a file no "
c750b0
+							"longer file is left over, but this may also cause "
c750b0
+							"some real trouble. Still the best we can do ",
c750b0
+							act->name);
c750b0
+						free((void*) pszSFNamHash);
c750b0
+						ABORT_FINALIZE(RS_RET_IO_ERROR);
c750b0
+					}
c750b0
+				}
c750b0
+				free((void*) pszSFNamHash);
c750b0
+			}
c750b0
+			if(fd < 0) {
c750b0
+				DBGPRINTF("state file %s for %s does not exist - trying to see if "
c750b0
+					"old-style file exists\n", pszSFNam, act->name);
c750b0
+				CHKiRet(OLD_openFileWithStateFile(act));
c750b0
+				FINALIZE;
c750b0
+			}
c750b0
 		} else {
c750b0
 			LogError(errno, RS_RET_IO_ERROR,
c750b0
 				"imfile error trying to access state file for '%s'",
c750b0
@@ -1156,6 +1256,7 @@
c750b0
 		}
c750b0
 	}
c750b0
 
c750b0
+	DBGPRINTF("opened state file %s for %s\n", pszSFNam, act->name);
c750b0
 	CHKiRet(strm.Construct(&act->pStrm));
c750b0
 
c750b0
 	struct json_object *jval;
c750b0
@@ -1289,6 +1390,7 @@
c750b0
 {
c750b0
 	int64 strtOffs;
c750b0
 	DEFiRet;
c750b0
+	int64_t startOffs = 0;
c750b0
 	int nProcessed = 0;
c750b0
 
c750b0
 	DBGPRINTF("pollFileReal enter, pStrm %p, name '%s'\n", act->pStrm, act->name);
c750b0
@@ -1301,6 +1403,7 @@
c750b0
 		CHKiRet(openFile(act)); /* open file */
c750b0
 	}
c750b0
 
c750b0
+	startOffs = act->pStrm->iCurrOffs;	
c750b0
 	/* loop below will be exited when strmReadLine() returns EOF */
c750b0
 	while(glbl.GetGlobalInputTermState() == 0) {
c750b0
 		if(inst->maxLinesAtOnce != 0 && nProcessed >= inst->maxLinesAtOnce)
c750b0
@@ -1313,6 +1416,11 @@
c750b0
 				inst->escapeLF, &strtOffs));
c750b0
 		}
c750b0
 		++nProcessed;
c750b0
+		if(startOffs < FILE_ID_SIZE && act->pStrm->iCurrOffs >= FILE_ID_SIZE) {
c750b0
+			dbgprintf("initiating state file write as sufficient data is now present; file=%s\n", act->name);
c750b0
+			persistStrmState(act);
c750b0
+			startOffs = act->pStrm->iCurrOffs; /* disable check */
c750b0
+		}
c750b0
 		runModConf->bHadFileData = 1; /* this is just a flag, so set it and forget it */
c750b0
 		CHKiRet(enqLine(act, *pCStr, strtOffs)); /* process line */
c750b0
 		rsCStrDestruct(pCStr); /* discard string (must be done by us!) */
c750b0
@@ -2122,7 +2230,8 @@
c750b0
 	uchar statefname[MAXFNAME];
c750b0
 
c750b0
 	uchar *const statefn = getStateFileName(act, statefile, sizeof(statefile));
c750b0
-	getFullStateFileName(statefn, statefname, sizeof(statefname));
c750b0
+	getFileID(act);
c750b0
+	getFullStateFileName(statefn, act->file_id, statefname, sizeof(statefname));
c750b0
 	DBGPRINTF("persisting state for '%s', state file '%s'\n", act->name, statefname);
c750b0
 
c750b0
 	struct json_object *jval = NULL;
c750b0
diff --git a/plugins/imfile/siphash.c b/plugins/imfile/siphash.c
c750b0
new file mode 100644
c750b0
index 0000000000..8d5fac7343
c750b0
--- /dev/null
c750b0
+++ b/plugins/imfile/siphash.c
c750b0
@@ -0,0 +1,185 @@
c750b0
+/* SipHash reference C implementation
c750b0
+ *
c750b0
+ * Copyright (c) 2012-2016 Jean-Philippe Aumasson
c750b0
+ * <jeanphilippe.aumasson@gmail.com>
c750b0
+ * Copyright (c) 2012-2014 Daniel J. Bernstein <djb@cr.yp.to>
c750b0
+ *
c750b0
+ * Slightly adapted by rsyslog in regard to build system and code style
c750b0
+ * check.
c750b0
+ *
c750b0
+ * To the extent possible under law, the author(s) have dedicated all copyright
c750b0
+ * and related and neighboring rights to this software to the public domain
c750b0
+ * worldwide. This software is distributed without any warranty.
c750b0
+ *
c750b0
+ * You should have received a copy of the CC0 Public Domain Dedication along
c750b0
+ * with
c750b0
+ * this software. If not, see
c750b0
+ * <http://creativecommons.org/publicdomain/zero/1.0/>.
c750b0
+ *
c750b0
+ * For details on siphash see https://131002.net/siphash/
c750b0
+ */
c750b0
+#include <assert.h>
c750b0
+#include <stdint.h>
c750b0
+#include <stdio.h>
c750b0
+#include <string.h>
c750b0
+
c750b0
+/* default: SipHash-2-4 */
c750b0
+#define cROUNDS 2
c750b0
+#define dROUNDS 4
c750b0
+
c750b0
+#define ROTL(x, b) (uint64_t)(((x) << (b)) | ((x) >> (64 - (b))))
c750b0
+
c750b0
+#define U32TO8_LE(p, v)                                                        \
c750b0
+	(p)[0] = (uint8_t)((v));                                                   \
c750b0
+	(p)[1] = (uint8_t)((v) >> 8);                                              \
c750b0
+	(p)[2] = (uint8_t)((v) >> 16);                                             \
c750b0
+	(p)[3] = (uint8_t)((v) >> 24);
c750b0
+
c750b0
+#define U64TO8_LE(p, v)                                                        \
c750b0
+	U32TO8_LE((p), (uint32_t)((v)));                                           \
c750b0
+	U32TO8_LE((p) + 4, (uint32_t)((v) >> 32));
c750b0
+
c750b0
+#define U8TO64_LE(p)                                                           \
c750b0
+	(((uint64_t)((p)[0])) | ((uint64_t)((p)[1]) << 8) |                        \
c750b0
+	((uint64_t)((p)[2]) << 16) | ((uint64_t)((p)[3]) << 24) |                 \
c750b0
+	((uint64_t)((p)[4]) << 32) | ((uint64_t)((p)[5]) << 40) |                 \
c750b0
+	((uint64_t)((p)[6]) << 48) | ((uint64_t)((p)[7]) << 56))
c750b0
+
c750b0
+#define SIPROUND                                                               \
c750b0
+	do {                                                                       \
c750b0
+		v0 += v1;                                                              \
c750b0
+		v1 = ROTL(v1, 13);                                                     \
c750b0
+		v1 ^= v0;                                                              \
c750b0
+		v0 = ROTL(v0, 32);                                                     \
c750b0
+		v2 += v3;                                                              \
c750b0
+		v3 = ROTL(v3, 16);                                                     \
c750b0
+		v3 ^= v2;                                                              \
c750b0
+		v0 += v3;                                                              \
c750b0
+		v3 = ROTL(v3, 21);                                                     \
c750b0
+		v3 ^= v0;                                                              \
c750b0
+		v2 += v1;                                                              \
c750b0
+		v1 = ROTL(v1, 17);                                                     \
c750b0
+		v1 ^= v2;                                                              \
c750b0
+		v2 = ROTL(v2, 32);                                                     \
c750b0
+	} while (0)
c750b0
+
c750b0
+#ifdef DEBUG
c750b0
+#define TRACE                                                                  \
c750b0
+	do {                                                                       \
c750b0
+		printf("(%3d) v0 %08x %08x\n", (int)inlen, (uint32_t)(v0 >> 32),       \
c750b0
+		       (uint32_t)v0);                                                  \
c750b0
+		printf("(%3d) v1 %08x %08x\n", (int)inlen, (uint32_t)(v1 >> 32),       \
c750b0
+		       (uint32_t)v1);                                                  \
c750b0
+		printf("(%3d) v2 %08x %08x\n", (int)inlen, (uint32_t)(v2 >> 32),       \
c750b0
+		       (uint32_t)v2);                                                  \
c750b0
+		printf("(%3d) v3 %08x %08x\n", (int)inlen, (uint32_t)(v3 >> 32),       \
c750b0
+		       (uint32_t)v3);                                                  \
c750b0
+	} while (0)
c750b0
+#else
c750b0
+#define TRACE
c750b0
+#endif
c750b0
+
c750b0
+extern int rs_siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
c750b0
+	uint8_t *out, const size_t outlen); /* avoid compiler warning */
c750b0
+#if defined(__clang__)
c750b0
+#pragma GCC diagnostic ignored "-Wunknown-attributes"
c750b0
+#endif
c750b0
+int
c750b0
+#if defined(__clang__)
c750b0
+__attribute__((no_sanitize("unsigned-integer-overflow")))
c750b0
+#endif
c750b0
+rs_siphash(const uint8_t *in, const size_t inlen, const uint8_t *k,
c750b0
+	uint8_t *out, const size_t outlen) {
c750b0
+
c750b0
+	uint64_t v0 = 0x736f6d6570736575ULL;
c750b0
+	uint64_t v1 = 0x646f72616e646f6dULL;
c750b0
+	uint64_t v2 = 0x6c7967656e657261ULL;
c750b0
+	uint64_t v3 = 0x7465646279746573ULL;
c750b0
+	uint64_t k0 = U8TO64_LE(k);
c750b0
+	uint64_t k1 = U8TO64_LE(k + 8);
c750b0
+	uint64_t m;
c750b0
+	int i;
c750b0
+	const uint8_t *end = in + inlen - (inlen % sizeof(uint64_t));
c750b0
+	const int left = inlen & 7;
c750b0
+	uint64_t b = ((uint64_t)inlen) << 56;
c750b0
+	assert((outlen == 8) || (outlen == 16));
c750b0
+	v3 ^= k1;
c750b0
+	v2 ^= k0;
c750b0
+	v1 ^= k1;
c750b0
+	v0 ^= k0;
c750b0
+
c750b0
+	if (outlen == 16)
c750b0
+	v1 ^= 0xee;
c750b0
+
c750b0
+	for (; in != end; in += 8) {
c750b0
+		m = U8TO64_LE(in);
c750b0
+		v3 ^= m;
c750b0
+
c750b0
+		TRACE;
c750b0
+		for (i = 0; i < cROUNDS; ++i)
c750b0
+		    SIPROUND;
c750b0
+
c750b0
+		v0 ^= m;
c750b0
+	}
c750b0
+
c750b0
+	switch (left) {
c750b0
+	case 7:
c750b0
+		b |= ((uint64_t)in[6]) << 48;
c750b0
+		/*FALLTHROUGH*/
c750b0
+	case 6:
c750b0
+		b |= ((uint64_t)in[5]) << 40;
c750b0
+		/*FALLTHROUGH*/
c750b0
+	case 5:
c750b0
+		b |= ((uint64_t)in[4]) << 32;
c750b0
+		/*FALLTHROUGH*/
c750b0
+	case 4:
c750b0
+		b |= ((uint64_t)in[3]) << 24;
c750b0
+		/*FALLTHROUGH*/
c750b0
+	case 3:
c750b0
+		b |= ((uint64_t)in[2]) << 16;
c750b0
+		/*FALLTHROUGH*/
c750b0
+	case 2:
c750b0
+		b |= ((uint64_t)in[1]) << 8;
c750b0
+		/*FALLTHROUGH*/
c750b0
+	case 1:
c750b0
+		b |= ((uint64_t)in[0]);
c750b0
+		break;
c750b0
+	case 0:
c750b0
+	default:
c750b0
+		break;
c750b0
+	}
c750b0
+
c750b0
+	v3 ^= b;
c750b0
+
c750b0
+	TRACE;
c750b0
+	for (i = 0; i < cROUNDS; ++i)
c750b0
+		SIPROUND;
c750b0
+
c750b0
+	v0 ^= b;
c750b0
+
c750b0
+	if (outlen == 16)
c750b0
+		v2 ^= 0xee;
c750b0
+	else
c750b0
+		v2 ^= 0xff;
c750b0
+
c750b0
+	TRACE;
c750b0
+	for (i = 0; i < dROUNDS; ++i)
c750b0
+		SIPROUND;
c750b0
+
c750b0
+	b = v0 ^ v1 ^ v2 ^ v3;
c750b0
+	U64TO8_LE(out, b);
c750b0
+
c750b0
+	if (outlen == 8)
c750b0
+		return 0;
c750b0
+
c750b0
+	v1 ^= 0xdd;
c750b0
+
c750b0
+	TRACE;
c750b0
+	for (i = 0; i < dROUNDS; ++i)
c750b0
+		SIPROUND;
c750b0
+
c750b0
+	b = v0 ^ v1 ^ v2 ^ v3;
c750b0
+	U64TO8_LE(out + 8, b);
c750b0
+
c750b0
+	return 0;
c750b0
+}
c750b0
--- a/plugins/imfile/imfile.c
c750b0
+++ b/plugins/imfile/imfile.c
c750b0
@@ -182,6 +182,7 @@ struct act_obj_s {
c750b0
 	time_t timeoutBase; /* what time to calculate the timeout against? */
c750b0
 	/* file dynamic data */
c750b0
 	char file_id[FILE_ID_HASH_SIZE]; /* file id for this entry, once we could obtain it */
c750b0
+	char file_id_prev[FILE_ID_HASH_SIZE]; /* previous file id for this entry, set if changed */
c750b0
 	int in_move;	/* workaround for inotify move: if set, state file must not be deleted */
c750b0
 	ino_t ino;	/* current inode nbr */
c750b0
 	int fd;		/* fd to file in order to obtain file_id (needs to be preserved across move) */
c750b0
@@ -727,6 +728,7 @@ act_obj_add(fs_edge_t *const edge, const char *const name, const int is_file,
c750b0
 	act->ino = ino;
c750b0
 	act->fd = fd;
c750b0
 	act->file_id[0] = '\0';
c750b0
+	act->file_id_prev[0] = '\0';
c750b0
 	act->is_symlink = is_symlink;
c750b0
        if (source) { /* we are target of symlink */
c750b0
                CHKmalloc(act->source_name = strdup(source));
c750b0
@@ -1378,28 +1380,13 @@ openFileWithStateFile(act_obj_t *const act)
c750b0
 	if(fd < 0) {
c750b0
 		if(errno == ENOENT) {
c750b0
 			if(act->file_id[0] != '\0') {
c750b0
-				const char *pszSFNamHash = strdup((const char*)pszSFNam);
c750b0
-				CHKmalloc(pszSFNamHash);
c750b0
 				DBGPRINTF("state file %s for %s does not exist - trying to see if "
c750b0
 					"inode-only file exists\n", pszSFNam, act->name);
c750b0
 				getFullStateFileName(statefn, "", pszSFNam, sizeof(pszSFNam));
c750b0
 				fd = open((char*)pszSFNam, O_CLOEXEC | O_NOCTTY | O_RDONLY, 0600);
c750b0
 				if(fd >= 0) {
c750b0
-					dbgprintf("found inode-only state file, renaming it now that we "
c750b0
-						"know the file_id, new name: %s\n", pszSFNamHash);
c750b0
-					/* we now can use identify the file, so let's rename it */
c750b0
-					if(rename((const char*)pszSFNam, pszSFNamHash) != 0) {
c750b0
-						LogError(errno, RS_RET_IO_ERROR,
c750b0
-							"imfile error trying to rename state file for '%s' - "
c750b0
-							"ignoring this error, usually this means a file no "
c750b0
-							"longer file is left over, but this may also cause "
c750b0
-							"some real trouble. Still the best we can do ",
c750b0
-							act->name);
c750b0
-						free((void*) pszSFNamHash);
c750b0
-						ABORT_FINALIZE(RS_RET_IO_ERROR);
c750b0
-					}
c750b0
+					dbgprintf("found inode-only state file, will be renamed at next persist\n");
c750b0
 				}
c750b0
-				free((void*) pszSFNamHash);
c750b0
 			}
c750b0
 			if(fd < 0) {
c750b0
 				DBGPRINTF("state file %s for %s does not exist - trying to see if "
ff08e8
@@ -2609,6 +2596,24 @@ atomicWriteStateFile(const char *fn, const char *content)
c750b0
 	RETiRet;
c750b0
 }
c750b0
 
c750b0
+static void
c750b0
+removeOldStatefile(const uchar *statefn, const char *hashToDelete)
c750b0
+{
c750b0
+	int ret;
c750b0
+	uchar statefname[MAXFNAME];
c750b0
+
c750b0
+	getFullStateFileName(statefn, hashToDelete, statefname, sizeof(statefname));
ff08e8
+	DBGPRINTF("removing old state file: '%s'\n", statefname);
c750b0
+	ret = unlink((const char*)statefname);
ff08e8
+	if(ret != 0 && errno != ENOENT) {
ff08e8
+		LogError(errno, RS_RET_IO_ERROR,
ff08e8
+			"imfile error trying to delete old state file: '%s' - ignoring this "
ff08e8
+			"error, usually this means a file no longer file is left over, but "
ff08e8
+			"this may also cause some real trouble. Still the best we can do ",
ff08e8
+			statefname);
c750b0
+	}
c750b0
+}
c750b0
+
c750b0
 
c750b0
 /* This function persists information for a specific file being monitored.
c750b0
  * To do so, it simply persists the stream object. We do NOT abort on error
ff08e8
@@ -2660,6 +2664,10 @@ persistStrmState(act_obj_t *const act)
c750b0
 	CHKiRet(atomicWriteStateFile((const char*)statefname, jstr));
c750b0
 	json_object_put(json);
c750b0
 
c750b0
+	if (strncmp((const char *)act->file_id_prev, (const char *)act->file_id, FILE_ID_HASH_SIZE)) {
c750b0
+		removeOldStatefile(statefn, act->file_id_prev);
c750b0
+	}
c750b0
+
c750b0
 finalize_it:
c750b0
 	if(iRet != RS_RET_OK) {
c750b0
 		errmsg.LogError(0, iRet, "imfile: could not persist state "