--- /dev/null
+--- config.c
++++ config.c
+@@ -441,6 +441,14 @@
+ newlog->flags &= ~LOG_FLAG_IFEMPTY;
+
+ *endtag = oldchar, start = endtag;
++ } else if (!strcmp(start, "dateext")) {
++ newlog->flags |= LOG_FLAG_DATEEXT;
++
++ *endtag = oldchar, start = endtag;
++ } else if (!strcmp(start, "nodateext")) {
++ newlog->flags &= ~LOG_FLAG_DATEEXT;
++
++ *endtag = oldchar, start = endtag;
+ } else if (!strcmp(start, "noolddir")) {
+ newlog->oldDir = NULL;
+
+--- logrotate.8
++++ logrotate.8
+@@ -200,6 +200,11 @@
+ Log files are rotated every day.
+
+ .TP
++\fBdateext\fR
++Archive old versions of log files adding a daily extension like YYYYMMDD
++instead of simply adding a number.
++
++.TP
+ \fBdelaycompress\fR
+ Postpone compression of the previous log file to the next rotation cycle.
+ This has only effect when used in combination with \fBcompress\fR.
+--- logrotate.c
++++ logrotate.c
+@@ -10,6 +10,7 @@
+ #include <sys/stat.h>
+ #include <time.h>
+ #include <unistd.h>
++#include <glob.h>
+
+ #ifdef WITH_SELINUX
+ #include <selinux/selinux.h>
+@@ -21,6 +22,10 @@
+ #include "log.h"
+ #include "logrotate.h"
+
++#if !defined(GLOB_ABORTED) && defined(GLOB_ABEND)
++#define GLOB_ABORTED GLOB_ABEND
++#endif
++
+ typedef struct {
+ char * fn;
+ struct tm lastRotated; /* only tm.mon, tm_mday, tm_year are good! */
+@@ -64,6 +69,14 @@
+ return newstr;
+ }
+
++static int globerr(const char * pathname, int theerr) {
++ message(MESS_ERROR, "error accessing %s: %s\n", pathname,
++ strerror(theerr));
++
++ /* We want the glob operation to continue, so return 0 */
++ return 1;
++}
++
+ static logState * findState(const char * fn, logState ** statesPtr,
+ int * numStatesPtr) {
+ int i;
+@@ -72,9 +85,11 @@
+ struct tm now = *localtime(&nowSecs);
+ time_t lr_time;
+
++ /* find the filename fn in the statesPtr list */
+ for (i = 0; i < numStates; i++)
+ if (!strcmp(fn, states[i].fn)) break;
+
++ /* not in statesPtr list, so add new entry */
+ if (i == numStates) {
+ i = numStates++;
+ states = realloc(states, sizeof(*states) * numStates);
+@@ -465,6 +480,9 @@
+ char * baseName;
+ char * dirName;
+ char * firstRotated;
++ char * glob_pattern;
++ glob_t globResult;
++ int rc;
+ size_t alloc_size;
+ int rotateCount = log->rotateCount ? log->rotateCount : 1;
+ int logStart = (log->logStart == -1) ? 1 : log->logStart;
+@@ -501,7 +519,7 @@
+
+ alloc_size = strlen(dirName) + strlen(baseName) +
+ strlen(log->files[logNum]) + strlen(fileext) +
+- strlen(compext) + 10;
++ strlen(compext) + 18;
+
+ oldName = alloca(alloc_size);
+ newName = alloca(alloc_size);
+@@ -523,18 +541,95 @@
+ /* First compress the previous log when necessary */
+ if (log->flags & LOG_FLAG_COMPRESS &&
+ log->flags & LOG_FLAG_DELAYCOMPRESS) {
+- sprintf(oldName, "%s/%s.%d%s", dirName, baseName, logStart, fileext);
+- hasErrors = compress_log(log, oldName, "previous", errorFile);
++ if(log->flags & LOG_FLAG_DATEEXT) {
++ /* glob for uncompressed files with our pattern */
++ glob_pattern = malloc(strlen(dirName) + strlen(baseName)
++ + strlen(fileext) + 44 );
++ sprintf(glob_pattern,
++ "%s/%s-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]%s",
++ dirName, baseName, fileext);
++ rc = glob(glob_pattern, 0, globerr, &globResult);
++ if (!rc && globResult.gl_pathc > 0) {
++ for (i = 0; i < globResult.gl_pathc && !hasErrors; i++) {
++ sprintf(oldName,"%s",(globResult.gl_pathv)[i]);
++ hasErrors = compress_log(log, oldName, "previous", errorFile);
++ }
++ } else {
++ message (MESS_DEBUG, "glob finding logs to compress failed\n");
++ /* fallback to old behaviour */
++ sprintf(oldName, "%s/%s.%d%s", dirName, baseName, logStart, fileext);
++ }
++ globfree(&globResult);
++ free(glob_pattern);
++ } else {
++ sprintf(oldName, "%s/%s.%d%s", dirName, baseName, logStart, fileext);
++ hasErrors = compress_log(log, oldName, "previous", errorFile);
++ }
+ }
+-
++ firstRotated = alloca(strlen(dirName) + strlen(baseName) +
++ strlen(fileext) + strlen(compext) + 30);
++
++ if(log->flags & LOG_FLAG_DATEEXT) {
++ /* glob for compressed files with our pattern
++ * and compress ext */
++ glob_pattern = malloc(strlen(dirName)+strlen(baseName)
++ +strlen(fileext)+strlen(compext)+44);
++ sprintf(glob_pattern,
++ "%s/%s-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]%s%s",
++ dirName, baseName, fileext, compext);
++ rc = glob(glob_pattern, 0, globerr, &globResult);
++ if (!rc) {
++ /* search for files to drop, if we find one remember it,
++ * if we find another one mail and remove the first and
++ * remember the second and so on */
++ struct stat fst_buf;
++ int mail_out = -1;
++ /* remove the first (n - rotateCount) matches
++ * no real rotation needed, since the files have
++ * the date in their name */
++ for (i = 0; i < globResult.gl_pathc; i++) {
++ if( !stat((globResult.gl_pathv)[i],&fst_buf) ) {
++ if (i < ((int)globResult.gl_pathc - rotateCount - 1)) {
++ if ( mail_out != -1 ) {
++ if (!hasErrors && log->logAddress)
++ hasErrors = mail_old_log(log, logNum, NULL,
++ (globResult.gl_pathv)[mail_out],
++ errorFile, compext);
++ if (!hasErrors)
++ hasErrors = remove_old_log(log,
++ (globResult.gl_pathv)[mail_out],
++ errorFile);
++ }
++ mail_out = i;
++ }
++ }
++ }
++ if ( mail_out != -1 ) {
++ /* oldName is oldest Backup found (for unlink later) */
++ sprintf(oldName, "%s", (globResult.gl_pathv)[mail_out]);
++ strcpy(disposeName, oldName);
++ } else
++ disposeName = NULL;
++ /* firstRotated is most recently created/compressed rotated log */
++ sprintf(firstRotated, "%s/%s-%04d%02d%02d%s%s",
++ dirName, baseName, now.tm_year+1900,
++ now.tm_mon+1, now.tm_mday, fileext, compext);
++ } else {
++ message (MESS_DEBUG, "glob finding old rotated logs failed\n");
++ disposeName = NULL;
++ }
++ /* firstRotated is most recently created/compressed rotated log */
++ /* copy these two out with strdup */
++ globfree(&globResult);
++ free(glob_pattern);
++ } else {
++
+ sprintf(oldName, "%s/%s.%d%s%s", dirName, baseName,
+ logStart + rotateCount, fileext, compext);
+ strcpy(newName, oldName);
+
+ strcpy(disposeName, oldName);
+
+- firstRotated = alloca(strlen(dirName) + strlen(baseName) +
+- strlen(fileext) + strlen(compext) + 30);
+ sprintf(firstRotated, "%s/%s.%d%s%s", dirName, baseName,
+ logStart, fileext, compext);
+
+@@ -584,11 +679,27 @@
+ }
+ }
+ }
++ } /* !LOG_FLAG_DATEEXT */
+
+ finalName = oldName;
++
+
+- /* note: the gzip extension is *not* used here! */
+- sprintf(finalName, "%s/%s.%d%s", dirName, baseName, logStart, fileext);
++ if(log->flags & LOG_FLAG_DATEEXT) {
++ char * destFile = alloca(strlen(dirName) + strlen(baseName) +
++ strlen(fileext) + strlen(compext) + 30);
++ struct stat fst_buf;
++ sprintf(finalName, "%s/%s-%04d%02d%02d%s",
++ dirName, baseName, now.tm_year+1900,
++ now.tm_mon+1, now.tm_mday, fileext);
++ sprintf(destFile, "%s%s", finalName, compext);
++ if(!stat(destFile,&fst_buf)) {
++ message (MESS_DEBUG, "destination %s already exists, skipping rotation\n", firstRotated);
++ hasErrors = 1;
++ }
++ } else {
++ /* note: the gzip extension is *not* used here! */
++ sprintf(finalName, "%s/%s.%d%s", dirName, baseName, logStart, fileext);
++ }
+
+ /* if the last rotation doesn't exist, that's okay */
+ if (!debug && access(disposeName, F_OK)) {
+@@ -597,9 +708,6 @@
+ disposeName = NULL;
+ }
+
+- free(dirName);
+- free(baseName);
+-
+ if (!hasErrors) {
+ if (log->pre && !(log->flags & LOG_FLAG_SHAREDSCRIPTS)) {
+ message(MESS_DEBUG, "running prerotate script\n");
+@@ -716,6 +824,8 @@
+ }
+ }
+ #endif
++ free(dirName);
++ free(baseName);
+ return hasErrors;
+ }
+
+--- logrotate.h
++++ logrotate.h
+@@ -15,6 +15,7 @@
+ #define LOG_FLAG_MAILFIRST (1 << 6)
+ #define LOG_FLAG_SHAREDSCRIPTS (1 << 7)
+ #define LOG_FLAG_COPY (1 << 8)
++#define LOG_FLAG_DATEEXT (1 << 9)
+
+ #define NO_FORCE_ROTATE 0
+ #define FORCE_ROTATE 1
--- /dev/null
+--- config.c
++++ config.c
+@@ -608,6 +608,21 @@
+ }
+ *endtag = oldchar, start = endtag;
+ }
++ } else if (!strcmp(start, "maxage")) {
++ *endtag = oldchar, start = endtag;
++
++ if (!isolateValue(configFile, lineNum, "maxage count", &start,
++ &endtag)) {
++ oldchar = *endtag, *endtag = '\0';
++
++ newlog->rotateAge = strtoul(start, &chptr, 0);
++ if (*chptr || newlog->rotateAge < 0) {
++ message(MESS_ERROR, "%s:%d bad maximum age '%s'\n",
++ configFile, lineNum, start);
++ return 1;
++ }
++ *endtag = oldchar, start = endtag;
++ }
+ } else if (!strcmp(start, "errors")) {
+ message(MESS_DEBUG, "%s: %d: the errors directive is deprecated and no longer used.\n",
+ configFile, lineNum);
+--- logrotate.8
++++ logrotate.8
+@@ -251,6 +251,12 @@
+ instead of the just-rotated file (this is the default).
+
+ .TP
++\fBmaxage\fR \fIcount\fR
++Remove rotated logs older than <count> days. The age is only checked
++if the logfile is to be rotated. The files are mailed to the
++configured address if \fBmaillast\fR and \fBmail\fR are configured.
++
++.TP
+ \fBmissingok\fR
+ If the log file is missing, go on to the next one without issuing an error
+ message. See also \fBnomissingok\fR.
+--- logrotate.c
++++ logrotate.c
+@@ -589,7 +589,10 @@
+ * the date in their name */
+ for (i = 0; i < globResult.gl_pathc; i++) {
+ if( !stat((globResult.gl_pathv)[i],&fst_buf) ) {
+- if (i < ((int)globResult.gl_pathc - rotateCount - 1)) {
++ if ((i < ((int)globResult.gl_pathc - rotateCount - 1))
++ || ((log->rotateAge > 0)
++ && (((nowSecs - fst_buf.st_mtime)/60/60/24)
++ > log->rotateAge))) {
+ if ( mail_out != -1 ) {
+ if (!hasErrors && log->logAddress)
+ hasErrors = mail_old_log(log, logNum, NULL,
+@@ -623,6 +626,25 @@
+ globfree(&globResult);
+ free(glob_pattern);
+ } else {
++ if ( log->rotateAge ) {
++ struct stat fst_buf;
++ for (i=1; i <= rotateCount; i++) {
++ sprintf(oldName, "%s/%s.%d%s%s", dirName, baseName,
++ rotateCount + 1, fileext, compext);
++ if(!stat(oldName,&fst_buf)
++ && (((nowSecs - fst_buf.st_mtime)/60/60/24)
++ > log->rotateAge)) {
++ if (!hasErrors && log->logAddress)
++ hasErrors = mail_old_log(log, logNum, NULL,
++ (globResult.gl_pathv)[i],
++ errorFile, compext);
++ if (!hasErrors)
++ hasErrors = remove_old_log(log,
++ (globResult.gl_pathv)[i],
++ errorFile);
++ }
++ }
++ }
+
+ sprintf(oldName, "%s/%s.%d%s%s", dirName, baseName,
+ logStart + rotateCount, fileext, compext);
+@@ -1110,8 +1132,10 @@
+ }
+
+ int main(int argc, const char ** argv) {
+- logInfo defConfig = { NULL, NULL, 0, NULL, ROT_SIZE,
+- /* threshHold */ 1024 * 1024, 0,
++ logInfo defConfig = { NULL, NULL, 0, NULL, ROT_SIZE,
++ /* threshHold */ 1024 * 1024,
++ /* rotateCount */ 0,
++ /* rotateAge */ 0,
+ /* log start */ -1,
+ /* pre, post */ NULL, NULL,
+ /* first, last */ NULL, NULL,
+--- logrotate.h
++++ logrotate.h
+@@ -28,6 +28,7 @@
+ enum { ROT_DAYS, ROT_WEEKLY, ROT_MONTHLY, ROT_SIZE, ROT_FORCE } criterium;
+ unsigned int threshhold;
+ int rotateCount;
++ int rotateAge;
+ int logStart;
+ char * pre, * post, * first, * last;
+ char * logAddress;
--- /dev/null
+--- logrotate.c
++++ logrotate.c
+@@ -260,6 +260,111 @@
+ return 0;
+ }
+
++int mail_old_log(logInfo * log, int logNum, char * firstRotated, char * disposeName, FILE * errorFile, char * compext) {
++ int hasErrors = 0;
++ char * command;
++ char * mailFilename;
++ int mailfile_is_compressed;
++
++ if (log->flags & LOG_FLAG_MAILFIRST)
++ mailFilename = firstRotated;
++ else
++ mailFilename = disposeName;
++
++ mailfile_is_compressed = (log->flags & LOG_FLAG_COMPRESS)
++ && ((log->flags & (LOG_FLAG_DELAYCOMPRESS|LOG_FLAG_MAILFIRST))
++ != (LOG_FLAG_DELAYCOMPRESS|LOG_FLAG_MAILFIRST));
++
++ if (!mailfile_is_compressed
++ && !strcmp(compext, mailFilename+strlen(mailFilename)-strlen(compext)))
++ {
++ char *ctmp;
++ ctmp = alloca(strlen(mailFilename));
++ strcpy(ctmp, mailFilename);
++ ctmp[strlen(ctmp)-strlen(compext)] = '\0';
++ mailFilename = ctmp;
++ }
++
++ if (mailFilename) {
++ char * escapedMailFilename = escapeSingleQuotes(mailFilename);
++ if (mailfile_is_compressed) {
++ char * escapedFN = escapeSingleQuotes(log->files[logNum]);
++ command = alloca(strlen(log->uncompress_prog) +
++ strlen(escapedMailFilename) +
++ strlen(mailCommand) +
++ strlen(escapedFN) +
++ strlen(log->logAddress) + 20);
++ sprintf(command, "%s < '%s' | %s '%s' %s",
++ log->uncompress_prog, escapedMailFilename,
++ mailCommand, escapedFN, log->logAddress);
++ free(escapedFN);
++ } else {
++ command = alloca(strlen(mailCommand) +
++ strlen(escapedMailFilename) +
++ strlen(log->logAddress) +
++ strlen(escapedMailFilename) + 20);
++ sprintf(command, "%s '%s' %s < '%s'", mailCommand,
++ escapedMailFilename, log->logAddress, escapedMailFilename);
++ }
++ free(escapedMailFilename);
++
++ message(MESS_DEBUG, "executing: \"%s\"\n", command);
++
++ if (!debug && system(command)) {
++ /* sprintf(newName, "%s.%d", log->files[logNum], (unsigned int)getpid()); */
++ fprintf(errorFile, "Failed to mail %s to %s!\n",
++ mailFilename, log->logAddress);
++
++ hasErrors = 1;
++ }
++ }
++ return hasErrors;
++}
++
++int remove_old_log(logInfo * log, char * disposeName, FILE * errorFile)
++{
++ int hasErrors = 0;
++ message(MESS_DEBUG, "removing old log %s\n", disposeName);
++
++ if (!debug && unlink(disposeName)) {
++ fprintf(errorFile, "Failed to remove old log %s: %s\n",
++ disposeName, strerror(errno));
++ hasErrors = 1;
++ }
++ return hasErrors;
++}
++
++int compress_log(logInfo * log, char * fileName, const char * action, FILE * errorFile)
++{
++ int hasErrors = 0;
++ struct stat sbprev;
++
++ if (stat(fileName, &sbprev)) {
++ message(MESS_DEBUG, "%s log %s does not exist\n",
++ action, fileName);
++ } else {
++ char * escapedName;
++ char * command;
++
++ escapedName = escapeSingleQuotes(fileName);
++ command = alloca(strlen(log->compress_prog) +
++ strlen(log->compress_options) +
++ strlen(escapedName) + 21);
++ sprintf(command, "%s %s '%s'", log->compress_prog,
++ log->compress_options, escapedName);
++ free(escapedName);
++ message(MESS_DEBUG, "compressing previous log with: %s\n",
++ command);
++ if (!debug && system(command)) {
++ fprintf(errorFile,
++ "failed to compress %s log %s\n", action, fileName);
++ hasErrors = 1;
++ }
++ }
++
++ return hasErrors;
++}
++
+ int findNeedRotating(logInfo * log, int logNum, logState ** statesPtr,
+ int * numStatesPtr) {
+ struct stat sb;
+@@ -418,31 +523,8 @@
+ /* First compress the previous log when necessary */
+ if (log->flags & LOG_FLAG_COMPRESS &&
+ log->flags & LOG_FLAG_DELAYCOMPRESS) {
+- struct stat sbprev;
+-
+- sprintf(oldName, "%s/%s.%d%s", dirName, baseName, logStart, fileext);
+- if (stat(oldName, &sbprev)) {
+- message(MESS_DEBUG, "previous log %s does not exist\n",
+- oldName);
+- } else {
+- char * escapedName;
+- char * command;
+-
+- escapedName = escapeSingleQuotes(oldName);
+- command = alloca(strlen(log->compress_prog) +
+- strlen(log->compress_options) +
+- strlen(escapedName) + 10);
+- sprintf(command, "%s %s '%s'", log->compress_prog,
+- log->compress_options, escapedName);
+- free(escapedName);
+- message(MESS_DEBUG, "compressing previous log with: %s\n",
+- command);
+- if (!debug && system(command)) {
+- fprintf(errorFile,
+- "failed to compress previous log %s\n", oldName);
+- hasErrors = 1;
+- }
+- }
++ sprintf(oldName, "%s/%s.%d%s", dirName, baseName, logStart, fileext);
++ hasErrors = compress_log(log, oldName, "previous", errorFile);
+ }
+
+ sprintf(oldName, "%s/%s.%d%s%s", dirName, baseName,
+@@ -613,92 +695,15 @@
+ if (!hasErrors &&
+ (log->flags & LOG_FLAG_COMPRESS) &&
+ !(log->flags & LOG_FLAG_DELAYCOMPRESS)) {
+- char * escapedName;
+- char * command;
+-
+- escapedName = escapeSingleQuotes(finalName);
+- command = alloca(strlen(log->compress_prog) +
+- strlen(log->compress_options) +
+- strlen(escapedName) + 10);
+- sprintf(command, "%s %s '%s'", log->compress_prog,
+- log->compress_options, escapedName);
+- free(escapedName);
+- message(MESS_DEBUG, "compressing new log with: %s\n", command);
+- if (!debug && system(command)) {
+- fprintf(errorFile, "failed to compress log %s\n",
+- finalName);
+- hasErrors = 1;
+- }
++ hasErrors = compress_log(log, finalName, "new", errorFile);
+ }
+
+ if (!hasErrors && log->logAddress) {
+- char * command;
+- char * mailFilename;
+- int mailfile_is_compressed;
+-
+- if (log->flags & LOG_FLAG_MAILFIRST)
+- mailFilename = firstRotated;
+- else
+- mailFilename = disposeName;
+-
+- mailfile_is_compressed = (log->flags & LOG_FLAG_COMPRESS)
+- && ((log->flags & (LOG_FLAG_DELAYCOMPRESS|LOG_FLAG_MAILFIRST))
+- != (LOG_FLAG_DELAYCOMPRESS|LOG_FLAG_MAILFIRST));
+-
+- if (!mailfile_is_compressed
+- && !strcmp(compext,
+- mailFilename+strlen(mailFilename)-strlen(compext)))
+- {
+- char *ctmp;
+- ctmp = alloca(strlen(mailFilename));
+- strcpy(ctmp, mailFilename);
+- ctmp[strlen(ctmp)-strlen(compext)] = '\0';
+- mailFilename = ctmp;
+- }
+-
+- if (mailFilename) {
+- char * escapedMailFilename = escapeSingleQuotes(mailFilename);
+- if (mailfile_is_compressed) {
+- char * escapedFN = escapeSingleQuotes(log->files[logNum]);
+- command = alloca(strlen(log->uncompress_prog) +
+- strlen(escapedMailFilename) +
+- strlen(mailCommand) +
+- strlen(escapedFN) +
+- strlen(log->logAddress) + 20);
+- sprintf(command, "%s < '%s' | %s '%s' %s",
+- log->uncompress_prog, escapedMailFilename,
+- mailCommand, escapedFN, log->logAddress);
+- free(escapedFN);
+- } else {
+- command = alloca(strlen(mailCommand) +
+- strlen(escapedMailFilename) +
+- strlen(log->logAddress) +
+- strlen(escapedMailFilename) + 20);
+- sprintf(command, "%s '%s' %s < '%s'", mailCommand,
+- escapedMailFilename, log->logAddress, escapedMailFilename);
+- }
+- free(escapedMailFilename);
+-
+- message(MESS_DEBUG, "executing: \"%s\"\n", command);
+-
+- if (!debug && system(command)) {
+- sprintf(newName, "%s.%d", log->files[logNum], (unsigned int)getpid());
+- fprintf(errorFile, "Failed to mail %s to %s!\n",
+- mailFilename, log->logAddress);
+-
+- hasErrors = 1;
+- }
+- }
++ hasErrors = mail_old_log(log, logNum, firstRotated, disposeName, errorFile, compext);
+ }
+
+ if (!hasErrors && disposeName) {
+- message(MESS_DEBUG, "removing old log %s\n", disposeName);
+-
+- if (!debug && unlink(disposeName)) {
+- fprintf(errorFile, "Failed to remove old log %s: %s\n",
+- disposeName, strerror(errno));
+- hasErrors = 1;
+- }
++ hasErrors = remove_old_log(log, disposeName, errorFile);
+ }
+ }
+