/* ====================================================================
* Copyright (c) 1995-1999 The Apache Group. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. All advertising materials mentioning features or use of this
* software must display the following acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* 4. The names "Apache Server" and "Apache Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache"
* nor may "Apache" appear in their names without prior written
* permission of the Apache Group.
*
* 6. Redistributions of any form whatsoever must retain the following
* acknowledgment:
* "This product includes software developed by the Apache Group
* for use in the Apache HTTP server project (http://www.apache.org/)."
*
* THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Group and was originally based
* on public domain software written at the National Center for
* Supercomputing Applications, University of Illinois, Urbana-Champaign.
* For more information on the Apache Group and the Apache HTTP server
* project, please see .
*
*/
/*
* cronolog -- simple Apache log rotation program
*
* Copyright (c) 1996-1999 by Ford & Mason Ltd
*
* This software was submitted by Ford & Mason Ltd to the Apache
* Software Foundation in December 1999. Future revisions and
* derivatives of this source code must acknowledge Ford & Mason Ltd
* as the original contributor of this module. All other licensing
* and usage conditions are those of the Apache Software Foundation.
*
* Originally written by Andrew Ford
*
* cronolog is loosly based on the rotatelogs program, which is part of the
* Apache package written by Ben Laurie
*
* The argument to this program is the log file name template as an
* strftime format string. For example to generate new error and
* access logs each day stored in subdirectories by year, month and day add
* the following lines to the httpd.conf:
*
* TransferLog "|/www/etc/cronolog /www/logs/%Y/%m/%d/access.log"
* ErrorLog "|/www/etc/cronolog /www/logs/%Y/%m/%d/error.log"
*
* The option "-x file" specifies that debugging messages should be
* written to "file" (e.g. /dev/console) or to stderr if "file" is "-".
*/
#include "cronoutils.h"
#include "getopt.h"
/* Forward function declaration */
int new_log_file(const char *, const char *, mode_t, const char *,
PERIODICITY, int, int, char *, size_t, time_t, time_t *);
/* Definition of version and usage messages */
#ifndef _WIN32
#define VERSION_MSG PACKAGE " version " VERSION "\n"
#else
#define VERSION_MSG "cronolog version 0.1\n"
#endif
#define USAGE_MSG "usage: %s [OPTIONS] logfile-spec\n" \
"\n" \
" -H NAME, --hardlink=NAME maintain a hard link from NAME to current log\n" \
" -S NAME, --symlink=NAME maintain a symbolic link from NAME to current log\n" \
" -P NAME, --prev-symlink=NAME maintain a symbolic link from NAME to previous log\n" \
" -l NAME, --link=NAME same as -S/--symlink\n" \
" -h, --help print this help, then exit\n" \
" -p PERIOD, --period=PERIOD set the rotation period explicitly\n" \
" -d DELAY, --delay=DELAY set the rotation period delay\n" \
" -o, --once-only create single output log from template (not rotated)\n" \
" -x FILE, --debug=FILE write debug messages to FILE\n" \
" ( or to standard error if FILE is \"-\")\n" \
" -a, --american American date formats\n" \
" -e, --european European date formats (default)\n" \
" -s, --start-time=TIME starting time\n" \
" -z TZ, --time-zone=TZ use TZ for timezone\n" \
" -V, --version print version number, then exit\n"
/* Definition of the short and long program options */
char *short_options = "ad:eop:s:z:H:P:S:l:hVx:";
#ifndef _WIN32
struct option long_options[] =
{
{ "american", no_argument, NULL, 'a' },
{ "european", no_argument, NULL, 'e' },
{ "start-time", required_argument, NULL, 's' },
{ "time-zone", required_argument, NULL, 'z' },
{ "hardlink", required_argument, NULL, 'H' },
{ "symlink", required_argument, NULL, 'S' },
{ "prev-symlink", required_argument, NULL, 'P' },
{ "link", required_argument, NULL, 'l' },
{ "period", required_argument, NULL, 'p' },
{ "delay", required_argument, NULL, 'd' },
{ "once-only", no_argument, NULL, 'o' },
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, 'V' }
};
#endif
/* Main function.
*/
int
main(int argc, char **argv)
{
PERIODICITY periodicity = UNKNOWN;
PERIODICITY period_delay_units = UNKNOWN;
int period_multiple = 1;
int period_delay = 0;
int use_american_date_formats = 0;
char read_buf[BUFSIZE];
char tzbuf[BUFSIZE];
char filename[MAX_PATH];
char *start_time = NULL;
char *template;
char *linkname = NULL;
char *prevlinkname = NULL;
mode_t linktype = 0;
int n_bytes_read;
int ch;
time_t time_now;
time_t time_offset = 0;
time_t next_period = 0;
int log_fd = -1;
#ifndef _WIN32
while ((ch = getopt_long(argc, argv, short_options, long_options, NULL)) != EOF)
#else
while ((ch = getopt(argc, argv, short_options)) != EOF)
#endif
{
switch (ch)
{
case 'a':
use_american_date_formats = 1;
break;
case 'e':
use_american_date_formats = 0;
break;
case 's':
start_time = optarg;
break;
case 'z':
sprintf(tzbuf, "TZ=%s", optarg);
putenv(tzbuf);
break;
case 'H':
linkname = optarg;
linktype = S_IFREG;
break;
case 'l':
case 'S':
linkname = optarg;
#ifndef _WIN32
linktype = S_IFLNK;
#endif
break;
case 'P':
if (linkname == NULL)
{
fprintf(stderr, "A current log symlink is needed to mantain a symlink to the previous log\n", argv[0]);
exit(1);
}
prevlinkname = optarg;
break;
case 'd':
period_delay_units = parse_timespec(optarg, &period_delay);
break;
case 'p':
periodicity = parse_timespec(optarg, &period_multiple);
if ( (periodicity == INVALID_PERIOD)
|| (periodicity == PER_SECOND) && (60 % period_multiple)
|| (periodicity == PER_MINUTE) && (60 % period_multiple)
|| (periodicity == HOURLY) && (24 % period_multiple)
|| (periodicity == DAILY) && (period_multiple > 365)
|| (periodicity == WEEKLY) && (period_multiple > 52)
|| (periodicity == MONTHLY) && (12 % period_multiple)) {
fprintf(stderr, "%s: invalid explicit period specification (%s)\n", argv[0], start_time);
exit(1);
}
break;
case 'o':
periodicity = ONCE_ONLY;
break;
case 'x':
if (strcmp(optarg, "-") == 0)
{
debug_file = stderr;
}
else
{
debug_file = fopen(optarg, "a+");
}
break;
case 'V':
fprintf(stderr, VERSION_MSG);
exit(0);
case 'h':
case '?':
fprintf(stderr, USAGE_MSG, argv[0]);
exit(1);
}
}
if ((argc - optind) != 1)
{
fprintf(stderr, USAGE_MSG, argv[0]);
exit(1);
}
DEBUG((VERSION_MSG "\n"));
if (start_time)
{
time_now = parse_time(start_time, use_american_date_formats);
if (time_now == -1)
{
fprintf(stderr, "%s: invalid start time (%s)\n", argv[0], start_time);
exit(1);
}
time_offset = time_now - time(NULL);
DEBUG(("Using offset of %d seconds from real time\n", time_offset));
}
/* The template should be the only argument.
* Unless the -o option was specified, determine the periodicity.
*/
template = argv[optind];
if (periodicity == UNKNOWN)
{
periodicity = determine_periodicity(template);
}
DEBUG(("periodicity = %d %s\n", period_multiple, periods[periodicity]));
if (period_delay) {
if ( (period_delay_units > periodicity)
|| ( period_delay_units == periodicity
&& abs(period_delay) >= period_multiple)) {
fprintf(stderr, "%s: period delay cannot be larger than the rollover period\n", argv[0], start_time);
exit(1);
}
period_delay *= period_seconds[period_delay_units];
}
DEBUG(("Rotation period is per %d %s\n", period_multiple, periods[periodicity]));
/* Loop, waiting for data on standard input */
for (;;)
{
/* Read a buffer's worth of log file data, exiting on errors
* or end of file.
*/
n_bytes_read = read(0, read_buf, sizeof read_buf);
if (n_bytes_read == 0)
{
exit(3);
}
if (errno == EINTR)
{
continue;
}
else if (n_bytes_read < 0)
{
exit(4);
}
time_now = time(NULL) + time_offset;
/* If the current period has finished and there is a log file
* open, close the log file
*/
if ((time_now >= next_period) && (log_fd >= 0))
{
close(log_fd);
log_fd = -1;
}
/* If there is no log file open then open a new one.
*/
if (log_fd < 0)
{
log_fd = new_log_file(template, linkname, linktype, prevlinkname,
periodicity, period_multiple, period_delay,
filename, sizeof (filename), time_now, &next_period);
}
DEBUG(("%s (%d): wrote message; next period starts at %s (%d) in %d secs\n",
timestamp(time_now), time_now,
timestamp(next_period), next_period,
next_period - time_now));
/* Write out the log data to the current log file.
*/
if (write(log_fd, read_buf, n_bytes_read) != n_bytes_read)
{
perror(filename);
exit(5);
}
}
/* NOTREACHED */
return 1;
}
/* Open a new log file: determine the start of the current
* period, generate the log file name from the template,
* determine the end of the period and open the new log file.
*
* Returns the file descriptor of the new log file and also sets the
* name of the file and the start time of the next period via pointers
* supplied.
*/
int
new_log_file(const char *template, const char *linkname, mode_t linktype, const char *prevlinkname,
PERIODICITY periodicity, int period_multiple, int period_delay,
char *pfilename, size_t pfilename_len,
time_t time_now, time_t *pnext_period)
{
time_t start_of_period;
struct tm *tm;
int log_fd;
start_of_period = start_of_this_period(time_now, periodicity, period_multiple);
tm = localtime(&start_of_period);
strftime(pfilename, BUFSIZE, template, tm);
*pnext_period = start_of_next_period(start_of_period, periodicity, period_multiple) + period_delay;
DEBUG(("%s (%d): using log file \"%s\" from %s (%d) until %s (%d) (for %d secs)\n",
timestamp(time_now), time_now, pfilename,
timestamp(start_of_period), start_of_period,
timestamp(*pnext_period), *pnext_period,
*pnext_period - time_now));
log_fd = open(pfilename, O_WRONLY|O_CREAT|O_APPEND, FILE_MODE);
#ifndef DONT_CREATE_SUBDIRS
if ((log_fd < 0) && (errno == ENOENT))
{
create_subdirs(pfilename);
log_fd = open(pfilename, O_WRONLY|O_CREAT|O_APPEND, FILE_MODE);
}
#endif
if (log_fd < 0)
{
perror(pfilename);
exit(2);
}
if (linkname)
{
create_link(pfilename, linkname, linktype, prevlinkname);
}
return log_fd;
}