#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <popt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <time.h>
#include <unistd.h>
#include <glob.h>
#ifdef WITH_SELINUX
#include <selinux/selinux.h>
static security_context_t prev_context=NULL;
int selinux_enabled=0;
#endif
#include "basenames.h"
#include "log.h"
#include "logrotate.h"
#if !defined(GLOB_ABORTED) && defined(GLOB_ABEND)
#define GLOB_ABORTED GLOB_ABEND
#endif
typedef struct {
char * fn;
struct tm lastRotated; /* only tm.mon, tm_mday, tm_year are good! */
struct stat sb;
int doRotate;
} logState;
struct stateSet {
logState * states;
int numStates;
};
#define NO_MODE ((mode_t) -1)
#define NO_UID ((uid_t) -1)
#define NO_GID ((gid_t) -1)
int debug = 0;
char * mailCommand = DEFAULT_MAIL_COMMAND;
time_t nowSecs = 0;
static int globerr(const char * pathname, int theerr) {
message(MESS_ERROR, "error accessing %s: %s\n", pathname,
strerror(theerr));
/* We want the glob operation to continue, so return 0 */
return 1;
}
static logState * findState(const char * fn, struct stateSet * sip) {
int i;
logState * states = sip->states;
int numStates = sip->numStates;
struct tm now = *localtime(&nowSecs);
time_t lr_time;
for (i = 0; i < numStates; i++)
if (!strcmp(fn, states[i].fn)) break;
if (i == numStates) {
i = numStates++;
states = realloc(states, sizeof(*states) * numStates);
states[i].fn = strdup(fn);
memset(&states[i].lastRotated, 0, sizeof(states[i].lastRotated));
states[i].doRotate = 0;
states[i].lastRotated.tm_mon = now.tm_mon;
states[i].lastRotated.tm_mday = now.tm_mday;
states[i].lastRotated.tm_year = now.tm_year;
/* fill in the rest of the st->lastRotated fields */
lr_time = mktime(&states[i].lastRotated);
states[i].lastRotated = *localtime(&lr_time);
sip->states = states;
sip->numStates = numStates;
}
return (states + i);
}
static int runScript(char * logfn, char * script) {
int fd;
char *filespec;
int rc;
char buf[256];
if (debug) {
message(MESS_DEBUG, "running script with arg %s: \"%s\"\n",
logfn, script);
return 0;
}
filespec = buf;
snprintf(buf, sizeof(buf), "%s/logrotate.XXXXXX", getenv("TMPDIR") ?: "/tmp");
fd = -1;
if (!filespec || (fd = mkstemp(filespec)) < 0 || fchmod(fd, 0700)) {
message(MESS_DEBUG, "error creating %s: %s\n", filespec,
strerror(errno));
if (fd >= 0) {
close(fd);
unlink(filespec);
}
return -1;
}
if (write(fd, "#!/bin/sh\n\n", 11) != 11 ||
write(fd, script, strlen(script)) != strlen(script)) {
message(MESS_DEBUG, "error writing %s\n", filespec);
close(fd);
unlink(filespec);
return -1;
}
close(fd);
if (!fork()) {
execlp(filespec, filespec, logfn, NULL);
exit(1);
}
wait(&rc);
unlink(filespec);
return rc;
}
static int removeLogFile(char * name) {
message(MESS_DEBUG, "removing old log %s\n", name);
if (!debug && unlink(name)) {
message(MESS_ERROR, "Failed to remove old log %s: %s\n",
name, strerror(errno));
return 1;
}
return 0;
}
static int compressLogFile(char * name, logInfo * log, struct stat *sb) {
char * compressedName;
const char ** fullCommand;
int inFile;
int outFile;
int i;
int status;
fullCommand = alloca(sizeof(*fullCommand) *
(log->compress_options_count + 2));
fullCommand[0] = log->compress_prog;
for (i = 0; i < log->compress_options_count; i++)
fullCommand[i + 1] = log->compress_options_list[i];
fullCommand[log->compress_options_count + 1] = NULL;
compressedName = alloca(strlen(name) +
strlen(log->compress_ext) + 2);
sprintf(compressedName, "%s%s", name, log->compress_ext);
if ((inFile = open(name, O_RDONLY)) < 0) {
message(MESS_ERROR, "unable to open %s for compression\n", name);
return 1;
}
if ((outFile = open(compressedName, O_RDWR | O_CREAT | O_TRUNC, sb->st_mode)) < 0) {
message(MESS_ERROR, "unable to open %s for compressed output\n",
compressedName);
close(inFile);
return 1;
}
if (fchmod(outFile, (S_IRUSR | S_IWUSR) & sb->st_mode)) {
message(MESS_ERROR, "error setting mode of %s: %s\n",
compressedName, strerror(errno));
close(outFile);
close(inFile);
return 1;
}
if (fchown(outFile, sb->st_uid, sb->st_gid)) {
message(MESS_ERROR, "error setting owner of %s: %s\n",
compressedName, strerror(errno));
close(outFile);
close(inFile);
return 1;
}
if (fchmod(outFile, sb->st_mode)) {
message(MESS_ERROR, "error setting mode of %s: %s\n",
compressedName, strerror(errno));
close(outFile);
close(inFile);
return 1;
}
message(MESS_DEBUG, "compressing log with: %s\n", fullCommand[0]);
if (!fork()) {
dup2(inFile, 0);
close(inFile);
dup2(outFile, 1);
close(outFile);
execvp(fullCommand[0], (void *) fullCommand);
exit(1);
}
close(inFile);
close(outFile);
wait(&status);
if (!WIFEXITED(status) || WEXITSTATUS(status)) {
message(MESS_ERROR, "failed to compress log %s\n", name);
return 1;
}
unlink(name);
return 0;
}
static int mailLog(char * logFile, char * mailCommand, char * uncompressCommand,
char * address, char * subject) {
int mailInput;
pid_t mailChild, uncompressChild;
int mailStatus, uncompressStatus;
int uncompressPipe[2];
char * mailArgv[] = { mailCommand, "-s", subject, address, NULL };
int rc = 0;
if ((mailInput = open(logFile, O_RDONLY)) < 0) {
message(MESS_ERROR, "failed to open %s for mailing: %s\n", logFile,
strerror(errno));
return 1;
}
if (uncompressCommand) {
pipe(uncompressPipe);
if (!(uncompressChild = fork())) {
/* uncompress child */
dup2(mailInput, 0);
close(mailInput);
dup2(uncompressPipe[1], 1);
close(uncompressPipe[0]);
close(uncompressPipe[1]);
execlp(uncompressCommand, uncompressCommand, NULL);
exit(1);
}
close(mailInput);
mailInput = uncompressPipe[0];
close(uncompressPipe[1]);
}
if (!(mailChild = fork())) {
dup2(mailInput, 0);
close(mailInput);
close(1);
execvp(mailArgv[0], mailArgv);
exit(1);
}
close(mailInput);
waitpid(mailChild, &mailStatus, 0);
if (!WIFEXITED(mailStatus) || WEXITSTATUS(mailStatus)) {
message(MESS_ERROR, "mail command failed for %s\n", logFile);
rc = 1;
}
if (uncompressCommand) {
waitpid(uncompressChild, &uncompressStatus, 0);
if (!WIFEXITED(uncompressStatus) || WEXITSTATUS(uncompressStatus)) {
message(MESS_ERROR, "uncompress command failed mailing %s\n",
logFile);
rc = 1;
}
}
return rc;
}
static int mailLogWrapper (char * mailFilename, char * mailCommand, int logNum, logInfo * log) {
/* if the log is compressed (and we're not mailing a
* file whose compression has been delayed), we need
* to uncompress it */
if ((log->flags & LOG_FLAG_COMPRESS) &&
!((log->flags & LOG_FLAG_DELAYCOMPRESS) &&
(log->flags & LOG_FLAG_MAILFIRST))) {
if (mailLog(mailFilename, mailCommand,
log->uncompress_prog, log->logAddress,
log->files[logNum]))
return 1;
} else {
if (mailLog(mailFilename, mailCommand, NULL,
log->logAddress, mailFilename))
return 1;
}
return 0;
}
static int copyTruncate(char * currLog, char * saveLog, struct stat * sb, int flags) {
char buf[BUFSIZ];
int fdcurr = -1, fdsave = -1;
ssize_t cnt;
message(MESS_DEBUG, "copying %s to %s\n", currLog, saveLog);
if (!debug) {
if ((fdcurr = open(currLog, O_RDWR)) < 0) {
message(MESS_ERROR, "error opening %s: %s\n", currLog,
strerror(errno));
return 1;
}
#ifdef WITH_SELINUX
if ((selinux_enabled=(is_selinux_enabled()>0)))
{
security_context_t oldContext;
if (fgetfilecon(fdcurr, &oldContext) >=0) {
if (getfscreatecon(&prev_context) < 0) {
message(MESS_ERROR, "error getting default context: %s\n",
strerror(errno));
freecon(oldContext);
return 1;
}
if (setfscreatecon(oldContext) < 0) {
message(MESS_ERROR, "error setting file context %s to %s: %s\n",
saveLog, oldContext,strerror(errno));
freecon(oldContext);
return 1;
}
freecon(oldContext);
} else {
message(MESS_ERROR, "error getting file context %s: %s\n", currLog,
strerror(errno));
return 1;
}
}
#endif
fdsave = open(saveLog, O_WRONLY | O_CREAT | O_TRUNC,sb->st_mode);
#ifdef WITH_SELINUX
if (selinux_enabled) {
setfscreatecon(prev_context);
if (prev_context!= NULL) {
freecon(prev_context);
prev_context=NULL;
}
}
#endif
if (fdsave < 0) {
message(MESS_ERROR, "error creating %s: %s\n", saveLog,
strerror(errno));
close(fdcurr);
return 1;
}
if (fchmod(fdsave, (S_IRUSR | S_IWUSR) & sb->st_mode)) {
message(MESS_ERROR, "error setting mode of %s: %s\n",
saveLog, strerror(errno));
close(fdcurr);
close(fdsave);
return 1;
}
if (fchown(fdsave, sb->st_uid, sb->st_gid)) {
message(MESS_ERROR, "error setting owner of %s: %s\n",
saveLog, strerror(errno));
close(fdcurr);
close(fdsave);
return 1;
}
if (fchmod(fdsave, sb->st_mode)) {
message(MESS_ERROR, "error setting mode of %s: %s\n",
saveLog, strerror(errno));
close(fdcurr);
close(fdsave);
return 1;
}
while ((cnt = read(fdcurr, buf, sizeof(buf))) > 0) {
if (write(fdsave, buf, cnt) != cnt) {
message(MESS_ERROR, "error writing to %s: %s\n",
saveLog, strerror(errno));
close(fdcurr);
close(fdsave);
return 1;
}
}
if (cnt != 0) {
message(MESS_ERROR, "error reading %s: %s\n",
currLog, strerror(errno));
close(fdcurr);
close(fdsave);
return 1;
}
}
if (flags & LOG_FLAG_COPYTRUNCATE) {
message(MESS_DEBUG, "truncating %s\n", currLog);
if (!debug) {
if (ftruncate(fdcurr, 0)) {
message(MESS_ERROR, "error truncating %s: %s\n", currLog,
strerror(errno));
close(fdcurr);
close(fdsave);
return 1;
} else {
close(fdcurr);
close(fdsave);
}
}
} else {
message(MESS_DEBUG, "Not truncating %s\n", currLog);
close(fdcurr);
close(fdsave);
}
return 0;
}
int findNeedRotating(logInfo * log, int logNum, struct stateSet * sip) {
struct stat sb;
logState * state = NULL;
struct tm now = *localtime(&nowSecs);
message(MESS_DEBUG, "considering log %s\n", log->files[logNum]);
if (stat(log->files[logNum], &sb)) {
if ((log->flags & LOG_FLAG_MISSINGOK) && (errno == ENOENT)) {
message(MESS_DEBUG, " log %s does not exist -- skipping\n",
log->files[logNum]);
return 0;
}
message(MESS_ERROR, "stat of %s failed: %s\n", log->files[logNum],
strerror(errno));
return 1;
}
state = findState(log->files[logNum], sip);
state->doRotate = 0;
state->sb = sb;
if (log->criterium == ROT_SIZE) {
state->doRotate = (sb.st_size >= log->threshhold);
} else if (log->criterium == ROT_FORCE) {
/* user forced rotation of logs from command line */
state->doRotate = 1;
} else if (state->lastRotated.tm_year > now.tm_year ||
(state->lastRotated.tm_year == now.tm_year &&
(state->lastRotated.tm_mon > now.tm_mon ||
(state->lastRotated.tm_mon == now.tm_mon &&
state->lastRotated.tm_mday > now.tm_mday)))) {
message(MESS_ERROR,
"log %s last rotated in the future -- rotation forced\n",
log->files[logNum]);
state->doRotate = 1;
} else if (state->lastRotated.tm_year != now.tm_year ||
state->lastRotated.tm_mon != now.tm_mon ||
state->lastRotated.tm_mday != now.tm_mday) {
switch (log->criterium) {
case ROT_WEEKLY:
/* rotate if:
1) the current weekday is before the weekday of the
last rotation
2) more then a week has passed since the last
rotation */
state->doRotate = ((now.tm_wday < state->lastRotated.tm_wday) ||
((mktime(&now) - mktime(&state->lastRotated)) >
(7 * 24 * 3600)));
break;
case ROT_MONTHLY:
/* rotate if the logs haven't been rotated this month or
this year */
state->doRotate = ((now.tm_mon != state->lastRotated.tm_mon) ||
(now.tm_year != state->lastRotated.tm_year));
break;
case ROT_DAYS:
/* FIXME: only days=1 is implemented!! */
state->doRotate = 1;
break;
default:
/* ack! */
state->doRotate = 0;
break;
}
}
/* The notifempty flag overrides the normal criteria */
if (!(log->flags & LOG_FLAG_IFEMPTY) && !sb.st_size)
state->doRotate = 0;
if (state->doRotate) {
message(MESS_DEBUG, " log needs rotating\n");
} else {
message(MESS_DEBUG, " log does not need rotating\n");
}
return 0;
}
int rotateSingleLog(logInfo * log, int logNum, logState * state) {
struct tm now = *localtime(&nowSecs);
char * oldName, * newName = NULL;
char * disposeName;
char * finalName;
char * tmp;
char * compext = "";
char * fileext = "";
int hasErrors = 0;
int i;
int fd;
uid_t createUid;
gid_t createGid;
mode_t createMode;
char * baseName;
char * dirName;
char * firstRotated;
char * glob_pattern;
glob_t globResult;
int rc;
size_t alloc_size;
int rotateCount = log->rotateCount ? log->rotateCount : 1;
int logStart = (log->logStart == -1) ? 1 : log->logStart;
if (!state->doRotate) return 0;
/* Logs with rotateCounts of 0 are rotated once, then removed. This
lets scripts run properly, and everything gets mailed properly. */
message(MESS_DEBUG, "rotating log %s, log->rotateCount is %d\n", log->files[logNum], log->rotateCount);
if (log->flags & LOG_FLAG_COMPRESS) compext = log->compress_ext;
state->lastRotated = now;
if (log->oldDir) {
if (log->oldDir[0] != '/') {
char *ld = ourDirName(log->files[logNum]);
dirName = malloc(strlen(ld) + strlen(log->oldDir) + 2);
sprintf(dirName, "%s/%s", ld, log->oldDir);
free(ld);
} else
dirName = strdup(log->oldDir);
} else
dirName = ourDirName(log->files[logNum]);
baseName = strdup(ourBaseName(log->files[logNum]));
alloc_size = strlen(dirName) + strlen(baseName) +
strlen(log->files[logNum]) + strlen(fileext) +
strlen(compext) + 18;
oldName = alloca(alloc_size);
newName = alloca(alloc_size);
disposeName = alloca(alloc_size);
if (log->extension &&
strncmp(&baseName[strlen(baseName)-strlen(log->extension)],
log->extension, strlen(log->extension)) == 0) {
char *tempstr;
fileext = log->extension;
tempstr = calloc(strlen(baseName)-strlen(log->extension)+1, sizeof(char));
strncat(tempstr, baseName,
strlen(baseName)-strlen(log->extension));
free(baseName);
baseName = tempstr;
}
/* First compress the previous log when necessary */
if (log->flags & LOG_FLAG_COMPRESS &&
log->flags & LOG_FLAG_DELAYCOMPRESS) {
if (log->flags & LOG_FLAG_DATEEXT) {
/* glob for uncompressed files with our pattern */
glob_pattern = malloc(strlen(dirName) + strlen(baseName)
+ strlen(fileext) + 44 );
sprintf(glob_pattern,
"%s/%s-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]%s",
dirName, baseName, fileext);
rc = glob(glob_pattern, 0, globerr, &globResult);
if (!rc && globResult.gl_pathc > 0) {
for (i = 0; i < globResult.gl_pathc && !hasErrors; i++) {
struct stat sbprev;
sprintf(oldName,"%s",(globResult.gl_pathv)[i]);
if (stat(oldName, &sbprev)) {
message(MESS_DEBUG, "previous log %s does not exist\n",
oldName);
} else {
hasErrors = compressLogFile(oldName, log, &sbprev);
}
}
} else {
message (MESS_DEBUG, "glob finding logs to compress failed\n");
/* fallback to old behaviour */
sprintf(oldName, "%s/%s.%d%s", dirName, baseName, logStart, fileext);
}
globfree(&globResult);
free(glob_pattern);
} else {
struct stat sbprev;
sprintf(oldName, "%s/%s.%d%s", dirName, baseName, logStart, fileext);
if (stat(oldName, &sbprev)) {
message(MESS_DEBUG, "previous log %s does not exist\n",
oldName);
} else {
hasErrors = compressLogFile(oldName, log, &sbprev);
}
}
}
firstRotated = alloca(strlen(dirName) + strlen(baseName) +
strlen(fileext) + strlen(compext) + 30);
if(log->flags & LOG_FLAG_DATEEXT) {
/* glob for compressed files with our pattern
* and compress ext */
glob_pattern = malloc(strlen(dirName)+strlen(baseName)
+strlen(fileext)+strlen(compext)+44);
sprintf(glob_pattern,
"%s/%s-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]%s%s",
dirName, baseName, fileext, compext);
rc = glob(glob_pattern, 0, globerr, &globResult);
if (!rc) {
/* search for files to drop, if we find one remember it,
* if we find another one mail and remove the first and
* remember the second and so on */
struct stat fst_buf;
int mail_out = -1;
/* remove the first (n - rotateCount) matches
* no real rotation needed, since the files have
* the date in their name */
for (i = 0; i < globResult.gl_pathc; i++) {
if( !stat((globResult.gl_pathv)[i],&fst_buf) ) {
if ((i <= ((int)globResult.gl_pathc - rotateCount))
|| ((log->rotateAge > 0)
&& (((nowSecs - fst_buf.st_mtime)/60/60/24)
> log->rotateAge))) {
if ( mail_out != -1 ) {
if (!hasErrors && log->logAddress) {
char * mailFilename = (globResult.gl_pathv)[mail_out];
hasErrors = mailLogWrapper(mailFilename, mailCommand, logNum, log);
if (!hasErrors)
hasErrors = removeLogFile(mailFilename);
}
}
mail_out = i;
}
}
}
if ( mail_out != -1 ) {
/* oldName is oldest Backup found (for unlink later) */
sprintf(oldName, "%s", (globResult.gl_pathv)[mail_out]);
strcpy(disposeName, oldName);
} else
disposeName = NULL;
} else {
message (MESS_DEBUG, "glob finding old rotated logs failed\n");
disposeName = NULL;
}
/* firstRotated is most recently created/compressed rotated log */
sprintf(firstRotated, "%s/%s-%04d%02d%02d%s%s",
dirName, baseName, now.tm_year+1900,
now.tm_mon+1, now.tm_mday, fileext, compext);
globfree(&globResult);
free(glob_pattern);
} else {
if ( log->rotateAge ) {
struct stat fst_buf;
for (i=1; i <= rotateCount; i++) {
sprintf(oldName, "%s/%s.%d%s%s", dirName, baseName,
rotateCount + 1, fileext, compext);
if(!stat(oldName,&fst_buf)
&& (((nowSecs - fst_buf.st_mtime)/60/60/24)
> log->rotateAge)) {
char * mailFilename = (globResult.gl_pathv)[i];
if (!hasErrors && log->logAddress)
hasErrors = mailLogWrapper(mailFilename, mailCommand, logNum, log);
if (!hasErrors)
hasErrors = removeLogFile(mailFilename);
}
}
}
sprintf(oldName, "%s/%s.%d%s%s", dirName, baseName,
logStart + rotateCount, fileext, compext);
strcpy(newName, oldName);
strcpy(disposeName, oldName);
sprintf(firstRotated, "%s/%s.%d%s%s", dirName, baseName,
logStart, fileext,
(log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext);
#ifdef WITH_SELINUX
if ((selinux_enabled=(is_selinux_enabled()>0))) {
security_context_t oldContext=NULL;
if (getfilecon(log->files[logNum], &oldContext)>0) {
if (getfscreatecon(&prev_context) < 0) {
message(MESS_ERROR, "error getting default context: %s\n",
strerror(errno));
freecon(oldContext);
return 1;
}
if (setfscreatecon(oldContext) < 0) {
message(MESS_ERROR, "error setting file context %s to %s: %s\n",
log->files[logNum], oldContext,strerror(errno));
freecon(oldContext);
return 1;
}
freecon(oldContext);
} else {
message(MESS_ERROR, "error getting file context %s: %s\n",
log->files[logNum],
strerror(errno));
return 1;
}
}
#endif
for (i = rotateCount + logStart - 1; (i >= 0) && !hasErrors; i--) {
tmp = newName;
newName = oldName;
oldName = tmp;
sprintf(oldName, "%s/%s.%d%s%s", dirName, baseName, i,
fileext, compext);
message(MESS_DEBUG, "renaming %s to %s (rotatecount %d, logstart %d, i %d), \n", oldName, newName,
rotateCount, logStart, i);
if (!debug && rename(oldName, newName)) {
if (errno == ENOENT) {
message(MESS_DEBUG, "old log %s does not exist\n",
oldName);
} else {
message(MESS_ERROR, "error renaming %s to %s: %s\n",
oldName, newName, strerror(errno));
hasErrors = 1;
}
}
}
} /* !LOG_FLAG_DATEEXT */
finalName = oldName;
if(log->flags & LOG_FLAG_DATEEXT) {
char * destFile = alloca(strlen(dirName) + strlen(baseName) +
strlen(fileext) + strlen(compext) + 30);
struct stat fst_buf;
sprintf(finalName, "%s/%s-%04d%02d%02d%s",
dirName, baseName, now.tm_year+1900,
now.tm_mon+1, now.tm_mday, fileext);
sprintf(destFile, "%s%s", finalName, compext);
if(!stat(destFile,&fst_buf)) {
message (MESS_DEBUG, "destination %s already exists, skipping rotation\n", firstRotated);
hasErrors = 1;
}
} else {
/* note: the gzip extension is *not* used here! */
sprintf(finalName, "%s/%s.%d%s", dirName, baseName, logStart, fileext);
}
/* if the last rotation doesn't exist, that's okay */
if (!debug && access(disposeName, F_OK)) {
message(MESS_DEBUG, "log %s doesn't exist -- won't try to "
"dispose of it\n", disposeName);
disposeName = NULL;
}
if (!hasErrors) {
if (log->pre && !(log->flags & LOG_FLAG_SHAREDSCRIPTS)) {
message(MESS_DEBUG, "running prerotate script\n");
if (runScript(log->files[logNum], log->pre)) {
message(MESS_ERROR, "error running prerotate script, "
"leaving old log in place\n");
hasErrors = 1;
}
}
if (!(log->flags & (LOG_FLAG_COPYTRUNCATE|LOG_FLAG_COPY))) {
message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum],
finalName);
if (!debug && !hasErrors &&
rename(log->files[logNum], finalName)) {
message(MESS_ERROR, "failed to rename %s to %s: %s\n",
log->files[logNum], finalName, strerror(errno));
}
if (!log->rotateCount) {
disposeName = alloca(strlen(dirName) + strlen(baseName) +
strlen(log->files[logNum]) + 10);
sprintf(disposeName, "%s%s", finalName, (log->compress_ext && (log->flags & LOG_FLAG_COMPRESS))?log->compress_ext:"");
message(MESS_DEBUG, "disposeName will be %s\n", disposeName);
}
}
if (!hasErrors && log->flags & LOG_FLAG_CREATE &&
!(log->flags & (LOG_FLAG_COPYTRUNCATE|LOG_FLAG_COPY))) {
if (log->createUid == NO_UID)
createUid = state->sb.st_uid;
else
createUid = log->createUid;
if (log->createGid == NO_GID)
createGid = state->sb.st_gid;
else
createGid = log->createGid;
if (log->createMode == NO_MODE)
createMode = state->sb.st_mode & 0777;
else
createMode = log->createMode;
message(MESS_DEBUG, "creating new log mode = 0%o uid = %d "
"gid = %d\n", (unsigned int)createMode, (int)createUid, (int)createGid);
if (!debug) {
fd = open(log->files[logNum], O_CREAT | O_RDWR, createMode);
if (fd < 0) {
message(MESS_ERROR, "error creating %s: %s\n",
log->files[logNum], strerror(errno));
hasErrors = 1;
} else {
if (fchmod(fd, (S_IRUSR | S_IWUSR) & createMode)) {
message(MESS_ERROR, "error setting mode of "
"%s: %s\n", log->files[logNum],
strerror(errno));
hasErrors = 1;
}
if (fchown(fd, createUid, createGid)) {
message(MESS_ERROR, "error setting owner of "
"%s: %s\n", log->files[logNum],
strerror(errno));
hasErrors = 1;
}
if (fchmod(fd, createMode)) {
message(MESS_ERROR, "error setting mode of "
"%s: %s\n", log->files[logNum],
strerror(errno));
hasErrors = 1;
}
close(fd);
}
}
}
if (!hasErrors && log->flags & (LOG_FLAG_COPYTRUNCATE|LOG_FLAG_COPY))
hasErrors = copyTruncate(log->files[logNum], finalName,
&state->sb, log->flags);
if (!hasErrors && log->post &&
!(log->flags & LOG_FLAG_SHAREDSCRIPTS)) {
message(MESS_DEBUG, "running postrotate script\n");
if (runScript(log->files[logNum], log->post)) {
message(MESS_ERROR, "error running postrotate script\n");
hasErrors = 1;
}
}
if (!hasErrors &&
(log->flags & LOG_FLAG_COMPRESS) &&
!(log->flags & LOG_FLAG_DELAYCOMPRESS)) {
hasErrors = compressLogFile(finalName, log, &state->sb);
}
if (!hasErrors && log->logAddress) {
char * mailFilename;
if (log->flags & LOG_FLAG_MAILFIRST)
mailFilename = firstRotated;
else
mailFilename = disposeName;
if (mailFilename) {
/* if the log is compressed (and we're not mailing a
file whose compression has been delayed), we need
to uncompress it */
if ((log->flags & LOG_FLAG_COMPRESS) &&
!((log->flags & LOG_FLAG_DELAYCOMPRESS) &&
(log->flags & LOG_FLAG_MAILFIRST))) {
if (mailLog(mailFilename, mailCommand,
log->uncompress_prog, log->logAddress,
log->files[logNum]))
hasErrors = 1;
} else {
if (mailLog(mailFilename, mailCommand, NULL,
log->logAddress, mailFilename))
hasErrors = 1;
}
}
}
if (!hasErrors && disposeName) {
message(MESS_DEBUG, "removing old log %s\n", disposeName);
if (!debug && unlink(disposeName)) {
message(MESS_ERROR, "Failed to remove old log %s: %s\n",
disposeName, strerror(errno));
hasErrors = 1;
}
}
}
#ifdef WITH_SELINUX
if (selinux_enabled) {
setfscreatecon(prev_context);
if (prev_context!= NULL) {
freecon(prev_context);
prev_context=NULL;
}
}
#endif
free(dirName);
free(baseName);
return hasErrors;
}
int rotateLogSet(logInfo * log, struct stateSet * sip, int force) {
int i;
int hasErrors = 0;
int numRotated = 0;
logState * state;
if (force)
log->criterium = ROT_FORCE;
message(MESS_DEBUG, "\nrotating pattern: %s ", log->pattern);
switch (log->criterium) {
case ROT_DAYS:
message(MESS_DEBUG, "after %d days ", log->threshhold);
break;
case ROT_WEEKLY:
message(MESS_DEBUG, "weekly ");
break;
case ROT_MONTHLY:
message(MESS_DEBUG, "monthly ");
break;
case ROT_SIZE:
message(MESS_DEBUG, "%d bytes ", log->threshhold);
break;
case ROT_FORCE:
message(MESS_DEBUG, "forced from command line ");
break;
}
if (log->rotateCount)
message(MESS_DEBUG, "(%d rotations)\n", log->rotateCount);
else
message(MESS_DEBUG, "(no old logs will be kept)\n");
if (log->oldDir)
message(MESS_DEBUG, "olddir is %s, ", log->oldDir);
if (log->flags & LOG_FLAG_IFEMPTY)
message(MESS_DEBUG, "empty log files are rotated, ");
else
message(MESS_DEBUG, "empty log files are not rotated, ");
if (log->logAddress) {
message(MESS_DEBUG, "old logs mailed to %s\n", log->logAddress);
} else {
message(MESS_DEBUG, "old logs are removed\n");
}
for (i = 0; i < log->numFiles; i++) {
hasErrors |= findNeedRotating(log, i, sip);
/* sure is a lot of findStating going on .. */
if ((findState(log->files[i], sip))->doRotate)
numRotated++;
}
if (log->first) {
if (!numRotated) {
message(MESS_DEBUG, "not running first action script, "
"since no logs will be rotated\n");
} else {
message(MESS_DEBUG, "running first action script\n");
if (runScript(log->pattern, log->first)) {
message(MESS_ERROR, "error running first action script "
"for %s\n", log->pattern);
hasErrors = 1;
}
}
}
if (log->pre && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) {
if (!numRotated) {
message(MESS_DEBUG, "not running shared prerotate script, "
"since no logs will be rotated\n");
} else {
message(MESS_DEBUG, "running shared prerotate script\n");
if (runScript(log->pattern, log->pre)) {
message(MESS_ERROR, "error running shared prerotate script "
"for %s\n", log->pattern);
hasErrors = 1;
}
}
}
/* should there be an if(!hasErrors) here? */
for (i = 0; i < log->numFiles; i++) {
state = findState(log->files[i], sip);
hasErrors |= rotateSingleLog(log, i, state);
}
if (log->post && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) {
if (!numRotated) {
message(MESS_DEBUG, "not running shared postrotate script, "
"since no logs were rotated\n");
} else {
message(MESS_DEBUG, "running shared postrotate script\n");
if (runScript(log->pattern, log->post)) {
message(MESS_ERROR, "error running shared postrotate script "
"for %s\n", log->pattern);
hasErrors = 1;
}
}
}
if (log->last) {
if (!numRotated) {
message(MESS_DEBUG, "not running last action script, "
"since no logs will be rotated\n");
} else {
message(MESS_DEBUG, "running last action script\n");
if (runScript(log->pattern, log->last)) {
message(MESS_ERROR, "error running last action script "
"for %s\n", log->pattern);
hasErrors = 1;
}
}
}
return hasErrors;
}
static int writeState(char * stateFilename, struct stateSet si) {
FILE * f;
char * chptr;
int i;
f = fopen(stateFilename, "w");
if (!f) {
message(MESS_ERROR, "error creating state file %s: %s\n",
stateFilename, strerror(errno));
return 1;
}
fprintf(f, "logrotate state -- version 2\n");
for (i = 0; i < si.numStates; i++) {
fputc('"', f);
for (chptr = si.states[i].fn; *chptr; chptr++) {
switch (*chptr) {
case '"':
fputc('\\', f);
}
fputc(*chptr, f);
}
fputc('"', f);
fprintf(f, " %d-%d-%d\n",
si.states[i].lastRotated.tm_year + 1900,
si.states[i].lastRotated.tm_mon + 1,
si.states[i].lastRotated.tm_mday);
}
fclose(f);
return 0;
}
static int readState(char * stateFilename, struct stateSet * sip) {
FILE * f;
char buf[1024];
const char ** argv;
int argc;
int year, month, day;
int i;
int line = 0;
int error;
logState * st;
time_t lr_time;
struct stat f_stat;
error = stat(stateFilename, &f_stat);
if ((error && errno == ENOENT) ||
(!error && f_stat.st_size == 0)) {
/* create the file before continuing to ensure we have write
access to the file */
f = fopen(stateFilename, "w");
if (!f) {
message(MESS_ERROR, "error creating state file %s: %s\n",
stateFilename, strerror(errno));
return 1;
}
fprintf(f, "logrotate state -- version 2\n");
fclose(f);
return 0;
} else if (error) {
message(MESS_ERROR, "error stat()ing state file %s: %s\n",
stateFilename, strerror(errno));
return 1;
}
f = fopen(stateFilename, "r");
if (!f) {
message(MESS_ERROR, "error opening state file %s: %s\n",
stateFilename, strerror(errno));
return 1;
}
if (!fgets(buf, sizeof(buf) - 1, f)) {
message(MESS_ERROR, "error reading top line of %s\n", stateFilename);
fclose(f);
return 1;
}
if (strcmp(buf, "logrotate state -- version 1\n") &&
strcmp(buf, "logrotate state -- version 2\n")) {
fclose(f);
message(MESS_ERROR, "bad top line in state file %s\n", stateFilename);
return 1;
}
line++;
while (fgets(buf, sizeof(buf) - 1, f)) {
line++;
i = strlen(buf);
if (buf[i - 1] != '\n') {
message(MESS_ERROR, "line too long in state file %s\n",
stateFilename);
fclose(f);
return 1;
}
buf[i - 1] = '\0';
if (i == 1) continue;
if (poptParseArgvString(buf, &argc, &argv) || (argc != 2) ||
(sscanf(argv[1], "%d-%d-%d", &year, &month, &day) != 3)) {
message(MESS_ERROR, "bad line %d in state file %s\n",
line, stateFilename);
fclose(f);
return 1;
}
/* Hack to hide earlier bug */
if ((year != 1900) && (year < 1996 || year > 2100)) {
message(MESS_ERROR, "bad year %d for file %s in state file %s\n",
year, argv[0], stateFilename);
fclose(f);
return 1;
}
if (month < 1 || month > 12) {
message(MESS_ERROR, "bad month %d for file %s in state file %s\n",
month, argv[0], stateFilename);
fclose(f);
return 1;
}
/* 0 to hide earlier bug */
if (day < 0 || day > 31) {
message(MESS_ERROR, "bad day %d for file %s in state file %s\n",
day, argv[0], stateFilename);
fclose(f);
return 1;
}
year -= 1900, month -= 1;
st = findState(argv[0], sip);
st->lastRotated.tm_mon = month;
st->lastRotated.tm_mday = day;
st->lastRotated.tm_year = year;
/* fill in the rest of the st->lastRotated fields */
lr_time = mktime(&st->lastRotated);
st->lastRotated = *localtime(&lr_time);
free(argv);
}
fclose(f);
return 0;
}
int main(int argc, const char ** argv) {
logInfo defConfig = { NULL, NULL, 0, NULL, ROT_SIZE,
/* threshHold */ 1024 * 1024,
/* rotateCount */ 0,
/* rotateAge */ 0,
/* log start */ -1,
/* pre, post */ NULL, NULL,
/* first, last */ NULL, NULL,
/* logAddress */ NULL,
/* extension */ NULL,
/* compression */ COMPRESS_COMMAND,
UNCOMPRESS_COMMAND, COMPRESS_EXT,
/* rotate pattern */ NULL,
/* flags */ LOG_FLAG_IFEMPTY,
/* createMode */ NO_MODE, NO_UID, NO_GID };
int numLogs = 0;
int force = 0;
logInfo * logs = NULL;
struct stateSet si = { NULL, 0 };
char * stateFile = STATEFILE;
int i;
int rc = 0;
int arg;
const char ** files, ** file;
poptContext optCon;
struct poptOption options[] = {
{ "debug", 'd', 0, 0, 'd',
"Don't do anything, just test (implies -v)" },
{ "force", 'f', 0 , &force, 0, "Force file rotation" },
{ "mail", 'm', POPT_ARG_STRING, &mailCommand, 0,
"Command to send mail (instead of `" DEFAULT_MAIL_COMMAND "')",
"command" },
{ "state", 's', POPT_ARG_STRING, &stateFile, 0, "Path of state file",
"statefile" },
{ "verbose", 'v', 0, 0, 'v', "Display messages during rotation" },
POPT_AUTOHELP
{ 0, 0, 0, 0, 0 }
};
logSetLevel(MESS_NORMAL);
optCon = poptGetContext("logrotate", argc, argv, options,0);
poptReadDefaultConfig(optCon, 1);
poptSetOtherOptionHelp(optCon, "[OPTION...] <configfile>");
while ((arg = poptGetNextOpt(optCon)) >= 0) {
switch (arg) {
case 'd':
debug = 1;
/* fallthrough */
case 'v':
logSetLevel(MESS_DEBUG);
break;
}
}
if (arg < -1) {
fprintf(stderr, "logrotate: bad argument %s: %s\n",
poptBadOption(optCon, POPT_BADOPTION_NOALIAS),
poptStrerror(rc));
return 2;
}
files = poptGetArgs((poptContext) optCon);
if (!files) {
fprintf(stderr, "logrotate " VERSION
" - Copyright (C) 1995-2001 Red Hat, Inc.\n");
fprintf(stderr, "This may be freely redistributed under the terms of "
"the GNU Public License\n\n");
poptPrintUsage(optCon, stderr, 0);
exit(1);
}
for (file = files; *file; file++) {
if (readConfigPath(*file, &defConfig, &logs, &numLogs)) {
exit(1);
}
}
nowSecs = time(NULL);
if (readState(stateFile, &si)) {
exit(1);
}
message(MESS_DEBUG, "\nHandling %d logs\n", numLogs);
for (i = 0; i < numLogs; i++) {
rc |= rotateLogSet(logs + i, &si, force);
}
if (!debug && writeState(stateFile, si)) {
exit(1);
}
return (rc != 0);
}
syntax highlighted by Code2HTML, v. 0.9.1