Blame SOURCES/rsyslog-8.37.0-rhbz1674471-imfile-log-rotation.patch

c16178
From f85ef7aabcec84497a5eaf9670616b3402c79d9c Mon Sep 17 00:00:00 2001
c16178
From: Rainer Gerhards <rgerhards@adiscon.com>
c16178
Date: Sun, 23 Sep 2018 13:19:31 +0200
c16178
Subject: File rotation with imfile broken
c16178
c16178
Previously, truncation was only detected at end of file. Especially with
c16178
busy files that could cause loss of data and possibly also stall imfile
c16178
reading. The new code now also checks during each read. Obviously, there
c16178
is some additional overhead associated with that, but this is unavoidable.
c16178
c16178
It still is highly recommended NOT to turn on "reopenOnTruncate" in imfile.
c16178
Note that there are also inherant reliability issues. There is no way to
c16178
"fix" these, as they are caused by races between the process(es) who truncate
c16178
and rsyslog reading the file. But with the new code, the "problem window"
c16178
should be much smaller and, more importantly, imfile should not stall.
c16178
c16178
A change in the inode was not detected under all circumstances,
c16178
most importantly not in some logrotate cases.
c16178
c16178
Includes new tests made by Andre Lorbach. They now use the
c16178
logrotate tool natively to reproduce the issue.
c16178
---
c16178
 runtime/rsyslog.h			       |   6 ++--
c16178
 plugins/imfile/imfile.c                       |  17 +++-
c16178
 runtime/stream.c                              | 122 ++++++++----
c16178
 runtime/stream.h                              |   7 ++
c16178
 4 files changed, 126 insertions(+), 36 deletions(-)
c16178
c16178
diff --git a/runtime/rsyslog.h b/runtime/rsyslog.h
c16178
index 61d0af623..22a1c46d1 100644
c16178
--- a/runtime/rsyslog.h
c16178
+++ b/runtime/rsyslog.h
c16178
@@ -221,9 +221,9 @@ enum rsRetVal_				/** return value. All methods return this if not specified oth
c16178
 	/* begin regular error codes */
c16178
 	RS_RET_NOT_IMPLEMENTED = -7,	/**< implementation is missing (probably internal error or lazyness ;)) */
c16178
 	RS_RET_OUT_OF_MEMORY = -6,	/**< memory allocation failed */
c16178
-	RS_RET_PROVIDED_BUFFER_TOO_SMALL = -50,
c16178
-/*< the caller provided a buffer, but the called function sees the size of this buffer is too small -
c16178
-operation not carried out */
c16178
+	RS_RET_PROVIDED_BUFFER_TOO_SMALL = -50, /*< the caller provided a buffer, but the called function sees
c16178
+						  the size of this buffer is too small - operation not carried out */
c16178
+	RS_RET_FILE_TRUNCATED = -51,	/**< (input) file was truncated, not an error but a status */
c16178
 	RS_RET_TRUE = -3,		/**< to indicate a true state (can be used as TRUE, legacy) */
c16178
 	RS_RET_FALSE = -2,		/**< to indicate a false state (can be used as FALSE, legacy) */
c16178
 	RS_RET_NO_IRET = -8,	/**< This is a trick for the debuging system - it means no iRet is provided  */
c16178
diff --git a/plugins/imfile/imfile.c b/plugins/imfile/imfile.c
c16178
index f4a4ef9b7..6be8b2999 100644
c16178
--- a/plugins/imfile/imfile.c
c16178
+++ b/plugins/imfile/imfile.c
c16178
@@ -740,8 +740,19 @@ detect_updates(fs_edge_t *const edge)
c16178
 			act_obj_unlink(act);
c16178
 			restart = 1;
c16178
 			break;
c16178
+		} else if(fileInfo.st_ino != act->ino) {
c16178
+			DBGPRINTF("file '%s' inode changed from %llu to %llu, unlinking from "
c16178
+				"internal lists\n", act->name, (long long unsigned) act->ino,
c16178
+				(long long unsigned) fileInfo.st_ino);
c16178
+			if(act->pStrm != NULL) {
c16178
+				/* we do no need to re-set later, as act_obj_unlink
c16178
+				 * will destroy the strm obj */
c16178
+				strmSet_checkRotation(act->pStrm, STRM_ROTATION_DO_NOT_CHECK);
c16178
+			}
c16178
+			act_obj_unlink(act);
c16178
+			restart = 1;
c16178
+			break;
c16178
 		}
c16178
-		// TODO: add inode check for change notification!
c16178
 
c16178
 	}
c16178
 
c16178
@@ -993,10 +1004,10 @@ chk_active(const act_obj_t *act, const act_obj_t *const deleted)
c16178
 /* unlink act object from linked list and then
c16178
  * destruct it.
c16178
  */
c16178
-static void //ATTR_NONNULL()
c16178
+static void ATTR_NONNULL()
c16178
 act_obj_unlink(act_obj_t *act)
c16178
 {
c16178
-	DBGPRINTF("act_obj_unlink %p: %s\n", act, act->name);
c16178
+	DBGPRINTF("act_obj_unlink %p: %s, pStrm %p\n", act, act->name, act->pStrm);
c16178
 	if(act->prev == NULL) {
c16178
 		act->edge->active = act->next;
c16178
 	} else {
c16178
diff --git a/runtime/stream.c b/runtime/stream.c
c16178
index 0f4197103..32a12b256 100644
c16178
--- a/runtime/stream.c
c16178
+++ b/runtime/stream.c
c16178
@@ -400,6 +400,7 @@ static rsRetVal strmOpenFile(strm_t *pThis)
c16178
 	CHKiRet(doPhysOpen(pThis));
c16178
 
c16178
 	pThis->iCurrOffs = 0;
c16178
+	pThis->iBufPtrMax = 0;
c16178
 	CHKiRet(getFileSize(pThis->pszCurrFName, &offset));
c16178
 	if(pThis->tOperationsMode == STREAMMODE_WRITE_APPEND) {
c16178
 		pThis->iCurrOffs = offset;
c16178
@@ -636,6 +637,78 @@ strmHandleEOF(strm_t *pThis)
c16178
 	RETiRet;
c16178
 }
c16178
 
c16178
+
c16178
+/* helper to checkTruncation */
c16178
+static rsRetVal ATTR_NONNULL()
c16178
+rereadTruncated(strm_t *const pThis, const char *const reason)
c16178
+{
c16178
+	DEFiRet;
c16178
+
c16178
+	LogMsg(errno, RS_RET_FILE_TRUNCATED, LOG_WARNING, "file '%s': truncation detected, "
c16178
+		"(%s) - re-start reading from beginning",
c16178
+		pThis->pszCurrFName, reason);
c16178
+	DBGPRINTF("checkTruncation, file %s last buffer CHANGED\n", pThis->pszCurrFName);
c16178
+	CHKiRet(strmCloseFile(pThis));
c16178
+	CHKiRet(strmOpenFile(pThis));
c16178
+	iRet = RS_RET_FILE_TRUNCATED;
c16178
+
c16178
+finalize_it:
c16178
+	RETiRet;
c16178
+}
c16178
+/* helper to read:
c16178
+ * Check if file has been truncated since last read and, if so, re-set reading
c16178
+ * to begin of file. To detect truncation, we try to re-read the last block.
c16178
+ * If that does not succeed or different data than from the original read is
c16178
+ * returned, truncation is assumed.
c16178
+ * NOTE: this function must be called only if truncation is enabled AND
c16178
+ * when the previous read buffer still is valid (aka "before the next read").
c16178
+ * It is ok to call with a 0-size buffer, which we than assume as begin of
c16178
+ * reading. In that case, no truncation will be detected.
c16178
+ * rgerhards, 2018-09-20
c16178
+ */
c16178
+static rsRetVal ATTR_NONNULL()
c16178
+checkTruncation(strm_t *const pThis)
c16178
+{
c16178
+	DEFiRet;
c16178
+	int ret;
c16178
+	off64_t backseek;
c16178
+	assert(pThis->bReopenOnTruncate);
c16178
+
c16178
+	DBGPRINTF("checkTruncation, file %s, iBufPtrMax %zd\n", pThis->pszCurrFName, pThis->iBufPtrMax);
c16178
+	if(pThis->iBufPtrMax == 0) {
c16178
+		FINALIZE;
c16178
+	}
c16178
+
c16178
+	int currpos = lseek64(pThis->fd, 0, SEEK_CUR);
c16178
+	backseek = -1 * (off64_t) pThis->iBufPtrMax;
c16178
+	dbgprintf("checkTruncation in actual processing, currpos %d, backseek is %d\n", (int)currpos, (int) backseek);
c16178
+	ret = lseek64(pThis->fd, backseek, SEEK_CUR);
c16178
+	if(ret < 0) {
c16178
+		iRet = rereadTruncated(pThis, "cannot seek backward to begin of last block");
c16178
+		FINALIZE;
c16178
+	}
c16178
+dbgprintf("checkTruncation seek backwrds: %d\n", ret);
c16178
+currpos = lseek64(pThis->fd, 0, SEEK_CUR);
c16178
+dbgprintf("checkTruncation in actual processing, NEW currpos %d, backseek is %d\n", (int)currpos, (int) backseek);
c16178
+
c16178
+	const ssize_t lenRead = read(pThis->fd, pThis->pIOBuf_truncation, pThis->iBufPtrMax);
c16178
+	dbgprintf("checkTruncation proof-read: %d bytes\n", (int) lenRead);
c16178
+	if(lenRead < 0) {
c16178
+		iRet = rereadTruncated(pThis, "last block could not be re-read");
c16178
+		FINALIZE;
c16178
+	}
c16178
+
c16178
+	if(!memcmp(pThis->pIOBuf_truncation, pThis->pIOBuf, pThis->iBufPtrMax)) {
c16178
+		DBGPRINTF("checkTruncation, file %s last buffer unchanged\n", pThis->pszCurrFName);
c16178
+	} else {
c16178
+		iRet = rereadTruncated(pThis, "last block data different");
c16178
+	}
c16178
+
c16178
+finalize_it:
c16178
+	RETiRet;
c16178
+}
c16178
+
c16178
+
c16178
 /* read the next buffer from disk
c16178
  * rgerhards, 2008-02-13
c16178
  */
c16178
@@ -668,6 +741,13 @@ strmReadBuf(strm_t *pThis, int *padBytes)
c16178
 				toRead = (size_t) bytesLeft;
c16178
 			}
c16178
 		}
c16178
+		if(pThis->bReopenOnTruncate) {
c16178
+			rsRetVal localRet = checkTruncation(pThis);
c16178
+			if(localRet == RS_RET_FILE_TRUNCATED) {
c16178
+				continue;
c16178
+			}
c16178
+			CHKiRet(localRet);
c16178
+		}
c16178
 		iLenRead = read(pThis->fd, pThis->pIOBuf, toRead);
c16178
 		DBGOPRINT((obj_t*) pThis, "file %d read %ld bytes\n", pThis->fd, iLenRead);
c16178
 		/* end crypto */
c16178
@@ -1184,6 +1264,7 @@ static rsRetVal strmConstructFinalize(strm_t *pThis)
c16178
 	} else {
c16178
 		/* we work synchronously, so we need to alloc a fixed pIOBuf */
c16178
 		CHKmalloc(pThis->pIOBuf = (uchar*) MALLOC(pThis->sIOBufSize));
c16178
+		CHKmalloc(pThis->pIOBuf_truncation = (char*) MALLOC(pThis->sIOBufSize));
c16178
 	}
c16178
 
c16178
 finalize_it:
c16178
@@ -1231,6 +1312,7 @@ CODESTARTobjDestruct(strm)
c16178
 		}
c16178
 	} else {
c16178
 		free(pThis->pIOBuf);
c16178
+		free(pThis->pIOBuf_truncation);
c16178
 	}
c16178
 
c16178
 	/* Finally, we can free the resources.
c16178
diff --git a/runtime/stream.c b/runtime/stream.c
c16178
index 2d494c612..5b52591ef 100644
c16178
--- a/runtime/stream.c
c16178
+++ b/runtime/stream.c
c16178
@@ -360,8 +360,8 @@ CheckFileChange(strm_t *pThis)
c16178
 	CHKiRet(strmSetCurrFName(pThis));
c16178
 	if(stat((char*) pThis->pszCurrFName, &statName) == -1)
c16178
 		ABORT_FINALIZE(RS_RET_IO_ERROR);
c16178
-	DBGPRINTF("stream/after deserialize checking for file change on '%s', "
c16178
-		"inode %u/%u, size/currOffs %llu/%llu\n",
c16178
+	DBGPRINTF("CheckFileChange: stream/after deserialize checking for file change "
c16178
+		"on '%s', inode %u/%u, size/currOffs %llu/%llu\n",
c16178
 		pThis->pszCurrFName, (unsigned) pThis->inode,
c16178
 		(unsigned) statName.st_ino,
c16178
 		(long long unsigned) statName.st_size,
c16178
@@ -574,8 +574,8 @@ strmNextFile(strm_t *pThis)
c16178
  * circumstances). So starting as of now, we only check the inode number and
c16178
  * a file change is detected only if the inode changes. -- rgerhards, 2011-01-10
c16178
  */
c16178
-static rsRetVal
c16178
-strmHandleEOFMonitor(strm_t *pThis)
c16178
+static rsRetVal ATTR_NONNULL()
c16178
+strmHandleEOFMonitor(strm_t *const pThis)
c16178
 {
c16178
 	DEFiRet;
c16178
 	struct stat statName;
c16178
@@ -611,8 +611,8 @@ strmHandleEOFMonitor(strm_t *pThis)
c16178
  * try to open the next one.
c16178
  * rgerhards, 2008-02-13
c16178
  */
c16178
-static rsRetVal
c16178
-strmHandleEOF(strm_t *pThis)
c16178
+static rsRetVal ATTR_NONNULL()
c16178
+strmHandleEOF(strm_t *const pThis)
c16178
 {
c16178
 	DEFiRet;
c16178
 
c16178
@@ -629,7 +629,13 @@ strmHandleEOF(strm_t *pThis)
c16178
 			CHKiRet(strmNextFile(pThis));
c16178
 			break;
c16178
 		case STREAMTYPE_FILE_MONITOR:
c16178
-			CHKiRet(strmHandleEOFMonitor(pThis));
c16178
+			DBGOPRINT((obj_t*) pThis, "file '%s' (%d) EOF, rotationCheck %d\n",
c16178
+				pThis->pszCurrFName, pThis->fd, pThis->rotationCheck);
c16178
+			if(pThis->rotationCheck == STRM_ROTATION_DO_CHECK) {
c16178
+				CHKiRet(strmHandleEOFMonitor(pThis));
c16178
+			} else {
c16178
+				ABORT_FINALIZE(RS_RET_EOF);
c16178
+			}
c16178
 			break;
c16178
 	}
c16178
 
c16178
@@ -687,9 +693,6 @@ checkTruncation(strm_t *const pThis)
c16178
 		iRet = rereadTruncated(pThis, "cannot seek backward to begin of last block");
c16178
 		FINALIZE;
c16178
 	}
c16178
-dbgprintf("checkTruncation seek backwrds: %d\n", ret);
c16178
-currpos = lseek64(pThis->fd, 0, SEEK_CUR);
c16178
-dbgprintf("checkTruncation in actual processing, NEW currpos %d, backseek is %d\n", (int)currpos, (int) backseek);
c16178
 
c16178
 	const ssize_t lenRead = read(pThis->fd, pThis->pIOBuf_truncation, pThis->iBufPtrMax);
c16178
 	dbgprintf("checkTruncation proof-read: %d bytes\n", (int) lenRead);
c16178
@@ -861,7 +864,7 @@ static rsRetVal strmUnreadChar(strm_t *pThis, uchar c)
c16178
  * a line, but following lines that are indented are part of the same log entry
c16178
  */
c16178
 static rsRetVal
c16178
-strmReadLine(strm_t *pThis, cstr_t **ppCStr, uint8_t mode, sbool bEscapeLF,
c16178
+strmReadLine(strm_t *const pThis, cstr_t **ppCStr, uint8_t mode, sbool bEscapeLF,
c16178
 	uint32_t trimLineOverBytes, int64 *const strtOffs)
c16178
 {
c16178
 	uchar c;
c16178
@@ -2147,14 +2150,25 @@ DEFpropSetMeth(strm, cryprov, cryprov_if_t*)
c16178
 DEFpropSetMeth(strm, cryprovData, void*)
c16178
 
c16178
 /* sets timeout in seconds */
c16178
-void
c16178
+void ATTR_NONNULL()
c16178
 strmSetReadTimeout(strm_t *const __restrict__ pThis, const int val)
c16178
 {
c16178
+	ISOBJ_TYPE_assert(pThis, strm);
c16178
 	pThis->readTimeout = val;
c16178
 }
c16178
 
c16178
-static rsRetVal strmSetbDeleteOnClose(strm_t *pThis, int val)
c16178
+void ATTR_NONNULL()
c16178
+strmSet_checkRotation(strm_t *const pThis, const int val) {
c16178
+	ISOBJ_TYPE_assert(pThis, strm);
c16178
+	assert(val == STRM_ROTATION_DO_CHECK || val == STRM_ROTATION_DO_NOT_CHECK);
c16178
+	pThis->rotationCheck = val;
c16178
+}
c16178
+
c16178
+
c16178
+static rsRetVal ATTR_NONNULL()
c16178
+strmSetbDeleteOnClose(strm_t *const pThis, const int val)
c16178
 {
c16178
+	ISOBJ_TYPE_assert(pThis, strm);
c16178
 	pThis->bDeleteOnClose = val;
c16178
 	if(pThis->cryprov != NULL) {
c16178
 		pThis->cryprov->SetDeleteOnClose(pThis->cryprovFileData, pThis->bDeleteOnClose);
c16178
@@ -2162,15 +2176,19 @@ static rsRetVal strmSetbDeleteOnClose(strm_t *pThis, int val)
c16178
 	return RS_RET_OK;
c16178
 }
c16178
 
c16178
-static rsRetVal strmSetiMaxFiles(strm_t *pThis, int iNewVal)
c16178
+static rsRetVal ATTR_NONNULL()
c16178
+strmSetiMaxFiles(strm_t *const pThis, const int iNewVal)
c16178
 {
c16178
+	ISOBJ_TYPE_assert(pThis, strm);
c16178
 	pThis->iMaxFiles = iNewVal;
c16178
 	pThis->iFileNumDigits = getNumberDigits(iNewVal);
c16178
 	return RS_RET_OK;
c16178
 }
c16178
 
c16178
-static rsRetVal strmSetFileNotFoundError(strm_t *pThis, int pFileNotFoundError)
c16178
+static rsRetVal ATTR_NONNULL()
c16178
+strmSetFileNotFoundError(strm_t *const pThis, const int pFileNotFoundError)
c16178
 {
c16178
+	ISOBJ_TYPE_assert(pThis, strm);
c16178
 	pThis->fileNotFoundError = pFileNotFoundError;
c16178
 	return RS_RET_OK;
c16178
 }
c16178
diff --git a/runtime/stream.h b/runtime/stream.h
c16178
index 7dc597ff5..e3d6c2372 100644
c16178
--- a/runtime/stream.h
c16178
+++ b/runtime/stream.h
c16178
@@ -124,6 +124,7 @@ typedef struct strm_s {
c16178
 	ino_t inode;	/* current inode for files being monitored (undefined else) */
c16178
 	uchar *pszCurrFName; /* name of current file (if open) */
c16178
 	uchar *pIOBuf;	/* the iobuffer currently in use to gather data */
c16178
+	char *pIOBuf_truncation; /* iobuffer used during trucation detection block re-reads */
c16178
 	size_t iBufPtrMax;	/* current max Ptr in Buffer (if partial read!) */
c16178
 	size_t iBufPtr;	/* pointer into current buffer */
c16178
 	int iUngetC;	/* char set via UngetChar() call or -1 if none set */
c16178
c16178
diff --git a/runtime/stream.h b/runtime/stream.h
c16178
index e3d6c2372..f6f48378a 100644
c16178
--- a/runtime/stream.h
c16178
+++ b/runtime/stream.h
c16178
@@ -91,6 +91,10 @@ typedef enum {				/* when extending, do NOT change existing modes! */
c16178
 	STREAMMODE_WRITE_APPEND = 4
c16178
 } strmMode_t;
c16178
 
c16178
+/* settings for stream rotation (applies not to all processing modes!) */
c16178
+#define	STRM_ROTATION_DO_CHECK		0
c16178
+#define	STRM_ROTATION_DO_NOT_CHECK	1
c16178
+
c16178
 #define STREAM_ASYNC_NUMBUFS 2 /* must be a power of 2 -- TODO: make configurable */
c16178
 /* The strm_t data structure */
c16178
 typedef struct strm_s {
c16178
@@ -114,6 +118,7 @@ typedef struct strm_s {
c16178
 	sbool bDisabled; /* should file no longer be written to? (currently set only if omfile file size limit fails) */
c16178
 	sbool bSync;	/* sync this file after every write? */
c16178
 	sbool bReopenOnTruncate;
c16178
+	int rotationCheck; /* rotation check mode */
c16178
 	size_t sIOBufSize;/* size of IO buffer */
c16178
 	uchar *pszDir; /* Directory */
c16178
 	int lenDir;
c16178
@@ -234,5 +239,6 @@ void strmSetReadTimeout(strm_t *const __restrict__ pThis, const int val);
c16178
 const uchar * ATTR_NONNULL() strmGetPrevLineSegment(strm_t *const pThis);
c16178
 const uchar * ATTR_NONNULL() strmGetPrevMsgSegment(strm_t *const pThis);
c16178
 int ATTR_NONNULL() strmGetPrevWasNL(const strm_t *const pThis);
c16178
+void ATTR_NONNULL() strmSet_checkRotation(strm_t *const pThis, const int val);
c16178
 
c16178
 #endif /* #ifndef STREAM_H_INCLUDED */