OSDN Git Service

2013.10.24
[uclinux-h8/uClinux-dist.git] / user / logrotate / logrotate.c
1 #include <sys/queue.h>
2 #include <alloca.h>
3 #include <ctype.h>
4 #include <dirent.h>
5 #include <errno.h>
6 #include <fcntl.h>
7 #include <popt.h>
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <sys/stat.h>
12 #include <sys/wait.h>
13 #include <time.h>
14 #include <unistd.h>
15 #include <glob.h>
16 #include <locale.h>
17
18 #ifdef WITH_SELINUX
19 #include <selinux/selinux.h>
20 static security_context_t prev_context = NULL;
21 int selinux_enabled = 0;
22 int selinux_enforce = 0;
23 #endif
24
25 #include "basenames.h"
26 #include "log.h"
27 #include "logrotate.h"
28
29 #if !defined(GLOB_ABORTED) && defined(GLOB_ABEND)
30 #define GLOB_ABORTED GLOB_ABEND
31 #endif
32
33 struct logState {
34     char *fn;
35     struct tm lastRotated;      /* only tm.mon, tm_mday, tm_year are good! */
36     struct stat sb;
37     int doRotate;
38     LIST_ENTRY(logState) list;
39 };
40
41 struct logNames {
42     char *firstRotated;
43     char *disposeName;
44     char *finalName;
45     char *dirName;
46     char *baseName;
47 };
48
49 LIST_HEAD(stateSet, logState) states;
50
51 int numLogs = 0;
52 int debug = 0;
53 char *mailCommand = DEFAULT_MAIL_COMMAND;
54 time_t nowSecs = 0;
55
56 static int shred_file(char *filename, struct logInfo *log);
57
58 static int globerr(const char *pathname, int theerr)
59 {
60     message(MESS_ERROR, "error accessing %s: %s\n", pathname,
61             strerror(theerr));
62
63     /* We want the glob operation to continue, so return 0 */
64     return 1;
65 }
66
67 static struct logState *newState(const char *fn)
68 {
69         struct tm now = *localtime(&nowSecs);
70         struct logState *new;
71         time_t lr_time;
72
73         if ((new = malloc(sizeof(*new))) == NULL)
74                 return NULL;
75
76         if ((new->fn = strdup(fn)) == NULL)
77                 return NULL;
78
79         new->doRotate = 0;
80
81         memset(&new->lastRotated, 0, sizeof(new->lastRotated));
82         new->lastRotated.tm_mon = now.tm_mon;
83         new->lastRotated.tm_mday = now.tm_mday;
84         new->lastRotated.tm_year = now.tm_year;
85
86         /* fill in the rest of the new->lastRotated fields */
87         lr_time = mktime(&new->lastRotated);
88         new->lastRotated = *localtime(&lr_time);
89
90         return new;
91 }
92
93 static struct logState *findState(const char *fn)
94 {
95         struct logState *p;
96
97         for (p = states.lh_first; p != NULL; p = p->list.le_next)
98                 if (!strcmp(fn, p->fn))
99                         break;
100
101         /* new state */
102         if (p == NULL) {
103                 if ((p = newState(fn)) == NULL)
104                         return NULL;
105
106                 LIST_INSERT_HEAD(&states, p, list);
107         }
108
109         return p;
110 }
111
112 static int runScript(char *logfn, char *script)
113 {
114     int rc;
115
116     if (debug) {
117         message(MESS_DEBUG, "running script with arg %s: \"%s\"\n",
118                 logfn, script);
119         return 0;
120     }
121
122         if (!fork()) {
123                 execl("/bin/sh", "sh", "-c", script, "logrotate_script", logfn, NULL);
124                 exit(1);
125         }
126
127     wait(&rc);
128     return rc;
129 }
130
131 int createOutputFile(char *fileName, int flags, struct stat *sb)
132 {
133     int fd;
134
135     fd = open(fileName, flags, sb->st_mode);
136     if (fd < 0) {
137         message(MESS_ERROR, "error creating output file %s: %s\n",
138                 fileName, strerror(errno));
139         return -1;
140     }
141     if (fchmod(fd, (S_IRUSR | S_IWUSR) & sb->st_mode)) {
142         message(MESS_ERROR, "error setting mode of %s: %s\n",
143                 fileName, strerror(errno));
144         close(fd);
145         return -1;
146     }
147     if (fchown(fd, sb->st_uid, sb->st_gid)) {
148         message(MESS_ERROR, "error setting owner of %s: %s\n",
149                 fileName, strerror(errno));
150         close(fd);
151         return -1;
152     }
153     if (fchmod(fd, sb->st_mode)) {
154         message(MESS_ERROR, "error setting mode of %s: %s\n",
155                 fileName, strerror(errno));
156         close(fd);
157         return -1;
158     }
159     return fd;
160 }
161
162 #define SHRED_CALL "shred -u "
163 #define SHRED_COUNT_FLAG "-n "
164 #define DIGITS 10
165 /* unlink, but try to call shred from GNU fileutils */
166 static int shred_file(char *filename, struct logInfo *log)
167 {
168         int len, ret;
169         char *cmd;
170         char count[DIGITS];    /*  that's a lot of shredding :)  */
171
172         if (!(log->flags & LOG_FLAG_SHRED)) {
173                 return unlink(filename);
174         }
175
176         len = strlen(filename) + strlen(SHRED_CALL);
177         len += strlen(SHRED_COUNT_FLAG) + DIGITS;
178         cmd = malloc(len);
179
180         if (!cmd) {
181                 message(MESS_ERROR, "malloc error while shredding");
182                 return unlink(filename);
183         }
184         strcpy(cmd, SHRED_CALL);
185         if (log->shred_cycles != 0) {
186                 strcat(cmd, SHRED_COUNT_FLAG);
187                 snprintf(count, DIGITS - 1, "%d", log->shred_cycles);
188                 strcat(count, " ");
189                 strcat(cmd, count);
190         }
191         strcat(cmd, filename);
192         ret = system(cmd);
193         free(cmd);
194         if (ret != 0) {
195                 message(MESS_ERROR, "Failed to shred %s\n, trying unlink", filename);
196                 if (ret != -1) {
197                         message(MESS_NORMAL, "Shred returned %d\n", ret);
198                 }
199                 return unlink(filename);
200         } else {
201                 return ret;
202         }
203 }
204
205 static int removeLogFile(char *name, struct logInfo *log)
206 {
207     message(MESS_DEBUG, "removing old log %s\n", name);
208
209     if (!debug && shred_file(name, log)) {
210         message(MESS_ERROR, "Failed to remove old log %s: %s\n",
211                 name, strerror(errno));
212         return 1;
213     }
214     return 0;
215 }
216
217 static int compressLogFile(char *name, struct logInfo *log, struct stat *sb)
218 {
219     char *compressedName;
220     const char **fullCommand;
221     int inFile;
222     int outFile;
223     int i;
224     int status;
225
226     message(MESS_DEBUG, "compressing log with: %s\n", log->compress_prog);
227     if (debug)
228         return 0;
229
230     fullCommand = alloca(sizeof(*fullCommand) *
231                          (log->compress_options_count + 2));
232     fullCommand[0] = log->compress_prog;
233     for (i = 0; i < log->compress_options_count; i++)
234         fullCommand[i + 1] = log->compress_options_list[i];
235     fullCommand[log->compress_options_count + 1] = NULL;
236
237     compressedName = alloca(strlen(name) + strlen(log->compress_ext) + 2);
238     sprintf(compressedName, "%s%s", name, log->compress_ext);
239
240     if ((inFile = open(name, O_RDONLY)) < 0) {
241         message(MESS_ERROR, "unable to open %s for compression\n", name);
242         return 1;
243     }
244
245     outFile =
246         createOutputFile(compressedName, O_RDWR | O_CREAT | O_TRUNC, sb);
247     if (outFile < 0) {
248         close(inFile);
249         return 1;
250     }
251
252     if (!fork()) {
253         dup2(inFile, 0);
254         close(inFile);
255         dup2(outFile, 1);
256         close(outFile);
257
258         execvp(fullCommand[0], (void *) fullCommand);
259         exit(1);
260     }
261
262     close(inFile);
263     close(outFile);
264
265     wait(&status);
266
267     if (!WIFEXITED(status) || WEXITSTATUS(status)) {
268         message(MESS_ERROR, "failed to compress log %s\n", name);
269         return 1;
270     }
271
272     shred_file(name, log);
273
274     return 0;
275 }
276
277 static int mailLog(char *logFile, char *mailCommand,
278                    char *uncompressCommand, char *address, char *subject)
279 {
280     int mailInput;
281     pid_t mailChild, uncompressChild;
282     int mailStatus, uncompressStatus;
283     int uncompressPipe[2];
284     char *mailArgv[] = { mailCommand, "-s", subject, address, NULL };
285     int rc = 0;
286
287     if ((mailInput = open(logFile, O_RDONLY)) < 0) {
288         message(MESS_ERROR, "failed to open %s for mailing: %s\n", logFile,
289                 strerror(errno));
290         return 1;
291     }
292
293     if (uncompressCommand) {
294         pipe(uncompressPipe);
295         if (!(uncompressChild = fork())) {
296             /* uncompress child */
297             dup2(mailInput, 0);
298             close(mailInput);
299             dup2(uncompressPipe[1], 1);
300             close(uncompressPipe[0]);
301             close(uncompressPipe[1]);
302
303             execlp(uncompressCommand, uncompressCommand, NULL);
304             exit(1);
305         }
306
307         close(mailInput);
308         mailInput = uncompressPipe[0];
309         close(uncompressPipe[1]);
310     }
311
312     if (!(mailChild = fork())) {
313         dup2(mailInput, 0);
314         close(mailInput);
315         close(1);
316
317         execvp(mailArgv[0], mailArgv);
318         exit(1);
319     }
320
321     close(mailInput);
322
323     waitpid(mailChild, &mailStatus, 0);
324
325     if (!WIFEXITED(mailStatus) || WEXITSTATUS(mailStatus)) {
326         message(MESS_ERROR, "mail command failed for %s\n", logFile);
327         rc = 1;
328     }
329
330     if (uncompressCommand) {
331         waitpid(uncompressChild, &uncompressStatus, 0);
332
333         if (!WIFEXITED(uncompressStatus) || WEXITSTATUS(uncompressStatus)) {
334             message(MESS_ERROR, "uncompress command failed mailing %s\n",
335                     logFile);
336             rc = 1;
337         }
338     }
339
340     return rc;
341 }
342
343 static int mailLogWrapper(char *mailFilename, char *mailCommand,
344                           int logNum, struct logInfo *log)
345 {
346     /* if the log is compressed (and we're not mailing a
347      * file whose compression has been delayed), we need
348      * to uncompress it */
349     if ((log->flags & LOG_FLAG_COMPRESS) &&
350         !((log->flags & LOG_FLAG_DELAYCOMPRESS) &&
351           (log->flags & LOG_FLAG_MAILFIRST))) {
352         if (mailLog(mailFilename, mailCommand,
353                     log->uncompress_prog, log->logAddress,
354                     log->files[logNum]))
355             return 1;
356     } else {
357         if (mailLog(mailFilename, mailCommand, NULL,
358                     log->logAddress, mailFilename))
359             return 1;
360     }
361     return 0;
362 }
363
364 static int copyTruncate(char *currLog, char *saveLog, struct stat *sb,
365                         int flags)
366 {
367     char buf[BUFSIZ];
368     int fdcurr = -1, fdsave = -1;
369     ssize_t cnt;
370
371     message(MESS_DEBUG, "copying %s to %s\n", currLog, saveLog);
372
373     if (!debug) {
374         if ((fdcurr = open(currLog, O_RDWR)) < 0) {
375             message(MESS_ERROR, "error opening %s: %s\n", currLog,
376                     strerror(errno));
377             return 1;
378         }
379 #ifdef WITH_SELINUX
380         if (selinux_enabled) {
381             security_context_t oldContext;
382             if (fgetfilecon_raw(fdcurr, &oldContext) >= 0) {
383                 if (getfscreatecon_raw(&prev_context) < 0) {
384                     message(MESS_ERROR,
385                             "getting default context: %s\n",
386                             strerror(errno));
387                     if (selinux_enforce) {
388                                 if (oldContext) {
389                                         freecon(oldContext);
390                                 }
391                                 return 1;
392                     }
393                 }
394                 if (setfscreatecon_raw(oldContext) < 0) {
395                     message(MESS_ERROR,
396                             "setting file context %s to %s: %s\n",
397                             saveLog, oldContext, strerror(errno));
398                         if (selinux_enforce) {
399                                 if (oldContext) {
400                                         freecon(oldContext);
401                                 }
402                                 return 1;
403                     }
404                 }
405                 message(MESS_DEBUG, "set default create context\n");
406                 if (oldContext) {
407                         freecon(oldContext);
408                 }
409             } else {
410                     if (errno != ENOTSUP) {
411                             message(MESS_ERROR, "getting file context %s: %s\n",
412                                     currLog, strerror(errno));
413                             if (selinux_enforce) {
414                                     return 1;
415                             }
416                     }
417             }
418         }
419 #endif
420         fdsave =
421             createOutputFile(saveLog, O_WRONLY | O_CREAT | O_TRUNC, sb);
422 #ifdef WITH_SELINUX
423         if (selinux_enabled) {
424             setfscreatecon_raw(prev_context);
425             if (prev_context != NULL) {
426                 freecon(prev_context);
427                 prev_context = NULL;
428             }
429         }
430 #endif
431         if (fdsave < 0) {
432             close(fdcurr);
433             return 1;
434         }
435         while ((cnt = read(fdcurr, buf, sizeof(buf))) > 0) {
436             if (write(fdsave, buf, cnt) != cnt) {
437                 message(MESS_ERROR, "error writing to %s: %s\n",
438                         saveLog, strerror(errno));
439                 close(fdcurr);
440                 close(fdsave);
441                 return 1;
442             }
443         }
444         if (cnt != 0) {
445             message(MESS_ERROR, "error reading %s: %s\n",
446                     currLog, strerror(errno));
447             close(fdcurr);
448             close(fdsave);
449             return 1;
450         }
451     }
452
453     if (flags & LOG_FLAG_COPYTRUNCATE) {
454         message(MESS_DEBUG, "truncating %s\n", currLog);
455
456         if (!debug)
457             if (ftruncate(fdcurr, 0)) {
458                 message(MESS_ERROR, "error truncating %s: %s\n", currLog,
459                         strerror(errno));
460                 close(fdcurr);
461                 close(fdsave);
462                 return 1;
463             }
464     } else
465         message(MESS_DEBUG, "Not truncating %s\n", currLog);
466
467     close(fdcurr);
468     close(fdsave);
469     return 0;
470 }
471
472 int findNeedRotating(struct logInfo *log, int logNum)
473 {
474     struct stat sb;
475     struct logState *state = NULL;
476     struct tm now = *localtime(&nowSecs);
477
478     message(MESS_DEBUG, "considering log %s\n", log->files[logNum]);
479
480     if (stat(log->files[logNum], &sb)) {
481         if ((log->flags & LOG_FLAG_MISSINGOK) && (errno == ENOENT)) {
482             message(MESS_DEBUG, "  log %s does not exist -- skipping\n",
483                     log->files[logNum]);
484             return 0;
485         }
486         message(MESS_ERROR, "stat of %s failed: %s\n", log->files[logNum],
487                 strerror(errno));
488         return 1;
489     }
490
491     state = findState(log->files[logNum]);
492     state->doRotate = 0;
493     state->sb = sb;
494
495     if (log->criterium == ROT_SIZE) {
496         state->doRotate = (sb.st_size >= log->threshhold);
497     } else if (log->criterium == ROT_FORCE) {
498         /* user forced rotation of logs from command line */
499         state->doRotate = 1;
500     } else if (state->lastRotated.tm_year > now.tm_year ||
501                (state->lastRotated.tm_year == now.tm_year &&
502                 (state->lastRotated.tm_mon > now.tm_mon ||
503                  (state->lastRotated.tm_mon == now.tm_mon &&
504                   state->lastRotated.tm_mday > now.tm_mday)))) {
505         message(MESS_ERROR,
506                 "log %s last rotated in the future -- rotation forced\n",
507                 log->files[logNum]);
508         state->doRotate = 1;
509     } else if (state->lastRotated.tm_year != now.tm_year ||
510                state->lastRotated.tm_mon != now.tm_mon ||
511                state->lastRotated.tm_mday != now.tm_mday) {
512         switch (log->criterium) {
513         case ROT_WEEKLY:
514             /* rotate if:
515                1) the current weekday is before the weekday of the
516                last rotation
517                2) more then a week has passed since the last
518                rotation */
519             state->doRotate = ((now.tm_wday < state->lastRotated.tm_wday)
520                                ||
521                                ((mktime(&now) -
522                                  mktime(&state->lastRotated)) >
523                                 (7 * 24 * 3600)));
524             break;
525         case ROT_MONTHLY:
526             /* rotate if the logs haven't been rotated this month or
527                this year */
528             state->doRotate = ((now.tm_mon != state->lastRotated.tm_mon) ||
529                                (now.tm_year !=
530                                 state->lastRotated.tm_year));
531             break;
532         case ROT_DAYS:
533             /* FIXME: only days=1 is implemented!! */
534             state->doRotate = 1;
535             break;
536         case ROT_YEARLY:
537             /* rotate if the logs haven't been rotated this year */
538             state->doRotate = (now.tm_year != state->lastRotated.tm_year);
539             break;
540         default:
541             /* ack! */
542             state->doRotate = 0;
543             break;
544         }
545         if (log->minsize && sb.st_size < log->minsize)
546             state->doRotate = 0;
547     }
548
549     /* The notifempty flag overrides the normal criteria */
550     if (!(log->flags & LOG_FLAG_IFEMPTY) && !sb.st_size)
551         state->doRotate = 0;
552
553     if (state->doRotate) {
554         message(MESS_DEBUG, "  log needs rotating\n");
555     } else {
556         message(MESS_DEBUG, "  log does not need rotating\n");
557     }
558
559     return 0;
560 }
561
562 int prerotateSingleLog(struct logInfo *log, int logNum, struct logState *state,
563                        struct logNames *rotNames)
564 {
565     struct tm now = *localtime(&nowSecs);
566     char *oldName, *newName = NULL;
567     char *tmp;
568     char *compext = "";
569     char *fileext = "";
570     int hasErrors = 0;
571     int i, j;
572     char *glob_pattern;
573     glob_t globResult;
574     int rc;
575     int rotateCount = log->rotateCount ? log->rotateCount : 1;
576     int logStart = (log->logStart == -1) ? 1 : log->logStart;
577 #define DATEEXT_LEN 64
578 #define PATTERN_LEN (DATEEXT_LEN * 2)
579         char dext_str[DATEEXT_LEN];
580         char dformat[DATEEXT_LEN];
581         char dext_pattern[PATTERN_LEN];
582         char *dext;
583
584     if (!state->doRotate)
585         return 0;
586
587     /* Logs with rotateCounts of 0 are rotated once, then removed. This
588        lets scripts run properly, and everything gets mailed properly. */
589
590     message(MESS_DEBUG, "rotating log %s, log->rotateCount is %d\n",
591             log->files[logNum], log->rotateCount);
592
593     if (log->flags & LOG_FLAG_COMPRESS)
594         compext = log->compress_ext;
595
596     state->lastRotated = now;
597
598     if (log->oldDir) {
599         if (log->oldDir[0] != '/') {
600             char *ld = ourDirName(log->files[logNum]);
601             rotNames->dirName =
602                 malloc(strlen(ld) + strlen(log->oldDir) + 2);
603             sprintf(rotNames->dirName, "%s/%s", ld, log->oldDir);
604             free(ld);
605         } else
606             rotNames->dirName = strdup(log->oldDir);
607     } else
608         rotNames->dirName = ourDirName(log->files[logNum]);
609
610     rotNames->baseName = strdup(ourBaseName(log->files[logNum]));
611
612     oldName = alloca(PATH_MAX);
613     newName = alloca(PATH_MAX);
614     rotNames->disposeName = malloc(PATH_MAX);
615
616     if (log->extension &&
617         strncmp(&
618                 (rotNames->
619                  baseName[strlen(rotNames->baseName) -
620                           strlen(log->extension)]), log->extension,
621                 strlen(log->extension)) == 0) {
622         char *tempstr;
623
624         fileext = log->extension;
625         tempstr =
626             calloc(strlen(rotNames->baseName) - strlen(log->extension) + 1,
627                    sizeof(char));
628         strncat(tempstr, rotNames->baseName,
629                 strlen(rotNames->baseName) - strlen(log->extension));
630         free(rotNames->baseName);
631         rotNames->baseName = tempstr;
632     }
633         
634         /* Allow only %Y %d %m and create valid strftime format string
635          * Construct the glob pattern corresponding to the date format */
636         dext_str[0] = '\0';
637         if (log->dateformat) {
638                 i = j = 0;
639                 memset(dext_pattern, 0, sizeof(dext_pattern));
640                 dext = log->dateformat;
641                 while (*dext == ' ')
642                         dext++;
643                 while ((*dext != '\0') && (!hasErrors)) {
644                         /* Will there be a space for a char and '\0'? */
645                         if (j >= (sizeof(dext_pattern) - 1)) {
646                                 message(MESS_ERROR, "Date format %s is too long\n",
647                                                 log->dateformat);
648                                 hasErrors = 1;
649                                 break;
650                         }
651                         if (*dext == '%') {
652                                 switch (*(dext + 1)) {
653                                         case 'Y':
654                                                 strncat(dext_pattern, "[0-9][0-9]",
655                                                                 sizeof(dext_pattern) - strlen(dext_pattern));
656                                                 j += 10; /* strlen("[0-9][0-9]") */
657                                         case 'm':
658                                         case 'd':
659                                                 strncat(dext_pattern, "[0-9][0-9]",
660                                                                 sizeof(dext_pattern) - strlen(dext_pattern));
661                                                 j += 10;
662                                                 if (j >= (sizeof(dext_pattern) - 1)) {
663                                                         message(MESS_ERROR, "Date format %s is too long\n",
664                                                                         log->dateformat);
665                                                         hasErrors = 1;
666                                                         break;
667                                                 }
668                                                 dformat[i++] = *(dext++);
669                                                 dformat[i] = *dext;
670                                                 break;
671                                         default:
672                                                 dformat[i++] = *dext;
673                                                 dformat[i] = '%';
674                                                 dext_pattern[j++] = *dext;
675                                                 break;
676                                 }
677                         } else {
678                                 dformat[i] = *dext;
679                                 dext_pattern[j++] = *dext;
680                         }
681                         ++i;
682                         ++dext;
683                 }
684                 dformat[i] = '\0';
685                 message(MESS_DEBUG, "Converted '%s' -> '%s'\n", log->dateformat, dformat);
686                 strftime(dext_str, sizeof(dext_str), dformat, &now);
687         } else {
688                 /* The default dateformat and glob pattern */
689                 strftime(dext_str, sizeof(dext_str), "-%Y%m%d", &now);
690                 strncpy(dext_pattern, "-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",
691                                 sizeof(dext_pattern));
692                 dext_pattern[PATTERN_LEN - 1] = '\0';
693         }
694         message(MESS_DEBUG, "dateext suffix '%s'\n", dext_str);
695         message(MESS_DEBUG, "glob pattern '%s'\n", dext_pattern);
696
697     /* First compress the previous log when necessary */
698     if (log->flags & LOG_FLAG_COMPRESS &&
699         log->flags & LOG_FLAG_DELAYCOMPRESS) {
700         if (log->flags & LOG_FLAG_DATEEXT) {
701                 /* glob for uncompressed files with our pattern */
702                 asprintf(&glob_pattern, "%s/%s%s%s",
703                                 rotNames->dirName, rotNames->baseName, dext_pattern, fileext);
704             rc = glob(glob_pattern, 0, globerr, &globResult);
705             if (!rc && globResult.gl_pathc > 0) {
706                 for (i = 0; i < globResult.gl_pathc && !hasErrors; i++) {
707                     struct stat sbprev;
708
709                         snprintf(oldName, PATH_MAX, "%s", (globResult.gl_pathv)[i]);
710                         if (stat(oldName, &sbprev)) {
711                         message(MESS_DEBUG,
712                                 "previous log %s does not exist\n",
713                                 oldName);
714                     } else {
715                         hasErrors = compressLogFile(oldName, log, &sbprev);
716                     }
717                 }
718             } else {
719                 message(MESS_DEBUG,
720                         "glob finding logs to compress failed\n");
721                 /* fallback to old behaviour */
722                 snprintf(oldName, PATH_MAX, "%s/%s.%d%s", rotNames->dirName,
723                         rotNames->baseName, logStart, fileext);
724             }
725             globfree(&globResult);
726             free(glob_pattern);
727         } else {
728             struct stat sbprev;
729
730             snprintf(oldName, PATH_MAX, "%s/%s.%d%s", rotNames->dirName,
731                     rotNames->baseName, logStart, fileext);
732             if (stat(oldName, &sbprev)) {
733                 message(MESS_DEBUG, "previous log %s does not exist\n",
734                         oldName);
735             } else {
736                 hasErrors = compressLogFile(oldName, log, &sbprev);
737             }
738         }
739     }
740
741     rotNames->firstRotated =
742         malloc(strlen(rotNames->dirName) + strlen(rotNames->baseName) +
743                strlen(fileext) + strlen(compext) + 30);
744
745     if (log->flags & LOG_FLAG_DATEEXT) {
746         /* glob for compressed files with our pattern
747          * and compress ext */
748         asprintf(&glob_pattern, "%s/%s%s%s%s",
749                         rotNames->dirName, rotNames->baseName, dext_pattern, fileext, compext);
750         rc = glob(glob_pattern, 0, globerr, &globResult);
751         if (!rc) {
752             /* search for files to drop, if we find one remember it,
753              * if we find another one mail and remove the first and
754              * remember the second and so on */
755             struct stat fst_buf;
756             int mail_out = -1;
757             /* remove the first (n - rotateCount) matches
758              * no real rotation needed, since the files have
759              * the date in their name */
760             for (i = 0; i < globResult.gl_pathc; i++) {
761                 if (!stat((globResult.gl_pathv)[i], &fst_buf)) {
762                     if ((i <= ((int) globResult.gl_pathc - rotateCount))
763                         || ((log->rotateAge > 0)
764                             &&
765                             (((nowSecs - fst_buf.st_mtime) / 60 / 60 / 24)
766                              > log->rotateAge))) {
767                         if (mail_out != -1) {
768                             char *mailFilename =
769                                 (globResult.gl_pathv)[mail_out];
770                             if (!hasErrors && log->logAddress)
771                                 hasErrors =
772                                     mailLogWrapper(mailFilename,
773                                                    mailCommand, logNum,
774                                                    log);
775                             if (!hasErrors)
776                                 message(MESS_DEBUG, "removing %s\n", mailFilename);
777                                 hasErrors = removeLogFile(mailFilename, log);
778                         }
779                         mail_out = i;
780                     }
781                 }
782             }
783             if (mail_out != -1) {
784                 /* oldName is oldest Backup found (for unlink later) */
785                 snprintf(oldName, PATH_MAX, "%s", (globResult.gl_pathv)[mail_out]);
786                 strcpy(rotNames->disposeName, oldName);
787             } else {
788                 free(rotNames->disposeName);
789                 rotNames->disposeName = NULL;
790             }
791         } else {
792             message(MESS_DEBUG, "glob finding old rotated logs failed\n");
793             free(rotNames->disposeName);
794             rotNames->disposeName = NULL;
795         }
796         /* firstRotated is most recently created/compressed rotated log */
797         sprintf(rotNames->firstRotated, "%s/%s%s%s%s",
798                 rotNames->dirName, rotNames->baseName, dext_str, fileext, compext);
799         globfree(&globResult);
800         free(glob_pattern);
801     } else {
802         if (log->rotateAge) {
803             struct stat fst_buf;
804             for (i = 1; i <= rotateCount + 1; i++) {
805                 snprintf(oldName, PATH_MAX, "%s/%s.%d%s%s", rotNames->dirName,
806                         rotNames->baseName, i, fileext, compext);
807                 if (!stat(oldName, &fst_buf)
808                     && (((nowSecs - fst_buf.st_mtime) / 60 / 60 / 24)
809                         > log->rotateAge)) {
810                     char *mailFilename = oldName;
811                     if (!hasErrors && log->logAddress)
812                         hasErrors =
813                             mailLogWrapper(mailFilename, mailCommand,
814                                            logNum, log);
815                     if (!hasErrors)
816                         hasErrors = removeLogFile(mailFilename, log);
817                 }
818             }
819         }
820
821         snprintf(oldName, PATH_MAX, "%s/%s.%d%s%s", rotNames->dirName,
822                 rotNames->baseName, logStart + rotateCount, fileext,
823                 compext);
824         strcpy(newName, oldName);
825
826         strcpy(rotNames->disposeName, oldName);
827
828         sprintf(rotNames->firstRotated, "%s/%s.%d%s%s", rotNames->dirName,
829                 rotNames->baseName, logStart, fileext,
830                 (log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext);
831
832 #ifdef WITH_SELINUX
833         if (selinux_enabled) {
834             security_context_t oldContext = NULL;
835             if (getfilecon_raw(log->files[logNum], &oldContext) > 0) {
836                         if (getfscreatecon_raw(&prev_context) < 0) {
837                                 message(MESS_ERROR,
838                                         "getting default context: %s\n",
839                                         strerror(errno));
840                                 if (selinux_enforce) {
841                                         if (oldContext) {
842                                                 freecon(oldContext);
843                                         }
844                                         return 1;
845                                 }
846                         }
847                         if (setfscreatecon_raw(oldContext) < 0) {
848                                 message(MESS_ERROR,
849                                         "setting file context %s to %s: %s\n",
850                                         log->files[logNum], oldContext,
851                                         strerror(errno));
852                                 if (selinux_enforce) {
853                                         if (oldContext) {
854                                                 freecon(oldContext);
855                                         }
856                                         return 1;
857                                 }
858                         }
859                         if (oldContext) {
860                                 freecon(oldContext);
861                         }
862             } else {
863                 if (errno != ENOENT && errno != ENOTSUP) {
864                         message(MESS_ERROR, "getting file context %s: %s\n",
865                                 log->files[logNum], strerror(errno));
866                         if (selinux_enforce) {
867                                 return 1;
868                         }
869                 }
870             }
871         }
872 #endif
873         for (i = rotateCount + logStart - 1; (i >= 0) && !hasErrors; i--) {
874             tmp = newName;
875             newName = oldName;
876             oldName = tmp;
877                 snprintf(oldName, PATH_MAX, "%s/%s.%d%s%s", rotNames->dirName,
878                     rotNames->baseName, i, fileext, compext);
879
880             message(MESS_DEBUG,
881                     "renaming %s to %s (rotatecount %d, logstart %d, i %d), \n",
882                     oldName, newName, rotateCount, logStart, i);
883
884             if (!debug && rename(oldName, newName)) {
885                 if (errno == ENOENT) {
886                     message(MESS_DEBUG, "old log %s does not exist\n",
887                             oldName);
888                 } else {
889                     message(MESS_ERROR, "error renaming %s to %s: %s\n",
890                             oldName, newName, strerror(errno));
891                     hasErrors = 1;
892                 }
893             }
894         }
895     }                           /* !LOG_FLAG_DATEEXT */
896
897     if (log->flags & LOG_FLAG_DATEEXT) {
898         char *destFile =
899             alloca(strlen(rotNames->dirName) + strlen(rotNames->baseName) +
900                    strlen(fileext) + strlen(compext) + 30);
901         struct stat fst_buf;
902         asprintf(&(rotNames->finalName), "%s/%s%s%s",
903                         rotNames->dirName, rotNames->baseName, dext_str, fileext);
904         sprintf(destFile, "%s%s", rotNames->finalName, compext);
905         if (!stat(destFile, &fst_buf)) {
906             message(MESS_DEBUG,
907                     "destination %s already exists, skipping rotation\n",
908                     rotNames->firstRotated);
909             hasErrors = 1;
910         }
911     } else {
912         /* note: the gzip extension is *not* used here! */
913         asprintf(&(rotNames->finalName), "%s/%s.%d%s", rotNames->dirName,
914                 rotNames->baseName, logStart, fileext);
915     }
916
917     /* if the last rotation doesn't exist, that's okay */
918     if (!debug && rotNames->disposeName
919         && access(rotNames->disposeName, F_OK)) {
920         message(MESS_DEBUG,
921                 "log %s doesn't exist -- won't try to " "dispose of it\n",
922                 rotNames->disposeName);
923         free(rotNames->disposeName);
924         rotNames->disposeName = NULL;
925     }
926
927     return hasErrors;
928 }
929
930 int rotateSingleLog(struct logInfo *log, int logNum, struct logState *state,
931                     struct logNames *rotNames)
932 {
933     int hasErrors = 0;
934     struct stat sb;
935     int fd;
936 #ifdef WITH_SELINUX
937         security_context_t savedContext = NULL;
938 #endif
939
940     if (!state->doRotate)
941         return 0;
942
943     if (!hasErrors) {
944
945         if (!(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) {
946 #ifdef WITH_SELINUX
947                 if (selinux_enabled) {
948                         security_context_t oldContext = NULL;
949                         int fdcurr = -1;
950
951                         if ((fdcurr = open(log->files[logNum], O_RDWR)) < 0) {
952                                 message(MESS_ERROR, "error opening %s: %s\n",
953                                                 log->files[logNum],
954                                         strerror(errno));
955                                 return 1;
956                         }
957                         if (fgetfilecon_raw(fdcurr, &oldContext) >= 0) {
958                                 if (getfscreatecon_raw(&savedContext) < 0) {
959                                         message(MESS_ERROR,
960                                                 "getting default context: %s\n",
961                                                 strerror(errno));
962                                         if (selinux_enforce) {
963                                                 if (oldContext) {
964                                                         freecon(oldContext);
965                                                 }
966                                                 if (close(fdcurr) < 0)
967                                                         message(MESS_ERROR, "error closing file %s",
968                                                                         log->files[logNum]);
969                                                 return 1;
970                                         }
971                                 }
972                                 if (setfscreatecon_raw(oldContext) < 0) {
973                                         message(MESS_ERROR,
974                                                 "setting file context %s to %s: %s\n",
975                                                 log->files[logNum], oldContext, strerror(errno));
976                                         if (selinux_enforce) {
977                                                 if (oldContext) {
978                                                         freecon(oldContext);
979                                                 }
980                                                 if (close(fdcurr) < 0)
981                                                         message(MESS_ERROR, "error closing file %s",
982                                                                         log->files[logNum]);
983                                                 return 1;
984                                         }
985                                 }
986                                 message(MESS_DEBUG, "fscreate context set to %s\n",
987                                                 oldContext);
988                                 if (oldContext) {
989                                         freecon(oldContext);
990                                 }
991                         } else {
992                                 if (errno != ENOTSUP) {
993                                         message(MESS_ERROR, "getting file context %s: %s\n",
994                                                 log->files[logNum], strerror(errno));
995                                         if (selinux_enforce) {
996                                                 if (close(fdcurr) < 0)
997                                                         message(MESS_ERROR, "error closing file %s",
998                                                                         log->files[logNum]);
999                                                 return 1;
1000                                         }
1001                                 }
1002                         }
1003                         if (close(fdcurr) < 0)
1004                                 message(MESS_ERROR, "error closing file %s",
1005                                                 log->files[logNum]);
1006                 }
1007 #endif
1008                 message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum],
1009                     rotNames->finalName);
1010             if (!debug && !hasErrors &&
1011                 rename(log->files[logNum], rotNames->finalName)) {
1012                 message(MESS_ERROR, "failed to rename %s to %s: %s\n",
1013                         log->files[logNum], rotNames->finalName,
1014                         strerror(errno));
1015             }
1016
1017             if (!log->rotateCount) {
1018                 rotNames->disposeName =
1019                     realloc(rotNames->disposeName,
1020                             strlen(rotNames->dirName) +
1021                             strlen(rotNames->baseName) +
1022                             strlen(log->files[logNum]) + 10);
1023                 sprintf(rotNames->disposeName, "%s%s", rotNames->finalName,
1024                         (log->compress_ext
1025                          && (log->flags & LOG_FLAG_COMPRESS)) ? log->
1026                         compress_ext : "");
1027                 message(MESS_DEBUG, "disposeName will be %s\n",
1028                         rotNames->disposeName);
1029             }
1030         }
1031
1032         if (!hasErrors && log->flags & LOG_FLAG_CREATE &&
1033             !(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) {
1034             if (log->createUid == NO_UID)
1035                 sb.st_uid = state->sb.st_uid;
1036             else
1037                 sb.st_uid = log->createUid;
1038
1039             if (log->createGid == NO_GID)
1040                 sb.st_gid = state->sb.st_gid;
1041             else
1042                 sb.st_gid = log->createGid;
1043
1044             if (log->createMode == NO_MODE)
1045                 sb.st_mode = state->sb.st_mode & 0777;
1046             else
1047                 sb.st_mode = log->createMode;
1048
1049             message(MESS_DEBUG, "creating new %s mode = 0%o uid = %d "
1050                     "gid = %d\n", log->files[logNum], (unsigned int) sb.st_mode,
1051                     (int) sb.st_uid, (int) sb.st_gid);
1052
1053             if (!debug) {
1054                         fd = createOutputFile(log->files[logNum], O_CREAT | O_RDWR,
1055                                                   &sb);
1056                         if (fd < 0)
1057                                 hasErrors = 1;
1058                         else
1059                                 close(fd);
1060             }
1061         }
1062 #ifdef WITH_SELINUX
1063         if (selinux_enabled) {
1064             setfscreatecon_raw(savedContext);
1065             if (savedContext != NULL) {
1066                         freecon(savedContext);
1067                         savedContext = NULL;
1068             }
1069         }
1070 #endif
1071
1072         if (!hasErrors
1073             && log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))
1074             hasErrors =
1075                 copyTruncate(log->files[logNum], rotNames->finalName,
1076                              &state->sb, log->flags);
1077
1078     }
1079     return hasErrors;
1080 }
1081
1082 int postrotateSingleLog(struct logInfo *log, int logNum, struct logState *state,
1083                         struct logNames *rotNames)
1084 {
1085     int hasErrors = 0;
1086
1087     if (!state->doRotate)
1088         return 0;
1089
1090     if ((log->flags & LOG_FLAG_COMPRESS) &&
1091         !(log->flags & LOG_FLAG_DELAYCOMPRESS)) {
1092         hasErrors = compressLogFile(rotNames->finalName, log, &state->sb);
1093     }
1094
1095     if (!hasErrors && log->logAddress) {
1096         char *mailFilename;
1097
1098         if (log->flags & LOG_FLAG_MAILFIRST)
1099             mailFilename = rotNames->firstRotated;
1100         else
1101             mailFilename = rotNames->disposeName;
1102
1103         if (mailFilename)
1104             hasErrors =
1105                 mailLogWrapper(mailFilename, mailCommand, logNum, log);
1106     }
1107
1108     if (!hasErrors && rotNames->disposeName)
1109         hasErrors = removeLogFile(rotNames->disposeName, log);
1110
1111 #ifdef WITH_SELINUX
1112     if (selinux_enabled) {
1113         setfscreatecon_raw(prev_context);
1114         if (prev_context != NULL) {
1115             freecon(prev_context);
1116             prev_context = NULL;
1117         }
1118     }
1119 #endif
1120     return hasErrors;
1121 }
1122
1123 int rotateLogSet(struct logInfo *log, int force)
1124 {
1125     int i, j;
1126     int hasErrors = 0;
1127     int logHasErrors[log->numFiles];
1128     int numRotated = 0;
1129     struct logState **state;
1130     struct logNames **rotNames;
1131
1132     if (force)
1133         log->criterium = ROT_FORCE;
1134
1135     message(MESS_DEBUG, "\nrotating pattern: %s ", log->pattern);
1136     switch (log->criterium) {
1137     case ROT_DAYS:
1138         message(MESS_DEBUG, "after %d days ", log->threshhold);
1139         break;
1140     case ROT_WEEKLY:
1141         message(MESS_DEBUG, "weekly ");
1142         break;
1143     case ROT_MONTHLY:
1144         message(MESS_DEBUG, "monthly ");
1145         break;
1146     case ROT_YEARLY:
1147         message(MESS_DEBUG, "yearly ");
1148         break;
1149     case ROT_SIZE:
1150         message(MESS_DEBUG, "%d bytes ", log->threshhold);
1151         break;
1152     case ROT_FORCE:
1153         message(MESS_DEBUG, "forced from command line ");
1154         break;
1155     }
1156
1157     if (log->rotateCount)
1158         message(MESS_DEBUG, "(%d rotations)\n", log->rotateCount);
1159     else
1160         message(MESS_DEBUG, "(no old logs will be kept)\n");
1161
1162     if (log->oldDir)
1163         message(MESS_DEBUG, "olddir is %s, ", log->oldDir);
1164
1165     if (log->flags & LOG_FLAG_IFEMPTY)
1166         message(MESS_DEBUG, "empty log files are rotated, ");
1167     else
1168         message(MESS_DEBUG, "empty log files are not rotated, ");
1169
1170     if (log->minsize) 
1171         message(MESS_DEBUG, "only log files >= %d bytes are rotated, ", log->minsize);
1172
1173     if (log->logAddress) {
1174         message(MESS_DEBUG, "old logs mailed to %s\n", log->logAddress);
1175     } else {
1176         message(MESS_DEBUG, "old logs are removed\n");
1177     }
1178
1179     for (i = 0; i < log->numFiles; i++) {
1180         logHasErrors[i] = findNeedRotating(log, i);
1181         hasErrors |= logHasErrors[i];
1182
1183         /* sure is a lot of findStating going on .. */
1184         if ((findState(log->files[i]))->doRotate)
1185             numRotated++;
1186     }
1187
1188     if (log->first) {
1189         if (!numRotated) {
1190             message(MESS_DEBUG, "not running first action script, "
1191                     "since no logs will be rotated\n");
1192         } else {
1193             message(MESS_DEBUG, "running first action script\n");
1194             if (runScript(log->pattern, log->first)) {
1195                 message(MESS_ERROR, "error running first action script "
1196                         "for %s\n", log->pattern);
1197                 hasErrors = 1;
1198                 /* finish early, firstaction failed, affects all logs in set */
1199                 return hasErrors;
1200             }
1201         }
1202     }
1203
1204     state = malloc(log->numFiles * sizeof(struct logState *));
1205     rotNames = malloc(log->numFiles * sizeof(struct logNames *));
1206
1207     for (j = 0;
1208          (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < log->numFiles)
1209          || ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < 1); j++) {
1210
1211         for (i = j;
1212              ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
1213              || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
1214             state[i] = findState(log->files[i]);
1215
1216             rotNames[i] = malloc(sizeof(struct logNames));
1217             memset(rotNames[i], 0, sizeof(struct logNames));
1218
1219             logHasErrors[i] |=
1220                 prerotateSingleLog(log, i, state[i], rotNames[i]);
1221             hasErrors |= logHasErrors[i];
1222         }
1223
1224         if (log->pre
1225             && (! ( (logHasErrors[j] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
1226                    || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) )) {
1227             if (!numRotated) {
1228                 message(MESS_DEBUG, "not running prerotate script, "
1229                         "since no logs will be rotated\n");
1230             } else {
1231                 message(MESS_DEBUG, "running prerotate script\n");
1232                 if (runScript(log->pattern, log->pre)) {
1233                     if (log->flags & LOG_FLAG_SHAREDSCRIPTS)
1234                         message(MESS_ERROR,
1235                                 "error running shared prerotate script "
1236                                 "for '%s'\n", log->pattern);
1237                     else {
1238                         message(MESS_ERROR,
1239                                 "error running non-shared prerotate script "
1240                                 "for %s of '%s'\n", log->files[j], log->pattern);
1241                     }
1242                     logHasErrors[j] = 1;
1243                     hasErrors = 1;
1244                 }
1245             }
1246         }
1247
1248         for (i = j;
1249              ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
1250              || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
1251             if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
1252                    || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) {
1253                 logHasErrors[i] |=
1254                     rotateSingleLog(log, i, state[i], rotNames[i]);
1255                 hasErrors |= logHasErrors[i];
1256             }
1257         }
1258
1259         if (log->post
1260             && (! ( (logHasErrors[j] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
1261                    || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) )) {
1262             if (!numRotated) {
1263                 message(MESS_DEBUG, "not running postrotate script, "
1264                         "since no logs were rotated\n");
1265             } else {
1266                 message(MESS_DEBUG, "running postrotate script\n");
1267                 if (runScript(log->pattern, log->post)) {
1268                     if (log->flags & LOG_FLAG_SHAREDSCRIPTS)
1269                         message(MESS_ERROR,
1270                                 "error running shared postrotate script "
1271                                 "for '%s'\n", log->pattern);
1272                     else {
1273                         message(MESS_ERROR,
1274                                 "error running non-shared postrotate script "
1275                                 "for %s of '%s'\n", log->files[j], log->pattern);
1276                     }
1277                     logHasErrors[j] = 1;
1278                     hasErrors = 1;
1279                 }
1280             }
1281         }
1282
1283         for (i = j;
1284              ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
1285              || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
1286             if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
1287                    || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) {
1288                 logHasErrors[i] |=
1289                     postrotateSingleLog(log, i, state[i], rotNames[i]);
1290                 hasErrors |= logHasErrors[i];
1291             }
1292         }
1293
1294     }
1295
1296     for (i = 0; i < log->numFiles; i++) {
1297         free(rotNames[i]->firstRotated);
1298         free(rotNames[i]->disposeName);
1299         free(rotNames[i]->finalName);
1300         free(rotNames[i]->dirName);
1301         free(rotNames[i]->baseName);
1302         free(rotNames[i]);
1303     }
1304     free(rotNames);
1305     free(state);
1306
1307     if (log->last) {
1308         if (!numRotated) {
1309             message(MESS_DEBUG, "not running last action script, "
1310                     "since no logs will be rotated\n");
1311         } else {
1312             message(MESS_DEBUG, "running last action script\n");
1313             if (runScript(log->pattern, log->last)) {
1314                 message(MESS_ERROR, "error running last action script "
1315                         "for %s\n", log->pattern);
1316                 hasErrors = 1;
1317             }
1318         }
1319     }
1320
1321     return hasErrors;
1322 }
1323
1324 static int writeState(char *stateFilename)
1325 {
1326     struct logState *p;
1327     FILE *f;
1328     char *chptr;
1329
1330     f = fopen(stateFilename, "w");
1331     if (!f) {
1332         message(MESS_ERROR, "error creating state file %s: %s\n",
1333                 stateFilename, strerror(errno));
1334         return 1;
1335     }
1336
1337     fprintf(f, "logrotate state -- version 2\n");
1338
1339     for (p = states.lh_first; p != NULL; p = p->list.le_next) {
1340         fputc('"', f);
1341         for (chptr = p->fn; *chptr; chptr++) {
1342             switch (*chptr) {
1343             case '"':
1344                 fputc('\\', f);
1345             }
1346
1347             fputc(*chptr, f);
1348         }
1349
1350         fputc('"', f);
1351         fprintf(f, " %d-%d-%d\n",
1352                 p->lastRotated.tm_year + 1900,
1353                 p->lastRotated.tm_mon + 1,
1354                 p->lastRotated.tm_mday);
1355     }
1356
1357     fclose(f);
1358
1359     return 0;
1360 }
1361
1362 static int readState(char *stateFilename)
1363 {
1364     FILE *f;
1365     char buf[1024];
1366     const char **argv;
1367     int argc;
1368     int year, month, day;
1369     int i;
1370     int line = 0;
1371     int error;
1372     struct logState *st;
1373     time_t lr_time;
1374     struct stat f_stat;
1375
1376     error = stat(stateFilename, &f_stat);
1377
1378     if ((error && errno == ENOENT) || (!error && f_stat.st_size == 0)) {
1379         /* create the file before continuing to ensure we have write
1380            access to the file */
1381         f = fopen(stateFilename, "w");
1382         if (!f) {
1383             message(MESS_ERROR, "error creating state file %s: %s\n",
1384                     stateFilename, strerror(errno));
1385             return 1;
1386         }
1387         fprintf(f, "logrotate state -- version 2\n");
1388         fclose(f);
1389         return 0;
1390     } else if (error) {
1391         message(MESS_ERROR, "error stat()ing state file %s: %s\n",
1392                 stateFilename, strerror(errno));
1393         return 1;
1394     }
1395
1396     f = fopen(stateFilename, "r");
1397     if (!f) {
1398         message(MESS_ERROR, "error opening state file %s: %s\n",
1399                 stateFilename, strerror(errno));
1400         return 1;
1401     }
1402
1403     if (!fgets(buf, sizeof(buf) - 1, f)) {
1404         message(MESS_ERROR, "error reading top line of %s\n",
1405                 stateFilename);
1406         fclose(f);
1407         return 1;
1408     }
1409
1410     if (strcmp(buf, "logrotate state -- version 1\n") &&
1411         strcmp(buf, "logrotate state -- version 2\n")) {
1412         fclose(f);
1413         message(MESS_ERROR, "bad top line in state file %s\n",
1414                 stateFilename);
1415         return 1;
1416     }
1417
1418     line++;
1419
1420     while (fgets(buf, sizeof(buf) - 1, f)) {
1421         argv = NULL;
1422         line++;
1423         i = strlen(buf);
1424         if (buf[i - 1] != '\n') {
1425             message(MESS_ERROR, "line %d too long in state file %s\n",
1426                     line, stateFilename);
1427             fclose(f);
1428             return 1;
1429         }
1430
1431         buf[i - 1] = '\0';
1432
1433         if (i == 1)
1434             continue;
1435
1436         if (poptParseArgvString(buf, &argc, &argv) || (argc != 2) ||
1437             (sscanf(argv[1], "%d-%d-%d", &year, &month, &day) != 3)) {
1438             message(MESS_ERROR, "bad line %d in state file %s\n",
1439                     line, stateFilename);
1440                 free(argv);
1441             fclose(f);
1442             return 1;
1443         }
1444
1445         /* Hack to hide earlier bug */
1446         if ((year != 1900) && (year < 1996 || year > 2100)) {
1447             message(MESS_ERROR,
1448                     "bad year %d for file %s in state file %s\n", year,
1449                     argv[0], stateFilename);
1450             free(argv);
1451             fclose(f);
1452             return 1;
1453         }
1454
1455         if (month < 1 || month > 12) {
1456             message(MESS_ERROR,
1457                     "bad month %d for file %s in state file %s\n", month,
1458                     argv[0], stateFilename);
1459             free(argv);
1460             fclose(f);
1461             return 1;
1462         }
1463
1464         /* 0 to hide earlier bug */
1465         if (day < 0 || day > 31) {
1466             message(MESS_ERROR,
1467                     "bad day %d for file %s in state file %s\n", day,
1468                     argv[0], stateFilename);
1469             free(argv);
1470             fclose(f);
1471             return 1;
1472         }
1473
1474         year -= 1900, month -= 1;
1475
1476         if ((st = findState(argv[0])) == NULL)
1477                 return 1;
1478
1479         st->lastRotated.tm_mon = month;
1480         st->lastRotated.tm_mday = day;
1481         st->lastRotated.tm_year = year;
1482
1483         /* fill in the rest of the st->lastRotated fields */
1484         lr_time = mktime(&st->lastRotated);
1485         st->lastRotated = *localtime(&lr_time);
1486
1487         free(argv);
1488     }
1489
1490     fclose(f);
1491     return 0;
1492 }
1493
1494 int main(int argc, const char **argv)
1495 {
1496     int force = 0;
1497     char *stateFile = STATEFILE;
1498     int rc = 0;
1499     int arg;
1500     const char **files;
1501     poptContext optCon;
1502         struct logInfo *log;
1503
1504     struct poptOption options[] = {
1505         {"debug", 'd', 0, 0, 'd',
1506          "Don't do anything, just test (implies -v)"},
1507         {"force", 'f', 0, &force, 0, "Force file rotation"},
1508         {"mail", 'm', POPT_ARG_STRING, &mailCommand, 0,
1509          "Command to send mail (instead of `" DEFAULT_MAIL_COMMAND "')",
1510          "command"},
1511         {"state", 's', POPT_ARG_STRING, &stateFile, 0,
1512          "Path of state file",
1513          "statefile"},
1514         {"verbose", 'v', 0, 0, 'v', "Display messages during rotation"},
1515         POPT_AUTOHELP {0, 0, 0, 0, 0}
1516     };
1517
1518     logSetLevel(MESS_NORMAL);
1519     setlocale (LC_ALL, "");
1520
1521     optCon = poptGetContext("logrotate", argc, argv, options, 0);
1522     poptReadDefaultConfig(optCon, 1);
1523     poptSetOtherOptionHelp(optCon, "[OPTION...] <configfile>");
1524
1525     while ((arg = poptGetNextOpt(optCon)) >= 0) {
1526         switch (arg) {
1527         case 'd':
1528             debug = 1;
1529             /* fallthrough */
1530         case 'v':
1531             logSetLevel(MESS_DEBUG);
1532             break;
1533         }
1534     }
1535
1536     if (arg < -1) {
1537         fprintf(stderr, "logrotate: bad argument %s: %s\n",
1538                 poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
1539                 poptStrerror(rc));
1540         poptFreeContext(optCon);
1541         return 2;
1542     }
1543
1544     files = poptGetArgs((poptContext) optCon);
1545     if (!files) {
1546         fprintf(stderr, "logrotate " VERSION
1547                 " - Copyright (C) 1995-2001 Red Hat, Inc.\n");
1548         fprintf(stderr,
1549                 "This may be freely redistributed under the terms of "
1550                 "the GNU Public License\n\n");
1551         poptPrintUsage(optCon, stderr, 0);
1552         poptFreeContext(optCon);
1553         exit(1);
1554     }
1555 #ifdef WITH_SELINUX
1556     selinux_enabled = (is_selinux_enabled() > 0);
1557     selinux_enforce = security_getenforce();
1558 #endif
1559
1560         TAILQ_INIT(&logs);
1561
1562         if (readAllConfigPaths(files)) {
1563         poptFreeContext(optCon);
1564         exit(1);
1565     }
1566
1567     poptFreeContext(optCon);
1568     nowSecs = time(NULL);
1569
1570         LIST_INIT(&states);
1571
1572         if (readState(stateFile))
1573         exit(1);
1574
1575     message(MESS_DEBUG, "\nHandling %d logs\n", numLogs);
1576
1577         for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next)
1578         rc |= rotateLogSet(log, force);
1579
1580     if (!debug)
1581         rc |= writeState(stateFile);
1582         
1583     return (rc != 0);
1584 }