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: */