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

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