From b9d82003002c98370e4131a7e43c76afcd23306a Mon Sep 17 00:00:00 2001
From: Kamil Dudka <kdudka@redhat.com>
Date: Wed, 8 Feb 2017 15:39:47 +0100
Subject: [PATCH] do not treat failure of readState() as fatal

This also prevents an empty state file from being written when running
with the --debug option.

Closes #45
---
 logrotate.c | 68 +++++++++++++++++++++++++++++++++++--------------------------
 test/test   |  5 ++---
 2 files changed, 41 insertions(+), 32 deletions(-)

Index: logrotate-3.7.7/logrotate.c
===================================================================
--- logrotate-3.7.7.orig/logrotate.c
+++ logrotate-3.7.7/logrotate.c
@@ -194,23 +194,17 @@ static void unescape(char *arg)
 }
 
 #define HASH_SIZE_MIN 64
-static int allocateHash(void)
+static int allocateHash(unsigned int hs)
 {
-	struct logInfo *log;
-	unsigned int hs;
 	int i;
 
-	hs = 0;
-
-	for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next)
-		hs += log->numFiles;
-
-	hs *= 2;
-
 	/* Enforce some reasonable minimum hash size */
 	if (hs < HASH_SIZE_MIN)
 		hs = HASH_SIZE_MIN;
 
+	message(MESS_DEBUG, "Allocating hash table for state file, size %d entries\n",
+		hs);
+
 	states = calloc(hs, sizeof(struct logStates *));
 	if (states == NULL) {
 		message(MESS_ERROR, "could not allocate memory for "
@@ -1676,7 +1670,7 @@ static int readState(char *stateFilename
 {
     FILE *f;
     char buf[STATEFILE_BUFFER_SIZE];
-	char *filename;
+    char *filename;
     const char **argv;
     int argc;
     int year, month, day;
@@ -1686,27 +1680,50 @@ static int readState(char *stateFilename
     struct logState *st;
     time_t lr_time;
     struct stat f_stat;
+    int rc = 0;
+
+    message(MESS_DEBUG, "Reading state from file: %s\n", stateFilename);
 
     error = stat(stateFilename, &f_stat);
+    if (error) {
+	    /* treat non-statable file as an empty file */
+	    f_stat.st_size = 0;
+	    if (errno != ENOENT) {
+		    message(MESS_ERROR, "error stat()ing state file %s: %s\n",
+			    stateFilename, strerror(errno));
+
+		    /* do not return until the hash table is allocated */
+		    rc = 1;
+	    }
+    }
 
-    if ((error && errno == ENOENT) || (!error && f_stat.st_size == 0)) {
-	/* create the file before continuing to ensure we have write
-	   access to the file */
-	f = fopen(stateFilename, "w");
-	if (!f) {
-	    message(MESS_ERROR, "error creating state file %s: %s\n",
-		    stateFilename, strerror(errno));
-	    return 1;
-	}
-	fprintf(f, "logrotate state -- version 2\n");
-	fclose(f);
-	return 0;
-    } else if (error) {
-	message(MESS_ERROR, "error stat()ing state file %s: %s\n",
-		stateFilename, strerror(errno));
-	return 1;
+    if (!debug && (f_stat.st_size == 0)) {
+	    /* create the file before continuing to ensure we have write
+	       access to the file */
+	    f = fopen(stateFilename, "w");
+	    if (f) {
+		    fprintf(f, "logrotate state -- version 2\n");
+		    fclose(f);
+	    } else {
+		    message(MESS_ERROR, "error creating state file %s: %s\n",
+			    stateFilename, strerror(errno));
+
+		    /* do not return until the hash table is allocated */
+		    rc = 1;
+	    }
     }
 
+    /* Try to estimate how many state entries we have in the state file.
+     * We expect single entry to have around 80 characters (Of course this is
+     * just an estimation). During the testing I've found out that 200 entries
+     * per single hash entry gives good mem/performance ratio. */
+    if (allocateHash(f_stat.st_size / 80 / 200))
+	    return 1;
+
+    if (rc || (f_stat.st_size == 0))
+	    /* error already occurred, or we have no state file to read from */
+	    return rc;
+
     f = fopen(stateFilename, "r");
     if (!f) {
 	message(MESS_ERROR, "error opening state file %s: %s\n",
@@ -1757,7 +1774,7 @@ static int readState(char *stateFilename
 	}
 
 	/* Hack to hide earlier bug */
-	if ((year != 1900) && (year < 1996 || year > 2100)) {
+	if ((year != 1900) && (year < 1970 || year > 2100)) {
 	    message(MESS_ERROR,
 		    "bad year %d for file %s in state file %s\n", year,
 		    argv[0], stateFilename);
@@ -1790,8 +1807,10 @@ static int readState(char *stateFilename
 	filename = strdup(argv[0]);
 	unescape(filename);
 	
-	if ((st = findState(filename)) == NULL)
-		return 1;
+	if ((st = findState(filename)) == NULL) {
+                fclose(f);
+                return 1;
+        }
 
 	st->lastRotated.tm_mon = month;
 	st->lastRotated.tm_mday = day;
@@ -1885,15 +1904,12 @@ int main(int argc, const char **argv)
     poptFreeContext(optCon);
     nowSecs = time(NULL);
 
-	if (allocateHash() != 0)
-		return 1;
-
-	if (readState(stateFile))
-	exit(1);
+    if (readState(stateFile))
+        rc = 1;
 
     message(MESS_DEBUG, "\nHandling %d logs\n", numLogs);
 
-	for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next)
+    for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next)
 	rc |= rotateLogSet(log, force);
 
     if (!debug)
Index: logrotate-3.7.7/test/test
===================================================================
--- logrotate-3.7.7.orig/test/test
+++ logrotate-3.7.7/test/test
@@ -480,7 +480,7 @@ EOF
 
 cleanup 45
 
-# ------------------------------- Test 44 ------------------------------------
+# ------------------------------- Test 45 ------------------------------------
 # Test that prerotate and postrotate scripts are not called when sharedscripts
 # is defined and one rotation fails
 preptest test.log 45 1
@@ -501,6 +501,38 @@ test.log 0 zero
 scriptout 0
 EOF
 
+cleanup 46
+
+# ------------------------------- Test 46 ------------------------------------
+# the state file is truncated and obviously corrupt
+preptest test.log 46 1
+
+cat > state << EOF
+logrotate state -- version 1
+"$PWD/test.log" 2000-1-1
+"$PWD/test2.l
+EOF
+
+DATESTRING=$(/bin/date +%Y%m%d)
+$RLR test-config.46 2>error.log
+
+grep "error: bad line 3 in state file state" error.log >/dev/null
+if [ $? != 0 ]; then
+        echo "No error printed, but there should be one."
+	exit 3
+fi
+
+rm -f error.log
+
+checkoutput <<EOF
+test.log 0
+EOF
+
+if [ $SELINUX_TESTS = 1 ]; then
+        chcon --type=logrotate_tmp_t test.log
+else
+        echo "Skipping SELinux part of test 6"
+fi
 
 cleanup 52
 
Index: logrotate-3.7.7/test/test-config.46.in
===================================================================
--- /dev/null
+++ logrotate-3.7.7/test/test-config.46.in
@@ -0,0 +1,7 @@
+create
+
+&DIR&/test.log {
+        daily
+        rotate 999
+        dateext
+}
