Radix cross Linux

The main Radix cross Linux repository contains the build scripts of packages, which have the most complete and common functionality for desktop machines

452 Commits   2 Branches   1 Tag
     5         kx #include "queue.h"
     5         kx #include <limits.h>
     5         kx #include <ctype.h>
     5         kx #include <dirent.h>
     5         kx #include <errno.h>
     5         kx #include <fcntl.h>
     5         kx #include <glob.h>
     5         kx #include <grp.h>
     5         kx #include <popt.h>
     5         kx #include <pwd.h>
     5         kx #include <stdio.h>
     5         kx #include <stdlib.h>
     5         kx #include <string.h>
     5         kx #include <sys/stat.h>
     5         kx #include <time.h>
     5         kx #include <unistd.h>
     5         kx #include <assert.h>
     5         kx #include <wchar.h>
     5         kx #include <wctype.h>
     5         kx #include <fnmatch.h>
     5         kx #include <sys/mman.h>
     5         kx #include <libgen.h>
     5         kx 
     5         kx #if !defined(PATH_MAX) && defined(__FreeBSD__)
     5         kx #include <sys/param.h>
     5         kx #endif
     5         kx 
     5         kx #include "log.h"
     5         kx #include "logrotate.h"
     5         kx 
     5         kx struct logInfoHead logs;
     5         kx 
     5         kx #if !defined(GLOB_ABORTED) && defined(GLOB_ABEND)
     5         kx #define GLOB_ABORTED GLOB_ABEND
     5         kx #endif
     5         kx 
     5         kx #define REALLOC_STEP            10
     5         kx #define GLOB_STR_REALLOC_STEP   0x100
     5         kx 
     5         kx #if defined(SunOS) && !defined(isblank)
     5         kx #define isblank(c) ( ( (c) == ' ' || (c) == '\t' ) ? 1 : 0 )
     5         kx #endif
     5         kx 
     5         kx #ifdef __hpux
     5         kx #include "asprintf.c"
     5         kx #endif
     5         kx 
     5         kx #if !defined(HAVE_SECURE_GETENV)
     5         kx #define secure_getenv getenv
     5         kx #endif
     5         kx 
     5         kx #if !defined(HAVE_ASPRINTF) && !defined(_FORTIFY_SOURCE)
     5         kx #include <stdarg.h>
     5         kx 
     5         kx int asprintf(char **string_ptr, const char *format, ...)
     5         kx {
     5         kx     va_list arg;
     5         kx     char *str;
     5         kx     int size;
     5         kx     int rv;
     5         kx 
     5         kx     va_start(arg, format);
     5         kx     size = vsnprintf(NULL, 0, format, arg);
     5         kx     size++;
     5         kx     va_end(arg);
     5         kx     va_start(arg, format);
     5         kx     str = malloc(size);
     5         kx     if (str == NULL) {
     5         kx         va_end(arg);
     5         kx         /*
     5         kx          * Strictly speaking, GNU asprintf doesn't do this,
     5         kx          * but the caller isn't checking the return value.
     5         kx          */
     5         kx         message_OOM();
     5         kx         exit(1);
     5         kx     }
     5         kx     rv = vsnprintf(str, size, format, arg);
     5         kx     va_end(arg);
     5         kx 
     5         kx     *string_ptr = str;
     5         kx     return (rv);
     5         kx }
     5         kx 
     5         kx #endif
     5         kx 
     5         kx #if !defined(HAVE_STRNDUP)
     5         kx char *strndup(const char *s, size_t n)
     5         kx {
     5         kx     size_t nAvail;
     5         kx     char *p;
     5         kx 
     5         kx     /* min() */
     5         kx     nAvail = strlen(s) + 1;
     5         kx     if ( (n + 1) < nAvail)
     5         kx         nAvail = n + 1;
     5         kx 
     5         kx     p = malloc(nAvail);
     5         kx     if (!p)
     5         kx         return NULL;
     5         kx     memcpy(p, s, nAvail);
     5         kx     p[nAvail - 1] = 0;
     5         kx     return p;
     5         kx }
     5         kx #endif
     5         kx 
     5         kx /* list of compression commands and the corresponding file extensions */
     5         kx struct compress_cmd_item {
     5         kx     const char *cmd;
     5         kx     const char *ext;
     5         kx };
     5         kx static const struct compress_cmd_item compress_cmd_list[] = {
     5         kx     {"gzip", ".gz"},
     5         kx     {"bzip2", ".bz2"},
     5         kx     {"xz", ".xz"},
     5         kx     {"zstd", ".zst"},
     5         kx     {"compress", ".Z"},
     5         kx     {"zip", "zip"},
     5         kx };
     5         kx static const unsigned compress_cmd_list_size = sizeof(compress_cmd_list)
     5         kx     / sizeof(compress_cmd_list[0]);
     5         kx 
     5         kx enum {
     5         kx     STATE_DEFAULT = 2,
     5         kx     STATE_SKIP_LINE = 4,
     5         kx     STATE_DEFINITION_END = 8,
     5         kx     STATE_SKIP_CONFIG = 16,
     5         kx     STATE_LOAD_SCRIPT = 32,
     5         kx     STATE_ERROR = 64,
     5         kx };
     5         kx 
     5         kx static const char *defTabooExts[] = {
     5         kx     ",v",
     5         kx     ".bak",
     5         kx     ".cfsaved",
     5         kx     ".disabled",
     5         kx     ".dpkg-bak",
     5         kx     ".dpkg-del",
     5         kx     ".dpkg-dist",
     5         kx     ".dpkg-new",
     5         kx     ".dpkg-old",
     5         kx     ".dpkg-tmp",
     5         kx     ".rhn-cfg-tmp-*",
     5         kx     ".rpmnew",
     5         kx     ".rpmorig",
     5         kx     ".rpmsave",
     5         kx     ".swp",
     5         kx     ".ucf-dist",
     5         kx     ".ucf-new",
     5         kx     ".ucf-old",
     5         kx     ".new",
     5         kx     ".old",
     5         kx     ".orig",
     5         kx     ".bak",
     5         kx     "~"
     5         kx };
     5         kx static const unsigned defTabooCount = sizeof(defTabooExts) / sizeof(char *);
     5         kx 
     5         kx /* I shouldn't use globals here :-( */
     5         kx static char **tabooPatterns = NULL;
     5         kx static unsigned tabooCount = 0;
     5         kx static int glob_errno = 0;
     5         kx 
     5         kx static int readConfigFile(const char *configFile, struct logInfo *defConfig);
     5         kx static int globerr(const char *pathname, int theerr);
     5         kx 
     5         kx static char *isolateLine(char **strt, char **buf, size_t length) {
     5         kx     char *endtag, *start, *tmp;
     5         kx     const char *max = *buf + length;
     5         kx     char *key;
     5         kx 
     5         kx     start = *strt;
     5         kx     endtag = start;
     5         kx     while (endtag < max && *endtag != '\n') {
     5         kx         endtag++;}
     5         kx     if (max < endtag)
     5         kx         return NULL;
     5         kx     tmp = endtag - 1;
     5         kx     while (isspace((unsigned char)*endtag))
     5         kx         endtag--;
     5         kx     key = strndup(start, (size_t)(endtag - start + 1));
     5         kx     if (key == NULL) {
     5         kx         message_OOM();
     5         kx         return NULL;
     5         kx     }
     5         kx     *strt = tmp;
     5         kx     return key;
     5         kx }
     5         kx 
     5         kx static char *isolateValue(const char *fileName, int lineNum, const char *key,
     5         kx                           char **startPtr, char **buf, size_t length)
     5         kx {
     5         kx     char *chptr = *startPtr;
     5         kx     const char *max = *startPtr + length;
     5         kx 
     5         kx     while (chptr < max && isblank((unsigned char)*chptr))
     5         kx         chptr++;
     5         kx     if (chptr < max && *chptr == '=') {
     5         kx         chptr++;
     5         kx         while ( chptr < max && isblank((unsigned char)*chptr))
     5         kx             chptr++;
     5         kx     }
     5         kx 
     5         kx     if (chptr < max && *chptr == '\n') {
     5         kx         message(MESS_ERROR, "%s:%d argument expected after %s\n",
     5         kx                 fileName, lineNum, key);
     5         kx         return NULL;
     5         kx     }
     5         kx 
     5         kx     *startPtr = chptr;
     5         kx     return isolateLine(startPtr, buf, length);
     5         kx }
     5         kx 
     5         kx static char *isolateWord(char **strt, char **buf, size_t length) {
     5         kx     char *endtag, *start;
     5         kx     const char *max = *buf + length;
     5         kx     char *key;
     5         kx     start = *strt;
     5         kx     while (start < max && isblank((unsigned char)*start))
     5         kx         start++;
     5         kx     endtag = start;
     5         kx     while (endtag < max && isalpha((unsigned char)*endtag)) {
     5         kx         endtag++;}
     5         kx     if (max < endtag)
     5         kx         return NULL;
     5         kx     key = strndup(start, (size_t)(endtag - start));
     5         kx     if (key == NULL) {
     5         kx         message_OOM();
     5         kx         return NULL;
     5         kx     }
     5         kx     *strt = endtag;
     5         kx     return key;
     5         kx }
     5         kx 
     5         kx static char *readPath(const char *configFile, int lineNum, const char *key,
     5         kx                       char **startPtr, char **buf, size_t length)
     5         kx {
     5         kx     char *path = isolateValue(configFile, lineNum, key, startPtr, buf, length);
     5         kx     if (path != NULL) {
     5         kx         wchar_t pwc;
     5         kx         size_t len;
     5         kx         const char *chptr = path;
     5         kx 
     5         kx         while (*chptr && (len = mbrtowc(&pwc, chptr, strlen(chptr), NULL)) != 0) {
     5         kx             if (len == (size_t)(-1) || len == (size_t)(-2) || !iswprint((wint_t)pwc) || iswblank((wint_t)pwc)) {
     5         kx                 message(MESS_ERROR, "%s:%d bad %s path %s\n",
     5         kx                         configFile, lineNum, key, path);
     5         kx                 free(path);
     5         kx                 return NULL;
     5         kx             }
     5         kx             chptr += len;
     5         kx         }
     5         kx     }
     5         kx     return path;
     5         kx }
     5         kx 
     5         kx /* set *pUid to UID of the given user, return non-zero on failure */
     5         kx static int resolveUid(const char *userName, uid_t *pUid)
     5         kx {
     5         kx     const struct passwd *pw;
     5         kx     char *endptr;
     5         kx     unsigned long int parsed_uid;
     5         kx 
     5         kx #ifdef __CYGWIN__
     5         kx     if (strcmp(userName, "root") == 0) {
     5         kx         *pUid = 0;
     5         kx         return 0;
     5         kx     }
     5         kx #endif
     5         kx 
     5         kx     pw = getpwnam(userName);
     5         kx     if (pw) {
     5         kx         *pUid = pw->pw_uid;
     5         kx         return 0;
     5         kx     }
     5         kx 
     5         kx     parsed_uid = strtoul(userName, &endptr, 10);
     5         kx     if (userName[0] != '\0' &&
     5         kx         *endptr == '\0' &&
     5         kx         parsed_uid < INT_MAX && /* parsed_uid != ULONG_MAX && */
     5         kx         getpwuid((uid_t)parsed_uid) != NULL) {
     5         kx 
     5         kx         *pUid = (uid_t)parsed_uid;
     5         kx         return 0;
     5         kx     }
     5         kx 
     5         kx     return -1;
     5         kx }
     5         kx 
     5         kx /* set *pGid to GID of the given group, return non-zero on failure */
     5         kx static int resolveGid(const char *groupName, gid_t *pGid)
     5         kx {
     5         kx     const struct group *gr;
     5         kx     char *endptr;
     5         kx     unsigned long int parsed_gid;
     5         kx 
     5         kx #ifdef __CYGWIN__
     5         kx     if (strcmp(groupName, "root") == 0) {
     5         kx         *pGid = 0;
     5         kx         return 0;
     5         kx     }
     5         kx #endif
     5         kx 
     5         kx     gr = getgrnam(groupName);
     5         kx     if (gr) {
     5         kx         *pGid = gr->gr_gid;
     5         kx         return 0;
     5         kx     }
     5         kx 
     5         kx     parsed_gid = strtoul(groupName, &endptr, 10);
     5         kx     if (groupName[0] != '\0' &&
     5         kx         *endptr == '\0' &&
     5         kx         parsed_gid < INT_MAX && /* parsed_gid != ULONG_MAX && */
     5         kx         getgrgid((gid_t)parsed_gid) != NULL) {
     5         kx 
     5         kx         *pGid = (gid_t)parsed_gid;
     5         kx         return 0;
     5         kx     }
     5         kx 
     5         kx     return -1;
     5         kx }
     5         kx 
     5         kx static int readModeUidGid(const char *configFile, int lineNum, char *key,
     5         kx                           const char *directive, mode_t *mode, uid_t *pUid,
     5         kx                           gid_t *pGid)
     5         kx {
     5         kx     char u[200], g[200];
     5         kx     mode_t m = 0;
     5         kx     char tmp;
     5         kx     int rc;
     5         kx 
     5         kx     if (!strcmp("su", directive))
     5         kx         /* do not read <mode> for the 'su' directive */
     5         kx         rc = 0;
     5         kx     else {
     5         kx         unsigned short int parsed_mode;
     5         kx         rc = sscanf(key, "%ho %199s %199s%c", &parsed_mode, u, g, &tmp);
     5         kx         m = parsed_mode;
     5         kx     }
     5         kx 
     5         kx     /* We support 'key <owner> <group> notation now */
     5         kx     if (rc == 0) {
     5         kx         rc = sscanf(key, "%199s %199s%c", u, g, &tmp);
     5         kx         /* Simulate that we have read mode and keep the default value. */
     5         kx         if (rc > 0) {
     5         kx             m = *mode;
     5         kx             rc += 1;
     5         kx         }
     5         kx     }
     5         kx 
     5         kx     if (rc == 4) {
     5         kx         message(MESS_ERROR, "%s:%d extra arguments for "
     5         kx                 "%s\n", configFile, lineNum, directive);
     5         kx         return -1;
     5         kx     }
     5         kx 
     5         kx     if (rc > 0) {
     5         kx         *mode = m;
     5         kx     }
     5         kx 
     5         kx     if (rc > 1) {
     5         kx         if (resolveUid(u, pUid) != 0) {
     5         kx             message(MESS_ERROR, "%s:%d unknown user '%s'\n",
     5         kx                     configFile, lineNum, u);
     5         kx             return -1;
     5         kx         }
     5         kx     }
     5         kx     if (rc > 2) {
     5         kx         if (resolveGid(g, pGid) != 0) {
     5         kx             message(MESS_ERROR, "%s:%d unknown group '%s'\n",
     5         kx                     configFile, lineNum, g);
     5         kx             return -1;
     5         kx         }
     5         kx     }
     5         kx 
     5         kx     return 0;
     5         kx }
     5         kx 
     5         kx static char *readAddress(const char *configFile, int lineNum, const char *key,
     5         kx                          char **startPtr, char **buf, size_t length)
     5         kx {
     5         kx     char *start = *startPtr;
     5         kx     char *address = isolateValue(configFile, lineNum, key, startPtr, buf, length);
     5         kx 
     5         kx     if (address != NULL) {
     5         kx         /* validate the address */
     5         kx         const char *chptr = address;
     5         kx         while (isprint((unsigned char) *chptr) && *chptr != ' ') {
     5         kx             chptr++;
     5         kx         }
     5         kx 
     5         kx         if (*chptr) {
     5         kx             message(MESS_ERROR, "%s:%d bad %s address %s\n",
     5         kx                     configFile, lineNum, key, start);
     5         kx             free(address);
     5         kx             return NULL;
     5         kx         }
     5         kx     }
     5         kx 
     5         kx     return address;
     5         kx }
     5         kx 
     5         kx static int do_mkdir(const char *path, mode_t mode, uid_t uid, gid_t gid) {
     5         kx     if (mkdir(path, mode) == 0) {
     5         kx         /* newly created directory, set the owner and permissions */
     5         kx         if (chown(path, uid, gid) != 0) {
     5         kx             message(MESS_ERROR, "error setting owner of %s to uid %u and gid %u: %s\n",
     5         kx                     path, (unsigned) uid, (unsigned) gid, strerror(errno));
     5         kx             return -1;
     5         kx         }
     5         kx 
     5         kx         if (chmod(path, mode) != 0) {
     5         kx             message(MESS_ERROR, "error setting permissions of %s to 0%o: %s\n",
     5         kx                     path, mode, strerror(errno));
     5         kx             return -1;
     5         kx         }
     5         kx 
     5         kx         return 0;
     5         kx     }
     5         kx 
     5         kx     if (errno == EEXIST) {
     5         kx         /* path already exists, check whether it is a directory or not */
     5         kx         struct stat sb;
     5         kx         if ((stat(path, &sb) == 0) && S_ISDIR(sb.st_mode))
     5         kx             return 0;
     5         kx 
     5         kx         message(MESS_ERROR, "path %s already exists, but it is not a directory\n", path);
     5         kx         errno = ENOTDIR;
     5         kx         return -1;
     5         kx     }
     5         kx 
     5         kx     message(MESS_ERROR, "error creating %s: %s\n", path, strerror(errno));
     5         kx     return -1;
     5         kx }
     5         kx 
     5         kx static int mkpath(const char *path, mode_t mode, uid_t uid, gid_t gid) {
     5         kx     char *pp;
     5         kx     char *sp;
     5         kx     int rv;
     5         kx     char *copypath = strdup(path);
     5         kx 
     5         kx     if (!copypath) {
     5         kx         message_OOM();
     5         kx         return 1;
     5         kx     }
     5         kx 
     5         kx     rv = 0;
     5         kx     pp = copypath;
     5         kx     while (rv == 0 && (sp = strchr(pp, '/')) != NULL) {
     5         kx         if (sp != pp) {
     5         kx             *sp = '\0';
     5         kx             rv = do_mkdir(copypath, mode, uid, gid);
     5         kx             *sp = '/';
     5         kx         }
     5         kx         pp = sp + 1;
     5         kx     }
     5         kx     if (rv == 0) {
     5         kx         rv = do_mkdir(path, mode, uid, gid);
     5         kx     }
     5         kx     free(copypath);
     5         kx     return rv;
     5         kx }
     5         kx 
     5         kx static int checkFile(const char *fname)
     5         kx {
     5         kx     unsigned i;
     5         kx 
     5         kx     /* Check if fname is '.' or '..'; if so, return false */
     5         kx     if (fname[0] == '.' && (!fname[1] || (fname[1] == '.' && !fname[2])))
     5         kx         return 0;
     5         kx 
     5         kx     /* Check if fname is ending in a taboo-extension; if so, return false */
     5         kx     for (i = 0; i < tabooCount; i++) {
     5         kx         const char *pattern = tabooPatterns[i];
     5         kx         if (!fnmatch(pattern, fname, FNM_PERIOD))
     5         kx         {
     5         kx             message(MESS_DEBUG, "Ignoring %s, because of %s pattern match\n",
     5         kx                     fname, pattern);
     5         kx             return 0;
     5         kx         }
     5         kx     }
     5         kx     /* All checks have been passed; return true */
     5         kx     return 1;
     5         kx }
     5         kx 
     5         kx /* Used by qsort to sort filelist */
     5         kx static int compar(const void *p, const void *q)
     5         kx {
     5         kx     return strcoll(*((char * const*) p), *((char * const*) q));
     5         kx }
     5         kx 
     5         kx /* Free memory blocks pointed to by pointers in a 2d array and the array itself */
     5         kx static void free_2d_array(char **array, unsigned lines_count)
     5         kx {
     5         kx     unsigned i;
     5         kx     for (i = 0; i < lines_count; ++i)
     5         kx         free(array[i]);
     5         kx     free(array);
     5         kx }
     5         kx 
     5         kx #define MEMBER_COPY(dest, src) \
     5         kx     do { \
     5         kx         if ((src) && rv == 0) { \
     5         kx             (dest) = strdup(src); \
     5         kx             if ((dest) == NULL) { \
     5         kx                 message_OOM(); \
     5         kx                 rv = 1; \
     5         kx             } \
     5         kx         } else { \
     5         kx             (dest) = NULL; \
     5         kx         } \
     5         kx     } while (0)
     5         kx static int copyLogInfo(struct logInfo *to, const struct logInfo *from)
     5         kx {
     5         kx     int rv = 0;
     5         kx 
     5         kx     memset(to, 0, sizeof(*to));
     5         kx     MEMBER_COPY(to->oldDir, from->oldDir);
     5         kx     to->criterium = from->criterium;
     5         kx     to->weekday = from->weekday;
     5         kx     to->threshold = from->threshold;
     5         kx     to->minsize = from->minsize;
     5         kx     to->maxsize = from->maxsize;
     5         kx     to->rotateCount = from->rotateCount;
     5         kx     to->rotateMinAge = from->rotateMinAge;
     5         kx     to->rotateAge = from->rotateAge;
     5         kx     to->logStart = from->logStart;
     5         kx     MEMBER_COPY(to->pre, from->pre);
     5         kx     MEMBER_COPY(to->post, from->post);
     5         kx     MEMBER_COPY(to->first, from->first);
     5         kx     MEMBER_COPY(to->last, from->last);
     5         kx     MEMBER_COPY(to->preremove, from->preremove);
     5         kx     MEMBER_COPY(to->logAddress , from->logAddress);
     5         kx     MEMBER_COPY(to->extension, from->extension);
     5         kx     MEMBER_COPY(to->compress_prog, from->compress_prog);
     5         kx     MEMBER_COPY(to->uncompress_prog, from->uncompress_prog);
     5         kx     MEMBER_COPY(to->compress_ext, from->compress_ext);
     5         kx     to->flags = from->flags;
     5         kx     to->shred_cycles = from->shred_cycles;
     5         kx     to->createMode = from->createMode;
     5         kx     to->createUid = from->createUid;
     5         kx     to->createGid = from->createGid;
     5         kx     to->suUid = from->suUid;
     5         kx     to->suGid = from->suGid;
     5         kx     to->olddirMode = from->olddirMode;
     5         kx     to->olddirUid = from->olddirUid;
     5         kx     to->olddirGid = from->olddirGid;
     5         kx 
     5         kx     if (from->compress_options_count) {
     5         kx         poptDupArgv(from->compress_options_count, from->compress_options_list,
     5         kx                     &to->compress_options_count,  &to->compress_options_list);
     5         kx         if (to->compress_options_list == NULL) {
     5         kx             message_OOM();
     5         kx             rv = 1;
     5         kx         }
     5         kx     }
     5         kx 
     5         kx     MEMBER_COPY(to->dateformat, from->dateformat);
     5         kx 
     5         kx     to->list = from->list;
     5         kx 
     5         kx     return rv;
     5         kx }
     5         kx #undef MEMBER_COPY
     5         kx 
     5         kx static void freeLogInfo(struct logInfo *log)
     5         kx {
     5         kx     free(log->pattern);
     5         kx     free_2d_array(log->files, log->numFiles);
     5         kx     free(log->oldDir);
     5         kx     free(log->pre);
     5         kx     free(log->post);
     5         kx     free(log->first);
     5         kx     free(log->last);
     5         kx     free(log->preremove);
     5         kx     free(log->logAddress);
     5         kx     free(log->extension);
     5         kx     free(log->compress_prog);
     5         kx     free(log->uncompress_prog);
     5         kx     free(log->compress_ext);
     5         kx     free(log->compress_options_list);
     5         kx     free(log->dateformat);
     5         kx }
     5         kx 
     5         kx static struct logInfo *newLogInfo(const struct logInfo *template)
     5         kx {
     5         kx     struct logInfo *new;
     5         kx 
     5         kx     new = malloc(sizeof(*new));
     5         kx     if (new == NULL) {
     5         kx         message_OOM();
     5         kx         return NULL;
     5         kx     }
     5         kx 
     5         kx     if (copyLogInfo(new, template)) {
     5         kx         freeLogInfo(new);
     5         kx         free(new);
     5         kx         return NULL;
     5         kx     }
     5         kx 
     5         kx     TAILQ_INSERT_TAIL(&logs, new, list);
     5         kx     numLogs++;
     5         kx 
     5         kx     return new;
     5         kx }
     5         kx 
     5         kx static void removeLogInfo(struct logInfo *log)
     5         kx {
     5         kx     if (log == NULL)
     5         kx         return;
     5         kx 
     5         kx     freeLogInfo(log);
     5         kx     TAILQ_REMOVE(&logs, log, list);
     5         kx     free(log);
     5         kx     numLogs--;
     5         kx }
     5         kx 
     5         kx static void freeTailLogs(int num)
     5         kx {
     5         kx     message(MESS_DEBUG, "removing last %d log configs\n", num);
     5         kx 
     5         kx     while (num--)
     5         kx         removeLogInfo(TAILQ_LAST(&logs, logInfoHead));
     5         kx 
     5         kx }
     5         kx 
     5         kx static const char *crit_to_string(enum criterium crit)
     5         kx {
     5         kx     switch (crit) {
     5         kx         case ROT_HOURLY:    return "hourly";
     5         kx         case ROT_DAYS:      return "daily";
     5         kx         case ROT_WEEKLY:    return "weekly";
     5         kx         case ROT_MONTHLY:   return "monthly";
     5         kx         case ROT_YEARLY:    return "yearly";
     5         kx         case ROT_SIZE:      return "size";
     5         kx         default:            return "XXX";
     5         kx     }
     5         kx }
     5         kx 
     5         kx static void set_criterium(enum criterium *pDst, enum criterium src, int *pSet)
     5         kx {
     5         kx     if (*pSet && (*pDst != src)) {
     5         kx         /* we are overriding a previously set criterium */
     5         kx         message(MESS_VERBOSE, "warning: '%s' overrides previously specified '%s'\n",
     5         kx                 crit_to_string(src), crit_to_string(*pDst));
     5         kx     }
     5         kx     *pDst = src;
     5         kx     *pSet = 1;
     5         kx }
     5         kx 
     5         kx static int readConfigPath(const char *path, struct logInfo *defConfig)
     5         kx {
     5         kx     struct stat sb;
     5         kx     int result = 0;
     5         kx     struct logInfo defConfigBackup;
     5         kx 
     5         kx     if (stat(path, &sb)) {
     5         kx         message(MESS_ERROR, "cannot stat %s: %s\n", path, strerror(errno));
     5         kx         return 1;
     5         kx     }
     5         kx 
     5         kx     if (S_ISDIR(sb.st_mode)) {
     5         kx         char **namelist = NULL;
     5         kx         struct dirent *dp;
     5         kx         int here;
     5         kx         unsigned files_count = 0, i;
     5         kx         DIR *dirp;
     5         kx 
     5         kx         if ((here = open(".", O_RDONLY)) == -1) {
     5         kx             message(MESS_ERROR, "cannot open current directory: %s\n",
     5         kx                     strerror(errno));
     5         kx             return 1;
     5         kx         }
     5         kx 
     5         kx         if ((dirp = opendir(path)) == NULL) {
     5         kx             message(MESS_ERROR, "cannot open directory %s: %s\n", path,
     5         kx                     strerror(errno));
     5         kx             close(here);
     5         kx             return 1;
     5         kx         }
     5         kx         while ((dp = readdir(dirp)) != NULL) {
     5         kx             if (checkFile(dp->d_name)) {
     5         kx                 /* Realloc memory for namelist array if necessary */
     5         kx                 if (files_count % REALLOC_STEP == 0) {
     5         kx                     char **p = (char **) realloc(namelist,
     5         kx                             (files_count +
     5         kx                              REALLOC_STEP) * sizeof(char *));
     5         kx                     if (p) {
     5         kx                         namelist = p;
     5         kx                         memset(namelist + files_count, '\0',
     5         kx                                REALLOC_STEP * sizeof(char *));
     5         kx                     } else {
     5         kx                         free_2d_array(namelist, files_count);
     5         kx                         closedir(dirp);
     5         kx                         close(here);
     5         kx                         message_OOM();
     5         kx                         return 1;
     5         kx                     }
     5         kx                 }
     5         kx                 /* Alloc memory for file name */
     5         kx                 namelist[files_count] = strdup(dp->d_name);
     5         kx                 if (namelist[files_count] != NULL) {
     5         kx                     files_count++;
     5         kx                 } else {
     5         kx                     free_2d_array(namelist, files_count);
     5         kx                     closedir(dirp);
     5         kx                     close(here);
     5         kx                     message_OOM();
     5         kx                     return 1;
     5         kx                 }
     5         kx             }
     5         kx         }
     5         kx         closedir(dirp);
     5         kx 
     5         kx         if (files_count > 0) {
     5         kx             qsort(namelist, files_count, sizeof(char *), compar);
     5         kx         } else {
     5         kx             close(here);
     5         kx             return 0;
     5         kx         }
     5         kx 
     5         kx         if (chdir(path)) {
     5         kx             message(MESS_ERROR, "error in chdir(\"%s\"): %s\n", path,
     5         kx                     strerror(errno));
     5         kx             close(here);
     5         kx             free_2d_array(namelist, files_count);
     5         kx             return 1;
     5         kx         }
     5         kx 
     5         kx         for (i = 0; i < files_count; ++i) {
     5         kx             assert(namelist[i] != NULL);
     5         kx             if (copyLogInfo(&defConfigBackup, defConfig)) {
     5         kx                 freeLogInfo(&defConfigBackup);
     5         kx                 close(here);
     5         kx                 free_2d_array(namelist, files_count);
     5         kx                 return 1;
     5         kx             }
     5         kx             if (readConfigFile(namelist[i], defConfig)) {
     5         kx                 message(MESS_ERROR, "found error in file %s, skipping\n", namelist[i]);
     5         kx                 freeLogInfo(defConfig);
     5         kx                 if (copyLogInfo(defConfig, &defConfigBackup)){} /* do not check, we are already in a error path */
     5         kx                 freeLogInfo(&defConfigBackup);
     5         kx                 result = 1;
     5         kx                 continue;
     5         kx             }
     5         kx             freeLogInfo(&defConfigBackup);
     5         kx         }
     5         kx 
     5         kx         if (fchdir(here) < 0) {
     5         kx             message(MESS_ERROR, "could not change directory to '.': %s\n", strerror(errno));
     5         kx         }
     5         kx         close(here);
     5         kx         free_2d_array(namelist, files_count);
     5         kx     } else {
     5         kx         if (copyLogInfo(&defConfigBackup, defConfig)) {
     5         kx             freeLogInfo(&defConfigBackup);
     5         kx             return 1;
     5         kx         }
     5         kx 
     5         kx         if (readConfigFile(path, defConfig)) {
     5         kx             freeLogInfo(defConfig);
     5         kx             if (copyLogInfo(defConfig, &defConfigBackup)){} /* do not check, we are already in a error path */
     5         kx             result = 1;
     5         kx         }
     5         kx         freeLogInfo(&defConfigBackup);
     5         kx     }
     5         kx 
     5         kx     return result;
     5         kx }
     5         kx 
     5         kx int readAllConfigPaths(const char **paths)
     5         kx {
     5         kx     int result = 0;
     5         kx     unsigned i;
     5         kx     const char **file;
     5         kx     struct logInfo defConfig = {
     5         kx         .pattern = NULL,
     5         kx         .files = NULL,
     5         kx         .numFiles = 0,
     5         kx         .oldDir = NULL,
     5         kx         .criterium = ROT_SIZE,
     5         kx         .threshold = 1024 * 1024,
     5         kx         .minsize = 0,
     5         kx         .maxsize = 0,
     5         kx         .rotateCount = 0,
     5         kx         .rotateMinAge = 0,
     5         kx         .rotateAge = 0,
     5         kx         .logStart = -1,
     5         kx         .pre = NULL,
     5         kx         .post = NULL,
     5         kx         .first = NULL,
     5         kx         .last = NULL,
     5         kx         .preremove = NULL,
     5         kx         .logAddress = NULL,
     5         kx         .extension = NULL,
     5         kx         .addextension = NULL,
     5         kx         .compress_prog = NULL,
     5         kx         .uncompress_prog = NULL,
     5         kx         .compress_ext = NULL,
     5         kx         .dateformat = NULL,
     5         kx         .flags = LOG_FLAG_IFEMPTY,
     5         kx         .shred_cycles = 0,
     5         kx         .createMode = NO_MODE,
     5         kx         .createUid = NO_UID,
     5         kx         .createGid = NO_GID,
     5         kx         .olddirMode = NO_MODE,
     5         kx         .olddirUid = NO_UID,
     5         kx         .olddirGid = NO_GID,
     5         kx         .suUid = NO_UID,
     5         kx         .suGid = NO_GID,
     5         kx         .compress_options_list = NULL,
     5         kx         .compress_options_count = 0
     5         kx     };
     5         kx 
     5         kx     tabooPatterns = malloc(sizeof(*tabooPatterns) * defTabooCount);
     5         kx     if (tabooPatterns == NULL) {
     5         kx         message_OOM();
     5         kx         return 1;
     5         kx     }
     5         kx 
     5         kx 
     5         kx     for (i = 0; i < defTabooCount; i++) {
     5         kx         int bytes;
     5         kx         char *pattern = NULL;
     5         kx 
     5         kx         /* generate a pattern by concatenating star (wildcard) to the
     5         kx          * suffix literal
     5         kx          */
     5         kx         bytes = asprintf(&pattern, "*%s", defTabooExts[i]);
     5         kx         if (bytes != -1) {
     5         kx             tabooPatterns[i] = pattern;
     5         kx             tabooCount++;
     5         kx         } else {
     5         kx             free_2d_array(tabooPatterns, tabooCount);
     5         kx             message_OOM();
     5         kx             return 1;
     5         kx         }
     5         kx     }
     5         kx 
     5         kx     for (file = paths; *file; file++) {
     5         kx         if (readConfigPath(*file, &defConfig))
     5         kx             result = 1;
     5         kx     }
     5         kx     free_2d_array(tabooPatterns, tabooCount);
     5         kx     freeLogInfo(&defConfig);
     5         kx     return result;
     5         kx }
     5         kx 
     5         kx static char* parseGlobString(const char *configFile, int lineNum,
     5         kx                              const char *buf, size_t length, char **ppos)
     5         kx {
     5         kx     /* output buffer */
     5         kx     char *globString = NULL;
     5         kx     size_t globStringPos = 0;
     5         kx     size_t globStringAlloc = 0;
     5         kx     enum {
     5         kx         PGS_INIT,   /* picking blanks, looking for '#' */
     5         kx         PGS_DATA,   /* picking data, looking for end of line */
     5         kx         PGS_COMMENT /* skipping comment, looking for end of line */
     5         kx     } state = PGS_INIT;
     5         kx 
     5         kx     /* move the cursor at caller's side while going through the input */
     5         kx     for (; ((size_t)(*ppos - buf) < length) && **ppos; (*ppos)++) {
     5         kx         /* state transition (see above) */
     5         kx         switch (state) {
     5         kx             case PGS_INIT:
     5         kx                 if ('#' == **ppos)
     5         kx                     state = PGS_COMMENT;
     5         kx                 else if (!isspace((unsigned char) **ppos))
     5         kx                     state = PGS_DATA;
     5         kx                 break;
     5         kx 
     5         kx             default:
     5         kx                 if ('\n' == **ppos)
     5         kx                     state = PGS_INIT;
     5         kx         }
     5         kx 
     5         kx         if (PGS_COMMENT == state)
     5         kx             /* skip comment */
     5         kx             continue;
     5         kx 
     5         kx         switch (**ppos) {
     5         kx             case '}':
     5         kx                 message(MESS_ERROR, "%s:%d unexpected } (missing previous '{')\n", configFile, lineNum);
     5         kx                 free(globString);
     5         kx                 return NULL;
     5         kx 
     5         kx             case '{':
     5         kx                 /* NUL-terminate globString */
     5         kx                 assert(globStringPos < globStringAlloc);
     5         kx                 globString[globStringPos] = '\0';
     5         kx                 return globString;
     5         kx 
     5         kx             default:
     5         kx                 break;
     5         kx         }
     5         kx 
     5         kx         /* grow the output buffer if needed */
     5         kx         if (globStringPos + 2 > globStringAlloc) {
     5         kx             char *ptr;
     5         kx             globStringAlloc += GLOB_STR_REALLOC_STEP;
     5         kx             ptr = realloc(globString, globStringAlloc);
     5         kx             if (!ptr) {
     5         kx                 message_OOM();
     5         kx                 free(globString);
     5         kx                 return NULL;
     5         kx             }
     5         kx             globString = ptr;
     5         kx         }
     5         kx 
     5         kx         /* copy a single character */
     5         kx         globString[globStringPos++] = **ppos;
     5         kx     }
     5         kx 
     5         kx     /* premature end of input */
     5         kx     message(MESS_ERROR, "%s:%d missing '{' after log files definition\n", configFile, lineNum);
     5         kx     free(globString);
     5         kx     return NULL;
     5         kx }
     5         kx 
     5         kx static int globerr(const char *pathname, int theerr)
     5         kx {
     5         kx     (void) pathname;
     5         kx 
     5         kx     /* prevent glob() from being aborted in certain cases */
     5         kx     switch (theerr) {
     5         kx         case ENOTDIR:
     5         kx             /* non-directory where directory was expected by the glob */
     5         kx             return 0;
     5         kx 
     5         kx         case ENOENT:
     5         kx             /* most likely symlink with non-existent target */
     5         kx             return 0;
     5         kx 
     5         kx         default:
     5         kx             break;
     5         kx     }
     5         kx 
     5         kx     glob_errno = theerr;
     5         kx 
     5         kx     /* We want the glob operation to abort on error, so return 1 */
     5         kx     return 1;
     5         kx }
     5         kx 
     5         kx #define freeLogItem(what) \
     5         kx     do { \
     5         kx         free(newlog->what); \
     5         kx         newlog->what = NULL; \
     5         kx     } while (0)
     5         kx #define RAISE_ERROR() \
     5         kx     if (newlog != defConfig) { \
     5         kx         state = STATE_ERROR; \
     5         kx         goto next_state; \
     5         kx     } else { \
     5         kx         goto error; \
     5         kx     }
     5         kx #define MAX_NESTING 16U
     5         kx 
     5         kx static int readConfigFile(const char *configFile, struct logInfo *defConfig)
     5         kx {
     5         kx     int fd;
     5         kx     char *buf, *key = NULL;
     5         kx     size_t length;
     5         kx     int lineNum = 1;
     5         kx     char *scriptStart = NULL;
     5         kx     char **scriptDest = NULL;
     5         kx     struct logInfo *newlog = defConfig;
     5         kx     char *start, *chptr;
     5         kx     struct stat sb;
     5         kx     int state = STATE_DEFAULT;
     5         kx     int logerror = 0;
     5         kx     /* to check if incompatible criteria are specified */
     5         kx     int criterium_set = 0;
     5         kx     static unsigned recursion_depth = 0U;
     5         kx     char *globerr_msg = NULL;
     5         kx     int in_config = 0;
     5         kx     struct flock fd_lock = {
     5         kx         .l_start = 0,
     5         kx         .l_len = 0,
     5         kx         .l_whence = SEEK_SET,
     5         kx         .l_type = F_RDLCK
     5         kx     };
     5         kx 
     5         kx     fd = open(configFile, O_RDONLY);
     5         kx     if (fd < 0) {
     5         kx         message(MESS_ERROR, "failed to open config file %s: %s\n",
     5         kx                 configFile, strerror(errno));
     5         kx         return 1;
     5         kx     }
     5         kx     /* We don't want anybody to change the file while we parse it,
     5         kx      * let's try to lock it for reading. */
     5         kx     if (fcntl(fd, F_SETLK, &fd_lock) == -1) {
     5         kx         message(MESS_ERROR, "Could not lock file %s for reading\n",
     5         kx                 configFile);
     5         kx     }
     5         kx     if (fstat(fd, &sb)) {
     5         kx         message(MESS_ERROR, "fstat of %s failed: %s\n", configFile,
     5         kx                 strerror(errno));
     5         kx         close(fd);
     5         kx         return 1;
     5         kx     }
     5         kx     if (!S_ISREG(sb.st_mode)) {
     5         kx         message(MESS_DEBUG,
     5         kx                 "Ignoring %s because it's not a regular file.\n",
     5         kx                 configFile);
     5         kx         close(fd);
     5         kx         return 0;
     5         kx     }
     5         kx 
     5         kx     if (!getpwuid(getuid())) {
     5         kx         message(MESS_ERROR, "Cannot find logrotate UID (%d) in passwd file: %s\n",
     5         kx                 getuid(), strerror(errno));
     5         kx         close(fd);
     5         kx         return 1;
     5         kx     }
     5         kx 
     5         kx     if (getuid() == ROOT_UID) {
     5         kx         if ((sb.st_mode & 07533) != 0400) {
     5         kx             message(MESS_DEBUG,
     5         kx                     "Potentially dangerous mode on %s: 0%o\n",
     5         kx                     configFile, (unsigned) (sb.st_mode & 07777));
     5         kx         }
     5         kx 
     5         kx         if (sb.st_mode & 0022) {
     5         kx             message(MESS_ERROR,
     5         kx                     "Ignoring %s because it is writable by group or others.\n",
     5         kx                     configFile);
     5         kx             close(fd);
     5         kx             return 0;
     5         kx         }
     5         kx 
     5         kx         if (sb.st_uid != ROOT_UID) {
     5         kx             message(MESS_ERROR,
     5         kx                     "Ignoring %s because the file owner is wrong (should be root or user with uid 0).\n",
     5         kx                     configFile);
     5         kx             close(fd);
     5         kx             return 0;
     5         kx         }
     5         kx     }
     5         kx 
     5         kx     length = (size_t)sb.st_size;
     5         kx 
     5         kx     if (length > 0xffffff) {
     5         kx         message(MESS_ERROR, "file %s too large, probably not a config file.\n",
     5         kx                 configFile);
     5         kx         close(fd);
     5         kx         return 1;
     5         kx     }
     5         kx 
     5         kx     /* We can't mmap empty file... */
     5         kx     if (length == 0) {
     5         kx         message(MESS_DEBUG,
     5         kx                 "Ignoring %s because it's empty.\n",
     5         kx                 configFile);
     5         kx         close(fd);
     5         kx         return 0;
     5         kx     }
     5         kx 
     5         kx #ifdef MAP_POPULATE
     5         kx     buf = mmap(NULL, length, PROT_READ,
     5         kx             MAP_PRIVATE | MAP_POPULATE, fd, (off_t) 0);
     5         kx #else /* MAP_POPULATE */
     5         kx     buf = mmap(NULL, length, PROT_READ,
     5         kx             MAP_PRIVATE, fd, (off_t) 0);
     5         kx #endif /* MAP_POPULATE */
     5         kx 
     5         kx     if (buf == MAP_FAILED) {
     5         kx         message(MESS_ERROR, "Error mapping config file %s: %s\n",
     5         kx                 configFile, strerror(errno));
     5         kx         close(fd);
     5         kx         return 1;
     5         kx     }
     5         kx 
     5         kx #ifdef HAVE_MADVISE
     5         kx #ifdef MADV_DONTFORK
     5         kx     madvise(buf, length + 2,
     5         kx             MADV_SEQUENTIAL | MADV_WILLNEED | MADV_DONTFORK);
     5         kx #else /* MADV_DONTFORK */
     5         kx     madvise(buf, length + 2,
     5         kx             MADV_SEQUENTIAL | MADV_WILLNEED);
     5         kx #endif /* MADV_DONTFORK */
     5         kx #endif /* HAVE_MADVISE */
     5         kx 
     5         kx     message(MESS_DEBUG, "reading config file %s\n", configFile);
     5         kx 
     5         kx     for (start = buf; (size_t)(start - buf) < length; start++) {
     5         kx         switch (state) {
     5         kx             case STATE_DEFAULT:
     5         kx                 if (isblank((unsigned char)*start))
     5         kx                     continue;
     5         kx                 /* Skip comment */
     5         kx                 if (*start == '#') {
     5         kx                     state = STATE_SKIP_LINE;
     5         kx                     continue;
     5         kx                 }
     5         kx 
     5         kx                 if (isalpha((unsigned char)*start)) {
     5         kx                     free(key);
     5         kx                     key = isolateWord(&start, &buf, length);
     5         kx                     if (key == NULL)
     5         kx                         continue;
     5         kx                     if (!strcmp(key, "compress")) {
     5         kx                         newlog->flags |= LOG_FLAG_COMPRESS;
     5         kx                     } else if (!strcmp(key, "nocompress")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_COMPRESS;
     5         kx                     } else if (!strcmp(key, "delaycompress")) {
     5         kx                         newlog->flags |= LOG_FLAG_DELAYCOMPRESS;
     5         kx                     } else if (!strcmp(key, "nodelaycompress")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_DELAYCOMPRESS;
     5         kx                     } else if (!strcmp(key, "shred")) {
     5         kx                         newlog->flags |= LOG_FLAG_SHRED;
     5         kx                     } else if (!strcmp(key, "noshred")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_SHRED;
     5         kx                     } else if (!strcmp(key, "sharedscripts")) {
     5         kx                         newlog->flags |= LOG_FLAG_SHAREDSCRIPTS;
     5         kx                     } else if (!strcmp(key, "nosharedscripts")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_SHAREDSCRIPTS;
     5         kx                     } else if (!strcmp(key, "copytruncate")) {
     5         kx                         newlog->flags |= LOG_FLAG_COPYTRUNCATE;
     5         kx                     } else if (!strcmp(key, "nocopytruncate")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_COPYTRUNCATE;
     5         kx                     } else if (!strcmp(key, "renamecopy")) {
     5         kx                         newlog->flags |= LOG_FLAG_TMPFILENAME;
     5         kx                     } else if (!strcmp(key, "norenamecopy")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_TMPFILENAME;
     5         kx                     } else if (!strcmp(key, "copy")) {
     5         kx                         newlog->flags |= LOG_FLAG_COPY;
     5         kx                     } else if (!strcmp(key, "nocopy")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_COPY;
     5         kx                     } else if (!strcmp(key, "ifempty")) {
     5         kx                         newlog->flags |= LOG_FLAG_IFEMPTY;
     5         kx                     } else if (!strcmp(key, "notifempty")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_IFEMPTY;
     5         kx                     } else if (!strcmp(key, "dateext")) {
     5         kx                         newlog->flags |= LOG_FLAG_DATEEXT;
     5         kx                     } else if (!strcmp(key, "nodateext")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_DATEEXT;
     5         kx                     } else if (!strcmp(key, "dateyesterday")) {
     5         kx                         newlog->flags |= LOG_FLAG_DATEYESTERDAY;
     5         kx                     } else if (!strcmp(key, "datehourago")) {
     5         kx                         newlog->flags |= LOG_FLAG_DATEHOURAGO;
     5         kx                     } else if (!strcmp(key, "dateformat")) {
     5         kx                         freeLogItem(dateformat);
     5         kx                         newlog->dateformat = isolateLine(&start, &buf, length);
     5         kx                         if (newlog->dateformat == NULL)
     5         kx                             continue;
     5         kx                     } else if (!strcmp(key, "noolddir")) {
     5         kx                         newlog->oldDir = NULL;
     5         kx                     } else if (!strcmp(key, "mailfirst")) {
     5         kx                         newlog->flags |= LOG_FLAG_MAILFIRST;
     5         kx                     } else if (!strcmp(key, "maillast")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_MAILFIRST;
     5         kx                     } else if (!strcmp(key, "su")) {
     5         kx                         int rv;
     5         kx                         mode_t tmp_mode = NO_MODE;
     5         kx                         free(key);
     5         kx                         key = isolateLine(&start, &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx 
     5         kx                         rv = readModeUidGid(configFile, lineNum, key, "su",
     5         kx                                             &tmp_mode, &newlog->suUid,
     5         kx                                             &newlog->suGid);
     5         kx                         if (rv == -1) {
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx                         else if (tmp_mode != NO_MODE) {
     5         kx                             message(MESS_ERROR, "%s:%d extra arguments for "
     5         kx                                     "su\n", configFile, lineNum);
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx                         else if (newlog->suUid == NO_UID) {
     5         kx                             message(MESS_ERROR, "%s:%d no user for "
     5         kx                                     "su\n", configFile, lineNum);
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx                         else if (newlog->suGid == NO_GID) {
     5         kx                             message(MESS_ERROR, "%s:%d no group for "
     5         kx                                     "su\n", configFile, lineNum);
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx 
     5         kx                         newlog->flags |= LOG_FLAG_SU;
     5         kx                     } else if (!strcmp(key, "create")) {
     5         kx                         int rv;
     5         kx 
     5         kx                         free(key);
     5         kx                         key = isolateLine(&start, &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx 
     5         kx                         rv = readModeUidGid(configFile, lineNum, key, "create",
     5         kx                                             &newlog->createMode, &newlog->createUid,
     5         kx                                             &newlog->createGid);
     5         kx                         if (rv == -1) {
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx 
     5         kx                         newlog->flags |= LOG_FLAG_CREATE;
     5         kx                     } else if (!strcmp(key, "createolddir")) {
     5         kx                         int rv;
     5         kx 
     5         kx                         free(key);
     5         kx                         key = isolateLine(&start, &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx 
     5         kx                         rv = readModeUidGid(configFile, lineNum, key, "createolddir",
     5         kx                                             &newlog->olddirMode, &newlog->olddirUid,
     5         kx                                             &newlog->olddirGid);
     5         kx                         if (rv == -1) {
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx 
     5         kx                         newlog->flags |= LOG_FLAG_OLDDIRCREATE;
     5         kx                     } else if (!strcmp(key, "nocreateolddir")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_OLDDIRCREATE;
     5         kx                     } else if (!strcmp(key, "nocreate")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_CREATE;
     5         kx                     } else if (!strcmp(key, "size") || !strcmp(key, "minsize") ||
     5         kx                             !strcmp(key, "maxsize")) {
     5         kx                         char *opt = key;
     5         kx 
     5         kx                         key = isolateValue(configFile, lineNum, opt, &start, &buf, length);
     5         kx                         if (key && key[0]) {
     5         kx                             off_t size;
     5         kx                             unsigned long multiplier;
     5         kx                             const size_t l = strlen(key) - 1;
     5         kx                             if (key[l] == 'k' || key[l] == 'K') {
     5         kx                                 key[l] = '\0';
     5         kx                                 multiplier = 1024;
     5         kx                             } else if (key[l] == 'M') {
     5         kx                                 key[l] = '\0';
     5         kx                                 multiplier = 1024 * 1024;
     5         kx                             } else if (key[l] == 'G') {
     5         kx                                 key[l] = '\0';
     5         kx                                 multiplier = 1024 * 1024 * 1024;
     5         kx                             } else if (!isdigit((unsigned char)key[l])) {
     5         kx                                 free(opt);
     5         kx                                 message(MESS_ERROR, "%s:%d unknown unit '%c'\n",
     5         kx                                         configFile, lineNum, key[l]);
     5         kx                                 RAISE_ERROR();
     5         kx                             } else {
     5         kx                                 multiplier = 1;
     5         kx                             }
     5         kx 
     5         kx                             size = (off_t) (multiplier * strtoull(key, &chptr, 0));
     5         kx                             if (*chptr || size < 0) {
     5         kx                                 message(MESS_ERROR, "%s:%d bad size '%s'\n",
     5         kx                                         configFile, lineNum, key);
     5         kx                                 free(opt);
     5         kx                                 RAISE_ERROR();
     5         kx                             }
     5         kx                             if (!strncmp(opt, "size", 4)) {
     5         kx                                 set_criterium(&newlog->criterium, ROT_SIZE, &criterium_set);
     5         kx                                 newlog->threshold = size;
     5         kx                             } else if (!strncmp(opt, "maxsize", 7)) {
     5         kx                                 newlog->maxsize = size;
     5         kx                             } else {
     5         kx                                 newlog->minsize = size;
     5         kx                             }
     5         kx                             free(opt);
     5         kx                         }
     5         kx                         else {
     5         kx                             free(opt);
     5         kx                             continue;
     5         kx                         }
     5         kx                     } else if (!strcmp(key, "shredcycles")) {
     5         kx                         free(key);
     5         kx                         key = isolateValue(configFile, lineNum, "shred cycles",
     5         kx                                            &start, &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx                         newlog->shred_cycles = (int)strtoul(key, &chptr, 0);
     5         kx                         if (*chptr || newlog->shred_cycles < 0) {
     5         kx                             message(MESS_ERROR, "%s:%d bad shred cycles '%s'\n",
     5         kx                                     configFile, lineNum, key);
     5         kx                             goto error;
     5         kx                         }
     5         kx                     } else if (!strcmp(key, "hourly")) {
     5         kx                         set_criterium(&newlog->criterium, ROT_HOURLY, &criterium_set);
     5         kx                     } else if (!strcmp(key, "daily")) {
     5         kx                         set_criterium(&newlog->criterium, ROT_DAYS, &criterium_set);
     5         kx                         newlog->threshold = 1;
     5         kx                     } else if (!strcmp(key, "monthly")) {
     5         kx                         set_criterium(&newlog->criterium, ROT_MONTHLY, &criterium_set);
     5         kx                     } else if (!strcmp(key, "weekly")) {
     5         kx                         unsigned weekday;
     5         kx                         char tmp;
     5         kx                         set_criterium(&newlog->criterium, ROT_WEEKLY, &criterium_set);
     5         kx                         free(key);
     5         kx                         key = isolateLine(&start, &buf, length);
     5         kx                         if (key == NULL || key[0] == '\0') {
     5         kx                             /* default to Sunday if no argument was given */
     5         kx                             newlog->weekday = 0;
     5         kx                             continue;
     5         kx                         }
     5         kx 
     5         kx                         if (1 == sscanf(key, "%u%c", &weekday, &tmp) && weekday <= 7) {
     5         kx                             /* use the selected weekday, 7 means "once per week" */
     5         kx                             newlog->weekday = weekday;
     5         kx                             continue;
     5         kx                         }
     5         kx                         message(MESS_ERROR, "%s:%d bad weekly directive '%s'\n",
     5         kx                                 configFile, lineNum, key);
     5         kx                         goto error;
     5         kx                     } else if (!strcmp(key, "yearly")) {
     5         kx                         set_criterium(&newlog->criterium, ROT_YEARLY, &criterium_set);
     5         kx                     } else if (!strcmp(key, "rotate")) {
     5         kx                         free(key);
     5         kx                         key = isolateValue(configFile, lineNum, "rotate count", &start,
     5         kx                                            &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx                         newlog->rotateCount = (int)strtol(key, &chptr, 0);
     5         kx                         if (*chptr || newlog->rotateCount < -1) {
     5         kx                             message(MESS_ERROR,
     5         kx                                     "%s:%d bad rotation count '%s'\n",
     5         kx                                     configFile, lineNum, key);
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx                     } else if (!strcmp(key, "start")) {
     5         kx                         free(key);
     5         kx                         key = isolateValue(configFile, lineNum, "start count", &start,
     5         kx                                            &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx                         newlog->logStart = (int)strtoul(key, &chptr, 0);
     5         kx                         if (*chptr || newlog->logStart < 0) {
     5         kx                             message(MESS_ERROR, "%s:%d bad start count '%s'\n",
     5         kx                                     configFile, lineNum, key);
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx                     } else if (!strcmp(key, "minage")) {
     5         kx                         free(key);
     5         kx                         key = isolateValue(configFile, lineNum, "minage count", &start,
     5         kx                                            &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx                         newlog->rotateMinAge = (int)strtoul(key, &chptr, 0);
     5         kx                         if (*chptr || newlog->rotateMinAge < 0) {
     5         kx                             message(MESS_ERROR, "%s:%d bad minimum age '%s'\n",
     5         kx                                     configFile, lineNum, start);
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx                     } else if (!strcmp(key, "maxage")) {
     5         kx                         free(key);
     5         kx                         key = isolateValue(configFile, lineNum, "maxage count", &start,
     5         kx                                            &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx                         newlog->rotateAge = (int)strtoul(key, &chptr, 0);
     5         kx                         if (*chptr || newlog->rotateAge < 0) {
     5         kx                             message(MESS_ERROR, "%s:%d bad maximum age '%s'\n",
     5         kx                                     configFile, lineNum, start);
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx                     } else if (!strcmp(key, "errors")) {
     5         kx                         message(MESS_DEBUG,
     5         kx                                 "%s: %d: the errors directive is deprecated and no longer used.\n",
     5         kx                                 configFile, lineNum);
     5         kx                     } else if (!strcmp(key, "mail")) {
     5         kx                         freeLogItem(logAddress);
     5         kx                         if (!(newlog->logAddress = readAddress(configFile, lineNum,
     5         kx                                         "mail", &start, &buf, length))) {
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx                         else continue;
     5         kx                     } else if (!strcmp(key, "nomail")) {
     5         kx                         freeLogItem(logAddress);
     5         kx                     } else if (!strcmp(key, "missingok")) {
     5         kx                         newlog->flags |= LOG_FLAG_MISSINGOK;
     5         kx                     } else if (!strcmp(key, "nomissingok")) {
     5         kx                         newlog->flags &= ~LOG_FLAG_MISSINGOK;
     5         kx                     } else if (!strcmp(key, "prerotate")) {
     5         kx                         freeLogItem (pre);
     5         kx                         scriptStart = start;
     5         kx                         scriptDest = &newlog->pre;
     5         kx                         state = STATE_LOAD_SCRIPT;
     5         kx                     } else if (!strcmp(key, "firstaction")) {
     5         kx                         freeLogItem (first);
     5         kx                         scriptStart = start;
     5         kx                         scriptDest = &newlog->first;
     5         kx                         state = STATE_LOAD_SCRIPT;
     5         kx                     } else if (!strcmp(key, "postrotate")) {
     5         kx                         freeLogItem (post);
     5         kx                         scriptStart = start;
     5         kx                         scriptDest = &newlog->post;
     5         kx                         state = STATE_LOAD_SCRIPT;
     5         kx                     } else if (!strcmp(key, "lastaction")) {
     5         kx                         freeLogItem (last);
     5         kx                         scriptStart = start;
     5         kx                         scriptDest = &newlog->last;
     5         kx                         state = STATE_LOAD_SCRIPT;
     5         kx                     } else if (!strcmp(key, "preremove")) {
     5         kx                         freeLogItem (preremove);
     5         kx                         scriptStart = start;
     5         kx                         scriptDest = &newlog->preremove;
     5         kx                         state = STATE_LOAD_SCRIPT;
     5         kx                     } else if (!strcmp(key, "tabooext")) {
     5         kx                         char *endtag;
     5         kx 
     5         kx                         if (newlog != defConfig) {
     5         kx                             message(MESS_ERROR,
     5         kx                                     "%s:%d tabooext may not appear inside "
     5         kx                                     "of log file definition\n", configFile,
     5         kx                                     lineNum);
     5         kx                             state = STATE_ERROR;
     5         kx                             continue;
     5         kx                         }
     5         kx                         free(key);
     5         kx                         key = isolateValue(configFile, lineNum, "tabooext", &start,
     5         kx                                            &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx                         endtag = key;
     5         kx                         if (*endtag == '+') {
     5         kx                             endtag++;
     5         kx                             while (isspace((unsigned char)*endtag) && *endtag)
     5         kx                                 endtag++;
     5         kx                         } else {
     5         kx                             free_2d_array(tabooPatterns, tabooCount);
     5         kx                             tabooCount = 0;
     5         kx                             /* realloc of NULL is safe by definition */
     5         kx                             tabooPatterns = NULL;
     5         kx                         }
     5         kx 
     5         kx                         while (*endtag) {
     5         kx                             int bytes;
     5         kx                             char *pattern = NULL;
     5         kx 
     5         kx                             chptr = endtag;
     5         kx                             while (!isspace((unsigned char)*chptr) && *chptr != ',' && *chptr)
     5         kx                                 chptr++;
     5         kx 
     5         kx                             /* accept only non-empty patterns to avoid exclusion of everything */
     5         kx                             if (endtag < chptr) {
     5         kx                                 char **tmp = realloc(tabooPatterns, sizeof(*tabooPatterns) *
     5         kx                                         (tabooCount + 1));
     5         kx                                 if (tmp == NULL) {
     5         kx                                     message_OOM();
     5         kx                                     RAISE_ERROR();
     5         kx                                 }
     5         kx                                 tabooPatterns = tmp;
     5         kx                                 bytes = asprintf(&pattern, "*%.*s", (int)(chptr - endtag), endtag);
     5         kx 
     5         kx                                 /* should test for malloc() failure */
     5         kx                                 assert(bytes != -1);
     5         kx                                 tabooPatterns[tabooCount] = pattern;
     5         kx                                 tabooCount++;
     5         kx                             }
     5         kx 
     5         kx                             endtag = chptr;
     5         kx                             if (*endtag == ',')
     5         kx                                 endtag++;
     5         kx                             while (*endtag && isspace((unsigned char)*endtag))
     5         kx                                 endtag++;
     5         kx                         }
     5         kx                     } else if (!strcmp(key, "taboopat")) {
     5         kx                         char *endtag;
     5         kx 
     5         kx                         if (newlog != defConfig) {
     5         kx                             message(MESS_ERROR,
     5         kx                                     "%s:%d taboopat may not appear inside "
     5         kx                                     "of log file definition\n", configFile,
     5         kx                                     lineNum);
     5         kx                             state = STATE_ERROR;
     5         kx                             continue;
     5         kx                         }
     5         kx                         free(key);
     5         kx                         key = isolateValue(configFile, lineNum, "taboopat", &start,
     5         kx                                            &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx 
     5         kx                         endtag = key;
     5         kx                         if (*endtag == '+') {
     5         kx                             endtag++;
     5         kx                             while (isspace((unsigned char)*endtag) && *endtag)
     5         kx                                 endtag++;
     5         kx                         } else {
     5         kx                             free_2d_array(tabooPatterns, tabooCount);
     5         kx                             tabooCount = 0;
     5         kx                             /* realloc of NULL is safe by definition */
     5         kx                             tabooPatterns = NULL;
     5         kx                         }
     5         kx 
     5         kx                         while (*endtag) {
     5         kx                             int bytes;
     5         kx                             char *pattern = NULL;
     5         kx                             char **tmp;
     5         kx 
     5         kx                             chptr = endtag;
     5         kx                             while (!isspace((unsigned char)*chptr) && *chptr != ',' && *chptr)
     5         kx                                 chptr++;
     5         kx 
     5         kx                             tmp = realloc(tabooPatterns, sizeof(*tabooPatterns) *
     5         kx                                     (tabooCount + 1));
     5         kx                             if (tmp == NULL) {
     5         kx                                 message_OOM();
     5         kx                                 RAISE_ERROR();
     5         kx                             }
     5         kx                             tabooPatterns = tmp;
     5         kx                             bytes = asprintf(&pattern, "%.*s", (int)(chptr - endtag), endtag);
     5         kx 
     5         kx                             /* should test for malloc() failure */
     5         kx                             assert(bytes != -1);
     5         kx                             tabooPatterns[tabooCount] = pattern;
     5         kx                             tabooCount++;
     5         kx 
     5         kx                             endtag = chptr;
     5         kx                             if (*endtag == ',')
     5         kx                                 endtag++;
     5         kx                             while (*endtag && isspace((unsigned char)*endtag))
     5         kx                                 endtag++;
     5         kx                         }
     5         kx                     } else if (!strcmp(key, "include")) {
     5         kx                         int rv;
     5         kx 
     5         kx                         free(key);
     5         kx                         key = isolateValue(configFile, lineNum, "include", &start,
     5         kx                                            &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx 
     5         kx                         if (key[0] == '~' && key[1] == '/') {
     5         kx                             /* replace '~' with content of $HOME cause low-level functions
     5         kx                              * like stat() do not support the glob ~
     5         kx                              */
     5         kx                             const char *env_home = secure_getenv("HOME");
     5         kx                             char *new_key = NULL;
     5         kx 
     5         kx                             if (!env_home) {
     5         kx                                 const struct passwd *pwd = getpwuid(getuid());
     5         kx                                 message(MESS_DEBUG,
     5         kx                                         "%s:%d cannot get HOME directory from environment "
     5         kx                                         "to replace ~/ in include directive\n",
     5         kx                                         configFile, lineNum);
     5         kx                                 if (!pwd) {
     5         kx                                     message(MESS_ERROR, "%s:%d cannot get passwd entry for "
     5         kx                                             "running user %u: %s\n",
     5         kx                                            configFile, lineNum, getuid(), strerror(errno));
     5         kx                                     RAISE_ERROR();
     5         kx                                 }
     5         kx                                 env_home = pwd->pw_dir;
     5         kx                             }
     5         kx 
     5         kx                             if (asprintf(&new_key, "%s/%s", env_home, key + 2) == -1) {
     5         kx                                 message_OOM();
     5         kx                                 RAISE_ERROR();
     5         kx                             }
     5         kx                             message(MESS_DEBUG, "%s:%d replaced %s with '%s' for include directive\n",
     5         kx                                     configFile, lineNum, key, env_home);
     5         kx                             free(key);
     5         kx                             key = new_key;
     5         kx                         }
     5         kx 
     5         kx                         message(MESS_DEBUG, "including %s\n", key);
     5         kx                         if (recursion_depth >= MAX_NESTING) {
     5         kx                             message(MESS_ERROR, "%s:%d include nesting too deep\n",
     5         kx                                     configFile, lineNum);
     5         kx                             logerror = 1;
     5         kx                             continue;
     5         kx                         }
     5         kx 
     5         kx                         ++recursion_depth;
     5         kx                         rv = readConfigPath(key, newlog);
     5         kx                         --recursion_depth;
     5         kx 
     5         kx                         if (rv) {
     5         kx                             logerror = 1;
     5         kx                             continue;
     5         kx                         }
     5         kx                     } else if (!strcmp(key, "olddir")) {
     5         kx                         freeLogItem (oldDir);
     5         kx 
     5         kx                         if (!(newlog->oldDir = readPath(configFile, lineNum,
     5         kx                                         "olddir", &start, &buf, length))) {
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx                         message(MESS_DEBUG, "olddir is now %s\n", newlog->oldDir);
     5         kx                     } else if (!strcmp(key, "extension")) {
     5         kx                         free(key);
     5         kx                         key = isolateValue(configFile, lineNum, "extension name", &start,
     5         kx                                            &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx                         freeLogItem (extension);
     5         kx                         newlog->extension = key;
     5         kx                         key = NULL;
     5         kx                         message(MESS_DEBUG, "extension is now %s\n", newlog->extension);
     5         kx 
     5         kx                     } else if (!strcmp(key, "addextension")) {
     5         kx                         free(key);
     5         kx                         key = isolateValue(configFile, lineNum, "addextension name", &start,
     5         kx                                            &buf, length);
     5         kx                         if (key == NULL)
     5         kx                             continue;
     5         kx                         freeLogItem (addextension);
     5         kx                         newlog->addextension = key;
     5         kx                         key = NULL;
     5         kx                         message(MESS_DEBUG, "addextension is now %s\n",
     5         kx                                 newlog->addextension);
     5         kx 
     5         kx                     } else if (!strcmp(key, "compresscmd")) {
     5         kx                         char *compresscmd_full;
     5         kx                         const char *compresscmd_base;
     5         kx                         unsigned i;
     5         kx 
     5         kx                         freeLogItem (compress_prog);
     5         kx 
     5         kx                         if (!
     5         kx                                 (newlog->compress_prog =
     5         kx                                  readPath(configFile, lineNum, "compress", &start, &buf, length))) {
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx 
     5         kx                         message(MESS_DEBUG, "compress_prog is now %s\n",
     5         kx                                 newlog->compress_prog);
     5         kx 
     5         kx                         compresscmd_full = strdup(newlog->compress_prog);
     5         kx                         if (compresscmd_full == NULL) {
     5         kx                             message_OOM();
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx 
     5         kx                         compresscmd_base = basename(compresscmd_full);
     5         kx 
     5         kx                         /* we check whether we changed the compress_cmd. In case we use the appropriate extension
     5         kx                            as listed in compress_cmd_list */
     5         kx                         for(i = 0; i < compress_cmd_list_size; i++) {
     5         kx                             if (!strcmp(compress_cmd_list[i].cmd, compresscmd_base)) {
     5         kx                                 freeLogItem (compress_ext);
     5         kx                                 newlog->compress_ext = strdup(compress_cmd_list[i].ext);
     5         kx                                 if (newlog->compress_ext == NULL) {
     5         kx                                     message_OOM();
     5         kx                                     free(compresscmd_full);
     5         kx                                     RAISE_ERROR();
     5         kx                                 }
     5         kx                                 message(MESS_DEBUG, "compress_ext was changed to %s\n", newlog->compress_ext);
     5         kx                                 break;
     5         kx                             }
     5         kx                         }
     5         kx                         free(compresscmd_full);
     5         kx                     } else if (!strcmp(key, "uncompresscmd")) {
     5         kx                         freeLogItem (uncompress_prog);
     5         kx 
     5         kx                         if (!
     5         kx                                 (newlog->uncompress_prog =
     5         kx                                  readPath(configFile, lineNum, "uncompress",
     5         kx                                           &start, &buf, length))) {
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx 
     5         kx                         message(MESS_DEBUG, "uncompress_prog is now %s\n",
     5         kx                                 newlog->uncompress_prog);
     5         kx 
     5         kx                     } else if (!strcmp(key, "compressoptions")) {
     5         kx                         char *options;
     5         kx 
     5         kx                         if (newlog->compress_options_list) {
     5         kx                             free(newlog->compress_options_list);
     5         kx                             newlog->compress_options_list = NULL;
     5         kx                             newlog->compress_options_count = 0;
     5         kx                         }
     5         kx 
     5         kx                         if (!(options = isolateLine(&start, &buf, length))) {
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx 
     5         kx                         if (poptParseArgvString(options,
     5         kx                                                 &newlog->compress_options_count,
     5         kx                                                 &newlog->compress_options_list)) {
     5         kx                             message(MESS_ERROR,
     5         kx                                     "%s:%d invalid compression options\n",
     5         kx                                     configFile, lineNum);
     5         kx                             free(options);
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx 
     5         kx                         message(MESS_DEBUG, "compress_options is now %s\n",
     5         kx                                 options);
     5         kx                         free(options);
     5         kx                     } else if (!strcmp(key, "compressext")) {
     5         kx                         freeLogItem (compress_ext);
     5         kx 
     5         kx                         if (!
     5         kx                                 (newlog->compress_ext =
     5         kx                                  readPath(configFile, lineNum, "compress-ext",
     5         kx                                           &start, &buf, length))) {
     5         kx                             RAISE_ERROR();
     5         kx                         }
     5         kx 
     5         kx                         message(MESS_DEBUG, "compress_ext is now %s\n",
     5         kx                                 newlog->compress_ext);
     5         kx                     } else {
     5         kx                         message(MESS_ERROR, "%s:%d unknown option '%s' "
     5         kx                                 "-- ignoring line\n", configFile, lineNum, key);
     5         kx                         if (*start != '\n')
     5         kx                             state = STATE_SKIP_LINE;
     5         kx                     }
     5         kx                 } else if (*start == '/' || *start == '"' || *start == '\''
     5         kx #ifdef GLOB_TILDE
     5         kx                         || *start == '~'
     5         kx #endif
     5         kx                         ) {
     5         kx                     char *glob_string;
     5         kx                     size_t glob_count;
     5         kx                     int argc, argNum;
     5         kx                     const char **argv;
     5         kx                     in_config = 0;
     5         kx                     if (newlog != defConfig) {
     5         kx                         message(MESS_ERROR, "%s:%d unexpected log filename\n",
     5         kx                                 configFile, lineNum);
     5         kx                         state = STATE_ERROR;
     5         kx                         continue;
     5         kx                     }
     5         kx 
     5         kx                     /* If no compression options were found in config file, set
     5         kx                        default values */
     5         kx                     if (!newlog->compress_prog)
     5         kx                         newlog->compress_prog = strdup(COMPRESS_COMMAND);
     5         kx                     if (!newlog->uncompress_prog)
     5         kx                         newlog->uncompress_prog = strdup(UNCOMPRESS_COMMAND);
     5         kx                     if (!newlog->compress_ext)
     5         kx                         newlog->compress_ext = strdup(COMPRESS_EXT);
     5         kx 
     5         kx                     if (!newlog->compress_prog || !newlog->uncompress_prog || !newlog->compress_ext) {
     5         kx                         message_OOM();
     5         kx                         goto error;
     5         kx                     }
     5         kx 
     5         kx                     /* Allocate a new logInfo structure and insert it into the logs
     5         kx                        queue, copying the actual values from defConfig */
     5         kx                     if ((newlog = newLogInfo(defConfig)) == NULL)
     5         kx                         goto error;
     5         kx 
     5         kx                     glob_string = parseGlobString(configFile, lineNum, buf, length, &start);
     5         kx                     if (glob_string)
     5         kx                         in_config = 1;
     5         kx                     else
     5         kx                         /* error already printed */
     5         kx                         goto error;
     5         kx 
     5         kx                     if (poptParseArgvString(glob_string, &argc, &argv)) {
     5         kx                         message(MESS_ERROR, "%s:%d error parsing filename\n",
     5         kx                                 configFile, lineNum);
     5         kx                         free(glob_string);
     5         kx                         goto error;
     5         kx                     } else if (argc < 1) {
     5         kx                         message(MESS_ERROR,
     5         kx                                 "%s:%d { expected after log file name(s)\n",
     5         kx                                 configFile, lineNum);
     5         kx                         free(glob_string);
     5         kx                         goto error;
     5         kx                     }
     5         kx 
     5         kx                     newlog->files = NULL;
     5         kx                     newlog->numFiles = 0;
     5         kx                     for (argNum = 0; argNum < argc; argNum++) {
     5         kx                         char **tmp;
     5         kx                         int rc;
     5         kx                         glob_t globResult;
     5         kx 
     5         kx                         if (globerr_msg) {
     5         kx                             free(globerr_msg);
     5         kx                             globerr_msg = NULL;
     5         kx                         }
     5         kx 
     5         kx                         rc = glob(argv[argNum], GLOB_NOCHECK
     5         kx #ifdef GLOB_TILDE
     5         kx                                 | GLOB_TILDE
     5         kx #endif
     5         kx                                 , globerr, &globResult);
     5         kx                         if (rc == GLOB_ABORTED) {
     5         kx                             if (newlog->flags & LOG_FLAG_MISSINGOK) {
     5         kx                                 continue;
     5         kx                             }
     5         kx 
     5         kx                             /* We don't yet know whether this stanza has "missingok"
     5         kx                              * set, so store the error message for later. */
     5         kx                             rc = asprintf(&globerr_msg, "%s:%d glob failed for %s: %s\n",
     5         kx                                           configFile, lineNum, argv[argNum], strerror(glob_errno));
     5         kx                             if (rc == -1)
     5         kx                                 globerr_msg = NULL;
     5         kx 
     5         kx                             globResult.gl_pathc = 0;
     5         kx                         }
     5         kx 
     5         kx                         tmp = realloc(newlog->files,
     5         kx                                     sizeof(*newlog->files) * (newlog->numFiles +
     5         kx                                         globResult.
     5         kx                                         gl_pathc));
     5         kx                         if (tmp == NULL) {
     5         kx                             message_OOM();
     5         kx                             logerror = 1;
     5         kx                             goto duperror;
     5         kx                         }
     5         kx 
     5         kx                         newlog->files = tmp;
     5         kx 
     5         kx                         for (glob_count = 0; glob_count < globResult.gl_pathc; glob_count++) {
     5         kx                             struct logInfo *log;
     5         kx 
     5         kx                             /* if we glob directories we can get false matches */
     5         kx                             if (!lstat(globResult.gl_pathv[glob_count], &sb) &&
     5         kx                                     S_ISDIR(sb.st_mode)) {
     5         kx                                 continue;
     5         kx                             }
     5         kx 
     5         kx                             for (log = logs.tqh_first; log != NULL;
     5         kx                                     log = log->list.tqe_next) {
     5         kx                                 unsigned k;
     5         kx                                 for (k = 0; k < log->numFiles; k++) {
     5         kx                                     if (!strcmp(log->files[k],
     5         kx                                                 globResult.gl_pathv[glob_count])) {
     5         kx                                         message(MESS_ERROR,
     5         kx                                                 "%s:%d duplicate log entry for %s\n",
     5         kx                                                 configFile, lineNum,
     5         kx                                                 globResult.gl_pathv[glob_count]);
     5         kx                                         logerror = 1;
     5         kx                                         goto duperror;
     5         kx                                     }
     5         kx                                 }
     5         kx                             }
     5         kx 
     5         kx                             newlog->files[newlog->numFiles] =
     5         kx                                 strdup(globResult.gl_pathv[glob_count]);
     5         kx                             if (newlog->files[newlog->numFiles] == NULL) {
     5         kx                                 message_OOM();
     5         kx                                 logerror = 1;
     5         kx                                 goto duperror;
     5         kx                             }
     5         kx                             newlog->numFiles++;
     5         kx                         }
     5         kx duperror:
     5         kx                         globfree(&globResult);
     5         kx                     }
     5         kx 
     5         kx                     newlog->pattern = glob_string;
     5         kx 
     5         kx                     free(argv);
     5         kx 
     5         kx                 } else if (*start == '}') {
     5         kx                     if (newlog == defConfig) {
     5         kx                         message(MESS_ERROR, "%s:%d unexpected }\n", configFile,
     5         kx                                 lineNum);
     5         kx                         goto error;
     5         kx                     }
     5         kx                     if (!in_config) {
     5         kx                         message(MESS_ERROR, "%s:%d unexpected } (missing previous '{')\n", configFile,
     5         kx                                 lineNum);
     5         kx                         goto error;
     5         kx                     }
     5         kx                     in_config = 0;
     5         kx                     if (globerr_msg) {
     5         kx                         if (!(newlog->flags & LOG_FLAG_MISSINGOK))
     5         kx                             message(MESS_ERROR, "%s", globerr_msg);
     5         kx                         free(globerr_msg);
     5         kx                         globerr_msg = NULL;
     5         kx                         if (!(newlog->flags & LOG_FLAG_MISSINGOK))
     5         kx                             goto error;
     5         kx                     }
     5         kx 
     5         kx                     if (newlog->oldDir) {
     5         kx                         unsigned j;
     5         kx                         for (j = 0; j < newlog->numFiles; j++) {
     5         kx                             char *ld;
     5         kx                             char *dirpath;
     5         kx                             const char *dirName;
     5         kx                             struct stat sb2;
     5         kx 
     5         kx                             dirpath = strdup(newlog->files[j]);
     5         kx                             if (dirpath == NULL) {
     5         kx                                 message_OOM();
     5         kx                                 goto error;
     5         kx                             }
     5         kx 
     5         kx                             dirName = dirname(dirpath);
     5         kx                             if (stat(dirName, &sb2)) {
     5         kx                                 if (!(newlog->flags & LOG_FLAG_MISSINGOK)) {
     5         kx                                     message(MESS_ERROR,
     5         kx                                             "%s:%d error verifying log file "
     5         kx                                             "path %s: %s\n", configFile, lineNum,
     5         kx                                             dirName, strerror(errno));
     5         kx                                     free(dirpath);
     5         kx                                     goto error;
     5         kx                                 }
     5         kx                                 else {
     5         kx                                     message(MESS_DEBUG,
     5         kx                                             "%s:%d verifying log file "
     5         kx                                             "path failed %s: %s, log is probably missing, "
     5         kx                                             "but missingok is set, so this is not an error.\n",
     5         kx                                             configFile, lineNum,
     5         kx                                             dirName, strerror(errno));
     5         kx                                     free(dirpath);
     5         kx                                     continue;
     5         kx                                 }
     5         kx                             }
     5         kx                             ld = malloc(strlen(dirName) + strlen(newlog->oldDir) + 2);
     5         kx                             if (ld == NULL) {
     5         kx                                 message_OOM();
     5         kx                                 free(dirpath);
     5         kx                                 goto error;
     5         kx                             }
     5         kx                             sprintf(ld, "%s/%s", dirName, newlog->oldDir);
     5         kx                             free(dirpath);
     5         kx 
     5         kx                             if (newlog->oldDir[0] != '/') {
     5         kx                                 dirName = ld;
     5         kx                             }
     5         kx                             else {
     5         kx                                 dirName = newlog->oldDir;
     5         kx                             }
     5         kx 
     5         kx                             if (stat(dirName, &sb)) {
     5         kx                                 if (errno == ENOENT && (newlog->flags & LOG_FLAG_OLDDIRCREATE)) {
     5         kx                                     int ret;
     5         kx                                     if (newlog->flags & LOG_FLAG_SU) {
     5         kx                                         if (switch_user(newlog->suUid, newlog->suGid) != 0) {
     5         kx                                             free(ld);
     5         kx                                             goto error;
     5         kx                                         }
     5         kx                                     }
     5         kx                                     ret = mkpath(dirName, newlog->olddirMode,
     5         kx                                             newlog->olddirUid, newlog->olddirGid);
     5         kx                                     if (newlog->flags & LOG_FLAG_SU) {
     5         kx                                         if (switch_user_back() != 0) {
     5         kx                                             free(ld);
     5         kx                                             goto error;
     5         kx                                         }
     5         kx                                     }
     5         kx                                     if (ret) {
     5         kx                                         free(ld);
     5         kx                                         goto error;
     5         kx                                     }
     5         kx                                 }
     5         kx                                 else {
     5         kx                                     message(MESS_ERROR, "%s:%d error verifying olddir "
     5         kx                                             "path %s: %s\n", configFile, lineNum,
     5         kx                                             dirName, strerror(errno));
     5         kx                                     free(ld);
     5         kx                                     goto error;
     5         kx                                 }
     5         kx                             }
     5         kx 
     5         kx                             free(ld);
     5         kx 
     5         kx                             if (sb.st_dev != sb2.st_dev
     5         kx                                     && !(newlog->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY | LOG_FLAG_TMPFILENAME))) {
     5         kx                                 message(MESS_ERROR,
     5         kx                                         "%s:%d olddir %s and log file %s "
     5         kx                                         "are on different devices\n", configFile,
     5         kx                                         lineNum, newlog->oldDir, newlog->files[j]);
     5         kx                                 goto error;
     5         kx                             }
     5         kx                         }
     5         kx                     }
     5         kx 
     5         kx                     criterium_set = 0;
     5         kx                     newlog = defConfig;
     5         kx                     state = STATE_DEFINITION_END;
     5         kx                 } else if (*start != '\n') {
     5         kx                     message(MESS_ERROR, "%s:%d lines must begin with a keyword "
     5         kx                             "or a filename (possibly in double quotes)\n",
     5         kx                             configFile, lineNum);
     5         kx                     state = STATE_SKIP_LINE;
     5         kx                 }
     5         kx                 break;
     5         kx             case STATE_SKIP_LINE:
     5         kx             case STATE_SKIP_LINE | STATE_SKIP_CONFIG:
     5         kx                 if (*start == '\n')
     5         kx                     state = (state & STATE_SKIP_CONFIG) ? STATE_SKIP_CONFIG : STATE_DEFAULT;
     5         kx                 break;
     5         kx             case STATE_SKIP_LINE | STATE_LOAD_SCRIPT:
     5         kx                 if (*start == '\n')
     5         kx                     state = STATE_LOAD_SCRIPT;
     5         kx                 break;
     5         kx             case STATE_SKIP_LINE | STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG:
     5         kx                 if (*start == '\n')
     5         kx                     state = STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG;
     5         kx                 break;
     5         kx             case STATE_DEFINITION_END:
     5         kx             case STATE_DEFINITION_END | STATE_SKIP_CONFIG:
     5         kx                 if (isblank((unsigned char)*start))
     5         kx                     continue;
     5         kx                 if (*start != '\n') {
     5         kx                     message(MESS_ERROR, "%s:%d, unexpected text after }\n",
     5         kx                             configFile, lineNum);
     5         kx                     state = STATE_SKIP_LINE | ((state & STATE_SKIP_CONFIG) ? STATE_SKIP_CONFIG : 0);
     5         kx                 }
     5         kx                 else
     5         kx                     state = (state & STATE_SKIP_CONFIG) ? STATE_SKIP_CONFIG : STATE_DEFAULT;
     5         kx                 break;
     5         kx             case STATE_ERROR:
     5         kx                 assert(newlog != defConfig);
     5         kx 
     5         kx                 message(MESS_ERROR, "found error in %s, skipping\n",
     5         kx                         newlog->pattern ? newlog->pattern : "log config");
     5         kx 
     5         kx                 logerror = 1;
     5         kx                 state = STATE_SKIP_CONFIG;
     5         kx                 break;
     5         kx             case STATE_LOAD_SCRIPT:
     5         kx             case STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG:
     5         kx                 free(key);
     5         kx                 key = isolateWord(&start, &buf, length);
     5         kx                 if (key == NULL)
     5         kx                     continue;
     5         kx 
     5         kx                 if (strcmp(key, "endscript") == 0) {
     5         kx                     if (state & STATE_SKIP_CONFIG) {
     5         kx                         state = STATE_SKIP_CONFIG;
     5         kx                     }
     5         kx                     else {
     5         kx                         const char *endtag = start - 9;
     5         kx                         while (*endtag != '\n')
     5         kx                             endtag--;
     5         kx                         endtag++;
     5         kx                         *scriptDest = strndup(scriptStart, (size_t)(endtag - scriptStart));
     5         kx                         if (*scriptDest == NULL) {
     5         kx                             message_OOM();
     5         kx                             goto error;
     5         kx                         }
     5         kx 
     5         kx                         scriptDest = NULL;
     5         kx                         scriptStart = NULL;
     5         kx                     }
     5         kx                     state = (state & STATE_SKIP_CONFIG) ? STATE_SKIP_CONFIG : STATE_DEFAULT;
     5         kx                 }
     5         kx                 else {
     5         kx                     state = (*start == '\n' ? 0 : STATE_SKIP_LINE) |
     5         kx                         STATE_LOAD_SCRIPT |
     5         kx                         ((state & STATE_SKIP_CONFIG) ? STATE_SKIP_CONFIG : 0);
     5         kx                 }
     5         kx                 break;
     5         kx             case STATE_SKIP_CONFIG:
     5         kx                 if (*start == '}') {
     5         kx                     state = STATE_DEFAULT;
     5         kx                     freeTailLogs(1);
     5         kx                     newlog = defConfig;
     5         kx                 }
     5         kx                 else {
     5         kx                     free(key);
     5         kx                     key = isolateWord(&start, &buf, length);
     5         kx                     if (key == NULL)
     5         kx                         continue;
     5         kx                     if (
     5         kx                             (strcmp(key, "postrotate") == 0) ||
     5         kx                             (strcmp(key, "prerotate") == 0) ||
     5         kx                             (strcmp(key, "firstaction") == 0) ||
     5         kx                             (strcmp(key, "lastaction") == 0) ||
     5         kx                             (strcmp(key, "preremove") == 0)
     5         kx                             ) {
     5         kx                         state = STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG;
     5         kx                     }
     5         kx                     else {
     5         kx                         /* isolateWord moves the "start" pointer.
     5         kx                          * If we have a line like
     5         kx                          *    rotate 5
     5         kx                          * after isolateWord "start" points to "5" and it
     5         kx                          * is OK to skip the line, but if we have a line
     5         kx                          * like the following
     5         kx                          *    nocompress
     5         kx                          * after isolateWord "start" points to "\n". In
     5         kx                          * this case if we skip a line, we skip the next
     5         kx                          * line, not the current "nocompress" one,
     5         kx                          * because in the for cycle the "start"
     5         kx                          * pointer is increased by one and, after this,
     5         kx                          * "start" points to the beginning of the next line.
     5         kx                          */
     5         kx                         if (*start != '\n') {
     5         kx                             state = STATE_SKIP_LINE | STATE_SKIP_CONFIG;
     5         kx                         }
     5         kx                     }
     5         kx                 }
     5         kx                 break;
     5         kx             default:
     5         kx                 message(MESS_FATAL,
     5         kx                         "%s: %d: readConfigFile() unknown state\n",
     5         kx                         configFile, lineNum);
     5         kx         }
     5         kx         if (*start == '\n') {
     5         kx             lineNum++;
     5         kx         }
     5         kx 
     5         kx next_state: ;
     5         kx     }
     5         kx 
     5         kx     if (scriptStart) {
     5         kx         message(MESS_ERROR,
     5         kx                 "%s:prerotate, postrotate or preremove without endscript\n",
     5         kx                 configFile);
     5         kx         goto error;
     5         kx     }
     5         kx 
     5         kx     free(key);
     5         kx 
     5         kx     munmap(buf, length);
     5         kx     close(fd);
     5         kx     return logerror;
     5         kx error:
     5         kx     /* free is a NULL-safe operation */
     5         kx     free(key);
     5         kx     munmap(buf, length);
     5         kx     close(fd);
     5         kx     return 1;
     5         kx }
     5         kx 
     5         kx /* vim: set et sw=4 ts=4: */