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 +}