diff --git a/logrotate-3.7.9-acl.patch b/logrotate-3.7.9-acl.patch new file mode 100644 index 0000000..63743a8 --- /dev/null +++ b/logrotate-3.7.9-acl.patch @@ -0,0 +1,161 @@ +Index: /trunk/logrotate.c +=================================================================== +--- /trunk/logrotate.c (revision 296) ++++ /trunk/logrotate.c (revision 299) +@@ -33,4 +33,9 @@ + #endif + ++#ifdef WITH_ACL ++#include "sys/acl.h" ++static acl_t prev_acl = NULL; ++#endif ++ + #include "basenames.h" + #include "log.h" +@@ -317,4 +322,29 @@ + return 1; + } ++ ++#ifdef WITH_ACL ++ if ((prev_acl = acl_get_fd(inFile)) == NULL) { ++ if (errno != ENOTSUP) { ++ message(MESS_ERROR, "getting file ACL %s: %s\n", ++ name, strerror(errno)); ++ close(inFile); ++ close(outFile); ++ return 1; ++ } ++ } ++ if (prev_acl) { ++ if (acl_set_fd(outFile, prev_acl) == -1) { ++ message(MESS_ERROR, "setting ACL for %s: %s\n", ++ compressedName, strerror(errno)); ++ acl_free(prev_acl); ++ prev_acl = NULL; ++ close(inFile); ++ close(outFile); ++ return 1; ++ } ++ acl_free(prev_acl); ++ prev_acl = NULL; ++ } ++#endif /* WITH_ACL */ + + if (!fork()) { +@@ -490,4 +520,14 @@ + } + #endif ++#ifdef WITH_ACL ++ if ((prev_acl = acl_get_fd(fdcurr)) == NULL) { ++ if (errno != ENOTSUP) { ++ message(MESS_ERROR, "getting file ACL %s: %s\n", ++ currLog, strerror(errno)); ++ close(fdcurr); ++ return 1; ++ } ++ } ++#endif /* WITH_ACL */ + fdsave = + createOutputFile(saveLog, O_WRONLY | O_CREAT | O_TRUNC, sb); +@@ -501,6 +541,26 @@ + if (fdsave < 0) { + close(fdcurr); ++#ifdef WITH_ACL ++ if (prev_acl) ++ acl_free(prev_acl); ++#endif /* WITH_ACL */ + return 1; + } ++#ifdef WITH_ACL ++ if (prev_acl) { ++ if (acl_set_fd(fdsave, prev_acl) == -1) { ++ message(MESS_ERROR, "setting ACL for %s: %s\n", ++ saveLog, strerror(errno)); ++ acl_free(prev_acl); ++ prev_acl = NULL; ++ close(fdsave); ++ close(fdcurr); ++ return 1; ++ } ++ acl_free(prev_acl); ++ prev_acl = NULL; ++ } ++#endif /* WITH_ACL */ ++ + while ((cnt = read(fdcurr, buf, sizeof(buf))) > 0) { + if (write(fdsave, buf, cnt) != cnt) { +@@ -1087,4 +1147,13 @@ + } + #endif ++#ifdef WITH_ACL ++ if ((prev_acl = acl_get_file(log->files[logNum], ACL_TYPE_ACCESS)) == NULL) { ++ if (errno != ENOTSUP) { ++ message(MESS_ERROR, "getting file ACL %s: %s\n", ++ log->files[logNum], strerror(errno)); ++ hasErrors = 1; ++ } ++ } ++#endif /* WITH_ACL */ + message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum], + rotNames->finalName); +@@ -1134,10 +1203,33 @@ + + if (!debug) { ++#ifdef WITH_ACL ++ if (prev_acl == NULL && (prev_acl = acl_get_file(log->files[logNum], ACL_TYPE_ACCESS)) == NULL) { ++ if (errno != ENOTSUP) { ++ message(MESS_ERROR, "getting file ACL %s: %s\n", ++ log->files[logNum], strerror(errno)); ++ hasErrors = 1; ++ } ++ } ++#endif /* WITH_ACL */ ++ if (!hasErrors) { + fd = createOutputFile(log->files[logNum], O_CREAT | O_RDWR, + &sb); + if (fd < 0) + hasErrors = 1; +- else ++ else { ++#ifdef WITH_ACL ++ if (prev_acl) { ++ if (acl_set_fd(fd, prev_acl) == -1) { ++ message(MESS_ERROR, "setting ACL for %s: %s\n", ++ log->files[logNum], strerror(errno)); ++ hasErrors = 1; ++ } ++ acl_free(prev_acl); ++ prev_acl = NULL; ++ } ++#endif /* WITH_ACL */ + close(fd); ++ } ++ } + } + } +@@ -1156,4 +1248,11 @@ + &state->sb, log->flags); + ++#ifdef WITH_ACL ++ if (prev_acl) { ++ acl_free(prev_acl); ++ prev_acl = NULL; ++ } ++#endif /* WITH_ACL */ ++ + } + return hasErrors; +Index: /trunk/Makefile +=================================================================== +--- /trunk/Makefile (revision 296) ++++ /trunk/Makefile (revision 299) +@@ -14,4 +14,9 @@ + CFLAGS += -DWITH_SELINUX + LOADLIBES += -lselinux ++endif ++ ++ifeq ($(WITH_ACL),yes) ++CFLAGS += -DWITH_ACL ++LOADLIBES += -lacl + endif + diff --git a/logrotate-3.7.9-config.patch b/logrotate-3.7.9-config.patch new file mode 100644 index 0000000..5fc0362 --- /dev/null +++ b/logrotate-3.7.9-config.patch @@ -0,0 +1,2024 @@ +Index: config.c +=================================================================== +--- config.c (revision 290) ++++ config.c (working copy) +@@ -1,5 +1,9 @@ + #include ++#ifdef _ALLOCA_H + #include ++#else ++#include ++#endif + #include + #include + #include +@@ -30,13 +34,55 @@ + + #define REALLOC_STEP 10 + +-#if defined(SunOS) +-#include ++#if defined(SunOS) ++#include + #if !defined(isblank) + #define isblank(c) ( (c) == ' ' || (c) == '\t' ) ? 1 : 0 + #endif + #endif + ++#if !defined(asprintf) ++#include ++ ++int asprintf(char **string_ptr, const char *format, ...) ++{ ++ va_list arg; ++ char *str; ++ int size; ++ int rv; ++ ++ va_start(arg, format); ++ size = vsnprintf(NULL, 0, format, arg); ++ size++; ++ va_start(arg, format); ++ str = malloc(size); ++ if (str == NULL) { ++ va_end(arg); ++ /* ++ * Strictly speaking, GNU asprintf doesn't do this, ++ * but the caller isn't checking the return value. ++ */ ++ fprintf(stderr, "failed to allocate memory\\n"); ++ exit(1); ++ } ++ rv = vsnprintf(str, size, format, arg); ++ va_end(arg); ++ ++ *string_ptr = str; ++ return (rv); ++} ++ ++#endif ++ ++enum { ++ STATE_DEFAULT = 2, ++ STATE_SKIP_LINE = 4, ++ STATE_DEFINITION_END = 8, ++ STATE_SKIP_CONFIG = 16, ++ STATE_LOAD_SCRIPT = 32, ++ STATE_ERROR = 64, ++}; ++ + static char *defTabooExts[] = { ".rpmsave", ".rpmorig", "~", ",v", + ".disabled", ".dpkg-old", ".dpkg-dist", ".dpkg-new", ".cfsaved", + ".ucf-old", ".ucf-dist", ".ucf-new", +@@ -52,51 +98,71 @@ + static int readConfigFile(const char *configFile, struct logInfo *defConfig); + static int globerr(const char *pathname, int theerr); + +-static int isolateValue(const char *fileName, int lineNum, char *key, +- char **startPtr, char **endPtr) ++static char *isolateLine(char **strt, char **buf, size_t length) { ++ char *endtag, *start, *tmp; ++ start = *strt; ++ endtag = start; ++ while (endtag - *buf < length && *endtag != '\n') { ++ endtag++;} ++ if (endtag - *buf > length) ++ return NULL; ++ tmp = endtag - 1; ++ while (isspace(*endtag)) ++ endtag--; ++ char *key = strndup(start, endtag - start + 1); ++ *strt = tmp; ++ return key; ++} ++ ++static char *isolateValue(const char *fileName, int lineNum, char *key, ++ char **startPtr, char **buf, size_t length) + { + char *chptr = *startPtr; + +- while (isblank(*chptr)) ++ while (chptr - *buf < length && isblank(*chptr)) + chptr++; +- if (*chptr == '=') { ++ if (chptr - *buf < length && *chptr == '=') { + chptr++; +- while (*chptr && isblank(*chptr)) ++ while ( chptr - *buf < length && isblank(*chptr)) + chptr++; + } + +- if (*chptr == '\n') { +- message(MESS_ERROR, "%s:%d argument expected after %s\n", +- fileName, lineNum, key); +- return 1; ++ if (chptr - *buf < length && *chptr == '\n') { ++ message(MESS_ERROR, "%s:%d argument expected after %s\n", ++ fileName, lineNum, key); ++ return NULL; + } + +- *startPtr = chptr; ++ *startPtr = chptr; ++ return isolateLine(startPtr, buf, length); ++} + +- while (*chptr != '\n') +- chptr++; +- +- while (isspace(*chptr)) +- chptr--; +- +- *endPtr = chptr + 1; +- +- return 0; ++static char *isolateWord(char **strt, char **buf, size_t length) { ++ char *endtag, *start; ++ start = *strt; ++ while (start - *buf < length && isblank(*start)) ++ start++; ++ endtag = start; ++ while (endtag - *buf < length && isalpha(*endtag)) { ++ endtag++;} ++ if (endtag - *buf > length) ++ return NULL; ++ char *key = strndup(start, endtag - start); ++ *strt = endtag; ++ return key; + } + + static char *readPath(const char *configFile, int lineNum, char *key, +- char **startPtr) ++ char **startPtr, char **buf, size_t length) + { +- char oldchar; +- char *endtag, *chptr; ++ char *chptr; + char *start = *startPtr; + char *path; + + wchar_t pwc; + size_t len; + +- if (!isolateValue(configFile, lineNum, key, &start, &endtag)) { +- oldchar = *endtag, *endtag = '\0'; ++ if ((start = isolateValue(configFile, lineNum, key, startPtr, buf, length)) != NULL) { + + chptr = start; + +@@ -120,29 +186,23 @@ + */ + + path = strdup(start); ++ free(start); + +- +- *endtag = oldchar, start = endtag; +- +- *startPtr = start; +- + return path; + } else + return NULL; + } + + static char *readAddress(const char *configFile, int lineNum, char *key, +- char **startPtr) ++ char **startPtr, char **buf, size_t length) + { +- char oldchar; + char *endtag, *chptr; + char *start = *startPtr; + char *address; + +- if (!isolateValue(configFile, lineNum, key, &start, &endtag)) { +- oldchar = *endtag, *endtag = '\0'; ++ if ((endtag = isolateValue(configFile, lineNum, key, startPtr, buf, length)) != NULL) { + +- chptr = start; ++ chptr = endtag; + while (*chptr && isprint(*chptr) && *chptr != ' ') + chptr++; + if (*chptr) { +@@ -151,12 +211,9 @@ + return NULL; + } + +- address = strdup(start); ++ address = strdup(chptr); ++ free(endtag); + +- *endtag = oldchar, start = endtag; +- +- *startPtr = start; +- + return address; + } else + return NULL; +@@ -484,8 +541,8 @@ + static int readConfigFile(const char *configFile, struct logInfo *defConfig) + { + int fd; +- char *buf, *endtag; +- char oldchar, foo; ++ char *buf, *endtag, *key = NULL; ++ char foo; + off_t length; + int lineNum = 1; + int multiplier; +@@ -504,6 +561,8 @@ + glob_t globResult; + const char **argv; + int argc, argNum; ++ int flags; ++ int state = STATE_DEFAULT; + int logerror = 0; + struct logInfo *log; + static unsigned recursion_depth = 0U; +@@ -519,17 +578,28 @@ + length arrays -- of course, if we aren't run setuid it doesn't + matter much */ + +- fd = open(configFile, O_RDONLY | O_CLOEXEC); +- if (fd < 0) { +- message(MESS_ERROR, "failed to open config file %s: %s\n", +- configFile, strerror(errno)); +- return 1; +- } ++ fd = open(configFile, O_RDONLY); ++ if (fd < 0) { ++ message(MESS_ERROR, "failed to open config file %s: %s\n", ++ configFile, strerror(errno)); ++ return 1; ++ } ++ if ((flags = fcntl(fd, F_GETFD)) == -1) { ++ message(MESS_ERROR, "Could not retrieve flags from file %s\n", ++ configFile); ++ return 1; ++ } ++ flags |= FD_CLOEXEC; ++ if (fcntl(fd, F_SETFD, flags) == -1) { ++ message(MESS_ERROR, "Could not set flags on file %s\n", ++ configFile); ++ return 1; ++ } + /* We don't want anybody to change the file while we parse it, + * let's try to lock it for reading. */ + if (fcntl(fd, F_SETLK, &fd_lock) == -1) { +- message(MESS_ERROR, "Could not lock file %s for reading\n", +- configFile); ++ message(MESS_ERROR, "Could not lock file %s for reading\n", ++ configFile); + } + if (fstat(fd, &sb)) { + message(MESS_ERROR, "fstat of %s failed: %s\n", configFile, +@@ -546,8 +616,24 @@ + } + + length = sb.st_size; +- buf = mmap(NULL, (size_t)(length + 2), PROT_READ | PROT_WRITE, +- MAP_PRIVATE | MAP_POPULATE, fd, (off_t) 0); ++ ++ /* We can't mmap empty file... */ ++ if (length == 0) { ++ message(MESS_DEBUG, ++ "Ignoring %s because it's empty.\n", ++ configFile); ++ close(fd); ++ return 0; ++ } ++ ++#ifdef MAP_POPULATE ++ buf = mmap(NULL, (size_t) length, PROT_READ, ++ MAP_PRIVATE | MAP_POPULATE, fd, (off_t) 0); ++#else /* MAP_POPULATE */ ++ buf = mmap(NULL, (size_t) length, PROT_READ, ++ MAP_PRIVATE, fd, (off_t) 0); ++#endif /* MAP_POPULATE */ ++ + if (buf == MAP_FAILED) { + message(MESS_ERROR, "Error mapping config file %s: %s\n", + configFile, strerror(errno)); +@@ -555,964 +641,812 @@ + return 1; + } + +- /* knowing the buffer ends with a newline makes things (a bit) cleaner */ +- buf[length + 1] = '\0'; +- buf[length] = '\n'; ++#ifdef MADV_DONTFORK + madvise(buf, (size_t)(length + 2), + MADV_SEQUENTIAL | MADV_WILLNEED | MADV_DONTFORK); ++#else /* MADV_DONTFORK */ ++ madvise(buf, (size_t)(length + 2), ++ MADV_SEQUENTIAL | MADV_WILLNEED); ++#endif /* MADV_DONTFORK */ + + message(MESS_DEBUG, "reading config file %s\n", configFile); + +- start = buf; +- while (*start) { +- if (logerror) { +- assert(newlog != defConfig); +- +- message(MESS_ERROR, "found error in %s, skipping\n", +- newlog->pattern ? newlog->pattern : "log config"); +- +- while (*start != '}') { +- if (*start == 0) { +- message(MESS_ERROR, "%s:%d } expected \n", +- configFile, lineNum); +- goto error; +- } else if (*start == '\n') { +- while (isspace(*start) && (*start)) { +- if (*start == '\n') +- lineNum++; +- start++; +- } +- } else if ( +- (strncmp(start, "postrotate", 10) == 0) || +- (strncmp(start, "prerotate", 9) == 0) || +- (strncmp(start, "firstrotate", 11) == 0) || +- (strncmp(start, "lastrotate", 10) == 0) +- ) +- { +- while (*start) { +- while ((*start != '\n') && (*start)) +- start++; +- while (isspace(*start) && (*start)) { +- if (*start == '\n') +- lineNum++; +- start++; ++ start = buf; ++ for (start = buf; start - buf < length; start++) { ++ if (key) { ++ free(key); ++ key = NULL; ++ } ++ switch (state) { ++ case STATE_DEFAULT: ++ if (isblank(*start)) ++ continue; ++ /* Skip comment */ ++ if (*start == '#') { ++ state = STATE_SKIP_LINE; ++ continue; + } +- if (strncmp(start, "endscript", 9) == 0) { +- start += 9; +- break; +- } +- } +- } else { +- start++; +- } +- } +- start++; ++ ++ if (isalpha(*start)) { ++ if ((key = isolateWord(&start, &buf, length)) == NULL) ++ continue; ++ if (!strcmp(key, "compress")) { ++ newlog->flags |= LOG_FLAG_COMPRESS; ++ } else if (!strcmp(key, "nocompress")) { ++ newlog->flags &= ~LOG_FLAG_COMPRESS; ++ } else if (!strcmp(key, "compress")) { ++ newlog->flags |= LOG_FLAG_COMPRESS; ++ } else if (!strcmp(key, "nocompress")) { ++ newlog->flags &= ~LOG_FLAG_COMPRESS; ++ } else if (!strcmp(key, "delaycompress")) { ++ newlog->flags |= LOG_FLAG_DELAYCOMPRESS; ++ } else if (!strcmp(key, "nodelaycompress")) { ++ newlog->flags &= ~LOG_FLAG_DELAYCOMPRESS; ++ } else if (!strcmp(key, "shred")) { ++ newlog->flags |= LOG_FLAG_SHRED; ++ } else if (!strcmp(key, "noshred")) { ++ newlog->flags &= ~LOG_FLAG_SHRED; ++ } else if (!strcmp(key, "sharedscripts")) { ++ newlog->flags |= LOG_FLAG_SHAREDSCRIPTS; ++ } else if (!strcmp(key, "nosharedscripts")) { ++ newlog->flags &= ~LOG_FLAG_SHAREDSCRIPTS; ++ } else if (!strcmp(key, "copytruncate")) { ++ newlog->flags |= LOG_FLAG_COPYTRUNCATE; ++ } else if (!strcmp(key, "nocopytruncate")) { ++ newlog->flags &= ~LOG_FLAG_COPYTRUNCATE; ++ } else if (!strcmp(key, "copy")) { ++ newlog->flags |= LOG_FLAG_COPY; ++ } else if (!strcmp(key, "nocopy")) { ++ newlog->flags &= ~LOG_FLAG_COPY; ++ } else if (!strcmp(key, "ifempty")) { ++ newlog->flags |= LOG_FLAG_IFEMPTY; ++ } else if (!strcmp(key, "notifempty")) { ++ newlog->flags &= ~LOG_FLAG_IFEMPTY; ++ } else if (!strcmp(key, "dateext")) { ++ newlog->flags |= LOG_FLAG_DATEEXT; ++ } else if (!strcmp(key, "nodateext")) { ++ newlog->flags &= ~LOG_FLAG_DATEEXT; ++ } else if (!strcmp(key, "dateformat")) { ++ freeLogItem(dateformat); ++ newlog->dateformat = isolateLine(&start, &buf, length); ++ if (newlog->dateformat == NULL) ++ continue; ++ } else if (!strcmp(key, "noolddir")) { ++ newlog->oldDir = NULL; ++ } else if (!strcmp(key, "mailfirst")) { ++ newlog->flags |= LOG_FLAG_MAILFIRST; ++ } else if (!strcmp(key, "maillast")) { ++ newlog->flags &= ~LOG_FLAG_MAILFIRST; ++ } else if (!strcmp(key, "create")) { ++ free(key); ++ key = isolateLine(&start, &buf, length); ++ if (key == NULL) ++ continue; + +- freeTailLogs(1); +- newlog = defConfig; +- logerror = 0; +- } +- while (isblank(*start) && (*start)) +- start++; +- if (*start == '#') { +- while (*start != '\n') +- start++; +- } ++ rc = sscanf(key, "%o %s %s%c", &createMode, ++ createOwner, createGroup, &foo); ++ if (rc == 4) { ++ message(MESS_ERROR, "%s:%d extra arguments for " ++ "create\n", configFile, lineNum); ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } + +- if (*start == '\n') { +- start++; +- lineNum++; +- continue; +- } ++ if (rc > 0) ++ newlog->createMode = createMode; + +- if (scriptStart) { +- if (!strncmp(start, "endscript", 9)) { +- chptr = start + 9; +- while (isblank(*chptr)) +- chptr++; +- if (*chptr == '\n') { +- endtag = start; +- while (*endtag != '\n') +- endtag--; +- endtag++; +- *scriptDest = malloc(endtag - scriptStart + 1); +- strncpy(*scriptDest, scriptStart, +- endtag - scriptStart); +- (*scriptDest)[endtag - scriptStart] = '\0'; +- start = chptr + 1; +- lineNum++; ++ if (rc > 1) { ++ pw = getpwnam(createOwner); ++ if (!pw) { ++ message(MESS_ERROR, "%s:%d unknown user '%s'\n", ++ configFile, lineNum, createOwner); ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } ++ newlog->createUid = pw->pw_uid; ++ endpwent(); ++ } ++ if (rc > 2) { ++ group = getgrnam(createGroup); ++ if (!group) { ++ message(MESS_ERROR, "%s:%d unknown group '%s'\n", ++ configFile, lineNum, createGroup); ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } ++ newlog->createGid = group->gr_gid; ++ endgrent(); ++ } + +- scriptDest = NULL; +- scriptStart = NULL; +- } +- } ++ newlog->flags |= LOG_FLAG_CREATE; ++ } else if (!strcmp(key, "nocreate")) { ++ newlog->flags &= ~LOG_FLAG_CREATE; ++ } else if (!strcmp(key, "size") || !strcmp(key, "minsize")) { ++ unsigned long long size = 0; ++ char *opt = key; ++ ++ if ((key = isolateValue(configFile, lineNum, opt, &start, ++ &buf, length)) != NULL) { ++ free(opt); ++ int l = strlen(key) - 1; ++ if (key[l] == 'k') { ++ key[l] = '\0'; ++ multiplier = 1024; ++ } else if (key[l] == 'M') { ++ key[l] = '\0'; ++ multiplier = 1024 * 1024; ++ } else if (key[l] == 'G') { ++ key[l] = '\0'; ++ multiplier = 1024 * 1024 * 1024; ++ } else if (!isdigit(key[l])) { ++ message(MESS_ERROR, "%s:%d unknown unit '%c'\n", ++ configFile, lineNum, key[l]); ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } else { ++ multiplier = 1; ++ } + +- if (scriptStart) { +- while (*start != '\n') +- start++; +- lineNum++; +- start++; +- } +- } else if (isalpha(*start)) { +- endtag = start; +- while (isalpha(*endtag)) +- endtag++; +- oldchar = *endtag; +- *endtag = '\0'; ++ size = multiplier * strtoul(key, &chptr, 0); ++ if (*chptr) { ++ message(MESS_ERROR, "%s:%d bad size '%s'\n", ++ configFile, lineNum, key); ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } ++ if (!strncmp(key, "size", 4)) { ++ newlog->criterium = ROT_SIZE; ++ newlog->threshhold = size; ++ } else ++ newlog->minsize = size; ++ } ++ else { ++ free(opt); ++ continue; ++ } ++ } else if (!strcmp(key, "shredcycles")) { ++ free(key); ++ if ((key = isolateValue(configFile, lineNum, "shred cycles", ++ &start, &buf, length)) != NULL) { ++ newlog->shred_cycles = strtoul(key, &chptr, 0); ++ if (*chptr || newlog->shred_cycles < 0) { ++ message(MESS_ERROR, "%s:%d bad shred cycles '%s'\n", ++ configFile, lineNum, key); ++ goto error; ++ } ++ } ++ else continue; ++ } else if (!strcmp(key, "daily")) { ++ newlog->criterium = ROT_DAYS; ++ newlog->threshhold = 1; ++ } else if (!strcmp(key, "monthly")) { ++ newlog->criterium = ROT_MONTHLY; ++ } else if (!strcmp(key, "weekly")) { ++ newlog->criterium = ROT_WEEKLY; ++ } else if (!strcmp(key, "yearly")) { ++ newlog->criterium = ROT_YEARLY; ++ } else if (!strcmp(key, "rotate")) { ++ free(key); ++ if ((key = isolateValue ++ (configFile, lineNum, "rotate count", &start, ++ &buf, length)) != NULL) { + +- if (!strcmp(start, "compress")) { +- newlog->flags |= LOG_FLAG_COMPRESS; ++ newlog->rotateCount = strtoul(key, &chptr, 0); ++ if (*chptr || newlog->rotateCount < 0) { ++ message(MESS_ERROR, ++ "%s:%d bad rotation count '%s'\n", ++ configFile, lineNum, key); ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } ++ } ++ else continue; ++ } else if (!strcmp(key, "start")) { ++ free(key); ++ if ((key = isolateValue ++ (configFile, lineNum, "start count", &start, ++ &buf, length)) != NULL) { + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "nocompress")) { +- newlog->flags &= ~LOG_FLAG_COMPRESS; ++ newlog->logStart = strtoul(start, &chptr, 0); ++ if (*chptr || newlog->logStart < 0) { ++ message(MESS_ERROR, "%s:%d bad start count '%s'\n", ++ configFile, lineNum, key); ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } ++ } ++ else continue; ++ } else if (!strcmp(key, "maxage")) { ++ free(key); ++ if ((key = isolateValue ++ (configFile, lineNum, "maxage count", &start, ++ &buf, length)) != NULL) { ++ newlog->rotateAge = strtoul(start, &chptr, 0); ++ if (*chptr || newlog->rotateAge < 0) { ++ message(MESS_ERROR, "%s:%d bad maximum age '%s'\n", ++ configFile, lineNum, start); ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } ++ } ++ else continue; ++ } else if (!strcmp(key, "errors")) { ++ message(MESS_DEBUG, ++ "%s: %d: the errors directive is deprecated and no longer used.\n", ++ configFile, lineNum); ++ } else if (!strcmp(key, "mail")) { ++ freeLogItem(logAddress); ++ if (!(newlog->logAddress = readAddress(configFile, lineNum, ++ "mail", &start, &buf, length))) { ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } ++ else continue; ++ } else if (!strcmp(key, "nomail")) { ++ freeLogItem(logAddress); ++ } else if (!strcmp(key, "missingok")) { ++ newlog->flags |= LOG_FLAG_MISSINGOK; ++ } else if (!strcmp(key, "nomissingok")) { ++ newlog->flags &= ~LOG_FLAG_MISSINGOK; ++ } else if (!strcmp(key, "prerotate")) { ++ freeLogItem (pre); ++ scriptStart = start; ++ scriptDest = &newlog->pre; ++ state = STATE_LOAD_SCRIPT; ++ } else if (!strcmp(key, "firstaction")) { ++ freeLogItem (first); ++ scriptStart = start; ++ scriptDest = &newlog->first; ++ state = STATE_LOAD_SCRIPT; ++ } else if (!strcmp(key, "postrotate")) { ++ freeLogItem (post); ++ scriptStart = start; ++ scriptDest = &newlog->post; ++ state = STATE_LOAD_SCRIPT; ++ } else if (!strcmp(key, "lastaction")) { ++ freeLogItem (last); ++ scriptStart = start; ++ scriptDest = &newlog->last; ++ state = STATE_LOAD_SCRIPT; ++ } else if (!strcmp(key, "tabooext")) { ++ if (newlog != defConfig) { ++ message(MESS_ERROR, ++ "%s:%d tabooext may not appear inside " ++ "of log file definition\n", configFile, ++ lineNum); ++ state = STATE_ERROR; ++ continue; ++ } ++ free(key); ++ if ((key = isolateValue(configFile, lineNum, "tabooext", &start, ++ &buf, length)) != NULL) { + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "delaycompress")) { +- newlog->flags |= LOG_FLAG_DELAYCOMPRESS; ++ if (*key == '+') { ++ key++; ++ while (isspace(*key) && *key) ++ key++; ++ } else { ++ free_2d_array(tabooExts, tabooCount); ++ tabooCount = 0; ++ tabooExts = malloc(1); ++ } + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "nodelaycompress")) { +- newlog->flags &= ~LOG_FLAG_DELAYCOMPRESS; ++ endtag = key; ++ while (*endtag) { ++ chptr = endtag; ++ while (!isspace(*chptr) && *chptr != ',' && *chptr) ++ chptr++; + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "shred")) { +- newlog->flags |= LOG_FLAG_SHRED; ++ tabooExts = realloc(tabooExts, sizeof(*tabooExts) * ++ (tabooCount + 1)); ++ tabooExts[tabooCount] = malloc(chptr - endtag + 1); ++ strncpy(tabooExts[tabooCount], endtag, ++ chptr - endtag); ++ tabooExts[tabooCount][chptr - endtag] = '\0'; ++ tabooCount++; + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "noshred")) { +- newlog->flags &= ~LOG_FLAG_SHRED; ++ endtag = chptr; ++ if (*endtag == ',') ++ start++; ++ while (isspace(*endtag) && *endtag) ++ endtag++; ++ } ++ } ++ else continue; ++ } else if (!strcmp(key, "include")) { ++ free(key); ++ if ((key = isolateValue(configFile, lineNum, "include", &start, ++ &buf, length)) != NULL) { + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "sharedscripts")) { +- newlog->flags |= LOG_FLAG_SHAREDSCRIPTS; ++ message(MESS_DEBUG, "including %s\n", key); ++ if (++recursion_depth > MAX_NESTING) { ++ message(MESS_ERROR, "%s:%d include nesting too deep\n", ++ configFile, lineNum); ++ --recursion_depth; ++ goto error; ++ } ++ if (readConfigPath(key, newlog)) { ++ --recursion_depth; ++ goto error; ++ } ++ --recursion_depth; ++ } ++ else continue; ++ } else if (!strcmp(key, "olddir")) { ++ freeLogItem (oldDir); + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "nosharedscripts")) { +- newlog->flags &= ~LOG_FLAG_SHAREDSCRIPTS; ++ if (!(newlog->oldDir = readPath(configFile, lineNum, ++ "olddir", &start, &buf, length))) { ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } ++ else continue; ++#if 0 ++ if (stat(newlog->oldDir, &sb)) { ++ message(MESS_ERROR, "%s:%d error verifying olddir " ++ "path %s: %s\n", configFile, lineNum, ++ newlog->oldDir, strerror(errno)); ++ free(newlog->oldDir); ++ goto error; ++ } + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "copytruncate")) { +- newlog->flags |= LOG_FLAG_COPYTRUNCATE; ++ if (!S_ISDIR(sb.st_mode)) { ++ message(MESS_ERROR, "%s:%d olddir path %s is not a " ++ "directory\n", configFile, lineNum, ++ newlog->oldDir); ++ free(newlog->oldDir); ++ goto error; ++ } ++#endif ++ message(MESS_DEBUG, "olddir is now %s\n", newlog->oldDir); ++ } else if (!strcmp(key, "extension")) { ++ if ((key = isolateValue ++ (configFile, lineNum, "extension name", &start, ++ &buf, length)) != NULL) { ++ freeLogItem (extension); ++ newlog->extension = key; ++ key = NULL; ++ } ++ else continue; + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "nocopytruncate")) { +- newlog->flags &= ~LOG_FLAG_COPYTRUNCATE; ++ message(MESS_DEBUG, "extension is now %s\n", ++ newlog->extension); + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "copy")) { +- newlog->flags |= LOG_FLAG_COPY; ++ } else if (!strcmp(key, "compresscmd")) { ++ freeLogItem (compress_prog); + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "nocopy")) { +- newlog->flags &= ~LOG_FLAG_COPY; ++ if (! ++ (newlog->compress_prog = ++ readPath(configFile, lineNum, "compress", &start, &buf, length))) { ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } ++ else continue; + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "ifempty")) { +- newlog->flags |= LOG_FLAG_IFEMPTY; ++ if (access(newlog->compress_prog, X_OK)) { ++ message(MESS_ERROR, ++ "%s:%d compression program %s is not an executable file\n", ++ configFile, lineNum, newlog->compress_prog); ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "notifempty")) { +- newlog->flags &= ~LOG_FLAG_IFEMPTY; ++ message(MESS_DEBUG, "compress_prog is now %s\n", ++ newlog->compress_prog); + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "dateext")) { +- newlog->flags |= LOG_FLAG_DATEEXT; ++ } else if (!strcmp(key, "uncompresscmd")) { ++ freeLogItem (uncompress_prog); + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "nodateext")) { +- newlog->flags &= ~LOG_FLAG_DATEEXT; +- +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "dateformat")) { +- *endtag = oldchar, start = endtag; +- +- endtag = start; +- while (*endtag != '\n') +- endtag++; +- while (isspace(*endtag)) +- endtag--; +- endtag++; +- oldchar = *endtag, *endtag = '\0'; ++ if (! ++ (newlog->uncompress_prog = ++ readPath(configFile, lineNum, "uncompress", ++ &start, &buf, length))) { ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } ++ else continue; + +- freeLogItem(dateformat); +- newlog->dateformat = strdup(start); ++ if (access(newlog->uncompress_prog, X_OK)) { ++ message(MESS_ERROR, ++ "%s:%d uncompression program %s is not an executable file\n", ++ configFile, lineNum, newlog->uncompress_prog); ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "noolddir")) { +- newlog->oldDir = NULL; ++ message(MESS_DEBUG, "uncompress_prog is now %s\n", ++ newlog->uncompress_prog); + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "mailfirst")) { +- newlog->flags |= LOG_FLAG_MAILFIRST; ++ } else if (!strcmp(key, "compressoptions")) { ++ char *options; + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "maillast")) { +- newlog->flags &= ~LOG_FLAG_MAILFIRST; ++ if (newlog->compress_options_list) { ++ free(newlog->compress_options_list); ++ newlog->compress_options_list = NULL; ++ newlog->compress_options_count = 0; ++ } + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "create")) { +- *endtag = oldchar, start = endtag; ++ if (! ++ (options = ++ readPath(configFile, lineNum, "compressoptions", ++ &start, &buf, length))) { ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } else continue; + +- endtag = start; +- while (*endtag != '\n') +- endtag++; +- while (isspace(*endtag)) +- endtag--; +- endtag++; +- oldchar = *endtag, *endtag = '\0'; ++ if (poptParseArgvString(options, ++ &newlog->compress_options_count, ++ &newlog->compress_options_list)) { ++ message(MESS_ERROR, ++ "%s:%d invalid compression options\n", ++ configFile, lineNum); ++ free(options); ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } + +- rc = sscanf(start, "%o %s %s%c", &createMode, +- createOwner, createGroup, &foo); +- if (rc == 4) { +- message(MESS_ERROR, "%s:%d extra arguments for " +- "create\n", configFile, lineNum); +- if (newlog != defConfig) { +- *endtag = oldchar, start = endtag; +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } ++ message(MESS_DEBUG, "compress_options is now %s\n", ++ options); ++ free(options); ++ } else if (!strcmp(key, "compressext")) { ++ freeLogItem (compress_ext); + +- if (rc > 0) +- newlog->createMode = createMode; ++ if (! ++ (newlog->compress_ext = ++ readPath(configFile, lineNum, "compress-ext", ++ &start, &buf, length))) { ++ if (newlog != defConfig) { ++ state = STATE_ERROR; ++ continue; ++ } else { ++ goto error; ++ } ++ } else continue; + +- if (rc > 1) { +- pw = getpwnam(createOwner); +- if (!pw) { +- message(MESS_ERROR, "%s:%d unknown user '%s'\n", +- configFile, lineNum, createOwner); +- if (newlog != defConfig) { +- *endtag = oldchar, start = endtag; +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +- newlog->createUid = pw->pw_uid; +- endpwent(); +- } +- if (rc > 2) { +- group = getgrnam(createGroup); +- if (!group) { +- message(MESS_ERROR, "%s:%d unknown group '%s'\n", +- configFile, lineNum, createGroup); +- if (newlog != defConfig) { +- *endtag = oldchar, start = endtag; +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +- newlog->createGid = group->gr_gid; +- endgrent(); +- } ++ message(MESS_DEBUG, "compress_ext is now %s\n", ++ newlog->compress_ext); ++ } else { ++ message(MESS_ERROR, "%s:%d unknown option '%s' " ++ "-- ignoring line\n", configFile, lineNum, key); ++ if (*start != '\n') ++ state = STATE_SKIP_LINE; ++ } ++ free(key); ++ key = NULL; ++ } else if (*start == '/' || *start == '"' || *start == '\'') { ++ if (newlog != defConfig) { ++ message(MESS_ERROR, "%s:%d unexpected log filename\n", ++ configFile, lineNum); ++ state = STATE_ERROR; ++ continue; ++ } + +- newlog->flags |= LOG_FLAG_CREATE; ++ /* If no compression options were found in config file, set ++ default values */ ++ if (!newlog->compress_prog) ++ newlog->compress_prog = strdup(COMPRESS_COMMAND); ++ if (!newlog->uncompress_prog) ++ newlog->uncompress_prog = strdup(UNCOMPRESS_COMMAND); ++ if (!newlog->compress_ext) ++ newlog->compress_ext = strdup(COMPRESS_EXT); + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "nocreate")) { +- newlog->flags &= ~LOG_FLAG_CREATE; ++ /* Allocate a new logInfo structure and insert it into the logs ++ queue, copying the actual values from defConfig */ ++ if ((newlog = newLogInfo(defConfig)) == NULL) ++ goto error; + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "size") || !strcmp(start, "minsize")) { +- unsigned long long size = 0; +- char *opt = start; +- *endtag = oldchar, start = endtag; ++ endtag = start; ++ while (endtag - buf < length && *endtag != '{' && *endtag != '\0') { ++ endtag++;} ++ if (endtag - buf > length) ++ continue; ++ char *key = strndup(start, endtag - start); ++ start = endtag; + +- if (!isolateValue(configFile, lineNum, opt, &start, +- &endtag)) { +- oldchar = *endtag, *endtag = '\0'; ++ if (poptParseArgvString(key, &argc, &argv)) { ++ message(MESS_ERROR, "%s:%d error parsing filename\n", ++ configFile, lineNum); ++ free(key); ++ goto error; ++ } else if (argc < 1) { ++ message(MESS_ERROR, ++ "%s:%d { expected after log file name(s)\n", ++ configFile, lineNum); ++ free(key); ++ goto error; ++ } + +- length = strlen(start) - 1; +- if (start[length] == 'k') { +- start[length] = '\0'; +- multiplier = 1024; +- } else if (start[length] == 'M') { +- start[length] = '\0'; +- multiplier = 1024 * 1024; +- } else if (start[length] == 'G') { +- start[length] = '\0'; +- multiplier = 1024 * 1024 * 1024; +- } else if (!isdigit(start[length])) { +- message(MESS_ERROR, "%s:%d unknown unit '%c'\n", +- configFile, lineNum, start[length]); +- if (newlog != defConfig) { +- *endtag = oldchar, start = endtag; +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } else { +- multiplier = 1; +- } ++ newlog->files = NULL; ++ newlog->numFiles = 0; ++ for (argNum = 0; argNum < argc && logerror != 1; argNum++) { ++ if (globerr_msg) { ++ free(globerr_msg); ++ globerr_msg = NULL; ++ } ++ ++ rc = glob(argv[argNum], GLOB_NOCHECK, globerr, ++ &globResult); ++ if (rc == GLOB_ABORTED) { ++ if (newlog->flags & LOG_FLAG_MISSINGOK) { ++ continue; ++ } + +- size = multiplier * strtoul(start, &chptr, 0); +- if (*chptr) { +- message(MESS_ERROR, "%s:%d bad size '%s'\n", +- configFile, lineNum, start); +- if (newlog != defConfig) { +- *endtag = oldchar, start = endtag; +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } ++ /* We don't yet know whether this stanza has "missingok" ++ * set, so store the error message for later. */ ++ rc = asprintf(&globerr_msg, "%s:%d glob failed for %s: %s\n", ++ configFile, lineNum, argv[argNum], strerror(glob_errno)); ++ if (rc == -1) ++ globerr_msg = NULL; ++ ++ globResult.gl_pathc = 0; ++ } + +- if (!strncmp(opt, "size", 4)) { +- newlog->criterium = ROT_SIZE; +- newlog->threshhold = size; +- } else +- newlog->minsize = size; ++ newlog->files = ++ realloc(newlog->files, ++ sizeof(*newlog->files) * (newlog->numFiles + ++ globResult. ++ gl_pathc)); + +- *endtag = oldchar, start = endtag; +- } +-#if 0 /* this seems like such a good idea :-( */ +- } else if (!strcmp(start, "days")) { +- *endtag = oldchar, start = endtag; ++ for (i = 0; i < globResult.gl_pathc; i++) { ++ /* if we glob directories we can get false matches */ ++ if (!lstat(globResult.gl_pathv[i], &sb) && ++ S_ISDIR(sb.st_mode)) { ++ continue; ++ } + +- if (!isolateValue(configFile, lineNum, "size", &start, +- &endtag)) { +- oldchar = *endtag, *endtag = '\0'; ++ for (log = logs.tqh_first; log != NULL; ++ log = log->list.tqe_next) { ++ for (k = 0; k < log->numFiles; k++) { ++ if (!strcmp(log->files[k], ++ globResult.gl_pathv[i])) { ++ message(MESS_ERROR, ++ "%s:%d duplicate log entry for %s\n", ++ configFile, lineNum, ++ globResult.gl_pathv[i]); ++ logerror = 1; ++ goto duperror; ++ } ++ } ++ } + +- newlog->threshhold = strtoul(start, &chptr, 0); +- if (*chptr) { +- message(MESS_ERROR, +- "%s:%d bad number of days'%s'\n", +- configFile, lineNum, start); +- goto error; +- } ++ newlog->files[newlog->numFiles] = ++ strdup(globResult.gl_pathv[i]); ++ newlog->numFiles++; ++ } ++ duperror: ++ globfree(&globResult); ++ } + +- newlog->criterium = ROT_DAYS; ++ newlog->pattern = key; + +- *endtag = oldchar, start = endtag; +- } +-#endif +- } else if (!strcmp(start, "shredcycles")) { +- *endtag = oldchar, start = endtag; ++// if (!logerror) ++// message(MESS_DEBUG, "reading config info for %s\n", start); + +- if (!isolateValue(configFile, lineNum, "shred cycles", +- &start, &endtag)) { +- oldchar = *endtag, *endtag = '\0'; ++ free(argv); + +- newlog->shred_cycles = strtoul(start, &chptr, 0); +- if (*chptr || newlog->shred_cycles < 0) { +- message(MESS_ERROR, "%s:%d bad shred cycles '%s'\n", +- configFile, lineNum, start); +- goto error; +- } +- *endtag = oldchar, start = endtag; +- } +- } else if (!strcmp(start, "daily")) { +- *endtag = oldchar, start = endtag; ++// start = endtag + 1; ++ } else if (*start == '}') { ++ if (newlog == defConfig) { ++ message(MESS_ERROR, "%s:%d unexpected }\n", configFile, ++ lineNum); ++ goto error; ++ } ++ if (globerr_msg) { ++ if (!(newlog->flags & LOG_FLAG_MISSINGOK)) ++ message(MESS_ERROR, globerr_msg); ++ free(globerr_msg); ++ globerr_msg = NULL; ++ if (!(newlog->flags & LOG_FLAG_MISSINGOK)) ++ return 1; ++ } + +- newlog->criterium = ROT_DAYS; +- newlog->threshhold = 1; +- } else if (!strcmp(start, "monthly")) { +- *endtag = oldchar, start = endtag; ++ if (newlog->oldDir) { ++ for (i = 0; i < newlog->numFiles; i++) { ++ char *ld; ++ dirName = ourDirName(newlog->files[i]); ++ if (stat(dirName, &sb2)) { ++ message(MESS_ERROR, ++ "%s:%d error verifying log file " ++ "path %s: %s\n", configFile, lineNum, ++ dirName, strerror(errno)); ++ free(dirName); ++ goto error; ++ } ++ ld = alloca(strlen(dirName) + strlen(newlog->oldDir) + ++ 2); ++ sprintf(ld, "%s/%s", dirName, newlog->oldDir); ++ free(dirName); + +- newlog->criterium = ROT_MONTHLY; +- } else if (!strcmp(start, "weekly")) { +- *endtag = oldchar, start = endtag; ++ if (newlog->oldDir[0] != '/') ++ dirName = ld; ++ else ++ dirName = newlog->oldDir; ++ if (stat(dirName, &sb)) { ++ message(MESS_ERROR, "%s:%d error verifying olddir " ++ "path %s: %s\n", configFile, lineNum, ++ dirName, strerror(errno)); ++ goto error; ++ } + +- newlog->criterium = ROT_WEEKLY; +- } else if (!strcmp(start, "yearly")) { +- *endtag = oldchar, start = endtag; ++ if (sb.st_dev != sb2.st_dev) { ++ message(MESS_ERROR, ++ "%s:%d olddir %s and log file %s " ++ "are on different devices\n", configFile, ++ lineNum, newlog->oldDir, newlog->files[i]); ++ goto error; ++ } ++ } ++ } + +- newlog->criterium = ROT_YEARLY; +- } else if (!strcmp(start, "rotate")) { +- *endtag = oldchar, start = endtag; +- +- if (!isolateValue +- (configFile, lineNum, "rotate count", &start, +- &endtag)) { +- oldchar = *endtag, *endtag = '\0'; +- +- newlog->rotateCount = strtoul(start, &chptr, 0); +- if (*chptr || newlog->rotateCount < 0) { +- message(MESS_ERROR, +- "%s:%d bad rotation count '%s'\n", +- configFile, lineNum, start); +- if (newlog != defConfig) { +- *endtag = oldchar, start = endtag; +- logerror = 1; +- continue; +- } else { +- goto error; ++ newlog = defConfig; ++ state = STATE_DEFINITION_END; ++ } else if (*start != '\n') { ++ message(MESS_ERROR, "%s:%d lines must begin with a keyword " ++ "or a filename (possibly in double quotes)\n", ++ configFile, lineNum); ++ state = STATE_SKIP_LINE; + } +- } +- *endtag = oldchar, start = endtag; +- } +- } else if (!strcmp(start, "start")) { +- *endtag = oldchar, start = endtag; +- +- if (!isolateValue +- (configFile, lineNum, "start count", &start, +- &endtag)) { +- oldchar = *endtag, *endtag = '\0'; +- +- newlog->logStart = strtoul(start, &chptr, 0); +- if (*chptr || newlog->logStart < 0) { +- message(MESS_ERROR, "%s:%d bad start count '%s'\n", +- configFile, lineNum, start); +- if (newlog != defConfig) { +- *endtag = oldchar, start = endtag; +- logerror = 1; +- continue; +- } else { +- goto error; ++ break; ++ case STATE_SKIP_LINE: ++ case STATE_SKIP_LINE | STATE_SKIP_CONFIG: ++ if (*start == '\n') ++ state = state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : STATE_DEFAULT; ++ break; ++ case STATE_SKIP_LINE | STATE_LOAD_SCRIPT: ++ if (*start == '\n') ++ state = STATE_LOAD_SCRIPT; ++ break; ++ case STATE_SKIP_LINE | STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG: ++ if (*start == '\n') ++ state = STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG; ++ break; ++ case STATE_DEFINITION_END: ++ case STATE_DEFINITION_END | STATE_SKIP_CONFIG: ++ if (isblank(*start)) ++ continue; ++ if (*start != '\n') { ++ message(MESS_ERROR, "%s:%d, unexpected text after }\n", ++ configFile, lineNum); ++ state = STATE_SKIP_LINE | (state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : 0); + } +- } +- *endtag = oldchar, start = endtag; +- } +- } else if (!strcmp(start, "maxage")) { +- *endtag = oldchar, start = endtag; ++ else ++ state = state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : STATE_DEFAULT; ++ break; ++ case STATE_ERROR: ++ assert(newlog != defConfig); + +- if (!isolateValue +- (configFile, lineNum, "maxage count", &start, +- &endtag)) { +- oldchar = *endtag, *endtag = '\0'; ++ message(MESS_ERROR, "found error in %s, skipping\n", ++ newlog->pattern ? newlog->pattern : "log config"); + +- newlog->rotateAge = strtoul(start, &chptr, 0); +- if (*chptr || newlog->rotateAge < 0) { +- message(MESS_ERROR, "%s:%d bad maximum age '%s'\n", +- configFile, lineNum, start); +- if (newlog != defConfig) { +- *endtag = oldchar, start = endtag; +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +- *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); +- } else if (!strcmp(start, "mail")) { +- *endtag = oldchar, start = endtag; +- freeLogItem(logAddress); +- if (!(newlog->logAddress = readAddress(configFile, lineNum, +- "mail", &start))) { +- if (newlog != defConfig) { +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +- } else if (!strcmp(start, "nomail")) { +- freeLogItem(logAddress); ++ state = STATE_SKIP_CONFIG; ++ break; ++ case STATE_LOAD_SCRIPT: ++ case STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG: ++ if ((key = isolateWord(&start, &buf, length)) == NULL) ++ continue; + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "missingok")) { +- newlog->flags |= LOG_FLAG_MISSINGOK; ++ if (strcmp(key, "endscript") == 0) { ++ if (state & STATE_SKIP_CONFIG) { ++ state = STATE_SKIP_CONFIG; ++ } ++ else { ++ endtag = start - 9; ++ while (*endtag != '\n') ++ endtag--; ++ endtag++; ++ *scriptDest = malloc(endtag - scriptStart + 1); ++ strncpy(*scriptDest, scriptStart, ++ endtag - scriptStart); ++ (*scriptDest)[endtag - scriptStart] = '\0'; + +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "nomissingok")) { +- newlog->flags &= ~LOG_FLAG_MISSINGOK; +- +- *endtag = oldchar, start = endtag; +- } else if (!strcmp(start, "prerotate")) { +- *endtag = oldchar, start = endtag; +- +- freeLogItem (pre); +- +- scriptStart = start; +- scriptDest = &newlog->pre; +- +- while (*start != '\n') +- start++; +- } else if (!strcmp(start, "firstaction")) { +- *endtag = oldchar, start = endtag; +- +- freeLogItem (first); +- +- scriptStart = start; +- scriptDest = &newlog->first; +- +- while (*start != '\n') +- start++; +- } else if (!strcmp(start, "postrotate")) { +- *endtag = oldchar, start = endtag; +- +- freeLogItem (post); +- +- scriptStart = start; +- scriptDest = &newlog->post; +- +- while (*start != '\n') +- start++; +- } else if (!strcmp(start, "lastaction")) { +- *endtag = oldchar, start = endtag; +- +- freeLogItem (last); +- +- scriptStart = start; +- scriptDest = &newlog->last; +- +- while (*start != '\n') +- start++; +- } else if (!strcmp(start, "tabooext")) { +- if (newlog != defConfig) { +- message(MESS_ERROR, +- "%s:%d tabooext may not appear inside " +- "of log file definition\n", configFile, +- lineNum); +- *endtag = oldchar, start = endtag; +- logerror = 1; +- continue; +- } +- +- *endtag = oldchar, start = endtag; +- if (!isolateValue(configFile, lineNum, "tabooext", &start, +- &endtag)) { +- oldchar = *endtag, *endtag = '\0'; +- +- if (*start == '+') { +- start++; +- while (isspace(*start) && *start) +- start++; +- } else { +- free_2d_array(tabooExts, tabooCount); +- tabooCount = 0; +- tabooExts = malloc(1); +- } +- +- while (*start) { +- chptr = start; +- while (!isspace(*chptr) && *chptr != ',' && *chptr) +- chptr++; +- +- tabooExts = realloc(tabooExts, sizeof(*tabooExts) * +- (tabooCount + 1)); +- tabooExts[tabooCount] = malloc(chptr - start + 1); +- strncpy(tabooExts[tabooCount], start, +- chptr - start); +- tabooExts[tabooCount][chptr - start] = '\0'; +- tabooCount++; +- +- start = chptr; +- if (*start == ',') +- start++; +- while (isspace(*start) && *start) +- start++; +- } +- +- *endtag = oldchar, start = endtag; +- } +- } else if (!strcmp(start, "include")) { +-// if (newlog != defConfig) { +-// message(MESS_ERROR, +-// "%s:%d include may not appear inside " +-// "of log file definition\n", configFile, +-// lineNum); +-// *endtag = oldchar, start = endtag; +-// logerror = 1; +-// continue; +-// } +- +- *endtag = oldchar, start = endtag; +- if (!isolateValue(configFile, lineNum, "include", &start, +- &endtag)) { +- oldchar = *endtag, *endtag = '\0'; +- +- message(MESS_DEBUG, "including %s\n", start); +- if (++recursion_depth > MAX_NESTING) { +- message(MESS_ERROR, "%s:%d include nesting too deep\n", +- configFile, lineNum); +- --recursion_depth; +- goto error; ++ scriptDest = NULL; ++ scriptStart = NULL; ++ } ++ state = state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : STATE_DEFAULT; + } +- if (readConfigPath(start, newlog)) { +- --recursion_depth; +- goto error; ++ else { ++ state = (*start == '\n' ? 0 : STATE_SKIP_LINE) | ++ STATE_LOAD_SCRIPT | ++ (state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : 0); + } +- --recursion_depth; +- +- *endtag = oldchar, start = endtag; +- } +- } else if (!strcmp(start, "olddir")) { +- *endtag = oldchar, start = endtag; +- +- freeLogItem (oldDir); +- +- if (!(newlog->oldDir = readPath(configFile, lineNum, +- "olddir", &start))) { +- if (newlog != defConfig) { +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +-#if 0 +- if (stat(newlog->oldDir, &sb)) { +- message(MESS_ERROR, "%s:%d error verifying olddir " +- "path %s: %s\n", configFile, lineNum, +- newlog->oldDir, strerror(errno)); +- free(newlog->oldDir); +- goto error; +- } +- +- if (!S_ISDIR(sb.st_mode)) { +- message(MESS_ERROR, "%s:%d olddir path %s is not a " +- "directory\n", configFile, lineNum, +- newlog->oldDir); +- free(newlog->oldDir); +- goto error; +- } +-#endif +- +- message(MESS_DEBUG, "olddir is now %s\n", newlog->oldDir); +- } else if (!strcmp(start, "extension")) { +- *endtag = oldchar, start = endtag; +- +- if (!isolateValue +- (configFile, lineNum, "extension name", &start, +- &endtag)) { +- oldchar = *endtag, *endtag = '\0'; +- +- freeLogItem (extension); +- newlog->extension = strdup(start); +- +- *endtag = oldchar, start = endtag; +- } +- +- message(MESS_DEBUG, "extension is now %s\n", +- newlog->extension); +- +- } else if (!strcmp(start, "compresscmd")) { +- *endtag = oldchar, start = endtag; +- +- freeLogItem (compress_prog); +- +- if (! +- (newlog->compress_prog = +- readPath(configFile, lineNum, "compress", &start))) { +- if (newlog != defConfig) { +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +- +- if (access(newlog->compress_prog, X_OK)) { +- message(MESS_ERROR, +- "%s:%d compression program %s is not an executable file\n", +- configFile, lineNum, newlog->compress_prog); +- if (newlog != defConfig) { +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +- +- message(MESS_DEBUG, "compress_prog is now %s\n", +- newlog->compress_prog); +- +- } else if (!strcmp(start, "uncompresscmd")) { +- *endtag = oldchar, start = endtag; +- +- freeLogItem (uncompress_prog); +- +- if (! +- (newlog->uncompress_prog = +- readPath(configFile, lineNum, "uncompress", +- &start))) { +- if (newlog != defConfig) { +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +- +- if (access(newlog->uncompress_prog, X_OK)) { +- message(MESS_ERROR, +- "%s:%d uncompression program %s is not an executable file\n", +- configFile, lineNum, newlog->uncompress_prog); +- if (newlog != defConfig) { +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +- +- message(MESS_DEBUG, "uncompress_prog is now %s\n", +- newlog->uncompress_prog); +- +- } else if (!strcmp(start, "compressoptions")) { +- char *options; +- +- if (newlog->compress_options_list) { +- free(newlog->compress_options_list); +- newlog->compress_options_list = NULL; +- newlog->compress_options_count = 0; +- } +- +- *endtag = oldchar, start = endtag; +- if (! +- (options = +- readPath(configFile, lineNum, "compressoptions", +- &start))) { +- if (newlog != defConfig) { +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +- +- if (poptParseArgvString(options, +- &newlog->compress_options_count, +- &newlog->compress_options_list)) { +- message(MESS_ERROR, +- "%s:%d invalid compression options\n", +- configFile, lineNum); +- free(options); +- if (newlog != defConfig) { +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +- +- message(MESS_DEBUG, "compress_options is now %s\n", +- options); +- free(options); +- } else if (!strcmp(start, "compressext")) { +- *endtag = oldchar, start = endtag; +- +- freeLogItem (compress_ext); +- +- if (! +- (newlog->compress_ext = +- readPath(configFile, lineNum, "compress-ext", +- &start))) { +- if (newlog != defConfig) { +- logerror = 1; +- continue; +- } else { +- goto error; +- } +- } +- +- message(MESS_DEBUG, "compress_ext is now %s\n", +- newlog->compress_ext); +- } else { +- message(MESS_ERROR, "%s:%d unknown option '%s' " +- "-- ignoring line\n", configFile, lineNum, start); +- +- *endtag = oldchar, start = endtag; +- } +- +- while (isblank(*start)) +- start++; +- +- if (*start != '\n') { +- message(MESS_ERROR, "%s:%d unexpected text\n", configFile, +- lineNum); +- while (*start != '\n') +- start++; +- } +- +- lineNum++; +- start++; +- } else if (*start == '/' || *start == '"' || *start == '\'') { +- if (newlog != defConfig) { +- message(MESS_ERROR, "%s:%d unexpected log filename\n", +- configFile, lineNum); +- logerror = 1; +- continue; +- } +- +- /* If no compression options were found in config file, set +- default values */ +- if (!newlog->compress_prog) +- newlog->compress_prog = strdup(COMPRESS_COMMAND); +- if (!newlog->uncompress_prog) +- newlog->uncompress_prog = strdup(UNCOMPRESS_COMMAND); +- if (!newlog->compress_ext) +- newlog->compress_ext = strdup(COMPRESS_EXT); +- +- /* Allocate a new logInfo structure and insert it into the logs +- queue, copying the actual values from defConfig */ +- if ((newlog = newLogInfo(defConfig)) == NULL) +- goto error; +- +- endtag = start; +- while (*endtag != '{' && *endtag != '\0') +- endtag++; +- if (*endtag != '{') { +- message(MESS_ERROR, "%s:%d missing end of line\n", +- configFile, lineNum); +- } +- *endtag = '\0'; +- +- if (poptParseArgvString(start, &argc, &argv)) { +- message(MESS_ERROR, "%s:%d error parsing filename\n", +- configFile, lineNum); +- goto error; +- } else if (argc < 1) { +- message(MESS_ERROR, +- "%s:%d { expected after log file name(s)\n", +- configFile, lineNum); +- goto error; +- } +- +- newlog->files = NULL; +- newlog->numFiles = 0; +- for (argNum = 0; argNum < argc && logerror != 1; argNum++) { +- if (globerr_msg) { +- free(globerr_msg); +- globerr_msg = NULL; +- } +- +- rc = glob(argv[argNum], GLOB_NOCHECK, globerr, +- &globResult); +- if (rc == GLOB_ABORTED) { +- if (newlog->flags & LOG_FLAG_MISSINGOK) +- continue; +- +- /* We don't yet know whether this stanza has "missingok" +- * set, so store the error message for later. */ +- rc = asprintf(&globerr_msg, "%s:%d glob failed for %s: %s\n", +- configFile, lineNum, argv[argNum], strerror(glob_errno)); +- if (rc == -1) +- globerr_msg = NULL; +- +- globResult.gl_pathc = 0; +- } +- +- newlog->files = +- realloc(newlog->files, +- sizeof(*newlog->files) * (newlog->numFiles + +- globResult. +- gl_pathc)); +- +- for (i = 0; i < globResult.gl_pathc; i++) { +- /* if we glob directories we can get false matches */ +- if (!lstat(globResult.gl_pathv[i], &sb) && +- S_ISDIR(sb.st_mode)) +- continue; +- +- for (log = logs.tqh_first; log != NULL; +- log = log->list.tqe_next) { +- for (k = 0; k < log->numFiles; k++) { +- if (!strcmp(log->files[k], +- globResult.gl_pathv[i])) { +- message(MESS_ERROR, +- "%s:%d duplicate log entry for %s\n", +- configFile, lineNum, +- globResult.gl_pathv[i]); +- logerror = 1; +- goto duperror; +- } ++ break; ++ case STATE_SKIP_CONFIG: ++ if (*start == '}') { ++ state = STATE_DEFAULT; ++ freeTailLogs(1); ++ newlog = defConfig; + } +- } +- +- newlog->files[newlog->numFiles] = +- strdup(globResult.gl_pathv[i]); +- newlog->numFiles++; +- } +-duperror: +- globfree(&globResult); +- } +- +- newlog->pattern = strdup(start); +- +- if (!logerror) +- message(MESS_DEBUG, "reading config info for %s\n", start); +- +- free(argv); +- +- start = endtag + 1; +- } else if (*start == '}') { +- if (newlog == defConfig) { +- message(MESS_ERROR, "%s:%d unexpected }\n", configFile, +- lineNum); +- goto error; +- } +- if (globerr_msg) { +- if (!(newlog->flags & LOG_FLAG_MISSINGOK)) +- message(MESS_ERROR, globerr_msg); +- free(globerr_msg); +- globerr_msg = NULL; +- if (!(newlog->flags & LOG_FLAG_MISSINGOK)) +- return 1; +- } +- +- if (newlog->oldDir) { +- for (i = 0; i < newlog->numFiles; i++) { +- char *ld; +- dirName = ourDirName(newlog->files[i]); +- if (stat(dirName, &sb2)) { +- message(MESS_ERROR, +- "%s:%d error verifying log file " +- "path %s: %s\n", configFile, lineNum, +- dirName, strerror(errno)); +- free(dirName); +- goto error; +- } +- ld = alloca(strlen(dirName) + strlen(newlog->oldDir) + +- 2); +- sprintf(ld, "%s/%s", dirName, newlog->oldDir); +- free(dirName); +- +- if (newlog->oldDir[0] != '/') +- dirName = ld; +- else +- dirName = newlog->oldDir; +- if (stat(dirName, &sb)) { +- message(MESS_ERROR, "%s:%d error verifying olddir " +- "path %s: %s\n", configFile, lineNum, +- dirName, strerror(errno)); +- goto error; +- } +- +- if (sb.st_dev != sb2.st_dev) { +- message(MESS_ERROR, +- "%s:%d olddir %s and log file %s " +- "are on different devices\n", configFile, +- lineNum, newlog->oldDir, newlog->files[i]); +- goto error; +- } +- } +- } +- +- newlog = defConfig; +- +- start++; +- while (isblank(*start)) +- start++; +- +- if (*start != '\n') { +- message(MESS_ERROR, "%s:%d, unexpected text after {\n", +- configFile, lineNum); +- } +- } else { +- message(MESS_ERROR, "%s:%d lines must begin with a keyword " +- "or a filename (possibly in double quotes)\n", +- configFile, lineNum); +- +- while (*start != '\n') +- start++; ++ else { ++ if ((key = isolateWord(&start, &buf, length)) == NULL) ++ continue; ++ if ( ++ (strcmp(key, "postrotate") == 0) || ++ (strcmp(key, "prerotate") == 0) || ++ (strcmp(key, "firstrotate") == 0) || ++ (strcmp(key, "lastrotate") == 0) ++ ) { ++ state = STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG; ++ } ++ else { ++ state = STATE_SKIP_LINE | STATE_SKIP_CONFIG; ++ } ++ free(key); ++ key = NULL; ++ } ++ break; ++ } ++ if (key) { ++ free(key); ++ key = NULL; ++ } ++ if (*start == '\n') { + lineNum++; +- start++; + } ++ + } + + if (scriptStart) { +@@ -1521,11 +1455,14 @@ + configFile); + goto error; + } +- munmap(buf, (size_t)(length + 2)); ++ ++ munmap(buf, (size_t) length); + close(fd); + return 0; + error: +- munmap(buf, (size_t)(length + 2)); ++ if (key) ++ free(key); ++ munmap(buf, (size_t) length); + close(fd); + return 1; + } diff --git a/logrotate.spec b/logrotate.spec index 8443c07..9f28658 100644 --- a/logrotate.spec +++ b/logrotate.spec @@ -1,16 +1,18 @@ Summary: Rotates, compresses, removes and mails system log files Name: logrotate Version: 3.7.9 -Release: 4%{?dist} +Release: 5%{?dist} License: GPL+ Group: System Environment/Base Source: https://fedorahosted.org/releases/l/o/logrotate/logrotate-%{version}.tar.gz Patch1: logrotate-3.7.8-man-authors.patch Patch2: logrotate-3.7.9-man-size.patch Patch3: logrotate-3.7.9-man-page.patch +Patch4: logrotate-3.7.9-config.patch +Patch5: logrotate-3.7.9-acl.patch -Requires: coreutils >= 5.92 libsepol libselinux popt -BuildRequires: libselinux-devel popt-devel +Requires: coreutils >= 5.92 libsepol libselinux popt libacl +BuildRequires: libselinux-devel popt-devel libacl-devel BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %description @@ -29,9 +31,11 @@ log files on your system. %patch1 -p2 %patch2 %patch3 -p1 +%patch4 +%patch5 -p2 %build -make %{?_smp_mflags} RPM_OPT_FLAGS="$RPM_OPT_FLAGS" WITH_SELINUX=yes +make %{?_smp_mflags} RPM_OPT_FLAGS="$RPM_OPT_FLAGS" WITH_SELINUX=yes WITH_ACL=yes %install rm -rf $RPM_BUILD_ROOT @@ -59,6 +63,10 @@ rm -rf $RPM_BUILD_ROOT %attr(0644, root, root) %verify(not size md5 mtime) %config(noreplace) %{_localstatedir}/lib/logrotate.status %changelog +* Wed Dec 15 2010 Jan Kaluza 3.7.9-5 +- fix #661181 - fixed SIGBUS when config file is empty or 4096 bytes +- fix #666677 - preserve ACLs when rotating files + * Tue Oct 19 2010 Jan Kaluza 3.7.9-4 - fix #644309 - mention all logrotate params in man page