Blob Blame History Raw
diff --git a/logrotate.c b/logrotate.c
index 9faf341..06b7100 100644
--- a/logrotate.c
+++ b/logrotate.c
@@ -62,11 +62,12 @@ extern int asprintf(char **str, const char *fmt, ...);
 #endif
 
 struct logState {
-    char *fn;
-    struct tm lastRotated;	/* only tm_hour, tm_mday, tm_mon, tm_year are good! */
-    struct stat sb;
-    int doRotate;
-    LIST_ENTRY(logState) list;
+	char *fn;
+	struct tm lastRotated;	/* only tm_hour, tm_mday, tm_mon, tm_year are good! */
+	struct stat sb;
+	int doRotate;
+	int isUsed;	/* True if there is real log file in system for this state. */
+	LIST_ENTRY(logState) list;
 };
 
 struct logNames {
@@ -204,23 +205,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 %lu B\n",
+			hs * (sizeof(struct logStates *) + sizeof(struct logState) ) );
+
 	states = calloc(hs, sizeof(struct logStates *));
 	if (states == NULL) {
 		message(MESS_ERROR, "could not allocate memory for "
@@ -271,6 +266,7 @@ static struct logState *newState(const char *fn)
 	}
 
 	new->doRotate = 0;
+	new->isUsed = 0;
 
 	memset(&new->lastRotated, 0, sizeof(new->lastRotated));
 	new->lastRotated.tm_hour = now.tm_hour;
@@ -850,9 +846,10 @@ int findNeedRotating(struct logInfo *log, int logNum, int force)
 	return 1;
     }
 
-    state = findState(log->files[logNum]);
-    state->doRotate = 0;
-    state->sb = sb;
+	state = findState(log->files[logNum]);
+	state->doRotate = 0;
+	state->sb = sb;
+	state->isUsed = 1;
 
 	if ((sb.st_mode & S_IFMT) == S_IFLNK) {
 	    message(MESS_DEBUG, "  log %s is symbolic link. Rotation of symbolic"
@@ -1820,6 +1817,8 @@ static int writeState(char *stateFilename)
 	int fdsave;
 	struct stat sb;
 	char *tmpFilename = NULL;
+	struct tm now = *localtime(&nowSecs);
+	time_t now_time, last_time;
 
 	tmpFilename = malloc(strlen(stateFilename) + 5 );
 	if (tmpFilename == NULL) {
@@ -1924,9 +1923,22 @@ static int writeState(char *stateFilename)
 	if (bytes < 0)
 		error = bytes;
 
+#define SECONDS_IN_YEAR 31556926
+
 	for (i = 0; i < hashSize && error == 0; i++) {
 		for (p = states[i]->head.lh_first; p != NULL && error == 0;
 				p = p->list.le_next) {
+
+			/* Skip states which are not used for more than a year. */
+			now_time = mktime(&now);
+			last_time = mktime(&p->lastRotated);
+			if (!p->isUsed && difftime(now_time, last_time) > SECONDS_IN_YEAR) {
+				message(MESS_DEBUG, "Removing %s from state file, "
+					"because it does not exist and has not been rotated for one year\n",
+					p->fn);
+				continue;
+			}
+
 			error = fputc('"', f) == EOF;
 			for (chptr = p->fn; *chptr && error == 0; chptr++) {
 				switch (*chptr) {
@@ -2010,23 +2022,27 @@ static int readState(char *stateFilename)
 
     error = stat(stateFilename, &f_stat);
 
-    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;
+	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);
+
+		if (allocateHash(64) != 0)
+			return 1;
+
+		return 0;
+	} else if (error) {
+		message(MESS_ERROR, "error stat()ing 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;
-    }
 
     f = fopen(stateFilename, "r");
     if (!f) {
@@ -2050,6 +2066,13 @@ static int readState(char *stateFilename)
 	return 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) != 0)
+		return 1;
+
     line++;
 
     while (fgets(buf, sizeof(buf) - 1, f)) {
@@ -2244,9 +2267,6 @@ int main(int argc, const char **argv)
     poptFreeContext(optCon);
     nowSecs = time(NULL);
 
-	if (allocateHash() != 0)
-		return 1;
-
 	if (readState(stateFile))
 		exit(1);
 
diff --git a/test/test b/test/test
index f7f3cf4..4048197 100755
--- a/test/test
+++ b/test/test
@@ -1460,4 +1460,61 @@ EOF
 checkmail test.log-$DATESTRING zero
 
 
+cleanup 67
+
+# ------------------------------- Test 67 ------------------------------------
+# firstaction and lastaction scripts should be called if no file is rotated
+preptest test.log 67 1 0
+
+DATESTRING=$(/bin/date +%Y%m%d)
+TODAY=$(/bin/date "+%Y-%m-%d" 2>/dev/null)
+
+echo removed > "test.log$TODAY"
+
+$RLR test-config.67 --force
+
+cat scriptout|grep firstaction >/dev/null
+if [ $? != 0 ]; then
+	echo "scriptout should contain 'firstaction'"
+	exit 3
+fi
+
+cat scriptout|grep lastaction >/dev/null
+if [ $? != 0 ]; then
+	echo "scriptout should contain 'lastaction'"
+	exit 3
+fi
+
+cleanup 68
+
+# ------------------------------- Test 68 ------------------------------------
+# Old state file entries should be removed when not used. Logrotate should
+# not freeze on big state file.
+preptest test.log 68 1 0
+
+cat > state << EOF
+logrotate state -- version 1
+"$PWD/test.log" 2000-1-1
+EOF
+
+for i in {1..200000}
+do
+   echo "\"$PWD/removed.log$i\" 2000-1-1" >> state
+done
+
+
+$RLR test-config.68 --force
+
+cat state|grep test.log >/dev/null
+if [ $? != 0 ]; then
+	echo "state file should contain 'test.log'"
+	exit 3
+fi
+
+cat state|grep removed.log >/dev/null
+if [ $? == 0 ]; then
+	echo "state file should not contain 'removed.log'"
+	exit 3
+fi
+
 cleanup
diff --git a/test/test-config.67.in b/test/test-config.67.in
new file mode 100644
index 0000000..69b9fff
--- /dev/null
+++ b/test/test-config.67.in
@@ -0,0 +1,16 @@
+create
+
+&DIR&/test.log {
+    daily
+    dateext
+    dateformat %Y-%m-%d
+    rotate 1
+
+	firstaction
+		echo "firstaction" > scriptout
+	endscript
+
+	lastaction
+		echo "lastaction" >> scriptout
+	endscript
+}
diff --git a/test/test-config.68.in b/test/test-config.68.in
new file mode 100644
index 0000000..e8e1c79
--- /dev/null
+++ b/test/test-config.68.in
@@ -0,0 +1,6 @@
+create
+
+&DIR&/test.log {
+    daily
+    rotate 1
+}