Blame SOURCES/rsyslog-8.24.0-rhbz1649250-imfile-rotation.patch

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