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

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