#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