17
#include <sys/types.h>
21
#include <selinux/selinux.h>
22
static security_context_t prev_context = NULL;
23
int selinux_enabled = 0;
24
int selinux_enforce = 0;
27
#include "basenames.h"
29
#include "logrotate.h"
31
#if !defined(GLOB_ABORTED) && defined(GLOB_ABEND)
32
#define GLOB_ABORTED GLOB_ABEND
37
struct tm lastRotated; /* only tm.mon, tm_mday, tm_year are good! */
40
LIST_ENTRY(logState) list;
52
LIST_HEAD(stateSet, logState) head;
55
unsigned int hashSize;
58
char *mailCommand = DEFAULT_MAIL_COMMAND;
61
static int shred_file(char *filename, struct logInfo *log);
63
static int globerr(const char *pathname, int theerr)
65
message(MESS_ERROR, "error accessing %s: %s\n", pathname,
68
/* We want the glob operation to continue, so return 0 */
72
#define HASH_SIZE_MIN 64
73
static int allocateHash(void)
81
for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next)
86
/* Enforce some reasonable minimum hash size */
87
if (hs < HASH_SIZE_MIN)
90
states = calloc(hs, sizeof(struct logStates *));
92
message(MESS_ERROR, "could not allocate memory for "
97
for (i = 0; i < hs; i++) {
98
states[i] = malloc(sizeof(struct logState));
99
if (states[i] == NULL) {
100
message(MESS_ERROR, "could not allocate memory for "
104
LIST_INIT(&(states[i]->head));
112
#define HASH_CONST 13
113
static unsigned hashIndex(const char *fn)
122
return hash % hashSize;
125
static struct logState *newState(const char *fn)
127
struct tm now = *localtime(&nowSecs);
128
struct logState *new;
131
if ((new = malloc(sizeof(*new))) == NULL)
134
if ((new->fn = strdup(fn)) == NULL)
139
memset(&new->lastRotated, 0, sizeof(new->lastRotated));
140
new->lastRotated.tm_mon = now.tm_mon;
141
new->lastRotated.tm_mday = now.tm_mday;
142
new->lastRotated.tm_year = now.tm_year;
143
new->lastRotated.tm_hour = now.tm_hour;
144
new->lastRotated.tm_isdst = now.tm_isdst;
146
/* fill in the rest of the new->lastRotated fields */
147
lr_time = mktime(&new->lastRotated);
148
new->lastRotated = *localtime(&lr_time);
153
static struct logState *findState(const char *fn)
155
unsigned int i = hashIndex(fn);
158
for (p = states[i]->head.lh_first; p != NULL; p = p->list.le_next)
159
if (!strcmp(fn, p->fn))
164
if ((p = newState(fn)) == NULL)
167
LIST_INSERT_HEAD(&(states[i]->head), p, list);
173
static int runScript(char *logfn, char *script)
178
message(MESS_DEBUG, "running script with arg %s: \"%s\"\n",
184
execl("/bin/sh", "sh", "-c", script, "logrotate_script", logfn, NULL);
192
static int runScriptMultiple(char *logfn, char *script, struct logNames **rotNames, int numFiles)
198
message(MESS_DEBUG, "running script (multiple) with arg %s: \"%s\"\n",
203
argv = calloc(numFiles+5,sizeof(char*));
205
message(MESS_ERROR,"error allocating memory to runScriptMultiple\n");
212
argv[3] = "logrotate_script";
214
for (i = 0; i<numFiles; i++) {
215
argv[i+4] = rotNames[i]->finalName;
220
execv("/bin/sh",argv);
228
int createOutputFile(char *fileName, int flags, struct stat *sb)
232
fd = open(fileName, flags, (S_IRUSR | S_IWUSR) & sb->st_mode);
234
message(MESS_ERROR, "error creating output file %s: %s\n",
235
fileName, strerror(errno));
238
if (fchmod(fd, (S_IRUSR | S_IWUSR) & sb->st_mode)) {
239
message(MESS_ERROR, "error setting mode of %s: %s\n",
240
fileName, strerror(errno));
244
if (fchown(fd, sb->st_uid, sb->st_gid)) {
245
message(MESS_ERROR, "error setting owner of %s: %s\n",
246
fileName, strerror(errno));
247
if (geteuid() == 0) {
248
/* If run as non-root, we may not be able to duplicate
249
the ownership of the file. Warn but don't fail. */
254
if (fchmod(fd, sb->st_mode)) {
255
message(MESS_ERROR, "error setting mode of %s: %s\n",
256
fileName, strerror(errno));
263
#define SHRED_CALL "shred -u "
264
#define SHRED_COUNT_FLAG "-n "
266
/* unlink, but try to call shred from GNU fileutils */
267
static int shred_file(char *filename, struct logInfo *log)
271
char count[DIGITS]; /* that's a lot of shredding :) */
273
if (!(log->flags & LOG_FLAG_SHRED)) {
274
return unlink(filename);
277
len = strlen(filename) + strlen(SHRED_CALL);
278
len += strlen(SHRED_COUNT_FLAG) + DIGITS;
282
message(MESS_ERROR, "malloc error while shredding");
283
return unlink(filename);
285
strcpy(cmd, SHRED_CALL);
286
if (log->shred_cycles != 0) {
287
strcat(cmd, SHRED_COUNT_FLAG);
288
snprintf(count, DIGITS - 1, "%d", log->shred_cycles);
292
strcat(cmd, filename);
296
message(MESS_ERROR, "Failed to shred %s\n, trying unlink", filename);
298
message(MESS_NORMAL, "Shred returned %d\n", ret);
300
return unlink(filename);
306
static int removeLogFile(char *name, struct logInfo *log)
308
message(MESS_DEBUG, "removing old log %s\n", name);
310
if (!debug && shred_file(name, log)) {
311
message(MESS_ERROR, "Failed to remove old log %s: %s\n",
312
name, strerror(errno));
318
static int compressLogFile(char *name, struct logInfo *log, struct stat *sb)
320
char *compressedName;
321
const char **fullCommand;
328
message(MESS_DEBUG, "compressing log with: %s\n", log->compress_prog);
332
fullCommand = alloca(sizeof(*fullCommand) *
333
(log->compress_options_count + 2));
334
fullCommand[0] = log->compress_prog;
335
for (i = 0; i < log->compress_options_count; i++)
336
fullCommand[i + 1] = log->compress_options_list[i];
337
fullCommand[log->compress_options_count + 1] = NULL;
339
compressedName = alloca(strlen(name) + strlen(log->compress_ext) + 2);
340
sprintf(compressedName, "%s%s", name, log->compress_ext);
342
if ((inFile = open(name, O_RDONLY)) < 0) {
343
message(MESS_ERROR, "unable to open %s for compression\n", name);
348
createOutputFile(compressedName, O_RDWR | O_CREAT | O_TRUNC, sb);
360
execvp(fullCommand[0], (void *) fullCommand);
369
if (!WIFEXITED(status) || WEXITSTATUS(status)) {
370
message(MESS_ERROR, "failed to compress log %s\n", name);
374
utim.actime = sb->st_atime;
375
utim.modtime = sb->st_mtime;
376
utime(compressedName,&utim);
377
/* If we can't change atime/mtime, it's not a disaster.
378
It might possibly fail under SELinux. */
380
shred_file(name, log);
385
static int mailLog(char *logFile, char *mailCommand,
386
char *uncompressCommand, char *address, char *subject)
389
pid_t mailChild, uncompressChild = 0;
390
int mailStatus, uncompressStatus;
391
int uncompressPipe[2];
392
char *mailArgv[] = { mailCommand, "-s", subject, address, NULL };
395
if ((mailInput = open(logFile, O_RDONLY)) < 0) {
396
message(MESS_ERROR, "failed to open %s for mailing: %s\n", logFile,
401
if (uncompressCommand) {
402
if (pipe(uncompressPipe) < 0) {
403
message(MESS_ERROR, "error opening pipe for uncompress: %s",
407
if (!(uncompressChild = fork())) {
408
/* uncompress child */
411
dup2(uncompressPipe[1], 1);
412
close(uncompressPipe[0]);
413
close(uncompressPipe[1]);
415
execlp(uncompressCommand, uncompressCommand, NULL);
420
mailInput = uncompressPipe[0];
421
close(uncompressPipe[1]);
424
if (!(mailChild = fork())) {
429
execvp(mailArgv[0], mailArgv);
435
waitpid(mailChild, &mailStatus, 0);
437
if (!WIFEXITED(mailStatus) || WEXITSTATUS(mailStatus)) {
438
message(MESS_ERROR, "mail command failed for %s\n", logFile);
442
if (uncompressCommand) {
443
waitpid(uncompressChild, &uncompressStatus, 0);
445
if (!WIFEXITED(uncompressStatus) || WEXITSTATUS(uncompressStatus)) {
446
message(MESS_ERROR, "uncompress command failed mailing %s\n",
455
static int mailLogWrapper(char *mailFilename, char *mailCommand,
456
int logNum, struct logInfo *log)
458
/* if the log is compressed (and we're not mailing a
459
* file whose compression has been delayed), we need
460
* to uncompress it */
461
if ((log->flags & LOG_FLAG_COMPRESS) &&
462
!((log->flags & LOG_FLAG_DELAYCOMPRESS) &&
463
(log->flags & LOG_FLAG_MAILFIRST))) {
464
if (mailLog(mailFilename, mailCommand,
465
log->uncompress_prog, log->logAddress,
469
if (mailLog(mailFilename, mailCommand, NULL,
470
log->logAddress, mailFilename))
476
static int copyTruncate(char *currLog, char *saveLog, struct stat *sb,
480
int fdcurr = -1, fdsave = -1;
483
message(MESS_DEBUG, "copying %s to %s\n", currLog, saveLog);
486
if ((fdcurr = open(currLog, O_RDWR)) < 0) {
487
message(MESS_ERROR, "error opening %s: %s\n", currLog,
492
if (selinux_enabled) {
493
security_context_t oldContext;
494
if (fgetfilecon_raw(fdcurr, &oldContext) >= 0) {
495
if (getfscreatecon_raw(&prev_context) < 0) {
497
"getting default context: %s\n",
499
if (selinux_enforce) {
504
if (setfscreatecon_raw(oldContext) < 0) {
506
"setting file context %s to %s: %s\n",
507
saveLog, oldContext, strerror(errno));
508
if (selinux_enforce) {
513
message(MESS_DEBUG, "set default create context\n");
516
if (errno != ENOTSUP) {
517
message(MESS_ERROR, "getting file context %s: %s\n",
518
currLog, strerror(errno));
519
if (selinux_enforce) {
527
createOutputFile(saveLog, O_WRONLY | O_CREAT | O_TRUNC, sb);
529
if (selinux_enabled) {
530
setfscreatecon_raw(prev_context);
531
freecon(prev_context);
539
while ((cnt = read(fdcurr, buf, sizeof(buf))) > 0) {
540
if (write(fdsave, buf, cnt) != cnt) {
541
message(MESS_ERROR, "error writing to %s: %s\n",
542
saveLog, strerror(errno));
549
message(MESS_ERROR, "error reading %s: %s\n",
550
currLog, strerror(errno));
557
if (flags & LOG_FLAG_COPYTRUNCATE) {
558
message(MESS_DEBUG, "truncating %s\n", currLog);
561
if (ftruncate(fdcurr, 0)) {
562
message(MESS_ERROR, "error truncating %s: %s\n", currLog,
569
message(MESS_DEBUG, "Not truncating %s\n", currLog);
576
int findNeedRotating(struct logInfo *log, int logNum)
579
struct logState *state = NULL;
580
struct tm now = *localtime(&nowSecs);
582
message(MESS_DEBUG, "considering log %s\n", log->files[logNum]);
584
if (stat(log->files[logNum], &sb)) {
585
if ((log->flags & LOG_FLAG_MISSINGOK) && (errno == ENOENT)) {
586
message(MESS_DEBUG, " log %s does not exist -- skipping\n",
590
message(MESS_ERROR, "stat of %s failed: %s\n", log->files[logNum],
595
state = findState(log->files[logNum]);
599
if (log->criterium == ROT_SIZE) {
600
state->doRotate = (sb.st_size >= log->threshhold);
601
} else if (log->criterium == ROT_FORCE) {
602
/* user forced rotation of logs from command line */
604
} else if (state->lastRotated.tm_year > now.tm_year ||
605
(state->lastRotated.tm_year == now.tm_year &&
606
(state->lastRotated.tm_mon > now.tm_mon ||
607
(state->lastRotated.tm_mon == now.tm_mon &&
608
state->lastRotated.tm_mday > now.tm_mday)))) {
610
"log %s last rotated in the future -- rotation forced\n",
613
} else if (state->lastRotated.tm_year != now.tm_year ||
614
state->lastRotated.tm_mon != now.tm_mon ||
615
state->lastRotated.tm_mday != now.tm_mday) {
616
switch (log->criterium) {
619
1) the current weekday is before the weekday of the
621
2) more then a week has passed since the last
623
state->doRotate = ((now.tm_wday < state->lastRotated.tm_wday)
626
mktime(&state->lastRotated)) >
630
/* rotate if the logs haven't been rotated this month or
632
state->doRotate = ((now.tm_mon != state->lastRotated.tm_mon) ||
634
state->lastRotated.tm_year));
637
/* FIXME: only days=1 is implemented!! */
641
/* rotate if the logs haven't been rotated this year */
642
state->doRotate = (now.tm_year != state->lastRotated.tm_year);
649
if (log->minsize && sb.st_size < log->minsize)
653
/* The notifempty flag overrides the normal criteria */
654
if (!(log->flags & LOG_FLAG_IFEMPTY) && !sb.st_size)
657
if (state->doRotate) {
658
message(MESS_DEBUG, " log needs rotating\n");
660
message(MESS_DEBUG, " log does not need rotating\n");
666
int prerotateSingleLog(struct logInfo *log, int logNum, struct logState *state,
667
struct logNames *rotNames)
669
struct tm now = *localtime(&nowSecs);
670
char *oldName, *newName = NULL;
679
int rotateCount = log->rotateCount ? log->rotateCount : 1;
680
int logStart = (log->logStart == -1) ? 1 : log->logStart;
681
#define DATEEXT_LEN 64
682
#define PATTERN_LEN (DATEEXT_LEN * 2)
683
char dext_str[DATEEXT_LEN];
684
char dformat[DATEEXT_LEN];
685
char dext_pattern[PATTERN_LEN];
688
if (!state->doRotate)
691
/* Logs with rotateCounts of 0 are rotated once, then removed. This
692
lets scripts run properly, and everything gets mailed properly. */
694
message(MESS_DEBUG, "rotating log %s, log->rotateCount is %d\n",
695
log->files[logNum], log->rotateCount);
697
if (log->flags & LOG_FLAG_COMPRESS)
698
compext = log->compress_ext;
700
state->lastRotated = now;
703
if (log->oldDir[0] != '/') {
704
char *ld = ourDirName(log->files[logNum]);
706
malloc(strlen(ld) + strlen(log->oldDir) + 2);
707
sprintf(rotNames->dirName, "%s/%s", ld, log->oldDir);
710
rotNames->dirName = strdup(log->oldDir);
712
rotNames->dirName = ourDirName(log->files[logNum]);
714
rotNames->baseName = strdup(ourBaseName(log->files[logNum]));
716
oldName = alloca(PATH_MAX);
717
newName = alloca(PATH_MAX);
718
rotNames->disposeName = malloc(PATH_MAX);
720
if (log->extension &&
723
baseName[strlen(rotNames->baseName) -
724
strlen(log->extension)]), log->extension,
725
strlen(log->extension)) == 0) {
728
fileext = log->extension;
730
calloc(strlen(rotNames->baseName) - strlen(log->extension) + 1,
732
strncat(tempstr, rotNames->baseName,
733
strlen(rotNames->baseName) - strlen(log->extension));
734
free(rotNames->baseName);
735
rotNames->baseName = tempstr;
738
/* Allow only %Y %d %m and create valid strftime format string
739
* Construct the glob pattern corresponding to the date format */
741
if (log->dateformat) {
743
memset(dext_pattern, 0, sizeof(dext_pattern));
744
dext = log->dateformat;
747
while ((*dext != '\0') && (!hasErrors)) {
748
/* Will there be a space for a char and '\0'? */
749
if (j >= (sizeof(dext_pattern) - 1)) {
750
message(MESS_ERROR, "Date format %s is too long\n",
756
switch (*(dext + 1)) {
758
strncat(dext_pattern, "[0-9][0-9]",
759
sizeof(dext_pattern) - strlen(dext_pattern));
760
j += 10; /* strlen("[0-9][0-9]") */
763
strncat(dext_pattern, "[0-9][0-9]",
764
sizeof(dext_pattern) - strlen(dext_pattern));
766
if (j >= (sizeof(dext_pattern) - 1)) {
767
message(MESS_ERROR, "Date format %s is too long\n",
772
dformat[i++] = *(dext++);
776
/* End of year 2293 this pattern does not work. */
777
strncat(dext_pattern,
778
"[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",
779
sizeof(dext_pattern) - strlen(dext_pattern));
781
if (j >= (sizeof(dext_pattern) - 1)) {
782
message(MESS_ERROR, "Date format %s is too long\n",
787
dformat[i++] = *(dext++);
791
dformat[i++] = *dext;
793
dext_pattern[j++] = *dext;
798
dext_pattern[j++] = *dext;
804
message(MESS_DEBUG, "Converted '%s' -> '%s'\n", log->dateformat, dformat);
805
strftime(dext_str, sizeof(dext_str), dformat, &now);
807
/* The default dateformat and glob pattern */
808
strftime(dext_str, sizeof(dext_str), "-%Y%m%d", &now);
809
strncpy(dext_pattern, "-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]",
810
sizeof(dext_pattern));
811
dext_pattern[PATTERN_LEN - 1] = '\0';
813
message(MESS_DEBUG, "dateext suffix '%s'\n", dext_str);
814
message(MESS_DEBUG, "glob pattern '%s'\n", dext_pattern);
816
/* First compress the previous log when necessary */
817
if (log->flags & LOG_FLAG_COMPRESS &&
818
log->flags & LOG_FLAG_DELAYCOMPRESS) {
819
if (log->flags & LOG_FLAG_DATEEXT) {
820
/* glob for uncompressed files with our pattern */
821
if (asprintf(&glob_pattern, "%s/%s%s%s", rotNames->dirName,
822
rotNames->baseName, dext_pattern, fileext) < 0) {
823
message(MESS_ERROR, "could not allocate glob pattern memory\n");
825
rc = glob(glob_pattern, 0, globerr, &globResult);
826
if (!rc && globResult.gl_pathc > 0) {
827
for (i = 0; i < globResult.gl_pathc && !hasErrors; i++) {
830
snprintf(oldName, PATH_MAX, "%s", (globResult.gl_pathv)[i]);
831
if (stat(oldName, &sbprev)) {
833
"previous log %s does not exist\n",
836
hasErrors = compressLogFile(oldName, log, &sbprev);
841
"glob finding logs to compress failed\n");
842
/* fallback to old behaviour */
843
snprintf(oldName, PATH_MAX, "%s/%s.%d%s", rotNames->dirName,
844
rotNames->baseName, logStart, fileext);
846
globfree(&globResult);
851
snprintf(oldName, PATH_MAX, "%s/%s.%d%s", rotNames->dirName,
852
rotNames->baseName, logStart, fileext);
853
if (stat(oldName, &sbprev)) {
854
message(MESS_DEBUG, "previous log %s does not exist\n",
857
hasErrors = compressLogFile(oldName, log, &sbprev);
862
rotNames->firstRotated =
863
malloc(strlen(rotNames->dirName) + strlen(rotNames->baseName) +
864
strlen(fileext) + strlen(compext) + 30);
866
if (log->flags & LOG_FLAG_DATEEXT) {
867
/* glob for compressed files with our pattern
868
* and compress ext */
869
if (asprintf(&glob_pattern, "%s/%s%s%s%s", rotNames->dirName,
870
rotNames->baseName, dext_pattern, fileext, compext) < 0) {
871
message(MESS_ERROR, "could not allocate glob pattern memory\n");
873
rc = glob(glob_pattern, 0, globerr, &globResult);
875
/* search for files to drop, if we find one remember it,
876
* if we find another one mail and remove the first and
877
* remember the second and so on */
880
/* remove the first (n - rotateCount) matches
881
* no real rotation needed, since the files have
882
* the date in their name */
883
for (i = 0; i < globResult.gl_pathc; i++) {
884
if (!stat((globResult.gl_pathv)[i], &fst_buf)) {
885
if ((i <= ((int) globResult.gl_pathc - rotateCount))
886
|| ((log->rotateAge > 0)
888
(((nowSecs - fst_buf.st_mtime) / 60 / 60 / 24)
889
> log->rotateAge))) {
890
if (mail_out != -1) {
892
(globResult.gl_pathv)[mail_out];
893
if (!hasErrors && log->logAddress)
895
mailLogWrapper(mailFilename,
899
message(MESS_DEBUG, "removing %s\n", mailFilename);
900
hasErrors = removeLogFile(mailFilename, log);
906
if (mail_out != -1) {
907
/* oldName is oldest Backup found (for unlink later) */
908
snprintf(oldName, PATH_MAX, "%s", (globResult.gl_pathv)[mail_out]);
909
strcpy(rotNames->disposeName, oldName);
911
free(rotNames->disposeName);
912
rotNames->disposeName = NULL;
915
message(MESS_DEBUG, "glob finding old rotated logs failed\n");
916
free(rotNames->disposeName);
917
rotNames->disposeName = NULL;
919
/* firstRotated is most recently created/compressed rotated log */
920
sprintf(rotNames->firstRotated, "%s/%s%s%s%s",
921
rotNames->dirName, rotNames->baseName, dext_str, fileext,
922
(log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext);
923
globfree(&globResult);
926
if (log->rotateAge) {
928
for (i = 1; i <= rotateCount + 1; i++) {
929
snprintf(oldName, PATH_MAX, "%s/%s.%d%s%s", rotNames->dirName,
930
rotNames->baseName, i, fileext, compext);
931
if (!stat(oldName, &fst_buf)
932
&& (((nowSecs - fst_buf.st_mtime) / 60 / 60 / 24)
934
char *mailFilename = oldName;
935
if (!hasErrors && log->logAddress)
937
mailLogWrapper(mailFilename, mailCommand,
940
hasErrors = removeLogFile(mailFilename, log);
945
snprintf(oldName, PATH_MAX, "%s/%s.%d%s%s", rotNames->dirName,
946
rotNames->baseName, logStart + rotateCount, fileext,
948
strcpy(newName, oldName);
950
strcpy(rotNames->disposeName, oldName);
952
sprintf(rotNames->firstRotated, "%s/%s.%d%s%s", rotNames->dirName,
953
rotNames->baseName, logStart, fileext,
954
(log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext);
957
if (selinux_enabled) {
958
security_context_t oldContext = NULL;
959
if (getfilecon_raw(log->files[logNum], &oldContext) > 0) {
960
if (getfscreatecon_raw(&prev_context) < 0) {
962
"getting default context: %s\n",
964
if (selinux_enforce) {
969
if (setfscreatecon_raw(oldContext) < 0) {
971
"setting file context %s to %s: %s\n",
972
log->files[logNum], oldContext,
974
if (selinux_enforce) {
981
if (errno != ENOENT && errno != ENOTSUP) {
982
message(MESS_ERROR, "getting file context %s: %s\n",
983
log->files[logNum], strerror(errno));
984
if (selinux_enforce) {
991
for (i = rotateCount + logStart - 1; (i >= 0) && !hasErrors; i--) {
995
snprintf(oldName, PATH_MAX, "%s/%s.%d%s%s", rotNames->dirName,
996
rotNames->baseName, i, fileext, compext);
999
"renaming %s to %s (rotatecount %d, logstart %d, i %d), \n",
1000
oldName, newName, rotateCount, logStart, i);
1002
if (!debug && rename(oldName, newName)) {
1003
if (errno == ENOENT) {
1004
message(MESS_DEBUG, "old log %s does not exist\n",
1007
message(MESS_ERROR, "error renaming %s to %s: %s\n",
1008
oldName, newName, strerror(errno));
1013
} /* !LOG_FLAG_DATEEXT */
1015
if (log->flags & LOG_FLAG_DATEEXT) {
1016
char *destFile = alloca(PATH_MAX);
1017
struct stat fst_buf;
1019
if (asprintf(&(rotNames->finalName), "%s/%s%s%s", rotNames->dirName,
1020
rotNames->baseName, dext_str, fileext) < 0) {
1021
message(MESS_ERROR, "could not allocate finalName memory\n");
1023
snprintf(destFile, PATH_MAX, "%s%s", rotNames->finalName, compext);
1024
if (!stat(destFile, &fst_buf)) {
1026
"destination %s already exists, skipping rotation\n",
1027
rotNames->firstRotated);
1031
/* note: the gzip extension is *not* used here! */
1032
if (asprintf(&(rotNames->finalName), "%s/%s.%d%s", rotNames->dirName,
1033
rotNames->baseName, logStart, fileext) < 0) {
1034
message(MESS_ERROR, "could not allocate finalName memory\n");
1038
/* if the last rotation doesn't exist, that's okay */
1039
if (!debug && rotNames->disposeName
1040
&& access(rotNames->disposeName, F_OK)) {
1042
"log %s doesn't exist -- won't try to " "dispose of it\n",
1043
rotNames->disposeName);
1044
free(rotNames->disposeName);
1045
rotNames->disposeName = NULL;
1051
int rotateSingleLog(struct logInfo *log, int logNum, struct logState *state,
1052
struct logNames *rotNames)
1058
security_context_t savedContext = NULL;
1061
if (!state->doRotate)
1066
if (!(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) {
1068
if (selinux_enabled) {
1069
security_context_t oldContext = NULL;
1072
if ((fdcurr = open(log->files[logNum], O_RDWR)) < 0) {
1073
message(MESS_ERROR, "error opening %s: %s\n",
1078
if (fgetfilecon_raw(fdcurr, &oldContext) >= 0) {
1079
if (getfscreatecon_raw(&savedContext) < 0) {
1081
"getting default context: %s\n",
1083
if (selinux_enforce) {
1084
freecon(oldContext);
1085
if (close(fdcurr) < 0)
1086
message(MESS_ERROR, "error closing file %s",
1087
log->files[logNum]);
1091
if (setfscreatecon_raw(oldContext) < 0) {
1093
"setting file context %s to %s: %s\n",
1094
log->files[logNum], oldContext, strerror(errno));
1095
if (selinux_enforce) {
1096
freecon(oldContext);
1097
if (close(fdcurr) < 0)
1098
message(MESS_ERROR, "error closing file %s",
1099
log->files[logNum]);
1103
message(MESS_DEBUG, "fscreate context set to %s\n",
1105
freecon(oldContext);
1107
if (errno != ENOTSUP) {
1108
message(MESS_ERROR, "getting file context %s: %s\n",
1109
log->files[logNum], strerror(errno));
1110
if (selinux_enforce) {
1111
if (close(fdcurr) < 0)
1112
message(MESS_ERROR, "error closing file %s",
1113
log->files[logNum]);
1118
if (close(fdcurr) < 0)
1119
message(MESS_ERROR, "error closing file %s",
1120
log->files[logNum]);
1123
message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum],
1124
rotNames->finalName);
1125
if (!debug && !hasErrors &&
1126
rename(log->files[logNum], rotNames->finalName)) {
1127
message(MESS_ERROR, "failed to rename %s to %s: %s\n",
1128
log->files[logNum], rotNames->finalName,
1132
if (!log->rotateCount) {
1133
rotNames->disposeName =
1134
realloc(rotNames->disposeName,
1135
strlen(rotNames->dirName) +
1136
strlen(rotNames->baseName) +
1137
strlen(log->files[logNum]) + 10);
1138
sprintf(rotNames->disposeName, "%s%s", rotNames->finalName,
1140
&& (log->flags & LOG_FLAG_COMPRESS)) ? log->
1142
message(MESS_DEBUG, "disposeName will be %s\n",
1143
rotNames->disposeName);
1147
if (!hasErrors && log->flags & LOG_FLAG_CREATE &&
1148
!(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) {
1149
if (log->createUid == NO_UID)
1150
sb.st_uid = state->sb.st_uid;
1152
sb.st_uid = log->createUid;
1154
if (log->createGid == NO_GID)
1155
sb.st_gid = state->sb.st_gid;
1157
sb.st_gid = log->createGid;
1159
if (log->createMode == NO_MODE)
1160
sb.st_mode = state->sb.st_mode & 0777;
1162
sb.st_mode = log->createMode;
1164
message(MESS_DEBUG, "creating new %s mode = 0%o uid = %d "
1165
"gid = %d\n", log->files[logNum], (unsigned int) sb.st_mode,
1166
(int) sb.st_uid, (int) sb.st_gid);
1169
fd = createOutputFile(log->files[logNum], O_CREAT | O_RDWR,
1178
if (selinux_enabled) {
1179
setfscreatecon_raw(savedContext);
1180
freecon(savedContext);
1181
savedContext = NULL;
1186
&& log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))
1188
copyTruncate(log->files[logNum], rotNames->finalName,
1189
&state->sb, log->flags);
1195
int postrotateSingleLog(struct logInfo *log, int logNum, struct logState *state,
1196
struct logNames *rotNames)
1200
if (!state->doRotate)
1203
if ((log->flags & LOG_FLAG_COMPRESS) &&
1204
!(log->flags & LOG_FLAG_DELAYCOMPRESS)) {
1205
hasErrors = compressLogFile(rotNames->finalName, log, &state->sb);
1208
if (!hasErrors && log->logAddress) {
1211
if (log->flags & LOG_FLAG_MAILFIRST)
1212
mailFilename = rotNames->firstRotated;
1214
mailFilename = rotNames->disposeName;
1218
mailLogWrapper(mailFilename, mailCommand, logNum, log);
1221
if (!hasErrors && rotNames->disposeName)
1222
hasErrors = removeLogFile(rotNames->disposeName, log);
1225
if (selinux_enabled) {
1226
setfscreatecon_raw(prev_context);
1227
freecon(prev_context);
1228
prev_context = NULL;
1234
int rotateLogSet(struct logInfo *log, int force)
1238
int logHasErrors[log->numFiles];
1240
struct logState **state;
1241
struct logNames **rotNames;
1244
log->criterium = ROT_FORCE;
1246
message(MESS_DEBUG, "\nrotating pattern: %s ", log->pattern);
1247
switch (log->criterium) {
1249
message(MESS_DEBUG, "after %d days ", log->threshhold);
1252
message(MESS_DEBUG, "weekly ");
1255
message(MESS_DEBUG, "monthly ");
1258
message(MESS_DEBUG, "yearly ");
1261
message(MESS_DEBUG, "%d bytes ", log->threshhold);
1264
message(MESS_DEBUG, "forced from command line ");
1268
if (log->rotateCount)
1269
message(MESS_DEBUG, "(%d rotations)\n", log->rotateCount);
1271
message(MESS_DEBUG, "(no old logs will be kept)\n");
1274
message(MESS_DEBUG, "olddir is %s, ", log->oldDir);
1276
if (log->flags & LOG_FLAG_IFEMPTY)
1277
message(MESS_DEBUG, "empty log files are rotated, ");
1279
message(MESS_DEBUG, "empty log files are not rotated, ");
1282
message(MESS_DEBUG, "only log files >= %d bytes are rotated, ", log->minsize);
1284
if (log->logAddress) {
1285
message(MESS_DEBUG, "old logs mailed to %s\n", log->logAddress);
1287
message(MESS_DEBUG, "old logs are removed\n");
1290
for (i = 0; i < log->numFiles; i++) {
1291
logHasErrors[i] = findNeedRotating(log, i);
1292
hasErrors |= logHasErrors[i];
1294
/* sure is a lot of findStating going on .. */
1295
if ((findState(log->files[i]))->doRotate)
1301
message(MESS_DEBUG, "not running first action script, "
1302
"since no logs will be rotated\n");
1304
message(MESS_DEBUG, "running first action script\n");
1305
if (runScript(log->pattern, log->first)) {
1306
message(MESS_ERROR, "error running first action script "
1307
"for %s\n", log->pattern);
1309
/* finish early, firstaction failed, affects all logs in set */
1315
state = malloc(log->numFiles * sizeof(struct logState *));
1316
rotNames = malloc(log->numFiles * sizeof(struct logNames *));
1319
(!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < log->numFiles)
1320
|| ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < 1); j++) {
1323
((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
1324
|| (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
1325
state[i] = findState(log->files[i]);
1327
rotNames[i] = malloc(sizeof(struct logNames));
1328
memset(rotNames[i], 0, sizeof(struct logNames));
1331
prerotateSingleLog(log, i, state[i], rotNames[i]);
1332
hasErrors |= logHasErrors[i];
1336
&& (! ( (logHasErrors[j] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
1337
|| (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) )) {
1339
message(MESS_DEBUG, "not running prerotate script, "
1340
"since no logs will be rotated\n");
1342
message(MESS_DEBUG, "running prerotate script\n");
1343
if (log->flags & LOG_FLAG_SHAREDSCRIPTS) {
1344
if (runScriptMultiple(log->pattern, log->pre, rotNames, log->numFiles)) {
1346
"error running shared prerotate script "
1347
"for '%s'\n", log->pattern);
1348
logHasErrors[j] = 1;
1352
if (runScriptMultiple(log->pattern, log->pre, &rotNames[j], 1)) {
1354
"error running non-shared prerotate script "
1355
"for %s of '%s'\n", log->files[j], log->pattern);
1356
logHasErrors[j] = 1;
1364
((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
1365
|| (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
1366
if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
1367
|| (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) {
1369
rotateSingleLog(log, i, state[i], rotNames[i]);
1370
hasErrors |= logHasErrors[i];
1375
&& (! ( (logHasErrors[j] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
1376
|| (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) )) {
1378
message(MESS_DEBUG, "not running postrotate script, "
1379
"since no logs were rotated\n");
1381
message(MESS_DEBUG, "running postrotate script\n");
1382
if (log->flags & LOG_FLAG_SHAREDSCRIPTS) {
1383
if (runScriptMultiple(log->pattern, log->post, rotNames, log->numFiles)) {
1385
"error running shared postrotate script "
1386
"for '%s'\n", log->pattern);
1387
logHasErrors[j] = 1;
1391
if (runScriptMultiple(log->pattern, log->post, &rotNames[j], 1)) {
1393
"error running non-shared postrotate script "
1394
"for %s of '%s'\n", log->files[j], log->pattern);
1395
logHasErrors[j] = 1;
1403
((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles)
1404
|| (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) {
1405
if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS))
1406
|| (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) {
1408
postrotateSingleLog(log, i, state[i], rotNames[i]);
1409
hasErrors |= logHasErrors[i];
1415
for (i = 0; i < log->numFiles; i++) {
1416
free(rotNames[i]->firstRotated);
1417
free(rotNames[i]->disposeName);
1418
free(rotNames[i]->finalName);
1419
free(rotNames[i]->dirName);
1420
free(rotNames[i]->baseName);
1428
message(MESS_DEBUG, "not running last action script, "
1429
"since no logs will be rotated\n");
1431
message(MESS_DEBUG, "running last action script\n");
1432
if (runScript(log->pattern, log->last)) {
1433
message(MESS_ERROR, "error running last action script "
1434
"for %s\n", log->pattern);
1443
static int writeState(char *stateFilename)
1450
f = fopen(stateFilename, "w");
1452
message(MESS_ERROR, "error creating state file %s: %s\n",
1453
stateFilename, strerror(errno));
1457
fprintf(f, "logrotate state -- version 2\n");
1459
for (i = 0; i < hashSize; i++) {
1460
for (p = states[i]->head.lh_first; p != NULL;
1461
p = p->list.le_next) {
1463
for (chptr = p->fn; *chptr; chptr++) {
1473
fprintf(f, " %d-%d-%d\n",
1474
p->lastRotated.tm_year + 1900,
1475
p->lastRotated.tm_mon + 1,
1476
p->lastRotated.tm_mday);
1484
static int readState(char *stateFilename)
1490
int year, month, day;
1494
struct logState *st;
1498
error = stat(stateFilename, &f_stat);
1500
if ((error && errno == ENOENT) || (!error && f_stat.st_size == 0)) {
1501
/* create the file before continuing to ensure we have write
1502
access to the file */
1503
f = fopen(stateFilename, "w");
1505
message(MESS_ERROR, "error creating state file %s: %s\n",
1506
stateFilename, strerror(errno));
1509
fprintf(f, "logrotate state -- version 2\n");
1513
message(MESS_ERROR, "error stat()ing state file %s: %s\n",
1514
stateFilename, strerror(errno));
1518
f = fopen(stateFilename, "r");
1520
message(MESS_ERROR, "error opening state file %s: %s\n",
1521
stateFilename, strerror(errno));
1525
if (!fgets(buf, sizeof(buf) - 1, f)) {
1526
message(MESS_ERROR, "error reading top line of %s\n",
1532
if (strcmp(buf, "logrotate state -- version 1\n") &&
1533
strcmp(buf, "logrotate state -- version 2\n")) {
1535
message(MESS_ERROR, "bad top line in state file %s\n",
1542
while (fgets(buf, sizeof(buf) - 1, f)) {
1546
if (buf[i - 1] != '\n') {
1547
message(MESS_ERROR, "line %d too long in state file %s\n",
1548
line, stateFilename);
1558
if (poptParseArgvString(buf, &argc, &argv) || (argc != 2) ||
1559
(sscanf(argv[1], "%d-%d-%d", &year, &month, &day) != 3)) {
1560
message(MESS_ERROR, "bad line %d in state file %s\n",
1561
line, stateFilename);
1567
/* Hack to hide earlier bug */
1568
if ((year != 1900) && (year < 1970 || year > 2100)) {
1570
"bad year %d for file %s in state file %s\n", year,
1571
argv[0], stateFilename);
1577
if (month < 1 || month > 12) {
1579
"bad month %d for file %s in state file %s\n", month,
1580
argv[0], stateFilename);
1586
/* 0 to hide earlier bug */
1587
if (day < 0 || day > 31) {
1589
"bad day %d for file %s in state file %s\n", day,
1590
argv[0], stateFilename);
1596
year -= 1900, month -= 1;
1598
if ((st = findState(argv[0])) == NULL)
1601
st->lastRotated.tm_mon = month;
1602
st->lastRotated.tm_mday = day;
1603
st->lastRotated.tm_year = year;
1605
/* fill in the rest of the st->lastRotated fields */
1606
lr_time = mktime(&st->lastRotated);
1607
st->lastRotated = *localtime(&lr_time);
1616
int main(int argc, const char **argv)
1619
char *stateFile = STATEFILE;
1624
struct logInfo *log;
1625
int state_file_ok = 1;
1627
struct poptOption options[] = {
1628
{"debug", 'd', 0, 0, 'd',
1629
"Don't do anything, just test (implies -v)"},
1630
{"force", 'f', 0, &force, 0, "Force file rotation"},
1631
{"mail", 'm', POPT_ARG_STRING, &mailCommand, 0,
1632
"Command to send mail (instead of `" DEFAULT_MAIL_COMMAND "')",
1634
{"state", 's', POPT_ARG_STRING, &stateFile, 0,
1635
"Path of state file",
1637
{"verbose", 'v', 0, 0, 'v', "Display messages during rotation"},
1638
POPT_AUTOHELP {0, 0, 0, 0, 0}
1641
logSetLevel(MESS_NORMAL);
1642
setlocale (LC_ALL, "");
1644
optCon = poptGetContext("logrotate", argc, argv, options, 0);
1645
poptReadDefaultConfig(optCon, 1);
1646
poptSetOtherOptionHelp(optCon, "[OPTION...] <configfile>");
1648
while ((arg = poptGetNextOpt(optCon)) >= 0) {
1654
logSetLevel(MESS_DEBUG);
1660
fprintf(stderr, "logrotate: bad argument %s: %s\n",
1661
poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
1663
poptFreeContext(optCon);
1667
files = poptGetArgs((poptContext) optCon);
1669
fprintf(stderr, "logrotate " VERSION
1670
" - Copyright (C) 1995-2001 Red Hat, Inc.\n");
1672
"This may be freely redistributed under the terms of "
1673
"the GNU Public License\n\n");
1674
poptPrintUsage(optCon, stderr, 0);
1675
poptFreeContext(optCon);
1679
selinux_enabled = (is_selinux_enabled() > 0);
1680
selinux_enforce = security_getenforce();
1685
if (readAllConfigPaths(files)) {
1686
poptFreeContext(optCon);
1690
poptFreeContext(optCon);
1691
nowSecs = time(NULL);
1693
if (allocateHash() != 0)
1696
if (readState(stateFile))
1702
message(MESS_DEBUG, "\nHandling %d logs\n", numLogs);
1704
for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next)
1705
rc |= rotateLogSet(log, force);
1707
if (!debug && state_file_ok)
1708
rc |= writeState(stateFile);
1712
message(MESS_ERROR, "could not read state file, "
1713
"will not attempt to write into it\n");