"Fossies" - the Fresh Open Source Software Archive

Member "logrotate-3.18.1/logrotate.c" (3 May 2021, 99547 Bytes) of package /linux/privat/logrotate-3.18.1.tar.xz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "logrotate.c" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 3.18.0_vs_3.18.1.

    1 #include "queue.h"
    2 /* alloca() is defined in stdlib.h in NetBSD */
    3 #if !defined(__NetBSD__) && !defined(__FreeBSD__) && !defined(__OpenBSD__)
    4 #include <alloca.h>
    5 #endif
    6 #include <limits.h>
    7 #include <ctype.h>
    8 #include <dirent.h>
    9 #include <errno.h>
   10 #include <fcntl.h>
   11 #include <popt.h>
   12 #include <stdio.h>
   13 #include <stdlib.h>
   14 #include <string.h>
   15 #include <sys/file.h>
   16 #include <sys/stat.h>
   17 #include <sys/wait.h>
   18 #include <time.h>
   19 #include <unistd.h>
   20 #include <glob.h>
   21 #include <locale.h>
   22 #include <sys/types.h>
   23 #include <utime.h>
   24 #include <stdint.h>
   25 #include <libgen.h>
   26 
   27 #if !defined(PATH_MAX) && defined(__FreeBSD__)
   28 #include <sys/param.h>
   29 #endif
   30 
   31 #include "log.h"
   32 #include "logrotate.h"
   33 
   34 static char *prev_context;
   35 #ifdef WITH_SELINUX
   36 #include <selinux/selinux.h>
   37 static int selinux_enabled = 0;
   38 static int selinux_enforce = 0;
   39 #endif
   40 
   41 #ifdef WITH_ACL
   42 #include "sys/acl.h"
   43 #define acl_type acl_t
   44 #else
   45 #define acl_type void *
   46 #endif
   47 
   48 static acl_type prev_acl = NULL;
   49 
   50 #if !defined(GLOB_ABORTED) && defined(GLOB_ABEND)
   51 #define GLOB_ABORTED GLOB_ABEND
   52 #endif
   53 
   54 #ifdef PATH_MAX
   55 #define STATEFILE_BUFFER_SIZE ( 2 * PATH_MAX + 16 )
   56 #else
   57 #define STATEFILE_BUFFER_SIZE 4096
   58 #endif
   59 
   60 #ifdef __hpux
   61 extern int asprintf(char **str, const char *fmt, ...);
   62 #endif
   63 
   64 /* Number of seconds in a day */
   65 #define DAY_SECONDS 86400
   66 
   67 struct logState {
   68     char *fn;
   69     struct tm lastRotated;  /* only tm_hour, tm_mday, tm_mon, tm_year are good! */
   70     struct stat sb;
   71     int doRotate;
   72     int isUsed;     /* True if there is real log file in system for this state. */
   73     LIST_ENTRY(logState) list;
   74 };
   75 
   76 struct logNames {
   77     char *firstRotated;
   78     char *disposeName;
   79     char *finalName;
   80     char *dirName;
   81     char *baseName;
   82 };
   83 
   84 struct compData {
   85     size_t prefix_len;
   86     const char *dformat;
   87 };
   88 
   89 static struct logStateList {
   90     LIST_HEAD(stateSet, logState) head;
   91 } **states;
   92 
   93 int numLogs = 0;
   94 int debug = 0;
   95 
   96 static unsigned int hashSize;
   97 static const char *mailCommand = DEFAULT_MAIL_COMMAND;
   98 static time_t nowSecs = 0;
   99 static uid_t save_euid;
  100 static gid_t save_egid;
  101 
  102 static int globerr(const char *pathname, int theerr)
  103 {
  104     message(MESS_ERROR, "error accessing %s: %s\n", pathname,
  105             strerror(theerr));
  106 
  107     /* We want the glob operation to abort on error, so return 1 */
  108     return 1;
  109 }
  110 
  111 #if defined(HAVE_STRPTIME) && defined(HAVE_QSORT)
  112 
  113 /* We could switch to qsort_r to get rid of this global variable,
  114  * but qsort_r is not portable enough (Linux vs. *BSD vs ...)... */
  115 static struct compData _compData;
  116 
  117 static int compGlobResult(const void *result1, const void *result2)  {
  118     struct tm time_tmp;
  119     time_t t1, t2;
  120     const char *r1 = *(char * const*)(result1);
  121     const char *r2 = *(char * const*)(result2);
  122 
  123     memset(&time_tmp, 0, sizeof(struct tm));
  124     strptime(r1 + _compData.prefix_len, _compData.dformat, &time_tmp);
  125     t1 = mktime(&time_tmp);
  126 
  127     memset(&time_tmp, 0, sizeof(struct tm));
  128     strptime(r2 + _compData.prefix_len, _compData.dformat, &time_tmp);
  129     t2 = mktime(&time_tmp);
  130 
  131     if (t1 < t2) return -1;
  132     if (t1 > t2) return  1;
  133     return 0;
  134 }
  135 
  136 static void sortGlobResult(glob_t *result, size_t prefix_len, const char *dformat) {
  137     if (!dformat || *dformat == '\0') {
  138         return;
  139     }
  140 
  141     _compData.prefix_len = prefix_len;
  142     _compData.dformat = dformat;
  143     qsort(result->gl_pathv, result->gl_pathc, sizeof(char *), compGlobResult);
  144 }
  145 #else
  146 static void sortGlobResult(glob_t *result, size_t prefix_len, const char *dformat) {
  147     /* TODO */
  148 }
  149 #endif
  150 
  151 int switch_user(uid_t user, gid_t group) {
  152     save_egid = getegid();
  153     save_euid = geteuid();
  154     if (save_euid == user && save_egid == group)
  155         return 0;
  156     message(MESS_DEBUG, "switching euid from %u to %u and egid from %u to %u (pid %d)\n",
  157             (unsigned) save_euid, (unsigned) user, (unsigned) save_egid, (unsigned) group, getpid());
  158     if (setegid(group) || seteuid(user)) {
  159         message(MESS_ERROR, "error switching euid from %u to %u and egid from %u to %u (pid %d): %s\n",
  160                 (unsigned) save_euid, (unsigned) user, (unsigned) save_egid, (unsigned) group, getpid(),
  161                 strerror(errno));
  162         return 1;
  163     }
  164     return 0;
  165 }
  166 
  167 static int switch_user_permanently(const struct logInfo *log) {
  168     const gid_t group = getegid();
  169     const uid_t user = geteuid();
  170 
  171     if (!(log->flags & LOG_FLAG_SU)) {
  172         return 0;
  173     }
  174 
  175     if (user != log->suUid) {
  176         message(MESS_ERROR, "current euid (%u) does not match uid of log configuration (%u) (pid %d)\n",
  177                 (unsigned) user, (unsigned) log->suUid, getpid());
  178         return 1;
  179     }
  180     if (group != log->suGid) {
  181         message(MESS_ERROR, "current egid (%u) does not match gid of log configuration (%u) (pid %d)\n",
  182                 (unsigned) group, (unsigned) log->suGid, getpid());
  183         return 1;
  184     }
  185 
  186     /* we are already the final configuration specified user/group */
  187     if (getuid() == user && getgid() == group) {
  188         return 0;
  189     }
  190 
  191     /* switch to full root first */
  192     if (setgid(getgid()) || setuid(getuid())) {
  193         message(MESS_ERROR, "error getting rid of euid != uid (pid %d): %s\n",
  194                 getpid(), strerror(errno));
  195         return 1;
  196     }
  197 
  198     message(MESS_DEBUG, "switching uid to %u and gid to %u permanently (pid %d)\n",
  199             (unsigned) user, (unsigned) group, getpid());
  200     if (setgid(group) || setuid(user)) {
  201         message(MESS_ERROR, "error switching uid to %u and gid to %u (pid %d): %s\n",
  202                 (unsigned) user, (unsigned) group, getpid(), strerror(errno));
  203         return 1;
  204     }
  205 
  206     if (user != ROOT_UID && setuid(ROOT_UID) != -1) {
  207         message(MESS_ERROR, "failed to switch user permanently, able to switch back (pid %d)\n",
  208                 getpid());
  209         return 1;
  210     }
  211 
  212     return 0;
  213 }
  214 
  215 int switch_user_back(void) {
  216     return switch_user(save_euid, save_egid);
  217 }
  218 
  219 static int switch_user_back_permanently(void) {
  220     gid_t tmp_egid = save_egid;
  221     uid_t tmp_euid = save_euid;
  222     int ret = switch_user(save_euid, save_egid);
  223     save_euid = tmp_euid;
  224     save_egid = tmp_egid;
  225     return ret;
  226 }
  227 
  228 static void unescape(char *arg)
  229 {
  230     char *p = arg;
  231     char *next;
  232     char escaped;
  233     while ((next = strchr(p, '\\')) != NULL) {
  234 
  235         p = next;
  236 
  237         switch (p[1]) {
  238             case 'n':
  239                 escaped = '\n';
  240                 break;
  241             case '\\':
  242                 escaped = '\\';
  243                 break;
  244             default:
  245                 ++p;
  246                 continue;
  247         }
  248 
  249         /* Overwrite the backslash with the intended character,
  250          * and shift everything down one */
  251         *p++ = escaped;
  252         memmove(p, p+1, 1 + strlen(p+1));
  253     }
  254 }
  255 
  256 #define HASH_SIZE_MIN 64
  257 #define HASH_SIZE_MAX 8192
  258 static int allocateHash(unsigned long hs)
  259 {
  260     unsigned int i;
  261 
  262     /* Enforce some reasonable minimum hash size */
  263     if (hs < HASH_SIZE_MIN)
  264         hs = HASH_SIZE_MIN;
  265 
  266     /* Enforce some reasonable maximum hash size */
  267     if (hs > HASH_SIZE_MAX)
  268         hs = HASH_SIZE_MAX;
  269 
  270     message(MESS_DEBUG, "Allocating hash table for state file, size %lu entries\n",
  271             hs);
  272 
  273     states = calloc(hs, sizeof(struct logStateList *));
  274     if (states == NULL) {
  275         message_OOM();
  276         return 1;
  277     }
  278 
  279     for (i = 0; i < hs; i++) {
  280         states[i] = malloc(sizeof *states[0]);
  281         if (states[i] == NULL) {
  282             message_OOM();
  283             return 1;
  284         }
  285         LIST_INIT(&(states[i]->head));
  286     }
  287 
  288     hashSize = (unsigned)hs;
  289 
  290     return 0;
  291 }
  292 
  293 #define HASH_CONST 13
  294 #if defined(__clang__) && defined(__clang_major__) && (__clang_major__ >= 4)
  295 __attribute__((no_sanitize("unsigned-integer-overflow")))
  296 #endif
  297 static int hashIndex(const char *fn)
  298 {
  299     unsigned hash = 0;
  300     if (!hashSize)
  301         /* hash table not yet allocated */
  302         return -1;
  303 
  304     while (*fn) {
  305         hash *= HASH_CONST;
  306         hash += (unsigned char)*fn++;
  307     }
  308 
  309     return (int)(hash % hashSize);
  310 }
  311 
  312 /* safe implementation of dup2(oldfd, nefd) followed by close(oldfd) */
  313 static int movefd(int oldfd, int newfd)
  314 {
  315     int rc;
  316     if (oldfd == newfd)
  317         /* avoid accidental close of newfd in case it is equal to oldfd */
  318         return 0;
  319 
  320     rc = dup2(oldfd, newfd);
  321     if (rc == 0)
  322         close(oldfd);
  323 
  324     return rc;
  325 }
  326 
  327 static int setSecCtx(int fdSrc, const char *src, char **pPrevCtx)
  328 {
  329 #ifdef WITH_SELINUX
  330     char *srcCtx;
  331     *pPrevCtx = NULL;
  332 
  333     if (!selinux_enabled)
  334         /* pretend success */
  335         return 0;
  336 
  337     /* read security context of fdSrc */
  338     if (fgetfilecon_raw(fdSrc, &srcCtx) < 0) {
  339         if (errno == ENOTSUP)
  340             /* pretend success */
  341             return 0;
  342 
  343         message(MESS_ERROR, "getting file context %s: %s\n", src,
  344                 strerror(errno));
  345         return selinux_enforce;
  346     }
  347 
  348     /* save default security context for restoreSecCtx() */
  349     if (getfscreatecon_raw(pPrevCtx) < 0) {
  350         message(MESS_ERROR, "getting default context: %s\n", strerror(errno));
  351         return selinux_enforce;
  352     }
  353 
  354     /* set default security context to match fdSrc */
  355     if (setfscreatecon_raw(srcCtx) < 0) {
  356         message(MESS_ERROR, "setting default context to %s: %s\n", srcCtx,
  357                 strerror(errno));
  358         freecon(srcCtx);
  359         return selinux_enforce;
  360     }
  361 
  362     message(MESS_DEBUG, "set default create context to %s\n", srcCtx);
  363     freecon(srcCtx);
  364 #else
  365     (void) fdSrc;
  366     (void) src;
  367     (void) pPrevCtx;
  368 #endif
  369     return 0;
  370 }
  371 
  372 static int setSecCtxByName(const char *src, char **pPrevCtx)
  373 {
  374     int hasErrors = 0;
  375 #ifdef WITH_SELINUX
  376     int fd = open(src, O_RDONLY | O_NOFOLLOW);
  377     if (fd < 0) {
  378         message(MESS_ERROR, "error opening %s: %s\n", src, strerror(errno));
  379         return 1;
  380     }
  381     hasErrors = setSecCtx(fd, src, pPrevCtx);
  382     close(fd);
  383 #else
  384     (void) src;
  385     (void) pPrevCtx;
  386 #endif
  387     return hasErrors;
  388 }
  389 
  390 static void restoreSecCtx(char **pPrevCtx)
  391 {
  392 #ifdef WITH_SELINUX
  393     if (!*pPrevCtx)
  394         /* no security context saved for restoration */
  395         return;
  396 
  397     /* set default security context to the previously stored one */
  398     if (selinux_enabled && setfscreatecon_raw(*pPrevCtx) < 0)
  399         message(MESS_ERROR, "setting default context to %s: %s\n", *pPrevCtx,
  400                 strerror(errno));
  401 
  402     /* free the memory allocated to save the security context */
  403     freecon(*pPrevCtx);
  404     *pPrevCtx = NULL;
  405 #else
  406     (void) pPrevCtx;
  407 #endif
  408 }
  409 
  410 static struct logState *newState(const char *fn)
  411 {
  412     struct tm now;
  413     struct logState *new;
  414     time_t lr_time;
  415 
  416     message(MESS_DEBUG, "Creating new state\n");
  417 
  418     localtime_r(&nowSecs, &now);
  419 
  420     new = malloc(sizeof(*new));
  421     if (new == NULL) {
  422         message_OOM();
  423         return NULL;
  424     }
  425 
  426     new->fn = strdup(fn);
  427     if (new->fn  == NULL) {
  428         message_OOM();
  429         free(new);
  430         return NULL;
  431     }
  432 
  433     new->doRotate = 0;
  434     new->isUsed = 0;
  435 
  436     memset(&new->lastRotated, 0, sizeof(new->lastRotated));
  437     new->lastRotated.tm_hour = now.tm_hour;
  438     new->lastRotated.tm_mday = now.tm_mday;
  439     new->lastRotated.tm_mon = now.tm_mon;
  440     new->lastRotated.tm_year = now.tm_year;
  441     new->lastRotated.tm_isdst = now.tm_isdst;
  442 
  443     /* fill in the rest of the new->lastRotated fields */
  444     lr_time = mktime(&new->lastRotated);
  445     localtime_r(&lr_time, &new->lastRotated);
  446 
  447     return new;
  448 }
  449 
  450 static struct logState *findState(const char *fn)
  451 {
  452     const int i = hashIndex(fn);
  453     struct logState *p;
  454     if (i < 0)
  455         /* hash table not yet allocated */
  456         return NULL;
  457 
  458     for (p = states[i]->head.lh_first; p != NULL; p = p->list.le_next)
  459         if (!strcmp(fn, p->fn))
  460             break;
  461 
  462     /* new state */
  463     if (p == NULL) {
  464         if ((p = newState(fn)) == NULL)
  465             return NULL;
  466 
  467         LIST_INSERT_HEAD(&(states[i]->head), p, list);
  468     }
  469 
  470     return p;
  471 }
  472 
  473 static int runScript(const struct logInfo *log, const char *logfn, const char *logrotfn, const char *script)
  474 {
  475     int rc;
  476     pid_t pid;
  477 
  478     if (debug) {
  479         message(MESS_DEBUG, "running script with args %s %s: \"%s\"\n",
  480                 logfn, logrotfn ? logrotfn : "", script);
  481         return 0;
  482     }
  483 
  484     pid = fork();
  485 
  486     if (pid == -1) {
  487         message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
  488         return 1;
  489     }
  490 
  491     if (pid == 0) {
  492         if (log->flags & LOG_FLAG_SU) {
  493             if (switch_user_back_permanently() != 0) {
  494                 exit(1);
  495             }
  496         }
  497         execl("/bin/sh", "sh", "-c", (char *) script, "logrotate_script", (char *) logfn, (char *) logrotfn, (char *) NULL);
  498         message(MESS_ERROR, "cannot execute sub-shell: %s\n", strerror(errno));
  499         exit(1);
  500     }
  501 
  502     wait(&rc);
  503     return rc;
  504 }
  505 
  506 #ifdef WITH_ACL
  507 static int is_acl_well_supported(int err)
  508 {
  509     switch (err) {
  510         case ENOTSUP:   /* no file system support */
  511         case EINVAL:    /* acl does not point to a valid ACL */
  512         case ENOSYS:    /* compatibility - acl_(g|s)et_fd(3) should never return this */
  513         case EBUSY:     /* compatibility - acl_(g|s)et_fd(3) should never return this */
  514             return 0;
  515         default:
  516             return 1;
  517     }
  518 }
  519 #endif /* WITH_ACL */
  520 
  521 static int createOutputFile(const char *fileName, int flags, const struct stat *sb,
  522                             acl_type acl, int force_mode)
  523 {
  524     int fd = -1;
  525     struct stat sb_create;
  526     int acl_set = 0;
  527     int i;
  528 
  529     for (i = 0; i < 2; ++i) {
  530         struct tm now;
  531         size_t fileName_size, buf_size;
  532         char *backupName, *ptr;
  533 
  534         fd = open(fileName, (flags | O_EXCL | O_NOFOLLOW),
  535                 (S_IRUSR | S_IWUSR) & sb->st_mode);
  536 
  537         if ((fd >= 0) || (errno != EEXIST))
  538             break;
  539 
  540         /* the destination file already exists, while it should not */
  541         localtime_r(&nowSecs, &now);
  542         fileName_size = strlen(fileName);
  543         buf_size = fileName_size + sizeof("-YYYYMMDDHH.backup");
  544         backupName = alloca(buf_size);
  545         ptr = backupName;
  546 
  547         /* construct backupName starting with fileName */
  548         strcpy(ptr, fileName);
  549         ptr += fileName_size;
  550         buf_size -= fileName_size;
  551 
  552         /* append the -YYYYMMDDHH time stamp and the .backup suffix */
  553         ptr += strftime(ptr, buf_size, "-%Y%m%d%H", &now);
  554         strcpy(ptr, ".backup");
  555 
  556         message(MESS_ERROR, "destination %s already exists, renaming to %s\n",
  557                 fileName, backupName);
  558         if (rename(fileName, backupName) != 0) {
  559             message(MESS_ERROR, "error renaming already existing output file"
  560                     " %s to %s: %s\n", fileName, backupName, strerror(errno));
  561             return -1;
  562         }
  563         /* existing file renamed, try it once again */
  564     }
  565 
  566     if (fd < 0) {
  567         message(MESS_ERROR, "error creating output file %s: %s\n",
  568                 fileName, strerror(errno));
  569         return -1;
  570     }
  571     if (fchmod(fd, (S_IRUSR | S_IWUSR) & sb->st_mode)) {
  572         message(MESS_ERROR, "error setting mode of %s: %s\n",
  573                 fileName, strerror(errno));
  574         close(fd);
  575         return -1;
  576     }
  577 
  578     if (fstat(fd, &sb_create)) {
  579         message(MESS_ERROR, "fstat of %s failed: %s\n", fileName,
  580                 strerror(errno));
  581         close(fd);
  582         return -1;
  583     }
  584 
  585     /* Only attempt to set user/group if running as root */
  586     if (
  587         ROOT_UID == geteuid() &&
  588         (sb_create.st_uid != sb->st_uid || sb_create.st_gid != sb->st_gid) &&
  589         fchown(fd, sb->st_uid, sb->st_gid)
  590     ) {
  591         message(MESS_ERROR, "error setting owner of %s to uid %u and gid %u: %s\n",
  592                 fileName, (unsigned) sb->st_uid, (unsigned) sb->st_gid, strerror(errno));
  593         close(fd);
  594         return -1;
  595     }
  596 
  597 #ifdef WITH_ACL
  598     if (!force_mode && acl) {
  599         if (acl_set_fd(fd, acl) == -1) {
  600             if (is_acl_well_supported(errno)) {
  601                 message(MESS_ERROR, "setting ACL for %s: %s\n",
  602                         fileName, strerror(errno));
  603                 close(fd);
  604                 return -1;
  605             }
  606             acl_set = 0;
  607         }
  608         else {
  609             acl_set = 1;
  610         }
  611     }
  612 #else
  613     (void) acl;
  614 #endif
  615 
  616     if (!acl_set || force_mode) {
  617         if (fchmod(fd, sb->st_mode)) {
  618             message(MESS_ERROR, "error setting mode of %s: %s\n",
  619                     fileName, strerror(errno));
  620             close(fd);
  621             return -1;
  622         }
  623     }
  624 
  625     return fd;
  626 }
  627 
  628 #define DIGITS 12
  629 
  630 /* unlink, but try to call shred from GNU coreutils if LOG_FLAG_SHRED
  631  * is enabled (in that case fd needs to be a valid file descriptor) */
  632 static int shred_file(int fd, const char *filename, const struct logInfo *log)
  633 {
  634     char count[DIGITS];    /*  that's a lot of shredding :)  */
  635     const char **fullCommand;
  636     int id = 0;
  637     int status;
  638     pid_t pid;
  639 
  640     if (log->preremove) {
  641         message(MESS_DEBUG, "running preremove script\n");
  642         if (runScript(log, filename, NULL, log->preremove)) {
  643             message(MESS_ERROR,
  644                     "error running preremove script "
  645                     "for %s of '%s'. Not removing this file.\n",
  646                     filename, log->pattern);
  647             /* What ever was supposed to happen did not happen,
  648              * therefore do not unlink the file yet.  */
  649             return 1;
  650         }
  651     }
  652 
  653     if (!(log->flags & LOG_FLAG_SHRED)) {
  654         goto unlink_file;
  655     }
  656 
  657     message(MESS_DEBUG, "Using shred to remove the file %s\n", filename);
  658 
  659     if (log->shred_cycles != 0) {
  660         fullCommand = alloca(sizeof(*fullCommand) * 6);
  661     }
  662     else {
  663         fullCommand = alloca(sizeof(*fullCommand) * 4);
  664     }
  665     fullCommand[id++] = "shred";
  666     fullCommand[id++] = "-u";
  667 
  668     if (log->shred_cycles != 0) {
  669         fullCommand[id++] = "-n";
  670         snprintf(count, DIGITS - 1, "%d", log->shred_cycles);
  671         fullCommand[id++] = count;
  672     }
  673     fullCommand[id++] = "-";
  674     fullCommand[id++] = NULL;
  675 
  676     pid = fork();
  677 
  678     if (pid == -1) {
  679         message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
  680         return 1;
  681     }
  682 
  683     if (pid == 0) {
  684         movefd(fd, STDOUT_FILENO);
  685 
  686         if (switch_user_permanently(log) != 0) {
  687             exit(1);
  688         }
  689 
  690         execvp(fullCommand[0], (void *) fullCommand);
  691         message(MESS_ERROR, "cannot execute shred command: %s\n", strerror(errno));
  692         exit(1);
  693     }
  694 
  695     wait(&status);
  696 
  697     if (!WIFEXITED(status) || WEXITSTATUS(status)) {
  698         message(MESS_ERROR, "Failed to shred %s, trying unlink\n", filename);
  699         return unlink(filename);
  700     }
  701 
  702     /* We have to unlink it after shred anyway,
  703      * because it doesn't remove the file itself */
  704 
  705 unlink_file:
  706     if (unlink(filename) == 0)
  707         return 0;
  708     if (errno != ENOENT)
  709         return 1;
  710 
  711     /* unlink of log file that no longer exists is not a fatal error */
  712     message(MESS_ERROR, "error unlinking log file %s: %s\n", filename,
  713             strerror(errno));
  714     return 0;
  715 }
  716 
  717 static int removeLogFile(const char *name, const struct logInfo *log)
  718 {
  719     int fd = -1;
  720     int result = 0;
  721     message(MESS_DEBUG, "removing old log %s\n", name);
  722 
  723     if (log->flags & LOG_FLAG_SHRED) {
  724         fd = open(name, O_RDWR | O_NOFOLLOW);
  725         if (fd < 0) {
  726             message(MESS_ERROR, "error opening %s: %s\n",
  727                     name, strerror(errno));
  728             return 1;
  729         }
  730     }
  731 
  732     if (!debug && shred_file(fd, name, log)) {
  733         message(MESS_ERROR, "Failed to remove old log %s: %s\n",
  734                 name, strerror(errno));
  735         result = 1;
  736     }
  737 
  738     if (fd != -1)
  739         close(fd);
  740     return result;
  741 }
  742 
  743 static void setAtimeMtime(const char *filename, const struct stat *sb)
  744 {
  745     /* If we can't change atime/mtime, it's not a disaster.  It might
  746        possibly fail under SELinux. But do try to preserve the
  747        fractional part if we have utimensat(). */
  748 #if defined HAVE_UTIMENSAT && !defined(__APPLE__)
  749     struct timespec ts[2];
  750 
  751     ts[0] = sb->st_atim;
  752     ts[1] = sb->st_mtim;
  753     utimensat(AT_FDCWD, filename, ts, 0);
  754 #else
  755     struct utimbuf utim;
  756 
  757     utim.actime = sb->st_atime;
  758     utim.modtime = sb->st_mtime;
  759     utime(filename, &utim);
  760 #endif
  761 }
  762 
  763 static int compressLogFile(const char *name, const struct logInfo *log, const struct stat *sb)
  764 {
  765     char *compressedName;
  766     const char **fullCommand;
  767     int inFile;
  768     int outFile;
  769     int i;
  770     int status;
  771     int compressPipe[2];
  772     char buff[4092];
  773     ssize_t n_read;
  774     int error_printed = 0;
  775     char *prevCtx;
  776     pid_t pid;
  777     int in_flags;
  778     const char *in_how;
  779 
  780     message(MESS_DEBUG, "compressing log with: %s\n", log->compress_prog);
  781     if (debug)
  782         return 0;
  783 
  784     fullCommand = alloca(sizeof(*fullCommand) *
  785             ((unsigned)log->compress_options_count + 2));
  786     fullCommand[0] = log->compress_prog;
  787     for (i = 0; i < log->compress_options_count; i++)
  788         fullCommand[i + 1] = log->compress_options_list[i];
  789     fullCommand[log->compress_options_count + 1] = NULL;
  790 
  791     compressedName = alloca(strlen(name) + strlen(log->compress_ext) + 2);
  792     sprintf(compressedName, "%s%s", name, log->compress_ext);
  793 
  794     in_flags = O_NOFOLLOW;
  795     if (log->flags & LOG_FLAG_SHRED) {
  796         /* need write access for shredding */
  797         in_flags |= O_RDWR;
  798         in_how = "read-write";
  799     } else {
  800         in_flags |= O_RDONLY;
  801         in_how = "read-only";
  802     }
  803     if ((inFile = open(name, in_flags)) < 0) {
  804         message(MESS_ERROR, "unable to open %s (%s) for compression: %s\n",
  805             name, in_how, strerror(errno));
  806         return 1;
  807     }
  808 
  809     if (setSecCtx(inFile, name, &prevCtx) != 0) {
  810         /* error msg already printed */
  811         close(inFile);
  812         return 1;
  813     }
  814 
  815 #ifdef WITH_ACL
  816     if ((prev_acl = acl_get_fd(inFile)) == NULL) {
  817         if (is_acl_well_supported(errno)) {
  818             message(MESS_ERROR, "getting file ACL %s: %s\n",
  819                     name, strerror(errno));
  820             restoreSecCtx(&prevCtx);
  821             close(inFile);
  822             return 1;
  823         }
  824     }
  825 #endif
  826 
  827     outFile =
  828         createOutputFile(compressedName, O_RDWR | O_CREAT, sb, prev_acl, 0);
  829     restoreSecCtx(&prevCtx);
  830 #ifdef WITH_ACL
  831     if (prev_acl) {
  832         acl_free(prev_acl);
  833         prev_acl = NULL;
  834     }
  835 #endif
  836     if (outFile < 0) {
  837         close(inFile);
  838         return 1;
  839     }
  840 
  841     /* pipe used to capture stderr of the compress process */
  842     if (pipe(compressPipe) < 0) {
  843         message(MESS_ERROR, "error opening pipe for compress: %s\n",
  844                 strerror(errno));
  845         close(inFile);
  846         close(outFile);
  847         return 1;
  848     }
  849 
  850     pid = fork();
  851 
  852     if (pid == -1) {
  853         message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
  854         close(inFile);
  855         close(outFile);
  856         close(compressPipe[1]);
  857         close(compressPipe[0]);
  858         return 1;
  859     }
  860 
  861     if (pid == 0) {
  862         char *envInFilename;
  863 
  864         /* close read end of pipe in the child process */
  865         close(compressPipe[0]);
  866 
  867         movefd(inFile, STDIN_FILENO);
  868         movefd(outFile, STDOUT_FILENO);
  869 
  870         if (switch_user_permanently(log) != 0) {
  871             exit(1);
  872         }
  873 
  874         movefd(compressPipe[1], STDERR_FILENO);
  875 
  876         envInFilename = alloca(strlen("LOGROTATE_COMPRESSED_FILENAME=") + strlen(name) + 2);
  877         sprintf(envInFilename, "LOGROTATE_COMPRESSED_FILENAME=%s", name);
  878         putenv(envInFilename);
  879         execvp(fullCommand[0], (void *) fullCommand);
  880         message(MESS_ERROR, "cannot execute compress command: %s\n", strerror(errno));
  881         exit(1);
  882     }
  883 
  884     /* close write end of pipe in the parent process */
  885     close(compressPipe[1]);
  886 
  887     while ((n_read = read(compressPipe[0], buff, sizeof(buff) - 1)) > 0) {
  888         if (!error_printed) {
  889             error_printed = 1;
  890             message(MESS_ERROR, "Compressing program wrote following message "
  891                     "to stderr when compressing log %s:\n", name);
  892         }
  893         buff[n_read] = '\0';
  894         fprintf(stderr, "%s", buff);
  895     }
  896     close(compressPipe[0]);
  897     wait(&status);
  898 
  899     fsync(outFile);
  900     close(outFile);
  901 
  902     if (!WIFEXITED(status) || WEXITSTATUS(status)) {
  903         message(MESS_ERROR, "failed to compress log %s\n", name);
  904         close(inFile);
  905         unlink(compressedName);
  906         return 1;
  907     }
  908 
  909     setAtimeMtime(compressedName, sb);
  910 
  911     if (shred_file(inFile, name, log)) {
  912         close(inFile);
  913         return 1;
  914     }
  915 
  916     close(inFile);
  917 
  918     return 0;
  919 }
  920 
  921 static int mailLog(const struct logInfo *log, const char *logFile, const char *mailComm,
  922                    const char *uncompressCommand, const char *address, const char *subject)
  923 {
  924     int mailInput;
  925     pid_t mailChild, uncompressChild = 0;
  926     int mailStatus, uncompressStatus;
  927     int uncompressPipe[2];
  928     char * const mailArgv[] = { (char *) mailComm, (char *) "-s", (char *) subject, (char *) address, NULL };
  929     int rc = 0;
  930 
  931     if ((mailInput = open(logFile, O_RDONLY | O_NOFOLLOW)) < 0) {
  932         message(MESS_ERROR, "failed to open %s for mailing: %s\n", logFile,
  933                 strerror(errno));
  934         return 1;
  935     }
  936 
  937     if (uncompressCommand) {
  938         /* pipe used to capture output of the uncompress process */
  939         if (pipe(uncompressPipe) < 0) {
  940             message(MESS_ERROR, "error opening pipe for uncompress: %s\n",
  941                     strerror(errno));
  942             close(mailInput);
  943             return 1;
  944         }
  945 
  946         uncompressChild = fork();
  947 
  948         if (uncompressChild == -1) {
  949             message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
  950             close(mailInput);
  951             close(uncompressPipe[1]);
  952             close(uncompressPipe[0]);
  953             return 1;
  954         }
  955 
  956         if (uncompressChild == 0) {
  957             /* uncompress child */
  958 
  959             /* close read end of pipe in the child process */
  960             close(uncompressPipe[0]);
  961 
  962             movefd(mailInput, STDIN_FILENO);
  963             movefd(uncompressPipe[1], STDOUT_FILENO);
  964 
  965             if (switch_user_permanently(log) != 0) {
  966                 exit(1);
  967             }
  968 
  969             execlp(uncompressCommand, uncompressCommand, (char *) NULL);
  970             message(MESS_ERROR, "cannot execute uncompress command: %s\n", strerror(errno));
  971             exit(1);
  972         }
  973 
  974         close(mailInput);
  975         mailInput = uncompressPipe[0];
  976         close(uncompressPipe[1]);
  977     }
  978 
  979     mailChild = fork();
  980 
  981     if (mailChild == -1) {
  982         message(MESS_ERROR, "cannot fork: %s\n", strerror(errno));
  983         close(mailInput);
  984         return 1;
  985     }
  986 
  987     if (mailChild == 0) {
  988         movefd(mailInput, STDIN_FILENO);
  989         close(STDOUT_FILENO);
  990 
  991         /* mail command runs as root */
  992         if (log->flags & LOG_FLAG_SU) {
  993             if (switch_user_back_permanently() != 0) {
  994                 exit(1);
  995             }
  996         }
  997 
  998         execvp(mailArgv[0], mailArgv);
  999         message(MESS_ERROR, "cannot execute mail command: %s\n", strerror(errno));
 1000         exit(1);
 1001     }
 1002 
 1003     close(mailInput);
 1004 
 1005     waitpid(mailChild, &mailStatus, 0);
 1006 
 1007     if (!WIFEXITED(mailStatus) || WEXITSTATUS(mailStatus)) {
 1008         message(MESS_ERROR, "mail command failed for %s\n", logFile);
 1009         rc = 1;
 1010     }
 1011 
 1012     if (uncompressCommand) {
 1013         waitpid(uncompressChild, &uncompressStatus, 0);
 1014 
 1015         if (!WIFEXITED(uncompressStatus) || WEXITSTATUS(uncompressStatus)) {
 1016             message(MESS_ERROR, "uncompress command failed mailing %s\n",
 1017                     logFile);
 1018             rc = 1;
 1019         }
 1020     }
 1021 
 1022     return rc;
 1023 }
 1024 
 1025 static int mailLogWrapper(const char *mailFilename, const char *mailComm,
 1026                           unsigned logNum, const struct logInfo *log)
 1027 {
 1028     /* uncompress already compressed log files before mailing them */
 1029     const char *uncompress_prog = (log->flags & LOG_FLAG_COMPRESS)
 1030         ? log->uncompress_prog
 1031         : NULL;
 1032 
 1033     const char *subject = mailFilename;
 1034     if (log->flags & LOG_FLAG_MAILFIRST) {
 1035         if (log->flags & LOG_FLAG_DELAYCOMPRESS)
 1036             /* the log we are mailing has not been compressed yet */
 1037             uncompress_prog = NULL;
 1038 
 1039         if (uncompress_prog)
 1040             /* use correct subject when mailfirst is enabled */
 1041             subject = log->files[logNum];
 1042     }
 1043 
 1044     return mailLog(log, mailFilename, mailComm, uncompress_prog,
 1045                    log->logAddress, subject);
 1046 }
 1047 
 1048 /* Use a heuristic to determine whether stat buffer SB comes from a file
 1049    with sparse blocks.  If the file has fewer blocks than would normally
 1050    be needed for a file of its size, then at least one of the blocks in
 1051    the file is a hole.  In that case, return true.  */
 1052 static int is_probably_sparse(struct stat const *sb)
 1053 {
 1054 #if defined(HAVE_STRUCT_STAT_ST_BLOCKS) && defined(HAVE_STRUCT_STAT_ST_BLKSIZE)
 1055     return (S_ISREG (sb->st_mode)
 1056             && sb->st_blksize != 0
 1057             && sb->st_blocks < sb->st_size / sb->st_blksize);
 1058 #else
 1059     return 0;
 1060 #endif
 1061 }
 1062 
 1063 #define MIN(a,b) ((a) < (b) ? (a) : (b))
 1064 
 1065 /* Return whether the buffer consists entirely of NULs.
 1066    Note the word after the buffer must be non NUL. */
 1067 
 1068 static int is_nul (void const *buf, size_t bufsize)
 1069 {
 1070     char const *cbuf = buf;
 1071     char const *cp = buf;
 1072 
 1073     /* Find the first nonzero *byte*, or the sentinel.  */
 1074     while (*cp++ == 0)
 1075         continue;
 1076 
 1077     return cbuf + bufsize < cp;
 1078 }
 1079 
 1080 static size_t full_write(int fd, const void *buf, size_t count)
 1081 {
 1082     size_t total = 0;
 1083     const char *ptr = (const char *) buf;
 1084 
 1085     while (count > 0)
 1086     {
 1087         size_t n_rw;
 1088         for (;;)
 1089         {
 1090             n_rw = (size_t)write (fd, buf, count);
 1091             if (errno == EINTR)
 1092                 continue;
 1093             else
 1094                 break;
 1095         }
 1096         if (n_rw == (size_t) -1)
 1097             break;
 1098         if (n_rw == 0)
 1099             break;
 1100         total += n_rw;
 1101         ptr += n_rw;
 1102         count -= n_rw;
 1103     }
 1104 
 1105     return total;
 1106 }
 1107 
 1108 static int sparse_copy(int src_fd, int dest_fd, const struct stat *sb,
 1109                        const char *saveLog, const char *currLog)
 1110 {
 1111     const int make_holes = is_probably_sparse(sb);
 1112     size_t max_n_read = SIZE_MAX;
 1113     int last_write_made_hole = 0;
 1114     off_t total_n_read = 0;
 1115     char buf[BUFSIZ + 1];
 1116 
 1117     while (max_n_read) {
 1118         int make_hole = 0;
 1119         size_t bytes_read;
 1120         const ssize_t n_read = read (src_fd, buf, MIN (max_n_read, BUFSIZ));
 1121         if (n_read < 0) {
 1122             if (errno == EINTR) {
 1123                 continue;
 1124             }
 1125             message(MESS_ERROR, "error reading %s: %s\n",
 1126                     currLog, strerror(errno));
 1127             return 0;
 1128         }
 1129 
 1130         if (n_read == 0)
 1131             break;
 1132 
 1133         bytes_read = (size_t)n_read;
 1134 
 1135         max_n_read -= bytes_read;
 1136         total_n_read += n_read;
 1137 
 1138         if (make_holes) {
 1139             /* Sentinel required by is_nul().  */
 1140             buf[bytes_read] = '\1';
 1141 
 1142             if ((make_hole = is_nul(buf, bytes_read))) {
 1143                 if (lseek (dest_fd, n_read, SEEK_CUR) < 0) {
 1144                     message(MESS_ERROR, "error seeking %s: %s\n",
 1145                             saveLog, strerror(errno));
 1146                     return 0;
 1147                 }
 1148             }
 1149         }
 1150 
 1151         if (!make_hole) {
 1152             if (full_write (dest_fd, buf, bytes_read) != bytes_read) {
 1153                 message(MESS_ERROR, "error writing to %s: %s\n",
 1154                         saveLog, strerror(errno));
 1155                 return 0;
 1156             }
 1157         }
 1158 
 1159         last_write_made_hole = make_hole;
 1160     }
 1161 
 1162     if (last_write_made_hole) {
 1163         if (ftruncate(dest_fd, total_n_read) < 0) {
 1164             message(MESS_ERROR, "error ftruncate %s: %s\n",
 1165                     saveLog, strerror(errno));
 1166             return 0;
 1167         }
 1168     }
 1169 
 1170     return 1;
 1171 }
 1172 
 1173 static int copyTruncate(const char *currLog, const char *saveLog, const struct stat *sb,
 1174                         int flags, int skip_copy)
 1175 {
 1176     int rc = 1;
 1177     int fdcurr = -1, fdsave = -1;
 1178 
 1179     message(MESS_DEBUG, "copying %s to %s\n", currLog, saveLog);
 1180 
 1181     if (!debug) {
 1182         /* read access is sufficient for 'copy' but not for 'copytruncate' */
 1183         const int read_only = (flags & LOG_FLAG_COPY)
 1184             && !(flags & LOG_FLAG_COPYTRUNCATE);
 1185         if ((fdcurr = open(currLog, ((read_only) ? O_RDONLY : O_RDWR) | O_NOFOLLOW)) < 0) {
 1186             message(MESS_ERROR, "error opening %s: %s\n", currLog,
 1187                     strerror(errno));
 1188             goto fail;
 1189         }
 1190 
 1191         if (!skip_copy) {
 1192             char *prevCtx;
 1193 
 1194             if (setSecCtx(fdcurr, currLog, &prevCtx) != 0) {
 1195                 /* error msg already printed */
 1196                 goto fail;
 1197             }
 1198 #ifdef WITH_ACL
 1199             if ((prev_acl = acl_get_fd(fdcurr)) == NULL) {
 1200                 if (is_acl_well_supported(errno)) {
 1201                     message(MESS_ERROR, "getting file ACL %s: %s\n",
 1202                             currLog, strerror(errno));
 1203                     restoreSecCtx(&prevCtx);
 1204                     goto fail;
 1205                 }
 1206             }
 1207 #endif /* WITH_ACL */
 1208             fdsave = createOutputFile(saveLog, O_WRONLY | O_CREAT, sb, prev_acl, 0);
 1209             restoreSecCtx(&prevCtx);
 1210 #ifdef WITH_ACL
 1211             if (prev_acl) {
 1212                 acl_free(prev_acl);
 1213                 prev_acl = NULL;
 1214             }
 1215 #endif
 1216             if (fdsave < 0)
 1217                 goto fail;
 1218 
 1219             if (sparse_copy(fdcurr, fdsave, sb, saveLog, currLog) != 1) {
 1220                 message(MESS_ERROR, "error copying %s to %s: %s\n", currLog,
 1221                         saveLog, strerror(errno));
 1222                 unlink(saveLog);
 1223                 goto fail;
 1224             }
 1225         }
 1226     }
 1227 
 1228     if (flags & LOG_FLAG_COPYTRUNCATE) {
 1229         message(MESS_DEBUG, "truncating %s\n", currLog);
 1230 
 1231         if (!debug) {
 1232             if (fdsave >= 0)
 1233                 fsync(fdsave);
 1234             if (ftruncate(fdcurr, 0)) {
 1235                 message(MESS_ERROR, "error truncating %s: %s\n", currLog,
 1236                         strerror(errno));
 1237                 goto fail;
 1238             }
 1239         }
 1240     } else
 1241         message(MESS_DEBUG, "Not truncating %s\n", currLog);
 1242 
 1243     rc = 0;
 1244 fail:
 1245     if (fdcurr >= 0) {
 1246         close(fdcurr);
 1247     }
 1248     if (fdsave >= 0) {
 1249         close(fdsave);
 1250     }
 1251     return rc;
 1252 }
 1253 
 1254 /* return value similar to mktime() but the exact time is ignored */
 1255 static time_t mktimeFromDateOnly(const struct tm *src)
 1256 {
 1257     /* explicit struct copy to retain C89 compatibility */
 1258     struct tm tmp;
 1259     memcpy(&tmp, src, sizeof tmp);
 1260 
 1261     /* abstract out (nullify) fields expressing the exact time */
 1262     tmp.tm_hour = 0;
 1263     tmp.tm_min  = 0;
 1264     tmp.tm_sec  = 0;
 1265     return mktime(&tmp);
 1266 }
 1267 
 1268 /* return by how many days the date was advanced but ignore exact time */
 1269 static time_t daysElapsed(const struct tm *now, const struct tm *last)
 1270 {
 1271     const time_t diff = mktimeFromDateOnly(now) - mktimeFromDateOnly(last);
 1272     return diff / (24 * 3600);
 1273 }
 1274 
 1275 static int findNeedRotating(const struct logInfo *log, unsigned logNum, int force)
 1276 {
 1277     struct stat sb;
 1278     struct logState *state = NULL;
 1279     struct tm now;
 1280 
 1281     message(MESS_DEBUG, "considering log %s\n", log->files[logNum]);
 1282 
 1283     localtime_r(&nowSecs, &now);
 1284 
 1285     /* Check if parent directory of this log has safe permissions */
 1286     if ((log->flags & LOG_FLAG_SU) == 0 && getuid() == 0) {
 1287         char *ld;
 1288         char *logpath = strdup(log->files[logNum]);
 1289         if (logpath == NULL) {
 1290             message_OOM();
 1291             return 1;
 1292         }
 1293         ld = dirname(logpath);
 1294         if (stat(ld, &sb)) {
 1295             /* If parent directory doesn't exist, it's not real error
 1296                (unless nomissingok is specified)
 1297                and rotation is not needed */
 1298             if (errno != ENOENT || (log->flags & LOG_FLAG_MISSINGOK) == 0) {
 1299                 message(MESS_ERROR, "stat of %s failed: %s\n", ld,
 1300                         strerror(errno));
 1301                 free(logpath);
 1302                 return 1;
 1303             }
 1304             free(logpath);
 1305             return 0;
 1306         }
 1307         /* Don't rotate in directories writable by others or group which is not "root"  */
 1308         if ((sb.st_gid != 0 && (sb.st_mode & S_IWGRP)) || (sb.st_mode & S_IWOTH)) {
 1309             message(MESS_ERROR, "skipping \"%s\" because parent directory has insecure permissions"
 1310                     " (It's world writable or writable by group which is not \"root\")"
 1311                     " Set \"su\" directive in config file to tell logrotate which user/group"
 1312                     " should be used for rotation.\n"
 1313                     ,log->files[logNum]);
 1314             free(logpath);
 1315             return 1;
 1316         }
 1317         free(logpath);
 1318     }
 1319 
 1320     if (lstat(log->files[logNum], &sb)) {
 1321         if ((log->flags & LOG_FLAG_MISSINGOK) && (errno == ENOENT)) {
 1322             message(MESS_DEBUG, "  log %s does not exist -- skipping\n",
 1323                     log->files[logNum]);
 1324             return 0;
 1325         }
 1326         message(MESS_ERROR, "stat of %s failed: %s\n", log->files[logNum],
 1327                 strerror(errno));
 1328         return 1;
 1329     }
 1330 
 1331     state = findState(log->files[logNum]);
 1332     if (!state)
 1333         return 1;
 1334 
 1335     state->doRotate = 0;
 1336     state->sb = sb;
 1337     state->isUsed = 1;
 1338 
 1339     if ((sb.st_mode & S_IFMT) == S_IFLNK) {
 1340         message(MESS_DEBUG, "  log %s is symbolic link. Rotation of symbolic"
 1341                 " links is not allowed to avoid security issues -- skipping.\n",
 1342                 log->files[logNum]);
 1343         return 0;
 1344     }
 1345 
 1346     message(MESS_DEBUG, "  Now: %d-%02d-%02d %02d:%02d\n", 1900 + now.tm_year,
 1347             1 + now.tm_mon, now.tm_mday,
 1348             now.tm_hour, now.tm_min);
 1349 
 1350     message(MESS_DEBUG, "  Last rotated at %d-%02d-%02d %02d:%02d\n", 1900 + state->lastRotated.tm_year,
 1351             1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
 1352             state->lastRotated.tm_hour, state->lastRotated.tm_min);
 1353 
 1354     if (force) {
 1355         /* user forced rotation of logs from command line */
 1356         state->doRotate = 1;
 1357     }
 1358     else if (log->maxsize && sb.st_size > log->maxsize) {
 1359         state->doRotate = 1;
 1360     }
 1361     else if (log->criterium == ROT_SIZE) {
 1362         state->doRotate = (sb.st_size >= log->threshold);
 1363         if (!state->doRotate) {
 1364             message(MESS_DEBUG, "  log does not need rotating "
 1365                     "(log size is below the 'size' threshold)\n");
 1366         }
 1367     } else if (mktime(&state->lastRotated) - mktime(&now) > (25 * 3600)) {
 1368         /* 25 hours allows for DST changes as well as geographical moves */
 1369         message(MESS_ERROR,
 1370                 "log %s last rotated in the future -- rotation forced\n",
 1371                 log->files[logNum]);
 1372         state->doRotate = 1;
 1373     } else if (state->lastRotated.tm_year != now.tm_year ||
 1374             state->lastRotated.tm_mon != now.tm_mon ||
 1375             state->lastRotated.tm_mday != now.tm_mday ||
 1376             state->lastRotated.tm_hour != now.tm_hour) {
 1377         time_t days;
 1378         switch (log->criterium) {
 1379             case ROT_WEEKLY:
 1380                 days = daysElapsed(&now, &state->lastRotated);
 1381                 /* rotate if date is advanced by 7+ days (exact time is ignored) */
 1382                 state->doRotate = (days >= 7)
 1383                     /* ... or if we have not yet rotated today */
 1384                     || (days >= 1
 1385                             /* ... and the selected weekday is today */
 1386                             && (unsigned)now.tm_wday == log->weekday);
 1387                 if (!state->doRotate) {
 1388                     message(MESS_DEBUG, "  log does not need rotating "
 1389                             "(log has been rotated at %d-%02d-%02d %02d:%02d, "
 1390                             "which is less than a week ago)\n", 1900 + state->lastRotated.tm_year,
 1391                             1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
 1392                             state->lastRotated.tm_hour, state->lastRotated.tm_min);
 1393                 }
 1394                 break;
 1395             case ROT_HOURLY:
 1396                 state->doRotate = ((now.tm_hour != state->lastRotated.tm_hour) ||
 1397                         (now.tm_mday != state->lastRotated.tm_mday) ||
 1398                         (now.tm_mon != state->lastRotated.tm_mon) ||
 1399                         (now.tm_year != state->lastRotated.tm_year));
 1400                 if (!state->doRotate) {
 1401                     message(MESS_DEBUG, "  log does not need rotating "
 1402                             "(log has been rotated at %d-%02d-%02d %02d:%02d, "
 1403                             "which is less than an hour ago)\n", 1900 + state->lastRotated.tm_year,
 1404                             1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
 1405                             state->lastRotated.tm_hour, state->lastRotated.tm_min);
 1406                 }
 1407                 break;
 1408             case ROT_DAYS:
 1409                 state->doRotate = ((now.tm_mday != state->lastRotated.tm_mday) ||
 1410                         (now.tm_mon != state->lastRotated.tm_mon) ||
 1411                         (now.tm_year != state->lastRotated.tm_year));
 1412                 if (!state->doRotate) {
 1413                     message(MESS_DEBUG, "  log does not need rotating "
 1414                             "(log has been rotated at %d-%02d-%02d %02d:%02d, "
 1415                             "which is less than a day ago)\n", 1900 + state->lastRotated.tm_year,
 1416                             1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
 1417                             state->lastRotated.tm_hour, state->lastRotated.tm_min);
 1418                 }
 1419                 break;
 1420             case ROT_MONTHLY:
 1421                 /* rotate if the logs haven't been rotated this month or
 1422                    this year */
 1423                 state->doRotate = ((now.tm_mon != state->lastRotated.tm_mon) ||
 1424                         (now.tm_year != state->lastRotated.tm_year));
 1425                 if (!state->doRotate) {
 1426                     message(MESS_DEBUG, "  log does not need rotating "
 1427                             "(log has been rotated at %d-%02d-%02d %02d:%02d, "
 1428                             "which is less than a month ago)\n", 1900 + state->lastRotated.tm_year,
 1429                             1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
 1430                             state->lastRotated.tm_hour, state->lastRotated.tm_min);
 1431                 }
 1432                 break;
 1433             case ROT_YEARLY:
 1434                 /* rotate if the logs haven't been rotated this year */
 1435                 state->doRotate = (now.tm_year != state->lastRotated.tm_year);
 1436                 if (!state->doRotate) {
 1437                     message(MESS_DEBUG, "  log does not need rotating "
 1438                             "(log has been rotated at %d-%02d-%02d %02d:%02d, "
 1439                             "which is less than a year ago)\n", 1900 + state->lastRotated.tm_year,
 1440                             1 + state->lastRotated.tm_mon, state->lastRotated.tm_mday,
 1441                             state->lastRotated.tm_hour, state->lastRotated.tm_min);
 1442                 }
 1443                 break;
 1444             case ROT_SIZE:
 1445             default:
 1446                 /* ack! */
 1447                 state->doRotate = 0;
 1448                 break;
 1449         }
 1450         if (log->minsize && sb.st_size < log->minsize) {
 1451             state->doRotate = 0;
 1452             message(MESS_DEBUG, "  log does not need rotating "
 1453                     "('minsize' directive is used and the log "
 1454                     "size is smaller than the minsize value)\n");
 1455         }
 1456         if (log->rotateMinAge && log->rotateMinAge * DAY_SECONDS >= nowSecs - sb.st_mtime) {
 1457             state->doRotate = 0;
 1458             message(MESS_DEBUG, "  log does not need rotating "
 1459                     "('minage' directive is used and the log "
 1460                     "age is smaller than the minage days)\n");
 1461         }
 1462     }
 1463     else if (!state->doRotate) {
 1464         message(MESS_DEBUG, "  log does not need rotating "
 1465                 "(log has already been rotated)\n");
 1466     }
 1467 
 1468     /* The notifempty flag overrides the normal criteria */
 1469     if (state->doRotate && !(log->flags & LOG_FLAG_IFEMPTY) && !sb.st_size) {
 1470         state->doRotate = 0;
 1471         message(MESS_DEBUG, "  log does not need rotating "
 1472                 "(log is empty)\n");
 1473     }
 1474 
 1475     if (state->doRotate) {
 1476         message(MESS_DEBUG, "  log needs rotating\n");
 1477     }
 1478 
 1479     return 0;
 1480 }
 1481 
 1482 /* find the rotated file with the highest index */
 1483 static int findLastRotated(const struct logNames *rotNames,
 1484                            const char *fileext, const char *compext)
 1485 {
 1486     char *pattern;
 1487     int glob_rc;
 1488     glob_t globResult;
 1489     size_t i;
 1490     int last = 0;
 1491     size_t prefixLen, suffixLen;
 1492 
 1493     if (asprintf(&pattern, "%s/%s.*%s%s", rotNames->dirName,
 1494                  rotNames->baseName, fileext, compext) < 0)
 1495         /* out of memory */
 1496         return -1;
 1497 
 1498     glob_rc = glob(pattern, 0, globerr, &globResult);
 1499     free(pattern);
 1500     switch (glob_rc) {
 1501         case 0:
 1502             /* glob() succeeded */
 1503             break;
 1504 
 1505         case GLOB_NOMATCH:
 1506             /* found nothing -> assume first rotation */
 1507             return 0;
 1508 
 1509         default:
 1510             /* glob() failed */
 1511             return -1;
 1512     }
 1513 
 1514     prefixLen = strlen(rotNames->dirName) + /* '/' */1
 1515         + strlen(rotNames->baseName) + /* '.' */ 1;
 1516     suffixLen = strlen(fileext) + strlen(compext);
 1517 
 1518     for (i = 0; i < globResult.gl_pathc; ++i) {
 1519         char *fileName = globResult.gl_pathv[i];
 1520         const size_t fileNameLen = strlen(fileName);
 1521         int num;
 1522         char c;
 1523         if (fileNameLen <= prefixLen + suffixLen)
 1524             /* not enough room for index in this file name */
 1525             continue;
 1526 
 1527         /* cut off prefix/suffix */
 1528         fileName[fileNameLen - suffixLen] = '\0';
 1529         fileName += prefixLen;
 1530 
 1531         if (sscanf(fileName, "%d%c", &num, &c) != 1)
 1532             /* index not matched in this file name */
 1533             continue;
 1534 
 1535         /* update last index */
 1536         if (last < num)
 1537             last = num;
 1538     }
 1539 
 1540     globfree(&globResult);
 1541     return last;
 1542 }
 1543 
 1544 static int prerotateSingleLog(const struct logInfo *log, unsigned logNum,
 1545                               struct logState *state, struct logNames *rotNames)
 1546 {
 1547     struct tm now;
 1548     const char *compext = "";
 1549     const char *fileext = "";
 1550     int hasErrors = 0;
 1551     char *glob_pattern;
 1552     glob_t globResult;
 1553     int rc;
 1554     int rotateCount = log->rotateCount ? log->rotateCount : 1;
 1555     int logStart = (log->logStart == -1) ? 1 : log->logStart;
 1556 #define DATEEXT_LEN 64
 1557 #define PATTERN_LEN (DATEEXT_LEN * 2)
 1558     char dext_str[DATEEXT_LEN];
 1559     char dformat[PATTERN_LEN] = "";
 1560     char dext_pattern[PATTERN_LEN];
 1561 
 1562     if (!state->doRotate)
 1563         return 0;
 1564 
 1565     /* Logs with rotateCounts of 0 are rotated once, then removed. This
 1566        lets scripts run properly, and everything gets mailed properly. */
 1567 
 1568     message(MESS_DEBUG, "rotating log %s, log->rotateCount is %d\n",
 1569             log->files[logNum], log->rotateCount);
 1570 
 1571     if (log->flags & LOG_FLAG_COMPRESS) {
 1572         if (!log->compress_ext) {
 1573             message(MESS_ERROR, "log %s: compression enabled, but compression "
 1574                 "extension is not set\n", log->files[logNum]);
 1575             return 1;
 1576         }
 1577 
 1578         compext = log->compress_ext;
 1579     }
 1580 
 1581     localtime_r(&nowSecs, &now);
 1582     state->lastRotated = now;
 1583 
 1584     {
 1585         const char *ld;
 1586         char *logpath = strdup(log->files[logNum]);
 1587         if (logpath == NULL) {
 1588             message_OOM();
 1589             return 1;
 1590         }
 1591         ld = dirname(logpath);
 1592         if (log->oldDir) {
 1593             if (log->oldDir[0] != '/') {
 1594                 if (asprintf(&rotNames->dirName, "%s/%s", ld, log->oldDir) < 0) {
 1595                     rotNames->dirName = NULL;
 1596                 }
 1597             } else
 1598                 rotNames->dirName = strdup(log->oldDir);
 1599         } else
 1600             rotNames->dirName = strdup(ld);
 1601         free(logpath);
 1602 
 1603         if (rotNames->dirName == NULL) {
 1604             message_OOM();
 1605             return 1;
 1606         }
 1607     }
 1608 
 1609     {
 1610         char *filename = strdup(log->files[logNum]);
 1611         if (filename == NULL) {
 1612             message_OOM();
 1613             return 1;
 1614         }
 1615 
 1616         rotNames->baseName = strdup(basename(filename));
 1617         if (rotNames->baseName == NULL) {
 1618             message_OOM();
 1619             free(filename);
 1620             return 1;
 1621         }
 1622 
 1623         free(filename);
 1624     }
 1625 
 1626     if (log->addextension) {
 1627         const size_t baseLen = strlen(rotNames->baseName);
 1628         const size_t extLen = strlen(log->addextension);
 1629         if (baseLen >= extLen &&
 1630                 strncmp(&(rotNames->baseName[baseLen - extLen]),
 1631                     log->addextension, extLen) == 0) {
 1632 
 1633             char *tempstr = strndup(rotNames->baseName, baseLen - extLen);
 1634             if (tempstr == NULL) {
 1635                 message_OOM();
 1636                 return 1;
 1637             }
 1638 
 1639             free(rotNames->baseName);
 1640             rotNames->baseName = tempstr;
 1641         }
 1642         fileext = log->addextension;
 1643     }
 1644 
 1645     if (log->extension) {
 1646         const size_t baseLen = strlen(rotNames->baseName);
 1647         const size_t extLen = strlen(log->extension);
 1648 
 1649         if (baseLen >= extLen &&
 1650                 strncmp(&(rotNames->baseName[baseLen - extLen]),
 1651                     log->extension, extLen) == 0) {
 1652             char *tempstr;
 1653 
 1654             fileext = log->extension;
 1655             tempstr = strndup(rotNames->baseName, baseLen - extLen);
 1656             if (tempstr == NULL) {
 1657                 message_OOM();
 1658                 return 1;
 1659             }
 1660             free(rotNames->baseName);
 1661             rotNames->baseName = tempstr;
 1662         }
 1663     }
 1664 
 1665     /* Adjust "now" if we want yesterday's date */
 1666     if (log->flags & LOG_FLAG_DATEYESTERDAY) {
 1667         now.tm_hour = 12; /* set hour to noon to work around DST issues */
 1668         now.tm_mday = now.tm_mday - 1;
 1669         mktime(&now);
 1670     }
 1671 
 1672     if (log->flags & LOG_FLAG_DATEHOURAGO) {
 1673         now.tm_hour -= 1;
 1674         mktime(&now);
 1675     }
 1676 
 1677     /* Construct the glob pattern corresponding to the date format */
 1678     dext_str[0] = '\0';
 1679     if (log->dateformat) {
 1680         char *dext;
 1681         size_t i = 0, j = 0;
 1682         memset(dext_pattern, 0, sizeof(dext_pattern));
 1683         dext = log->dateformat;
 1684         while (*dext == ' ')
 1685             dext++;
 1686         while ((*dext != '\0') && (!hasErrors)) {
 1687             /* Will there be a space for a char and '\0'? */
 1688             if (j >= (sizeof(dext_pattern) - 1)) {
 1689                 message(MESS_ERROR, "Date format %s is too long\n",
 1690                         log->dateformat);
 1691                 hasErrors = 1;
 1692                 break;
 1693             }
 1694             if (*dext == '%') {
 1695                 switch (*(dext + 1)) {
 1696                     case 'Y':
 1697                         strncat(dext_pattern, "[0-9][0-9]",
 1698                                 sizeof(dext_pattern) - strlen(dext_pattern) - 1);
 1699                         j += 10; /* strlen("[0-9][0-9]") */
 1700                         /* FALLTHRU */
 1701                     case 'm':
 1702                     case 'd':
 1703                     case 'H':
 1704                     case 'M':
 1705                     case 'S':
 1706                     case 'V':
 1707                         strncat(dext_pattern, "[0-9][0-9]",
 1708                                 sizeof(dext_pattern) - strlen(dext_pattern) - 1);
 1709                         j += 10;
 1710                         if (j >= (sizeof(dext_pattern) - 1)) {
 1711                             message(MESS_ERROR, "Date format %s is too long\n",
 1712                                     log->dateformat);
 1713                             hasErrors = 1;
 1714                             break;
 1715                         }
 1716                         dformat[i++] = *(dext++);
 1717                         dformat[i] = *dext;
 1718                         break;
 1719                     case 's':
 1720                         /* End of year 2293 this pattern does not work. */
 1721                         strncat(dext_pattern,
 1722                                 "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",
 1723                                 sizeof(dext_pattern) - strlen(dext_pattern) - 1);
 1724                         j += 50;
 1725                         if (j >= (sizeof(dext_pattern) - 1)) {
 1726                             message(MESS_ERROR, "Date format %s is too long\n",
 1727                                     log->dateformat);
 1728                             hasErrors = 1;
 1729                             break;
 1730                         }
 1731                         dformat[i++] = *(dext++);
 1732                         dformat[i] = *dext;
 1733                         break;
 1734                     default:
 1735                         dformat[i++] = *dext;
 1736                         dformat[i] = '%';
 1737                         dext_pattern[j++] = *dext;
 1738                         break;
 1739                 }
 1740             } else {
 1741                 dformat[i] = *dext;
 1742                 dext_pattern[j++] = *dext;
 1743             }
 1744             ++i;
 1745             ++dext;
 1746         }
 1747         dformat[i] = '\0';
 1748         message(MESS_DEBUG, "Converted '%s' -> '%s'\n", log->dateformat, dformat);
 1749         strftime(dext_str, sizeof(dext_str), dformat, &now);
 1750     } else {
 1751         if (log->criterium == ROT_HOURLY) {
 1752             /* hourly adds another two digits */
 1753             strftime(dext_str, sizeof(dext_str), "-%Y%m%d%H", &now);
 1754             strncpy(dext_pattern, "-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",
 1755                     sizeof(dext_pattern));
 1756         } else {
 1757             /* The default dateformat and glob pattern */
 1758             strftime(dext_str, sizeof(dext_str), "-%Y%m%d", &now);
 1759             strncpy(dext_pattern, "-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",
 1760                     sizeof(dext_pattern));
 1761         }
 1762         dext_pattern[PATTERN_LEN - 1] = '\0';
 1763     }
 1764     message(MESS_DEBUG, "dateext suffix '%s'\n", dext_str);
 1765     message(MESS_DEBUG, "glob pattern '%s'\n", dext_pattern);
 1766 
 1767     if (setSecCtxByName(log->files[logNum], &prev_context) != 0) {
 1768         /* error msg already printed */
 1769         return 1;
 1770     }
 1771 
 1772     /* First compress the previous log when necessary */
 1773     if ((log->flags & LOG_FLAG_COMPRESS) &&
 1774             (log->flags & LOG_FLAG_DELAYCOMPRESS)) {
 1775         if (log->flags & LOG_FLAG_DATEEXT) {
 1776             /* glob for uncompressed files with our pattern */
 1777             if (asprintf(&glob_pattern, "%s/%s%s%s", rotNames->dirName,
 1778                          rotNames->baseName, dext_pattern, fileext) < 0) {
 1779                 message_OOM();
 1780                 return 1;
 1781             }
 1782             rc = glob(glob_pattern, 0, globerr, &globResult);
 1783             if (!rc && globResult.gl_pathc > 0) {
 1784                 size_t glob_count;
 1785                 sortGlobResult(&globResult, strlen(rotNames->dirName) + 1 + strlen(rotNames->baseName), dformat);
 1786                 for (glob_count = 0; glob_count < globResult.gl_pathc && !hasErrors; glob_count++) {
 1787                     struct stat sbprev;
 1788                     const char *oldName = globResult.gl_pathv[glob_count];
 1789 
 1790                     if (stat(oldName, &sbprev)) {
 1791                         if (errno == ENOENT)
 1792                             message(MESS_DEBUG, "previous log %s does not exist\n", oldName);
 1793                         else
 1794                             message(MESS_ERROR, "cannot stat %s: %s\n", oldName, strerror(errno));
 1795                     } else {
 1796                         hasErrors = compressLogFile(oldName, log, &sbprev);
 1797                     }
 1798                 }
 1799             } else {
 1800                 message(MESS_DEBUG,
 1801                         "glob finding logs to compress failed\n");
 1802             }
 1803             globfree(&globResult);
 1804             free(glob_pattern);
 1805         } else {
 1806             struct stat sbprev;
 1807             char *oldName;
 1808             if (asprintf(&oldName, "%s/%s.%d%s", rotNames->dirName,
 1809                          rotNames->baseName, logStart, fileext) < 0) {
 1810                 message_OOM();
 1811                 return 1;
 1812             }
 1813             if (stat(oldName, &sbprev)) {
 1814                 if (errno == ENOENT)
 1815                     message(MESS_DEBUG, "previous log %s does not exist\n", oldName);
 1816                 else
 1817                     message(MESS_ERROR, "cannot stat %s: %s\n", oldName, strerror(errno));
 1818             } else {
 1819                 hasErrors = compressLogFile(oldName, log, &sbprev);
 1820             }
 1821             free(oldName);
 1822         }
 1823     }
 1824 
 1825     if (log->flags & LOG_FLAG_DATEEXT) {
 1826         /* glob for compressed files with our pattern
 1827          * and compress ext */
 1828         if (asprintf(&glob_pattern, "%s/%s%s%s%s", rotNames->dirName,
 1829                      rotNames->baseName, dext_pattern, fileext, compext) < 0) {
 1830             message_OOM();
 1831             return 1;
 1832         }
 1833         rc = glob(glob_pattern, 0, globerr, &globResult);
 1834         if (!rc) {
 1835             /* search for files to drop, if we find one remember it,
 1836              * if we find another one mail and remove the first and
 1837              * remember the second and so on */
 1838             struct stat fst_buf;
 1839             size_t glob_count, mail_out = (size_t)-1;
 1840             /* Remove the first (n - rotateCount) matches no real rotation
 1841              * needed, since the files have the date in their name. Note that
 1842              * (size_t)-1 == SIZE_T_MAX in rotateCount */
 1843             sortGlobResult(&globResult, strlen(rotNames->dirName) + 1 + strlen(rotNames->baseName), dformat);
 1844             for (glob_count = 0; glob_count < globResult.gl_pathc; glob_count++) {
 1845                 if (!stat((globResult.gl_pathv)[glob_count], &fst_buf)) {
 1846                     if (((globResult.gl_pathc >= (size_t)rotateCount) && (glob_count <= (globResult.gl_pathc - (size_t)rotateCount)))
 1847                             || ((log->rotateAge > 0)
 1848                                 &&
 1849                                 (((nowSecs - fst_buf.st_mtime) / DAY_SECONDS)
 1850                                  > log->rotateAge))) {
 1851                         if (mail_out != (size_t)-1) {
 1852                             char *mailFilename =
 1853                                 (globResult.gl_pathv)[mail_out];
 1854                             if (!hasErrors && log->logAddress)
 1855                                 hasErrors = mailLogWrapper(mailFilename, mailCommand,
 1856                                                            logNum, log);
 1857                             if (!hasErrors) {
 1858                                 message(MESS_DEBUG, "removing %s\n", mailFilename);
 1859                                 hasErrors = removeLogFile(mailFilename, log);
 1860                             }
 1861                         }
 1862                         mail_out = glob_count;
 1863                     }
 1864                 }
 1865             }
 1866             if (mail_out != (size_t)-1) {
 1867                 /* oldName is oldest Backup found (for unlink later) */
 1868                 const char *oldName = globResult.gl_pathv[mail_out];
 1869                 rotNames->disposeName = strdup(oldName);
 1870                 if (rotNames->disposeName == NULL) {
 1871                     message_OOM();
 1872                     globfree(&globResult);
 1873                     free(glob_pattern);
 1874                     return 1;
 1875                 }
 1876             } else {
 1877                 free(rotNames->disposeName);
 1878                 rotNames->disposeName = NULL;
 1879             }
 1880         } else {
 1881             message(MESS_DEBUG, "glob finding old rotated logs failed\n");
 1882             free(rotNames->disposeName);
 1883             rotNames->disposeName = NULL;
 1884         }
 1885         /* firstRotated is most recently created/compressed rotated log */
 1886         if (asprintf(&rotNames->firstRotated, "%s/%s%s%s%s",
 1887                 rotNames->dirName, rotNames->baseName, dext_str, fileext,
 1888                 (log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext) < 0) {
 1889             message_OOM();
 1890             rotNames->firstRotated = NULL;
 1891             globfree(&globResult);
 1892             free(glob_pattern);
 1893             return 1;
 1894         }
 1895         globfree(&globResult);
 1896         free(glob_pattern);
 1897     } else {
 1898         int i;
 1899         char *newName = NULL;
 1900         char *oldName;
 1901 
 1902         if (rotateCount == -1) {
 1903             rotateCount = findLastRotated(rotNames, fileext, compext);
 1904             if (rotateCount < 0) {
 1905                 message(MESS_ERROR, "could not find last rotated file: %s/%s.*%s%s\n",
 1906                         rotNames->dirName, rotNames->baseName, fileext, compext);
 1907                 return 1;
 1908             }
 1909         }
 1910 
 1911         if (asprintf(&oldName, "%s/%s.%d%s%s", rotNames->dirName,
 1912                      rotNames->baseName, logStart + rotateCount, fileext,
 1913                      compext) < 0) {
 1914             message_OOM();
 1915             return 1;
 1916         }
 1917 
 1918         if (log->rotateCount != -1) {
 1919             rotNames->disposeName = strdup(oldName);
 1920             if (rotNames->disposeName == NULL) {
 1921                 message_OOM();
 1922                 free(oldName);
 1923                 return 1;
 1924             }
 1925         }
 1926 
 1927         if (asprintf(&rotNames->firstRotated, "%s/%s.%d%s%s", rotNames->dirName,
 1928                 rotNames->baseName, logStart, fileext,
 1929                 (log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext) < 0) {
 1930             message_OOM();
 1931             free(oldName);
 1932             rotNames->firstRotated = NULL;
 1933             return 1;
 1934         }
 1935 
 1936         for (i = rotateCount + logStart - 1; (i >= 0) && !hasErrors; i--) {
 1937             free(newName);
 1938             newName = oldName;
 1939             if (asprintf(&oldName, "%s/%s.%d%s%s", rotNames->dirName,
 1940                          rotNames->baseName, i, fileext, compext) < 0) {
 1941                 message_OOM();
 1942                 oldName = NULL;
 1943                 break;
 1944             }
 1945 
 1946             /* remove files hit by maxage */
 1947             if (log->rotateAge) {
 1948                 struct stat fst_buf;
 1949 
 1950                 if (stat(oldName, &fst_buf)) {
 1951                     if (errno == ENOENT) {
 1952                         message(MESS_DEBUG, "old log %s does not exist\n",
 1953                                 oldName);
 1954                     } else {
 1955                         message(MESS_ERROR, "cannot stat %s: %s\n", oldName,
 1956                                 strerror(errno));
 1957                         hasErrors = 1;
 1958                     }
 1959 
 1960                     continue;
 1961                 }
 1962 
 1963                 if (((nowSecs - fst_buf.st_mtime) / DAY_SECONDS) > log->rotateAge) {
 1964                     if (!hasErrors && log->logAddress)
 1965                         hasErrors = mailLogWrapper(oldName, mailCommand,
 1966                                                    logNum, log);
 1967                     if (!hasErrors)
 1968                         hasErrors = removeLogFile(oldName, log);
 1969 
 1970                     continue;
 1971                 }
 1972             }
 1973 
 1974             message(MESS_DEBUG,
 1975                     "renaming %s to %s (rotatecount %d, logstart %d, i %d), \n",
 1976                     oldName, newName, rotateCount, logStart, i);
 1977 
 1978             if (!debug && rename(oldName, newName)) {
 1979                 if (errno == ENOENT) {
 1980                     message(MESS_DEBUG, "old log %s does not exist\n",
 1981                             oldName);
 1982                 } else {
 1983                     message(MESS_ERROR, "error renaming %s to %s: %s\n",
 1984                             oldName, newName, strerror(errno));
 1985                     hasErrors = 1;
 1986                 }
 1987             }
 1988         }
 1989         free(newName);
 1990         free(oldName);
 1991     } /* !LOG_FLAG_DATEEXT */
 1992 
 1993     if (log->flags & LOG_FLAG_DATEEXT) {
 1994         char *destFile;
 1995         struct stat fst_buf;
 1996 
 1997         if (asprintf(&(rotNames->finalName), "%s/%s%s%s", rotNames->dirName,
 1998                      rotNames->baseName, dext_str, fileext) < 0) {
 1999             message_OOM();
 2000             rotNames->finalName = NULL;
 2001             return 1;
 2002         }
 2003         if (asprintf(&destFile, "%s%s", rotNames->finalName, compext) < 0) {
 2004             message_OOM();
 2005             return 1;
 2006         }
 2007         if (!stat(destFile, &fst_buf)) {
 2008             message(MESS_ERROR,
 2009                     "destination %s already exists, skipping rotation\n",
 2010                     rotNames->firstRotated);
 2011             hasErrors = 1;
 2012         }
 2013         free(destFile);
 2014     } else {
 2015         /* note: the gzip extension is *not* used here! */
 2016         if (asprintf(&(rotNames->finalName), "%s/%s.%d%s", rotNames->dirName,
 2017                      rotNames->baseName, logStart, fileext) < 0) {
 2018             message_OOM();
 2019             rotNames->finalName = NULL;
 2020         }
 2021     }
 2022 
 2023     /* if the last rotation doesn't exist, that's okay */
 2024     if (rotNames->disposeName && access(rotNames->disposeName, F_OK)) {
 2025         message(MESS_DEBUG,
 2026                 "log %s doesn't exist -- won't try to dispose of it\n",
 2027                 rotNames->disposeName);
 2028         free(rotNames->disposeName);
 2029         rotNames->disposeName = NULL;
 2030     }
 2031 
 2032     return hasErrors;
 2033 }
 2034 
 2035 static int rotateSingleLog(const struct logInfo *log, unsigned logNum,
 2036                            struct logState *state, struct logNames *rotNames)
 2037 {
 2038     int hasErrors = 0;
 2039     struct stat sb;
 2040     char *savedContext = NULL;
 2041 
 2042     if (!state->doRotate)
 2043         return 0;
 2044 
 2045     if (!hasErrors) {
 2046 
 2047         if (!(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) {
 2048             if (setSecCtxByName(log->files[logNum], &savedContext) != 0) {
 2049                 /* error msg already printed */
 2050                 return 1;
 2051             }
 2052 #ifdef WITH_ACL
 2053             if ((prev_acl = acl_get_file(log->files[logNum], ACL_TYPE_ACCESS)) == NULL) {
 2054                 if (is_acl_well_supported(errno)) {
 2055                     message(MESS_ERROR, "getting file ACL %s: %s\n",
 2056                             log->files[logNum], strerror(errno));
 2057                     hasErrors = 1;
 2058                 }
 2059             }
 2060 #endif /* WITH_ACL */
 2061             if (log->flags & LOG_FLAG_TMPFILENAME) {
 2062                 char *tmpFilename;
 2063 
 2064                 if (asprintf(&tmpFilename, "%s%s", log->files[logNum], ".tmp") < 0) {
 2065                     message_OOM();
 2066                     restoreSecCtx(&savedContext);
 2067                     return 1;
 2068                 }
 2069 
 2070                 message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum],
 2071                         tmpFilename);
 2072                 if (!debug && !hasErrors && rename(log->files[logNum], tmpFilename)) {
 2073                     message(MESS_ERROR, "failed to rename %s to %s: %s\n",
 2074                             log->files[logNum], tmpFilename,
 2075                             strerror(errno));
 2076                     hasErrors = 1;
 2077                 }
 2078 
 2079                 free(tmpFilename);
 2080             }
 2081             else {
 2082                 message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum],
 2083                         rotNames->finalName);
 2084                 if (!debug && !hasErrors &&
 2085                         rename(log->files[logNum], rotNames->finalName)) {
 2086                     message(MESS_ERROR, "failed to rename %s to %s: %s\n",
 2087                             log->files[logNum], rotNames->finalName,
 2088                             strerror(errno));
 2089                     hasErrors = 1;
 2090                 }
 2091             }
 2092 
 2093             if (!log->rotateCount) {
 2094                 const char *ext = "";
 2095                 if (log->compress_ext
 2096                         && (log->flags & LOG_FLAG_COMPRESS)
 2097                         && !(log->flags & LOG_FLAG_DELAYCOMPRESS))
 2098                     ext = log->compress_ext;
 2099 
 2100                 free(rotNames->disposeName);
 2101                 if (asprintf(&rotNames->disposeName, "%s%s", rotNames->finalName, ext) < 0) {
 2102                     message_OOM();
 2103                     rotNames->disposeName = NULL;
 2104                     return 1;
 2105                 }
 2106 
 2107                 message(MESS_DEBUG, "disposeName will be %s\n", rotNames->disposeName);
 2108             }
 2109         }
 2110 
 2111         if (!hasErrors && (log->flags & LOG_FLAG_CREATE) &&
 2112                 !(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) {
 2113             int have_create_mode = 0;
 2114 
 2115             if (log->createUid == NO_UID)
 2116                 sb.st_uid = state->sb.st_uid;
 2117             else
 2118                 sb.st_uid = log->createUid;
 2119 
 2120             if (log->createGid == NO_GID)
 2121                 sb.st_gid = state->sb.st_gid;
 2122             else
 2123                 sb.st_gid = log->createGid;
 2124             if (log->createMode == NO_MODE)
 2125                 sb.st_mode = state->sb.st_mode & 0777;
 2126             else {
 2127                 sb.st_mode = log->createMode;
 2128                 have_create_mode = 1;
 2129             }
 2130 
 2131             message(MESS_DEBUG, "creating new %s mode = 0%o uid = %d "
 2132                     "gid = %d\n", log->files[logNum], (unsigned int) sb.st_mode,
 2133                     (int) sb.st_uid, (int) sb.st_gid);
 2134 
 2135             if (!debug) {
 2136                 if (!hasErrors) {
 2137                     int fd = createOutputFile(log->files[logNum], O_CREAT | O_RDWR,
 2138                             &sb, prev_acl, have_create_mode);
 2139 #ifdef WITH_ACL
 2140                     if (prev_acl) {
 2141                         acl_free(prev_acl);
 2142                         prev_acl = NULL;
 2143                     }
 2144 #endif
 2145                     if (fd < 0)
 2146                         hasErrors = 1;
 2147                     else {
 2148                         close(fd);
 2149                     }
 2150                 }
 2151             }
 2152         }
 2153 
 2154         restoreSecCtx(&savedContext);
 2155 
 2156         if (!hasErrors
 2157                 && (log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))
 2158                 && !(log->flags & LOG_FLAG_TMPFILENAME)) {
 2159             hasErrors = copyTruncate(log->files[logNum], rotNames->finalName,
 2160                                      &state->sb, log->flags, !log->rotateCount);
 2161         }
 2162 
 2163 #ifdef WITH_ACL
 2164         if (prev_acl) {
 2165             acl_free(prev_acl);
 2166             prev_acl = NULL;
 2167         }
 2168 #endif /* WITH_ACL */
 2169 
 2170     }
 2171     return hasErrors;
 2172 }
 2173 
 2174 static int postrotateSingleLog(const struct logInfo *log, unsigned logNum,
 2175                                struct logState *state,
 2176                                struct logNames *rotNames)
 2177 {
 2178     int hasErrors = 0;
 2179 
 2180     if (!state->doRotate) {
 2181         return 0;
 2182     }
 2183 
 2184     if (!hasErrors && (log->flags & LOG_FLAG_TMPFILENAME)) {
 2185         char *tmpFilename;
 2186         if (asprintf(&tmpFilename, "%s%s", log->files[logNum], ".tmp") < 0) {
 2187             message_OOM();
 2188             return 1;
 2189         }
 2190         hasErrors = copyTruncate(tmpFilename, rotNames->finalName,
 2191                                  &state->sb, LOG_FLAG_COPY, /* skip_copy */ 0);
 2192         message(MESS_DEBUG, "removing tmp log %s\n", tmpFilename);
 2193         if (!debug && !hasErrors) {
 2194             unlink(tmpFilename);
 2195         }
 2196         free(tmpFilename);
 2197     }
 2198 
 2199     if (!hasErrors && (log->flags & LOG_FLAG_COMPRESS) &&
 2200             !(log->flags & LOG_FLAG_DELAYCOMPRESS)) {
 2201         hasErrors = compressLogFile(rotNames->finalName, log, &state->sb);
 2202     }
 2203 
 2204     if (!hasErrors && log->logAddress) {
 2205         char *mailFilename;
 2206 
 2207         if (log->flags & LOG_FLAG_MAILFIRST)
 2208             mailFilename = rotNames->firstRotated;
 2209         else
 2210             mailFilename = rotNames->disposeName;
 2211 
 2212         if (mailFilename)
 2213             hasErrors = mailLogWrapper(mailFilename, mailCommand, logNum, log);
 2214     }
 2215 
 2216     if (!hasErrors && rotNames->disposeName)
 2217         hasErrors = removeLogFile(rotNames->disposeName, log);
 2218 
 2219     restoreSecCtx(&prev_context);
 2220     return hasErrors;
 2221 }
 2222 
 2223 static int rotateLogSet(const struct logInfo *log, int force)
 2224 {
 2225     unsigned i, j;
 2226     int hasErrors = 0;
 2227     int *logHasErrors;
 2228     int numRotated = 0;
 2229     struct logState **state;
 2230     struct logNames **rotNames;
 2231 
 2232     message(MESS_DEBUG, "\nrotating pattern: %s ", log->pattern);
 2233     if (force) {
 2234         message(MESS_DEBUG, "forced from command line ");
 2235     }
 2236     else {
 2237         switch (log->criterium) {
 2238             case ROT_HOURLY:
 2239                 message(MESS_DEBUG, "hourly ");
 2240                 break;
 2241             case ROT_DAYS:
 2242                 message(MESS_DEBUG, "after %jd days ", (intmax_t)log->threshold);
 2243                 break;
 2244             case ROT_WEEKLY:
 2245                 message(MESS_DEBUG, "weekly ");
 2246                 break;
 2247             case ROT_MONTHLY:
 2248                 message(MESS_DEBUG, "monthly ");
 2249                 break;
 2250             case ROT_YEARLY:
 2251                 message(MESS_DEBUG, "yearly ");
 2252                 break;
 2253             case ROT_SIZE:
 2254                 message(MESS_DEBUG, "%jd bytes ", (intmax_t)log->threshold);
 2255                 break;
 2256             default:
 2257                 message(MESS_FATAL, "rotateLogSet() does not have case for: %u ",
 2258                         (unsigned) log->criterium);
 2259         }
 2260     }
 2261 
 2262     if (log->rotateCount > 0)
 2263         message(MESS_DEBUG, "(%d rotations)\n", log->rotateCount);
 2264     else if (log->rotateCount == 0)
 2265         message(MESS_DEBUG, "(no old logs will be kept)\n");
 2266 
 2267     if (log->oldDir)
 2268         message(MESS_DEBUG, "olddir is %s, ", log->oldDir);
 2269 
 2270     if (log->flags & LOG_FLAG_IFEMPTY)
 2271         message(MESS_DEBUG, "empty log files are rotated, ");
 2272     else
 2273         message(MESS_DEBUG, "empty log files are not rotated, ");
 2274 
 2275     if (log->minsize)
 2276         message(MESS_DEBUG, "only log files >= %jd bytes are rotated, ", (intmax_t)log->minsize);
 2277 
 2278     if (log->maxsize)
 2279         message(MESS_DEBUG, "log files >= %jd are rotated earlier, ", (intmax_t)log->maxsize);
 2280 
 2281     if (log->rotateMinAge)
 2282         message(MESS_DEBUG, "only log files older than %d days are rotated, ", log->rotateMinAge);
 2283 
 2284     if (log->logAddress) {
 2285         message(MESS_DEBUG, "old logs mailed to %s\n", log->logAddress);
 2286     } else {
 2287         message(MESS_DEBUG, "old logs are removed\n");
 2288     }
 2289 
 2290     if (log->numFiles == 0) {
 2291         message(MESS_DEBUG, "No logs found. Rotation not needed.\n");
 2292         return 0;
 2293     }
 2294 
 2295     logHasErrors = calloc(log->numFiles, sizeof(int));
 2296     if (!logHasErrors) {
 2297         message_OOM();
 2298         return 1;
 2299     }
 2300 
 2301     if (log->flags & LOG_FLAG_SU) {
 2302         if (switch_user(log->suUid, log->suGid) != 0) {
 2303             free(logHasErrors);
 2304             return 1;
 2305         }
 2306     }
 2307 
 2308     for (i = 0; i < log->numFiles; i++) {
 2309         struct logState *logState;
 2310         logHasErrors[i] = findNeedRotating(log, i, force);
 2311         hasErrors |= logHasErrors[i];
 2312 
 2313         /* sure is a lot of findStating going on .. */
 2314         if (((logState = findState(log->files[i]))) && logState->doRotate)
 2315             numRotated++;
 2316     }
 2317 
 2318     if (log->first) {
 2319         if (!numRotated) {
 2320             message(MESS_DEBUG, "not running first action script, "
 2321                     "since no logs will be rotated\n");
 2322         } else {
 2323             message(MESS_DEBUG, "running first action script\n");
 2324             if (runScript(log, log->pattern, NULL, log->first)) {
 2325                 message(MESS_ERROR, "error running first action script "
 2326                         "for %s\n", log->pattern);
 2327                 hasErrors = 1;
 2328                 if (log->flags & LOG_FLAG_SU) {
 2329                     if (switch_user_back() != 0) {
 2330                         free(logHasErrors);
 2331                         return 1;
 2332                     }
 2333                 }
 2334                 /* finish early, firstaction failed, affects all logs in set */
 2335                 free(logHasErrors);
 2336                 return hasErrors;
 2337             }
 2338         }
 2339     }
 2340 
 2341     state = malloc(log->numFiles * sizeof(struct logState *));
 2342     rotNames = malloc(log->numFiles * sizeof(struct logNames *));
 2343 
 2344     if (state == NULL || rotNames == NULL) {
 2345         message_OOM();
 2346         if (log->flags & LOG_FLAG_SU) {
 2347             switch_user_back();
 2348         }
 2349         free(rotNames);
 2350         free(state);
 2351         free(logHasErrors);
 2352         return 1;
 2353     }
 2354 
 2355     for (j = 0;
 2356             (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < log->numFiles)
 2357             || ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < 1); j++) {
 2358 
 2359         for (i = j;
 2360                 ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
 2361                 || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
 2362             state[i] = findState(log->files[i]);
 2363             if (!state[i])
 2364                 logHasErrors[i] = 1;
 2365 
 2366             rotNames[i] = malloc(sizeof(struct logNames));
 2367             if (rotNames[i] == NULL) {
 2368                 message_OOM();
 2369                 if (log->flags & LOG_FLAG_SU) {
 2370                     switch_user_back();
 2371                 }
 2372                 free(rotNames);
 2373                 free(state);
 2374                 free(logHasErrors);
 2375                 return 1;
 2376             }
 2377             memset(rotNames[i], 0, sizeof(struct logNames));
 2378 
 2379             logHasErrors[i] |=
 2380                 prerotateSingleLog(log, i, state[i], rotNames[i]);
 2381             hasErrors |= logHasErrors[i];
 2382         }
 2383 
 2384         if (log->pre
 2385                 && (!(
 2386                         (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && (logHasErrors[j] || !state[j]->doRotate))
 2387                         || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS))
 2388                      ))
 2389            ) {
 2390             if (!numRotated) {
 2391                 message(MESS_DEBUG, "not running prerotate script, "
 2392                         "since no logs will be rotated\n");
 2393             } else {
 2394                 message(MESS_DEBUG, "running prerotate script\n");
 2395                 if (runScript(log, (log->flags & LOG_FLAG_SHAREDSCRIPTS) ? log->pattern : log->files[j], NULL, log->pre)) {
 2396                     if (log->flags & LOG_FLAG_SHAREDSCRIPTS)
 2397                         message(MESS_ERROR,
 2398                                 "error running shared prerotate script "
 2399                                 "for '%s'\n", log->pattern);
 2400                     else {
 2401                         message(MESS_ERROR,
 2402                                 "error running non-shared prerotate script "
 2403                                 "for %s of '%s'\n", log->files[j], log->pattern);
 2404                     }
 2405                     logHasErrors[j] = 1;
 2406                     hasErrors = 1;
 2407                 }
 2408             }
 2409         }
 2410 
 2411         for (i = j;
 2412                 ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
 2413                 || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
 2414             if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
 2415                         || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) {
 2416                 logHasErrors[i] |=
 2417                     rotateSingleLog(log, i, state[i], rotNames[i]);
 2418                 hasErrors |= logHasErrors[i];
 2419             }
 2420         }
 2421 
 2422         if (log->post
 2423                 && (!(
 2424                         (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && (logHasErrors[j] || !state[j]->doRotate))
 2425                         || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS))
 2426                      ))
 2427            ) {
 2428             if (!numRotated) {
 2429                 message(MESS_DEBUG, "not running postrotate script, "
 2430                         "since no logs were rotated\n");
 2431             } else {
 2432                 char *logfn = (log->flags & LOG_FLAG_SHAREDSCRIPTS) ? log->pattern : log->files[j];
 2433 
 2434                 /* It only makes sense to pass in a final rotated filename if scripts are not shared */
 2435                 char *logrotfn = (log->flags & LOG_FLAG_SHAREDSCRIPTS) ? NULL : rotNames[j]->finalName;
 2436 
 2437                 message(MESS_DEBUG, "running postrotate script\n");
 2438                 if (runScript(log, logfn, logrotfn, log->post)) {
 2439                     if (log->flags & LOG_FLAG_SHAREDSCRIPTS)
 2440                         message(MESS_ERROR,
 2441                                 "error running shared postrotate script "
 2442                                 "for '%s'\n", log->pattern);
 2443                     else {
 2444                         message(MESS_ERROR,
 2445                                 "error running non-shared postrotate script "
 2446                                 "for %s of '%s'\n", log->files[j], log->pattern);
 2447                     }
 2448                     logHasErrors[j] = 1;
 2449                     hasErrors = 1;
 2450                 }
 2451             }
 2452         }
 2453 
 2454         for (i = j;
 2455                 ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
 2456                 || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
 2457             if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
 2458                         || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) {
 2459                 logHasErrors[i] |=
 2460                     postrotateSingleLog(log, i, state[i], rotNames[i]);
 2461                 hasErrors |= logHasErrors[i];
 2462             }
 2463         }
 2464 
 2465     }
 2466 
 2467     for (i = 0; i < log->numFiles; i++) {
 2468         free(rotNames[i]->firstRotated);
 2469         free(rotNames[i]->disposeName);
 2470         free(rotNames[i]->finalName);
 2471         free(rotNames[i]->dirName);
 2472         free(rotNames[i]->baseName);
 2473         free(rotNames[i]);
 2474     }
 2475     free(rotNames);
 2476     free(state);
 2477 
 2478     if (log->last) {
 2479         if (!numRotated) {
 2480             message(MESS_DEBUG, "not running last action script, "
 2481                     "since no logs will be rotated\n");
 2482         } else {
 2483             message(MESS_DEBUG, "running last action script\n");
 2484             if (runScript(log, log->pattern, NULL, log->last)) {
 2485                 message(MESS_ERROR, "error running last action script "
 2486                         "for %s\n", log->pattern);
 2487                 hasErrors = 1;
 2488             }
 2489         }
 2490     }
 2491 
 2492     if (log->flags & LOG_FLAG_SU) {
 2493         if (switch_user_back() != 0) {
 2494             free(logHasErrors);
 2495             return 1;
 2496         }
 2497     }
 2498     free(logHasErrors);
 2499     return hasErrors;
 2500 }
 2501 
 2502 static int writeState(const char *stateFilename)
 2503 {
 2504     struct logState *p;
 2505     FILE *f;
 2506     char *chptr;
 2507     unsigned int i = 0;
 2508     int error = 0;
 2509     int bytes = 0;
 2510     int fdcurr;
 2511     int fdsave;
 2512     struct stat sb;
 2513     char *tmpFilename = NULL;
 2514     struct tm now;
 2515     time_t now_time, last_time;
 2516     char *prevCtx;
 2517 
 2518     localtime_r(&nowSecs, &now);
 2519 
 2520     tmpFilename = malloc(strlen(stateFilename) + 5 );
 2521     if (tmpFilename == NULL) {
 2522         message_OOM();
 2523         return 1;
 2524     }
 2525     strcpy(tmpFilename, stateFilename);
 2526     strcat(tmpFilename, ".tmp");
 2527     /* Remove possible tmp state file from previous run */
 2528     error = unlink(tmpFilename);
 2529     if (error == -1 && errno != ENOENT) {
 2530         message(MESS_ERROR, "error removing old temporary state file %s: %s\n",
 2531                 tmpFilename, strerror(errno));
 2532         free(tmpFilename);
 2533         return 1;
 2534     }
 2535     error = 0;
 2536 
 2537     fdcurr = open(stateFilename, O_RDONLY);
 2538     if (fdcurr == -1) {
 2539         /* the statefile should exist, lockState() already created an empty
 2540          * state file in case it did not exist initially */
 2541         message(MESS_ERROR, "error opening state file %s: %s\n",
 2542                 stateFilename, strerror(errno));
 2543         free(tmpFilename);
 2544         return 1;
 2545     }
 2546 
 2547     /* get attributes, to assign them to the new state file */
 2548 
 2549     if (setSecCtx(fdcurr, stateFilename, &prevCtx) != 0) {
 2550         /* error msg already printed */
 2551         free(tmpFilename);
 2552         close(fdcurr);
 2553         return 1;
 2554     }
 2555 
 2556 #ifdef WITH_ACL
 2557     if ((prev_acl = acl_get_fd(fdcurr)) == NULL) {
 2558         if (is_acl_well_supported(errno)) {
 2559             message(MESS_ERROR, "getting file ACL %s: %s\n",
 2560                     stateFilename, strerror(errno));
 2561             restoreSecCtx(&prevCtx);
 2562             free(tmpFilename);
 2563             close(fdcurr);
 2564             return 1;
 2565         }
 2566     }
 2567 #endif
 2568 
 2569     if (fstat(fdcurr, &sb) == -1) {
 2570         message(MESS_ERROR, "error stating %s: %s\n", stateFilename, strerror(errno));
 2571         restoreSecCtx(&prevCtx);
 2572         free(tmpFilename);
 2573 #ifdef WITH_ACL
 2574         if (prev_acl) {
 2575             acl_free(prev_acl);
 2576             prev_acl = NULL;
 2577         }
 2578 #endif
 2579         return 1;
 2580     }
 2581 
 2582     close(fdcurr);
 2583 
 2584     fdsave = createOutputFile(tmpFilename, O_RDWR | O_CREAT | O_TRUNC, &sb, prev_acl, 0);
 2585 #ifdef WITH_ACL
 2586     if (prev_acl) {
 2587         acl_free(prev_acl);
 2588         prev_acl = NULL;
 2589     }
 2590 #endif
 2591     restoreSecCtx(&prevCtx);
 2592 
 2593     if (fdsave < 0) {
 2594         free(tmpFilename);
 2595         return 1;
 2596     }
 2597 
 2598     f = fdopen(fdsave, "w");
 2599     if (!f) {
 2600         message(MESS_ERROR, "error creating temp state file %s: %s\n",
 2601                 tmpFilename, strerror(errno));
 2602         free(tmpFilename);
 2603         return 1;
 2604     }
 2605 
 2606     bytes =  fprintf(f, "logrotate state -- version 2\n");
 2607     if (bytes < 0)
 2608         error = bytes;
 2609 
 2610     /*
 2611      * Time in seconds it takes earth to go around sun.  The value is
 2612      * astronomical measurement (solar year) rather than something derived from
 2613      * a convention (calendar year).
 2614      */
 2615 #define SECONDS_IN_YEAR 31556926
 2616 
 2617     for (i = 0; i < hashSize && error == 0; i++) {
 2618         for (p = states[i]->head.lh_first; p != NULL && error == 0;
 2619                 p = p->list.le_next) {
 2620 
 2621             /* Skip states which are not used for more than a year. */
 2622             now_time = mktime(&now);
 2623             last_time = mktime(&p->lastRotated);
 2624             if (!p->isUsed && difftime(now_time, last_time) > SECONDS_IN_YEAR) {
 2625                 message(MESS_DEBUG, "Removing %s from state file, "
 2626                         "because it does not exist and has not been rotated for one year\n",
 2627                         p->fn);
 2628                 continue;
 2629             }
 2630 
 2631             error = fputc('"', f) == EOF;
 2632             for (chptr = p->fn; *chptr && error == 0; chptr++) {
 2633                 switch (*chptr) {
 2634                     case '"':
 2635                     case '\\':
 2636                         error = fputc('\\', f) == EOF;
 2637                         break;
 2638                     case '\n':
 2639                         error = fputc('\\', f) == EOF;
 2640                         if (error == 0) {
 2641                             error = fputc('n', f) == EOF;
 2642                         }
 2643                         continue;
 2644                     default:
 2645                         break;
 2646                 }
 2647                 if (error == 0 && fputc(*chptr, f) == EOF) {
 2648                     error = 1;
 2649                 }
 2650             }
 2651 
 2652             if (error == 0 && fputc('"', f) == EOF)
 2653                 error = 1;
 2654 
 2655             if (error == 0) {
 2656                 bytes = fprintf(f, " %d-%d-%d-%d:%d:%d\n",
 2657                                 p->lastRotated.tm_year + 1900,
 2658                                 p->lastRotated.tm_mon + 1,
 2659                                 p->lastRotated.tm_mday,
 2660                                 p->lastRotated.tm_hour,
 2661                                 p->lastRotated.tm_min,
 2662                                 p->lastRotated.tm_sec);
 2663                 if (bytes < 0)
 2664                     error = bytes;
 2665             }
 2666         }
 2667     }
 2668 
 2669     if (error == 0)
 2670         error = fflush(f);
 2671 
 2672     if (error == 0)
 2673         error = fsync(fdsave);
 2674 
 2675     if (error == 0)
 2676         error = fclose(f);
 2677     else
 2678         fclose(f);
 2679 
 2680     if (error == 0) {
 2681         if (rename(tmpFilename, stateFilename)) {
 2682             message(MESS_ERROR, "error renaming temp state file %s to %s: %s\n",
 2683                     tmpFilename, stateFilename, strerror(errno));
 2684             unlink(tmpFilename);
 2685             error = 1;
 2686         }
 2687     }
 2688     else {
 2689         if (errno)
 2690             message(MESS_ERROR, "error creating temp state file %s: %s\n",
 2691                     tmpFilename, strerror(errno));
 2692         else
 2693             message(MESS_ERROR, "error creating temp state file %s%s\n",
 2694                     tmpFilename, error == ENOMEM ?
 2695                     ": Insufficient storage space is available." : "" );
 2696         unlink(tmpFilename);
 2697     }
 2698     free(tmpFilename);
 2699     return error;
 2700 }
 2701 
 2702 static int readState(const char *stateFilename)
 2703 {
 2704     FILE *f;
 2705     char buf[STATEFILE_BUFFER_SIZE];
 2706     char *filename;
 2707     const char **argv;
 2708     int argc;
 2709     int year, month, day, hour, minute, second;
 2710     int line = 0;
 2711     int fd;
 2712     struct logState *st;
 2713     time_t lr_time;
 2714     struct stat f_stat;
 2715     int rc = 0;
 2716 
 2717     message(MESS_DEBUG, "Reading state from file: %s\n", stateFilename);
 2718 
 2719     fd = open(stateFilename, O_RDONLY);
 2720     if (fd == -1) {
 2721         /* treat non-openable file as an empty file for allocateHash() */
 2722         f_stat.st_size = 0;
 2723 
 2724         /* the statefile should exist, lockState() already created an empty
 2725          * state file in case it did not exist initially */
 2726         message(MESS_ERROR, "error opening state file %s: %s\n",
 2727                 stateFilename, strerror(errno));
 2728 
 2729         /* Do not return until the hash table is allocated.
 2730          * In debug mode the state file might not exist,
 2731          * cause lockState() is not called */
 2732         if (!debug) {
 2733             rc = 1;
 2734         }
 2735     } else {
 2736         if (fstat(fd, &f_stat) == -1) {
 2737             /* treat non-statable file as an empty file for allocateHash() */
 2738             f_stat.st_size = 0;
 2739 
 2740             message(MESS_ERROR, "error stat()ing state file %s: %s\n",
 2741                     stateFilename, strerror(errno));
 2742 
 2743             /* do not return until the hash table is allocated */
 2744             rc = 1;
 2745         }
 2746     }
 2747 
 2748     /* Try to estimate how many state entries we have in the state file.
 2749      * We expect single entry to have around 80 characters (Of course this is
 2750      * just an estimation). During the testing I've found out that 200 entries
 2751      * per single hash entry gives good mem/performance ratio. */
 2752     if (allocateHash((size_t)f_stat.st_size / 80 / 200))
 2753         rc = 1;
 2754 
 2755     if (rc || (f_stat.st_size == 0)) {
 2756         /* error already occurred, or we have no state file to read from */
 2757         if (fd != -1)
 2758             close(fd);
 2759         return rc;
 2760     }
 2761 
 2762     f = fdopen(fd, "r");
 2763     if (!f) {
 2764         message(MESS_ERROR, "error opening state file %s: %s\n",
 2765                 stateFilename, strerror(errno));
 2766         close(fd);
 2767         return 1;
 2768     }
 2769 
 2770     if (!fgets(buf, sizeof(buf) - 1, f)) {
 2771         message(MESS_ERROR, "error reading top line of %s\n",
 2772                 stateFilename);
 2773         fclose(f);
 2774         return 1;
 2775     }
 2776 
 2777     if (strcmp(buf, "logrotate state -- version 1\n") != 0 &&
 2778             strcmp(buf, "logrotate state -- version 2\n") != 0) {
 2779         fclose(f);
 2780         message(MESS_ERROR, "bad top line in state file %s\n",
 2781                 stateFilename);
 2782         return 1;
 2783     }
 2784 
 2785     line++;
 2786 
 2787     while (fgets(buf, sizeof(buf) - 1, f)) {
 2788         const size_t i = strlen(buf);
 2789         argv = NULL;
 2790         line++;
 2791         if (i == 0) {
 2792             message(MESS_ERROR, "line %d not parsable in state file %s\n",
 2793                     line, stateFilename);
 2794             fclose(f);
 2795             return 1;
 2796         }
 2797         if (buf[i - 1] != '\n') {
 2798             message(MESS_ERROR, "line %d too long in state file %s\n",
 2799                     line, stateFilename);
 2800             fclose(f);
 2801             return 1;
 2802         }
 2803 
 2804         buf[i - 1] = '\0';
 2805 
 2806         if (i == 1)
 2807             continue;
 2808 
 2809         year = month = day = hour = minute = second = 0;
 2810         if (poptParseArgvString(buf, &argc, &argv) || (argc != 2) ||
 2811                 (sscanf(argv[1], "%d-%d-%d-%d:%d:%d", &year, &month, &day, &hour, &minute, &second) < 3)) {
 2812             message(MESS_ERROR, "bad line %d in state file %s\n",
 2813                     line, stateFilename);
 2814             free(argv);
 2815             fclose(f);
 2816             return 1;
 2817         }
 2818 
 2819         /* Hack to hide earlier bug */
 2820         if ((year != 1900) && (year < 1970 || year > 2100)) {
 2821             message(MESS_ERROR,
 2822                     "bad year %d for file %s in state file %s\n", year,
 2823                     argv[0], stateFilename);
 2824             free(argv);
 2825             fclose(f);
 2826             return 1;
 2827         }
 2828 
 2829         if (month < 1 || month > 12) {
 2830             message(MESS_ERROR,
 2831                     "bad month %d for file %s in state file %s\n", month,
 2832                     argv[0], stateFilename);
 2833             free(argv);
 2834             fclose(f);
 2835             return 1;
 2836         }
 2837 
 2838         /* 0 to hide earlier bug */
 2839         if (day < 0 || day > 31) {
 2840             message(MESS_ERROR,
 2841                     "bad day %d for file %s in state file %s\n", day,
 2842                     argv[0], stateFilename);
 2843             free(argv);
 2844             fclose(f);
 2845             return 1;
 2846         }
 2847 
 2848         if (hour < 0 || hour > 23) {
 2849             message(MESS_ERROR,
 2850                     "bad hour %d for file %s in state file %s\n", hour,
 2851                     argv[0], stateFilename);
 2852             free(argv);
 2853             fclose(f);
 2854             return 1;
 2855         }
 2856 
 2857         if (minute < 0 || minute > 59) {
 2858             message(MESS_ERROR,
 2859                     "bad minute %d for file %s in state file %s\n", minute,
 2860                     argv[0], stateFilename);
 2861             free(argv);
 2862             fclose(f);
 2863             return 1;
 2864         }
 2865 
 2866         if (second < 0 || second > 59) {
 2867             message(MESS_ERROR,
 2868                     "bad second %d for file %s in state file %s\n", second,
 2869                     argv[0], stateFilename);
 2870             free(argv);
 2871             fclose(f);
 2872             return 1;
 2873         }
 2874 
 2875         year -= 1900;
 2876         month -= 1;
 2877 
 2878         filename = strdup(argv[0]);
 2879         if (filename == NULL) {
 2880             message_OOM();
 2881             free(argv);
 2882             fclose(f);
 2883             return 1;
 2884         }
 2885         unescape(filename);
 2886 
 2887         if ((st = findState(filename)) == NULL) {
 2888             free(argv);
 2889             free(filename);
 2890             fclose(f);
 2891             return 1;
 2892         }
 2893 
 2894         memset(&st->lastRotated, 0, sizeof(st->lastRotated));
 2895         st->lastRotated.tm_year = year;
 2896         st->lastRotated.tm_mon = month;
 2897         st->lastRotated.tm_mday = day;
 2898         st->lastRotated.tm_hour = hour;
 2899         st->lastRotated.tm_min = minute;
 2900         st->lastRotated.tm_sec = second;
 2901         st->lastRotated.tm_isdst = -1;
 2902 
 2903         /* fill in the rest of the st->lastRotated fields */
 2904         lr_time = mktime(&st->lastRotated);
 2905         localtime_r(&lr_time, &st->lastRotated);
 2906 
 2907         free(argv);
 2908         free(filename);
 2909     }
 2910 
 2911     fclose(f);
 2912     return 0;
 2913 }
 2914 
 2915 static int lockState(const char *stateFilename, int skip_state_lock)
 2916 {
 2917     int lockFd = open(stateFilename, O_RDWR | O_CLOEXEC);
 2918     if (lockFd == -1) {
 2919         if (errno == ENOENT) {
 2920             message(MESS_DEBUG, "Creating stub state file: %s\n",
 2921                     stateFilename);
 2922 
 2923             /* create a stub state file with mode 0644 */
 2924             lockFd = open(stateFilename, O_CREAT | O_EXCL | O_WRONLY,
 2925                           S_IWUSR | S_IRUSR | S_IRGRP | S_IROTH);
 2926             if (lockFd == -1) {
 2927                 message(MESS_ERROR, "error creating stub state file %s: %s\n",
 2928                         stateFilename, strerror(errno));
 2929                 return 1;
 2930             }
 2931         } else {
 2932             message(MESS_ERROR, "error opening state file %s: %s\n",
 2933                     stateFilename, strerror(errno));
 2934             return 1;
 2935         }
 2936     }
 2937 
 2938     if (skip_state_lock) {
 2939         message(MESS_DEBUG, "Skip locking state file %s\n",
 2940                 stateFilename);
 2941         close(lockFd);
 2942         return 0;
 2943     }
 2944 
 2945     if (flock(lockFd, LOCK_EX | LOCK_NB) == -1) {
 2946         if (errno == EWOULDBLOCK) {
 2947             message(MESS_ERROR, "state file %s is already locked\n"
 2948                     "logrotate does not support parallel execution on the"
 2949                     " same set of logfiles.\n", stateFilename);
 2950         } else {
 2951             message(MESS_ERROR, "error acquiring lock on state file %s: %s\n",
 2952                     stateFilename, strerror(errno));
 2953         }
 2954         close(lockFd);
 2955         return 1;
 2956     }
 2957 
 2958     /* keep lockFd open till we terminate */
 2959     return 0;
 2960 }
 2961 
 2962 int main(int argc, const char **argv)
 2963 {
 2964     int force = 0;
 2965     int skip_state_lock = 0;
 2966     const char *stateFile = STATEFILE;
 2967     const char *logFile = NULL;
 2968     FILE *logFd = NULL;
 2969     int rc = 0;
 2970     int arg;
 2971     const char **files;
 2972     poptContext optCon;
 2973     struct logInfo *log;
 2974 
 2975     struct poptOption options[] = {
 2976         {"debug", 'd', 0, NULL, 'd',
 2977             "Don't do anything, just test and print debug messages", NULL},
 2978         {"force", 'f', 0, &force, 0, "Force file rotation", NULL},
 2979         {"mail", 'm', POPT_ARG_STRING, &mailCommand, 0,
 2980             "Command to send mail (instead of `" DEFAULT_MAIL_COMMAND "')",
 2981             "command"},
 2982         {"state", 's', POPT_ARG_STRING, &stateFile, 0,
 2983             "Path of state file",
 2984             "statefile"},
 2985         {"skip-state-lock", '\0', POPT_ARG_NONE, &skip_state_lock, 0, "Do not lock the state file", NULL},
 2986         {"verbose", 'v', 0, NULL, 'v', "Display messages during rotation", NULL},
 2987         {"log", 'l', POPT_ARG_STRING, &logFile, 'l', "Log file or 'syslog' to log to syslog",
 2988             "logfile"},
 2989         {"version", '\0', POPT_ARG_NONE, NULL, 'V', "Display version information", NULL},
 2990         POPT_AUTOHELP { NULL, 0, 0, NULL, 0, NULL, NULL }
 2991     };
 2992 
 2993     logSetLevel(MESS_NORMAL);
 2994     setlocale (LC_ALL, "");
 2995 
 2996     optCon = poptGetContext("logrotate", argc, argv, options, 0);
 2997     poptReadDefaultConfig(optCon, 1);
 2998     poptSetOtherOptionHelp(optCon, "[OPTION...] <configfile>");
 2999 
 3000     while ((arg = poptGetNextOpt(optCon)) >= 0) {
 3001         switch (arg) {
 3002             case 'd':
 3003                 debug = 1;
 3004                 message(MESS_NORMAL, "WARNING: logrotate in debug mode does nothing"
 3005                         " except printing debug messages!  Consider using verbose"
 3006                         " mode (-v) instead if this is not what you want.\n\n");
 3007                 /* fallthrough */
 3008             case 'v':
 3009                 logSetLevel(MESS_DEBUG);
 3010                 break;
 3011             case 'l':
 3012                 if (strcmp(logFile, "syslog") == 0) {
 3013                     logToSyslog(1);
 3014                 }
 3015                 else {
 3016                     logFd = fopen(logFile, "w");
 3017                     if (!logFd) {
 3018                         message(MESS_ERROR, "error opening log file %s: %s\n",
 3019                                 logFile, strerror(errno));
 3020                         break;
 3021                     }
 3022                     logSetMessageFile(logFd);
 3023                 }
 3024                 break;
 3025             case 'V':
 3026                 printf("logrotate %s\n", VERSION);
 3027                 printf("\n");
 3028                 printf("    Default mail command:       %s\n", DEFAULT_MAIL_COMMAND);
 3029                 printf("    Default compress command:   %s\n", COMPRESS_COMMAND);
 3030                 printf("    Default uncompress command: %s\n", UNCOMPRESS_COMMAND);
 3031                 printf("    Default compress extension: %s\n", COMPRESS_EXT);
 3032                 printf("    Default state file path:    %s\n", STATEFILE);
 3033 #ifdef WITH_ACL
 3034                 printf("    ACL support:                yes\n");
 3035 #else
 3036                 printf("    ACL support:                no\n");
 3037 #endif
 3038 #ifdef WITH_SELINUX
 3039                 printf("    SELinux support:            yes\n");
 3040 #else
 3041                 printf("    SELinux support:            no\n");
 3042 #endif
 3043                 poptFreeContext(optCon);
 3044                 exit(0);
 3045             default:
 3046                 break;
 3047         }
 3048     }
 3049 
 3050     if (arg < -1) {
 3051         fprintf(stderr, "logrotate: bad argument %s: %s\n",
 3052                 poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
 3053                 poptStrerror(rc));
 3054         poptFreeContext(optCon);
 3055         return 2;
 3056     }
 3057 
 3058     files = poptGetArgs(optCon);
 3059     if (!files) {
 3060         fprintf(stderr, "logrotate " VERSION
 3061                 " - Copyright (C) 1995-2001 Red Hat, Inc.\n");
 3062         fprintf(stderr,
 3063                 "This may be freely redistributed under the terms of "
 3064                 "the GNU General Public License\n\n");
 3065         poptPrintUsage(optCon, stderr, 0);
 3066         poptFreeContext(optCon);
 3067         exit(1);
 3068     }
 3069 #ifdef WITH_SELINUX
 3070     selinux_enabled = (is_selinux_enabled() > 0);
 3071     selinux_enforce = security_getenforce();
 3072 #endif
 3073 
 3074     TAILQ_INIT(&logs);
 3075 
 3076     if (readAllConfigPaths(files))
 3077         rc = 1;
 3078 
 3079     poptFreeContext(optCon);
 3080     nowSecs = time(NULL);
 3081 
 3082     if (!debug && lockState(stateFile, skip_state_lock)) {
 3083         exit(3);
 3084     }
 3085 
 3086     if (readState(stateFile))
 3087         rc = 1;
 3088 
 3089     message(MESS_DEBUG, "\nHandling %d logs\n", numLogs);
 3090 
 3091     for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next)
 3092         rc |= rotateLogSet(log, force);
 3093 
 3094     if (!debug)
 3095         rc |= writeState(stateFile);
 3096 
 3097     return (rc != 0);
 3098 }
 3099 
 3100 /* vim: set et sw=4 ts=4: */