logrotate-3.8.7/0000775000076500007650000000000012225464314012616 5ustar hanzzhanzzlogrotate-3.8.7/log.h0000664000076500007650000000103612006205557013547 0ustar hanzzhanzz#ifndef H_LOG #define H_LOG #include #define MESS_REALDEBUG 1 #define MESS_DEBUG 2 #define MESS_VERBOSE 3 #define MESS_NORMAL 4 #define MESS_ERROR 5 #define MESS_FATAL 6 #define LOG_TIMES (1 << 0) void message(int level, char *format, ...) #ifdef __GNUC__ __attribute__ ((format(printf, 2, 3))); #else ; #endif #if 0 void log(int fd, char *format, ...); #endif void logSetErrorFile(FILE * f); void logSetMessageFile(FILE * f); void logSetFlags(int flags); void logClearFlags(int flags); void logSetLevel(int level); #endif logrotate-3.8.7/logrotate.80000664000076500007650000005064212155334214014713 0ustar hanzzhanzz.TH LOGROTATE 8 "Wed Nov 5 2002" "Linux" "System Administrator's Manual" .SH NAME logrotate \(hy rotates, compresses, and mails system logs .SH SYNOPSIS \fBlogrotate\fR [\fB\-dv\fR] [\fB\-f\fR|\fB\-\-force\fR] [\fB\-s\fR|\fB\-\-state \fIfile\fR] \fIconfig_file\fR .. .SH DESCRIPTION \fBlogrotate\fR is designed to ease administration of systems that generate large numbers of log files. It allows automatic rotation, compression, removal, and mailing of log files. Each log file may be handled daily, weekly, monthly, or when it grows too large. .P Normally, \fBlogrotate\fR is run as a daily cron job. It will not modify a log multiple times in one day unless the criterion for that log is based on the log's size and \fBlogrotate\fR is being run multiple times each day, or unless the \fB\-f\fR or \fB\-\-force\fR option is used. .P Any number of config files may be given on the command line. Later config files may override the options given in earlier files, so the order in which the \fBlogrotate\fR config files are listed is important. Normally, a single config file which includes any other config files which are needed should be used. See below for more information on how to use the \fBinclude\fR directive to accomplish this. If a directory is given on the command line, every file in that directory is used as a config file. .P If no command line arguments are given, \fBlogrotate\fR will print version and copyright information, along with a short usage summary. If any errors occur while rotating logs, \fBlogrotate\fR will exit with non-zero status. .SH OPTIONS .TP \fB\-?\fR, \fB\-\-help\fR Prints help message. .TP \fB\-d\fR, \fB\-\-debug\fR Turns on debug mode and implies \fB-v\fR. In debug mode, no changes will be made to the logs or to the \fBlogrotate\fR state file. .TP \fB\-f\fR, \fB\-\-force\fR Tells \fBlogrotate\fR to force the rotation, even if it doesn't think this is necessary. Sometimes this is useful after adding new entries to a \fBlogrotate\fR config file, or if old log files have been removed by hand, as the new files will be created, and logging will continue correctly. .TP \fB\-m\fR, \fB\-\-mail \fR Tells \fBlogrotate\fR which command to use when mailing logs. This command should accept two arguments: 1) the subject of the message, and 2) the recipient. The command must then read a message on standard input and mail it to the recipient. The default mail command is \fB/bin/mail -s\fR. .TP \fB\-s\fR, \fB\-\-state \fR Tells \fBlogrotate\fR to use an alternate state file. This is useful if logrotate is being run as a different user for various sets of log files. The default state file is \fI/var/lib/logrotate.status\fR. .TP \fB\-\-usage\fR Prints a short usage message. .TP +\fB\-v\fR, \fB\-\-verbose\fR Turns on verbose mode. .SH CONFIGURATION FILE \fBlogrotate\fR reads everything about the log files it should be handling from the series of configuration files specified on the command line. Each configuration file can set global options (local definitions override global ones, and later definitions override earlier ones) and specify logfiles to rotate. A simple configuration file looks like this: .nf .ta +8n # sample logrotate configuration file compress /var/log/messages { rotate 5 weekly postrotate /usr/bin/killall \-HUP syslogd endscript } "/var/log/httpd/access.log" /var/log/httpd/error.log { rotate 5 mail www@my.org size 100k sharedscripts postrotate /usr/bin/killall \-HUP httpd endscript } /var/log/news/* { monthly rotate 2 olddir /var/log/news/old missingok postrotate kill \-HUP `cat /var/run/inn.pid` endscript nocompress } ~/log/*.log {} .fi .PP The first few lines set global options; in the example, logs are compressed after they are rotated. Note that comments may appear anywhere in the config file as long as the first non-whitespace character on the line is a #. The next section of the config files defined how to handle the log file \fI/var/log/messages\fR. The log will go through five weekly rotations before being removed. After the log file has been rotated (but before the old version of the log has been compressed), the command \fI/sbin/killall \-HUP syslogd\fR will be executed. The next section defines the parameters for both \fI/var/log/httpd/access.log\fR and \fI/var/log/httpd/error.log\fR. They are rotated whenever it grows over 100k in size, and the old logs files are mailed (uncompressed) to www@my.org after going through 5 rotations, rather than being removed. The \fBsharedscripts\fR means that the \fBpostrotate\fR script will only be run once (after the old logs have been compressed), not once for each log which is rotated. Note that the double quotes around the first filename at the beginning of this section allows logrotate to rotate logs with spaces in the name. Normal shell quoting rules apply, with ', ", and \\ characters supported. The next section defines the parameters for all of the files in \fI/var/log/news\fR. Each file is rotated on a monthly basis. This is considered a single rotation directive and if errors occur for more than one file, the log files are not compressed. The last section uses tilde expansion to rotate log files in the home directory of the current user. This is only available, if your glob library supports tilde expansion. GNU glob does support this. Please use wildcards with caution. If you specify *, \fBlogrotate\fR will rotate all files, including previously rotated ones. A way around this is to use the \fBolddir\fR directive or a more exact wildcard (such as *.log). Here is more information on the directives which may be included in a \fBlogrotate\fR configuration file: .TP \fBcompress\fR Old versions of log files are compressed with \fBgzip\fR(1) by default. See also \fBnocompress\fR. .TP \fBcompresscmd\fR Specifies which command to use to compress log files. The default is \fBgzip\fR. See also \fBcompress\fR. .TP \fBuncompresscmd\fR Specifies which command to use to uncompress log files. The default is \fBgunzip\fR. .TP \fBcompressext\fR Specifies which extension to use on compressed logfiles, if compression is enabled. The default follows that of the configured compression command. .TP \fBcompressoptions\fR Command line options may be passed to the compression program, if one is in use. The default, for \fBgzip\fR(1), is "\-6" (biased towards high compression at the expense of speed). If you use a different compression command, you may need to change the \fBcompressoptions\fR to match. .TP \fBcopy\fR Make a copy of the log file, but don't change the original at all. This option can be used, for instance, to make a snapshot of the current log file, or when some other utility needs to truncate or parse the file. When this option is used, the \fBcreate\fR option will have no effect, as the old log file stays in place. .TP \fBcopytruncate\fR Truncate the original log file in place after creating a copy, instead of moving the old log file and optionally creating a new one. It can be used when some program cannot be told to close its logfile and thus might continue writing (appending) to the previous log file forever. Note that there is a very small time slice between copying the file and truncating it, so some logging data might be lost. When this option is used, the \fBcreate\fR option will have no effect, as the old log file stays in place. .TP \fBcreate \fImode\fR \fIowner\fR \fIgroup\fR, \fBcreate \fIowner\fR \fIgroup\fR Immediately after rotation (before the \fBpostrotate\fR script is run) the log file is created (with the same name as the log file just rotated). \fImode\fR specifies the mode for the log file in octal (the same as \fBchmod\fR(2)), \fIowner\fR specifies the user name who will own the log file, and \fIgroup\fR specifies the group the log file will belong to. Any of the log file attributes may be omitted, in which case those attributes for the new file will use the same values as the original log file for the omitted attributes. This option can be disabled using the \fBnocreate\fR option. .TP \fBdaily\fR Log files are rotated every day. .TP \fBdateext\fR Archive old versions of log files adding a date extension like YYYYMMDD instead of simply adding a number. The extension may be configured using the \fBdateformat\fR and \fBdateyesterday\fR options. .TP \fBdateformat\fR \fIformat_string\fR Specify the extension for \fBdateext\fR using the notation similar to \fBstrftime\fR(3) function. Only %Y %m %d and %s specifiers are allowed. The default value is \-%Y%m%d. Note that also the character separating log name from the extension is part of the dateformat string. The system clock must be set past Sep 9th 2001 for %s to work correctly. Note that the datestamps generated by this format must be lexically sortable (i.e., first the year, then the month then the day. e.g., 2001/12/01 is ok, but 01/12/2001 is not, since 01/11/2002 would sort lower while it is later). This is because when using the \fBrotate\fR option, logrotate sorts all rotated filenames to find out which logfiles are older and should be removed. .TP \fBdateyesterday\fR Use yesterday's instead of today's date to create the \fBdateext\fR extension, so that the rotated log file has a date in its name that is the same as the timestamps within it. .TP \fBdelaycompress\fR Postpone compression of the previous log file to the next rotation cycle. This only has effect when used in combination with \fBcompress\fR. It can be used when some program cannot be told to close its logfile and thus might continue writing to the previous log file for some time. .TP \fBextension \fIext\fR Log files with \fIext\fR extension can keep it after the rotation. If compression is used, the compression extension (normally \fB.gz\fR) appears after \fIext\fR. For example you have a logfile named mylog.foo and want to rotate it to mylog.1.foo.gz instead of mylog.foo.1.gz. .TP \fBhourly\fR Log files are rotated every hour. Note that usually \fIlogrotate\fR is configured to be run by cron daily. You have to change this configuration and run \fIlogrotate\fR hourly to be able to really rotate logs hourly. .TP \fBifempty\fR Rotate the log file even if it is empty, overriding the \fBnotifempty\fR option (\fBifempty\fR is the default). .TP \fBinclude \fIfile_or_directory\fR Reads the file given as an argument as if it was included inline where the \fBinclude\fR directive appears. If a directory is given, most of the files in that directory are read in alphabetic order before processing of the including file continues. The only files which are ignored are files which are not regular files (such as directories and named pipes) and files whose names end with one of the taboo extensions, as specified by the \fBtabooext\fR directive. .TP \fBmail \fIaddress\fR When a log is rotated out-of-existence, it is mailed to \fIaddress\fR. If no mail should be generated by a particular log, the \fBnomail\fR directive may be used. .TP \fBmailfirst\fR When using the \fBmail\fR command, mail the just-rotated file, instead of the about-to-expire file. .TP \fBmaillast\fR When using the \fBmail\fR command, mail the about-to-expire file, instead of the just-rotated file (this is the default). .TP \fBmaxage\fR \fIcount\fR Remove rotated logs older than days. The age is only checked if the logfile is to be rotated. The files are mailed to the configured address if \fBmaillast\fR and \fBmail\fR are configured. .TP \fBmaxsize\fR \fIsize\fR Log files are rotated when they grow bigger than \fIsize\fR bytes even before the additionally specified time interval (\fBdaily\fR, \fBweekly\fR, \fBmonthly\fR, or \fByearly\fR). The related \fBsize\fR option is similar except that it is mutually exclusive with the time interval options, and it causes log files to be rotated without regard for the last rotation time. When \fBmaxsize\fR is used, both the size and timestamp of a log file are considered. .TP \fBminsize\fR \fIsize\fR Log files are rotated when they grow bigger than \fIsize\fR bytes, but not before the additionally specified time interval (\fBdaily\fR, \fBweekly\fR, \fBmonthly\fR, or \fByearly\fR). The related \fBsize\fR option is similar except that it is mutually exclusive with the time interval options, and it causes log files to be rotated without regard for the last rotation time. When \fBminsize\fR is used, both the size and timestamp of a log file are considered. .TP \fBmissingok\fR If the log file is missing, go on to the next one without issuing an error message. See also \fBnomissingok\fR. .TP \fBmonthly\fR Log files are rotated the first time \fBlogrotate\fR is run in a month (this is normally on the first day of the month). .TP \fBnocompress\fR Old versions of log files are not compressed. See also \fBcompress\fR. .TP \fBnocopy\fR Do not copy the original log file and leave it in place. (this overrides the \fBcopy\fR option). .TP \fBnocopytruncate\fR Do not truncate the original log file in place after creating a copy (this overrides the \fBcopytruncate\fR option). .TP \fBnocreate\fR New log files are not created (this overrides the \fBcreate\fR option). .TP \fBnodelaycompress\fR Do not postpone compression of the previous log file to the next rotation cycle (this overrides the \fBdelaycompress\fR option). .TP \fBnodateext\fR Do not archive old versions of log files with date extension (this overrides the \fBdateext\fR option). .TP \fBnomail\fR Don't mail old log files to any address. .TP \fBnomissingok\fR If a log file does not exist, issue an error. This is the default. .TP \fBnoolddir\fR Logs are rotated in the same directory the log normally resides in (this overrides the \fBolddir\fR option). .TP \fBnosharedscripts\fR Run \fBprerotate\fR and \fBpostrotate\fR scripts for every log file which is rotated (this is the default, and overrides the \fBsharedscripts\fR option). The absolute path to the log file is passed as first argument to the script. If the scripts exit with error, the remaining actions will not be executed for the affected log only. .TP \fBnoshred\fR Do not use \fBshred\fR when deleting old log files. See also \fBshred\fR. .TP \fBnotifempty\fR Do not rotate the log if it is empty (this overrides the \fBifempty\fR option). .TP \fBolddir \fIdirectory\fR Logs are moved into \fIdirectory\fR for rotation. The \fIdirectory\fR must be on the same physical device as the log file being rotated, and is assumed to be relative to the directory holding the log file unless an absolute path name is specified. When this option is used all old versions of the log end up in \fIdirectory\fR. This option may be overridden by the \fBnoolddir\fR option. .TP \fBpostrotate\fR/\fBendscript\fR The lines between \fBpostrotate\fR and \fBendscript\fR (both of which must appear on lines by themselves) are executed (using \fB/bin/sh\fR) after the log file is rotated. These directives may only appear inside a log file definition. Normally, the absolute path to the log file is passed as first argument to the script. If \fBsharedscripts\fR is specified, whole pattern is passed to the script. See also \fBprerotate\fR. See \fBsharedscripts\fR and \fBnosharedscripts\fR for error handling. .TP \fBprerotate\fR/\fBendscript\fR The lines between \fBprerotate\fR and \fBendscript\fR (both of which must appear on lines by themselves) are executed (using \fB/bin/sh\fR) before the log file is rotated and only if the log will actually be rotated. These directives may only appear inside a log file definition. Normally, the absolute path to the log file is passed as first argument to the script. If \fBsharedscripts\fR is specified, whole pattern is passed to the script. See also \fBpostrotate\fR. See \fBsharedscripts\fR and \fBnosharedscripts\fR for error handling. .TP \fBfirstaction\fR/\fBendscript\fR The lines between \fBfirstaction\fR and \fBendscript\fR (both of which must appear on lines by themselves) are executed (using \fB/bin/sh\fR) once before all log files that match the wildcarded pattern are rotated, before prerotate script is run and only if at least one log will actually be rotated. These directives may only appear inside a log file definition. Whole pattern is passed to the script as first argument. If the script exits with error, no further processing is done. See also \fBlastaction\fR. .TP \fBlastaction\fR/\fBendscript\fR The lines between \fBlastaction\fR and \fBendscript\fR (both of which must appear on lines by themselves) are executed (using \fB/bin/sh\fR) once after all log files that match the wildcarded pattern are rotated, after postrotate script is run and only if at least one log is rotated. These directives may only appear inside a log file definition. Whole pattern is passed to the script as first argument. If the script exits with error, just an error message is shown (as this is the last action). See also \fBfirstaction\fR. .TP \fBpreremove\fR/\fBendscript\fR The lines between \fBpreremove\fR and \fBendscript\fR (both of which must appear on lines by themselves) are executed (using \fB/bin/sh\fR) once just before removal of a log file. The logrotate will pass the name of file which is soon to be removed. See also \fBfirstaction\fR. .TP \fBrotate \fIcount\fR Log files are rotated \fIcount\fR times before being removed or mailed to the address specified in a \fBmail\fR directive. If \fIcount\fR is 0, old versions are removed rather than rotated. .TP \fBsize \fIsize\fR Log files are rotated only if they grow bigger then \fIsize\fR bytes. If \fIsize\fR is followed by \fIk\fR, the size is assumed to be in kilobytes. If the \fIM\fR is used, the size is in megabytes, and if \fIG\fR is used, the size is in gigabytes. So \fIsize 100\fR, \fIsize 100k\fR, \fIsize 100M\fR and \fIsize 100G\fR are all valid. .TP \fBsharedscripts\fR Normally, \fBprerotate\fR and \fBpostrotate\fR scripts are run for each log which is rotated and the absolute path to the log file is passed as first argument to the script. That means a single script may be run multiple times for log file entries which match multiple files (such as the \fI/var/log/news/*\fR example). If \fBsharedscripts\fR is specified, the scripts are only run once, no matter how many logs match the wildcarded pattern, and whole pattern is passed to them. However, if none of the logs in the pattern require rotating, the scripts will not be run at all. If the scripts exit with error, the remaining actions will not be executed for any logs. This option overrides the \fBnosharedscripts\fR option and implies \fBcreate\fR option. .TP \fBshred\fR Delete log files using \fBshred\fR \-u instead of unlink(). This should ensure that logs are not readable after their scheduled deletion; this is off by default. See also \fBnoshred\fR. .TP \fBshredcycles\fR \fIcount\fR Asks GNU \fBshred\fR(1) to overwrite log files \fBcount\fR times before deletion. Without this option, \fBshred\fR's default will be used. .TP \fBstart \fIcount\fR This is the number to use as the base for rotation. For example, if you specify 0, the logs will be created with a .0 extension as they are rotated from the original log files. If you specify 9, log files will be created with a .9, skipping 0-8. Files will still be rotated the number of times specified with the \fBrotate\fR directive. .TP \fBsu \fIuser\fR \fIgroup\fR Rotate log files set under this user and group instead of using default user/group (usually root). \fIuser\fR specifies the user name used for rotation and \fIgroup\fR specifies the group used for rotation. .TP \fBtabooext\fR [+] \fIlist\fR The current taboo extension list is changed (see the \fBinclude\fR directive for information on the taboo extensions). If a + precedes the list of extensions, the current taboo extension list is augmented, otherwise it is replaced. At startup, the taboo extension list contains .rpmsave, .rpmorig, ~, .disabled, .dpkg\-old, .dpkg\-dist, .dpkg\-new, .cfsaved, .ucf\-old, .ucf\-dist, .ucf\-new, .rpmnew, .swp, .cfsaved, .rhn\-cfg\-tmp\-* .TP \fBweekly\fR Log files are rotated if the current weekday is less than the weekday of the last rotation or if more than a week has passed since the last rotation. This is normally the same as rotating logs on the first day of the week, but it works better if \fIlogrotate\fR is not run every night. .TP \fByearly\fR Log files are rotated if the current year is not the same as the last rotation. .SH FILES .PD 0 .TP 27 \fI/var/lib/logrotate.status\fR Default state file. .TP 27 \fI/etc/logrotate.conf\fR Configuration options. .SH SEE ALSO .BR gzip (1) .SH AUTHORS .nf Erik Troan, Preston Brown, Jan Kaluza. .fi logrotate-3.8.7/basenames.h0000664000076500007650000000033312006205557014723 0ustar hanzzhanzz#ifndef H_BASENAMES #define H_BASENAMES /* returns a pointer inside of name */ char *ourBaseName(char *name); /* returns a malloc'd string which must be freed by the caller */ char *ourDirName(char *origname); #endif logrotate-3.8.7/README.HPUX0000664000076500007650000000331612006205557014263 0ustar hanzzhanzzHow to build and install logrotate on HP-UX 11.00 (these instructions should also work on HP-UX 10.20): 1. Obtain and install the following GNU packages for HP-UX: binutils 2.9.1 gcc 2.95.2 make 3.78.1 I used the packages at the Software Porting and Archive Centre for HP-UX at http://hpux.cs.utah.edu/. Obtain and install the following GNU/Linux to HP-UX Porting package: libhplx library See http://devresource.hp.com/LPK/index.html for downloads. This library is needed to provide the ??? function. 2. Obtain, build, and install popt 1.4 (there doesn't seem to be a build at the Porting Centre.) See ftp://ftp.rpm.org/pub/rpm/dist/rpm-4.0.x/popt-1.6.4.tar.gz Install it into the directory of your choice (i.e. "./configure --prefix=/opt/popt"). 3. Build logrotate, telling it where to find popt and hplx installation. The POPT_DIR defaults to /usr/local and HPLX_DIR defaults to /usr/local/hplx: gmake POPT_DIR=/usr/local HPLX_DIR=/usr/local/hplx 4. Install logrotate into your desired directory (BASEDIR defaults to /usr/local): gmake install BASEDIR=/usr/local 5. Copy the configuration files into your desired location. cp examples/logrotate-default /etc/logrotate.conf mkdir /etc/logrotate.d 6. Set up a cron job to run logrotate daily. See examples/logrotate.cron. 7. I also recommend setting CLEAN_ADM=0 in /etc/rc.config.d/clean, and setting up an init script to use logrotate for this instead. This way, logrotate manages all logfile pruning. Questions, comments, abuse to: Paul D. Gear , Danial M. Howard , logrotate-3.8.7/README.Solaris0000664000076500007650000000102512006205557015106 0ustar hanzzhanzzSteps to build and install logrotate on Solaris 2.6 1. Obtain and install the following GNU packages from http://Sunfreeware.com: gcc 2.95.2 make 3.80 popt-1.6.3 2. Build and install logrotate: gmake gmake install OBS.: If you want to use the test script on Solaris 2.6, you'll need to have bash installed, adjust the path after the sha-bang (#!) properly and substitute the sintax $(...) for backticks `...` in all "test-config.?.in" files. -- Fidelis Assis logrotate-3.8.7/logrotate.c0000664000076500007650000015671312225464270015000 0ustar hanzzhanzz#include /* alloca() is defined in stdlib.h in NetBSD */ #ifndef __NetBSD__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if defined(SunOS) #include #endif #ifdef WITH_SELINUX #include static security_context_t prev_context = NULL; int selinux_enabled = 0; int selinux_enforce = 0; #endif #ifdef WITH_ACL #include "sys/acl.h" #define acl_type acl_t #define ACL_NOT_WELL_SUPPORTED(Err) \ ((Err) == ENOTSUP || (Err) == ENOSYS || (Err) == EINVAL || (Err) == EBUSY) #else #define acl_type void * #endif static acl_type prev_acl = NULL; #include "basenames.h" #include "log.h" #include "logrotate.h" #if !defined(GLOB_ABORTED) && defined(GLOB_ABEND) #define GLOB_ABORTED GLOB_ABEND #endif #ifdef PATH_MAX #define STATEFILE_BUFFER_SIZE 2 * PATH_MAX + 16 #else #define STATEFILE_BUFFER_SIZE 4096 #endif #ifdef __hpux extern int asprintf(char **str, const char *fmt, ...); #endif struct logState { char *fn; struct tm lastRotated; /* only tm_hour, tm_mday, tm_mon, tm_year are good! */ struct stat sb; int doRotate; LIST_ENTRY(logState) list; }; struct logNames { char *firstRotated; char *disposeName; char *finalName; char *dirName; char *baseName; }; struct logStates { LIST_HEAD(stateSet, logState) head; } **states; unsigned int hashSize; int numLogs = 0; int debug = 0; char *mailCommand = DEFAULT_MAIL_COMMAND; time_t nowSecs = 0; static uid_t save_euid; static gid_t save_egid; static int shred_file(int fd, char *filename, struct logInfo *log); 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; } int switch_user(uid_t user, gid_t group) { save_egid = getegid(); save_euid = geteuid(); if (save_euid == user && save_egid == group) return 0; message(MESS_DEBUG, "switching euid to %d and egid to %d\n", user, group); if (setegid(group) || seteuid(user)) { message(MESS_ERROR, "error switching euid to %d and egid to %d: %s\n", user, group, strerror(errno)); return 1; } return 0; } int switch_user_back() { return switch_user(save_euid, save_egid); } int switch_user_permanently(const struct logInfo *log) { gid_t group = getegid(); uid_t user = geteuid(); if (!(log->flags & LOG_FLAG_SU)) { return 0; } if (getuid() == user && getgid() == group) return 0; // switch to full root first if (setgid(getgid()) || setuid(getuid())) { message(MESS_ERROR, "error getting rid of euid != uid\n"); return 1; } message(MESS_DEBUG, "switching uid to %d and gid to %d\n", user, group); if (setgid(group) || setuid(user)) { message(MESS_ERROR, "error switching euid to %d and egid to %d: %s\n", user, group, strerror(errno)); return 1; } return 0; } static void unescape(char *arg) { char *p = arg; char *next; char escaped; while ((next = strchr(p, '\\')) != NULL) { p = next; switch (p[1]) { case 'n': escaped = '\n'; break; case '\\': escaped = '\\'; break; default: ++p; continue; } /* Overwrite the backslash with the intended character, * and shift everything down one */ *p++ = escaped; memmove(p, p+1, 1 + strlen(p+1)); } } #define HASH_SIZE_MIN 64 static int allocateHash(void) { struct logInfo *log; unsigned int hs; int i; hs = 0; for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next) hs += log->numFiles; hs *= 2; /* Enforce some reasonable minimum hash size */ if (hs < HASH_SIZE_MIN) hs = HASH_SIZE_MIN; states = calloc(hs, sizeof(struct logStates *)); if (states == NULL) { message(MESS_ERROR, "could not allocate memory for " "hash table\n"); return 1; } for (i = 0; i < hs; i++) { states[i] = malloc(sizeof(struct logState)); if (states[i] == NULL) { message(MESS_ERROR, "could not allocate memory for " "hash element\n"); return 1; } LIST_INIT(&(states[i]->head)); } hashSize = hs; return 0; } #define HASH_CONST 13 static unsigned hashIndex(const char *fn) { unsigned hash = 0; while (*fn) { hash *= HASH_CONST; hash += *fn++; } return hash % hashSize; } static struct logState *newState(const char *fn) { struct tm now = *localtime(&nowSecs); struct logState *new; time_t lr_time; if ((new = malloc(sizeof(*new))) == NULL) return NULL; if ((new->fn = strdup(fn)) == NULL) { free(new); return NULL; } new->doRotate = 0; memset(&new->lastRotated, 0, sizeof(new->lastRotated)); new->lastRotated.tm_hour = now.tm_hour; new->lastRotated.tm_mday = now.tm_mday; new->lastRotated.tm_mon = now.tm_mon; new->lastRotated.tm_year = now.tm_year; new->lastRotated.tm_isdst = now.tm_isdst; /* fill in the rest of the new->lastRotated fields */ lr_time = mktime(&new->lastRotated); new->lastRotated = *localtime(&lr_time); return new; } static struct logState *findState(const char *fn) { unsigned int i = hashIndex(fn); struct logState *p; for (p = states[i]->head.lh_first; p != NULL; p = p->list.le_next) if (!strcmp(fn, p->fn)) break; /* new state */ if (p == NULL) { if ((p = newState(fn)) == NULL) return NULL; LIST_INSERT_HEAD(&(states[i]->head), p, list); } return p; } static int runScript(struct logInfo *log, char *logfn, char *script) { int rc; if (debug) { message(MESS_DEBUG, "running script with arg %s: \"%s\"\n", logfn, script); return 0; } if (!fork()) { if (log->flags & LOG_FLAG_SU) { if (switch_user_back() != 0) { exit(1); } } execl("/bin/sh", "sh", "-c", script, "logrotate_script", logfn, NULL); exit(1); } wait(&rc); return rc; } int createOutputFile(char *fileName, int flags, struct stat *sb, acl_type acl, int force_mode) { int fd; struct stat sb_create; int acl_set = 0; fd = open(fileName, (flags | O_EXCL | O_NOFOLLOW), (S_IRUSR | S_IWUSR) & sb->st_mode); if (fd < 0) { message(MESS_ERROR, "error creating output file %s: %s\n", fileName, strerror(errno)); return -1; } if (fchmod(fd, (S_IRUSR | S_IWUSR) & sb->st_mode)) { message(MESS_ERROR, "error setting mode of %s: %s\n", fileName, strerror(errno)); close(fd); return -1; } if (fstat(fd, &sb_create)) { message(MESS_ERROR, "fstat of %s failed: %s\n", fileName, strerror(errno)); close(fd); return -1; } if ((sb_create.st_uid != sb->st_uid || sb_create.st_gid != sb->st_gid) && fchown(fd, sb->st_uid, sb->st_gid)) { message(MESS_ERROR, "error setting owner of %s to uid %d and gid %d: %s\n", fileName, sb->st_uid, sb->st_gid, strerror(errno)); close(fd); return -1; } #ifdef WITH_ACL if (!force_mode && acl) { if (acl_set_fd(fd, acl) == -1) { if (!ACL_NOT_WELL_SUPPORTED(errno)) { message(MESS_ERROR, "setting ACL for %s: %s\n", fileName, strerror(errno)); close(fd); return -1; } acl_set = 0; } else { acl_set = 1; } } #endif if (!acl_set || force_mode) { if (fchmod(fd, sb->st_mode)) { message(MESS_ERROR, "error setting mode of %s: %s\n", fileName, strerror(errno)); close(fd); return -1; } } return fd; } #define DIGITS 10 /* unlink, but try to call shred from GNU fileutils */ static int shred_file(int fd, char *filename, struct logInfo *log) { char count[DIGITS]; /* that's a lot of shredding :) */ const char **fullCommand; int id = 0; int status; if (log->preremove) { message(MESS_DEBUG, "running preremove script\n"); if (runScript(log, filename, log->preremove)) { message(MESS_ERROR, "error running preremove script " "for %s of '%s'. Not removing this file.\n", filename, log->pattern); /* What ever was supposed to happen did not happen, * therefore do not unlink the file yet. */ return 1; } } if (!(log->flags & LOG_FLAG_SHRED)) { return unlink(filename); } message(MESS_DEBUG, "Using shred to remove the file %s\n", filename); if (log->shred_cycles != 0) { fullCommand = alloca(sizeof(*fullCommand) * 6); } else { fullCommand = alloca(sizeof(*fullCommand) * 4); } fullCommand[id++] = "shred"; fullCommand[id++] = "-u"; if (log->shred_cycles != 0) { fullCommand[id++] = "-n"; snprintf(count, DIGITS - 1, "%d", log->shred_cycles); fullCommand[id++] = count; } fullCommand[id++] = "-"; fullCommand[id++] = NULL; if (!fork()) { dup2(fd, 1); close(fd); if (switch_user_permanently(log) != 0) { exit(1); } execvp(fullCommand[0], (void *) fullCommand); exit(1); } wait(&status); if (!WIFEXITED(status) || WEXITSTATUS(status)) { message(MESS_ERROR, "Failed to shred %s\n, trying unlink", filename); return unlink(filename); } /* We have to unlink it after shred anyway, * because it doesn't remove the file itself */ return unlink(filename); } static int removeLogFile(char *name, struct logInfo *log) { int fd; message(MESS_DEBUG, "removing old log %s\n", name); if ((fd = open(name, O_RDWR | O_NOFOLLOW)) < 0) { message(MESS_ERROR, "error opening %s: %s\n", name, strerror(errno)); return 1; } if (!debug && shred_file(fd, name, log)) { message(MESS_ERROR, "Failed to remove old log %s: %s\n", name, strerror(errno)); close(fd); return 1; } close(fd); return 0; } static int compressLogFile(char *name, struct logInfo *log, struct stat *sb) { char *compressedName; const char **fullCommand; struct utimbuf utim; int inFile; int outFile; int i; int status; message(MESS_DEBUG, "compressing log with: %s\n", log->compress_prog); if (debug) return 0; 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_RDWR | O_NOFOLLOW)) < 0) { message(MESS_ERROR, "unable to open %s for compression\n", name); return 1; } #ifdef WITH_ACL if ((prev_acl = acl_get_fd(inFile)) == NULL) { if (!ACL_NOT_WELL_SUPPORTED(errno)) { message(MESS_ERROR, "getting file ACL %s: %s\n", name, strerror(errno)); close(inFile); return 1; } } #endif outFile = createOutputFile(compressedName, O_RDWR | O_CREAT, sb, prev_acl, 0); #ifdef WITH_ACL if (prev_acl) { acl_free(prev_acl); prev_acl = NULL; } #endif if (outFile < 0) { close(inFile); return 1; } if (!fork()) { dup2(inFile, 0); close(inFile); dup2(outFile, 1); close(outFile); if (switch_user_permanently(log) != 0) { exit(1); } execvp(fullCommand[0], (void *) fullCommand); exit(1); } wait(&status); fsync(outFile); close(outFile); if (!WIFEXITED(status) || WEXITSTATUS(status)) { message(MESS_ERROR, "failed to compress log %s\n", name); close(inFile); return 1; } utim.actime = sb->st_atime; utim.modtime = sb->st_mtime; utime(compressedName,&utim); /* If we can't change atime/mtime, it's not a disaster. It might possibly fail under SELinux. */ shred_file(inFile, name, log); close(inFile); return 0; } static int mailLog(struct logInfo *log, char *logFile, char *mailCommand, char *uncompressCommand, char *address, char *subject) { int mailInput; pid_t mailChild, uncompressChild = 0; int mailStatus, uncompressStatus; int uncompressPipe[2]; char *mailArgv[] = { mailCommand, "-s", subject, address, NULL }; int rc = 0; if ((mailInput = open(logFile, O_RDONLY | O_NOFOLLOW)) < 0) { message(MESS_ERROR, "failed to open %s for mailing: %s\n", logFile, strerror(errno)); return 1; } if (uncompressCommand) { if (pipe(uncompressPipe) < 0) { message(MESS_ERROR, "error opening pipe for uncompress: %s", strerror(errno)); close(mailInput); return 1; } if (!(uncompressChild = fork())) { /* uncompress child */ dup2(mailInput, 0); close(mailInput); dup2(uncompressPipe[1], 1); close(uncompressPipe[0]); close(uncompressPipe[1]); if (switch_user_permanently(log) != 0) { exit(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); // mail command runs as root if (log->flags & LOG_FLAG_SU) { if (switch_user_back() != 0) { exit(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, struct 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(log, mailFilename, mailCommand, log->uncompress_prog, log->logAddress, log->files[logNum])) return 1; } else { if (mailLog(log, 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, ((flags & LOG_FLAG_COPY) ? O_RDONLY : O_RDWR) | O_NOFOLLOW)) < 0) { message(MESS_ERROR, "error opening %s: %s\n", currLog, strerror(errno)); return 1; } #ifdef WITH_SELINUX if (selinux_enabled) { security_context_t oldContext; if (fgetfilecon_raw(fdcurr, &oldContext) >= 0) { if (getfscreatecon_raw(&prev_context) < 0) { message(MESS_ERROR, "getting default context: %s\n", strerror(errno)); if (selinux_enforce) { freecon(oldContext); close(fdcurr); return 1; } } if (setfscreatecon_raw(oldContext) < 0) { message(MESS_ERROR, "setting file context %s to %s: %s\n", saveLog, oldContext, strerror(errno)); if (selinux_enforce) { freecon(oldContext); close(fdcurr); return 1; } } message(MESS_DEBUG, "set default create context\n"); freecon(oldContext); } else { if (errno != ENOTSUP) { message(MESS_ERROR, "getting file context %s: %s\n", currLog, strerror(errno)); if (selinux_enforce) { return 1; } } } } #endif #ifdef WITH_ACL if ((prev_acl = acl_get_fd(fdcurr)) == NULL) { if (!ACL_NOT_WELL_SUPPORTED(errno)) { message(MESS_ERROR, "getting file ACL %s: %s\n", currLog, strerror(errno)); close(fdcurr); return 1; } } #endif /* WITH_ACL */ fdsave = createOutputFile(saveLog, O_WRONLY | O_CREAT, sb, prev_acl, 0); #ifdef WITH_SELINUX if (selinux_enabled) { setfscreatecon_raw(prev_context); freecon(prev_context); prev_context = NULL; } #endif #ifdef WITH_ACL if (prev_acl) { acl_free(prev_acl); prev_acl = NULL; } #endif if (fdsave < 0) { close(fdcurr); 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) { fsync(fdsave); if (ftruncate(fdcurr, 0)) { message(MESS_ERROR, "error truncating %s: %s\n", currLog, strerror(errno)); close(fdcurr); close(fdsave); return 1; } } } else message(MESS_DEBUG, "Not truncating %s\n", currLog); if (fdcurr >= 0) { close(fdcurr); } if (fdsave >= 0) { close(fdsave); } return 0; } int findNeedRotating(struct logInfo *log, int logNum, int force) { struct stat sb; struct logState *state = NULL; struct tm now = *localtime(&nowSecs); message(MESS_DEBUG, "considering log %s\n", log->files[logNum]); /* Check if parent directory of this log has safe permissions */ if ((log->flags & LOG_FLAG_SU) == 0 && getuid() == 0) { char *ld = ourDirName(log->files[logNum]); if (stat(ld, &sb)) { /* If parent directory doesn't exist, it's not real error and rotation is not needed */ if (errno != ENOENT) { message(MESS_ERROR, "stat of %s failed: %s\n", ld, strerror(errno)); free(ld); return 1; } free(ld); return 0; } /* Don't rotate in directories writable by others or group which is not "root" */ if ((sb.st_gid != 0 && sb.st_mode & S_IWGRP) || sb.st_mode & S_IWOTH) { message(MESS_ERROR, "skipping \"%s\" because parent directory has insecure permissions" " (It's world writable or writable by group which is not \"root\")" " Set \"su\" directive in config file to tell logrotate which user/group" " should be used for rotation.\n" ,log->files[logNum]); free(ld); return 1; } free(ld); } if (lstat(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]); state->doRotate = 0; state->sb = sb; if ((sb.st_mode & S_IFMT) == S_IFLNK) { message(MESS_DEBUG, " log %s is symbolic link. Rotation of symbolic" " links is not allowed to avoid security issues -- skipping.\n", log->files[logNum]); return 0; } if (force) { /* user forced rotation of logs from command line */ state->doRotate = 1; } else if (log->criterium == ROT_SIZE) { state->doRotate = (sb.st_size >= log->threshhold); } else if (mktime(&state->lastRotated) - mktime(&now) > (25 * 3600)) { /* 25 hours allows for DST changes as well as geographical moves */ 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 || state->lastRotated.tm_hour != now.tm_hour) { 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_HOURLY: state->doRotate = ((now.tm_hour != state->lastRotated.tm_hour) || (now.tm_mday != state->lastRotated.tm_mday) || (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 = ((now.tm_mday != state->lastRotated.tm_mday) || (now.tm_mon != state->lastRotated.tm_mon) || (now.tm_year != state->lastRotated.tm_year)); 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_YEARLY: /* rotate if the logs haven't been rotated this year */ state->doRotate = (now.tm_year != state->lastRotated.tm_year); break; default: /* ack! */ state->doRotate = 0; break; } if (log->minsize && sb.st_size < log->minsize) state->doRotate = 0; } if (log->maxsize && sb.st_size > log->maxsize) state->doRotate = 1; /* 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 prerotateSingleLog(struct logInfo *log, int logNum, struct logState *state, struct logNames *rotNames) { struct tm now = *localtime(&nowSecs); char *oldName, *newName = NULL; char *compext = ""; char *fileext = ""; int hasErrors = 0; int i, j; char *glob_pattern; glob_t globResult; int rc; int rotateCount = log->rotateCount ? log->rotateCount : 1; int logStart = (log->logStart == -1) ? 1 : log->logStart; #define DATEEXT_LEN 64 #define PATTERN_LEN (DATEEXT_LEN * 2) char dext_str[DATEEXT_LEN]; char dformat[DATEEXT_LEN]; char dext_pattern[PATTERN_LEN]; char *dext; 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]); rotNames->dirName = malloc(strlen(ld) + strlen(log->oldDir) + 2); sprintf(rotNames->dirName, "%s/%s", ld, log->oldDir); free(ld); } else rotNames->dirName = strdup(log->oldDir); } else rotNames->dirName = ourDirName(log->files[logNum]); rotNames->baseName = strdup(ourBaseName(log->files[logNum])); if (log->extension && strncmp(& (rotNames-> baseName[strlen(rotNames->baseName) - strlen(log->extension)]), log->extension, strlen(log->extension)) == 0) { char *tempstr; fileext = log->extension; tempstr = calloc(strlen(rotNames->baseName) - strlen(log->extension) + 1, sizeof(char)); strncat(tempstr, rotNames->baseName, strlen(rotNames->baseName) - strlen(log->extension)); free(rotNames->baseName); rotNames->baseName = tempstr; } /* Adjust "now" if we want yesterday's date */ if (log->flags & LOG_FLAG_DATEYESTERDAY) { now.tm_hour = 12; /* set hour to noon to work around DST issues */ now.tm_mday = now.tm_mday - 1; mktime(&now); } /* Allow only %Y %d %m and create valid strftime format string * Construct the glob pattern corresponding to the date format */ dext_str[0] = '\0'; if (log->dateformat) { i = j = 0; memset(dext_pattern, 0, sizeof(dext_pattern)); dext = log->dateformat; while (*dext == ' ') dext++; while ((*dext != '\0') && (!hasErrors)) { /* Will there be a space for a char and '\0'? */ if (j >= (sizeof(dext_pattern) - 1)) { message(MESS_ERROR, "Date format %s is too long\n", log->dateformat); hasErrors = 1; break; } if (*dext == '%') { switch (*(dext + 1)) { case 'Y': strncat(dext_pattern, "[0-9][0-9]", sizeof(dext_pattern) - strlen(dext_pattern)); j += 10; /* strlen("[0-9][0-9]") */ case 'm': case 'd': strncat(dext_pattern, "[0-9][0-9]", sizeof(dext_pattern) - strlen(dext_pattern)); j += 10; if (j >= (sizeof(dext_pattern) - 1)) { message(MESS_ERROR, "Date format %s is too long\n", log->dateformat); hasErrors = 1; break; } dformat[i++] = *(dext++); dformat[i] = *dext; break; case 's': /* End of year 2293 this pattern does not work. */ strncat(dext_pattern, "[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]", sizeof(dext_pattern) - strlen(dext_pattern)); j += 50; if (j >= (sizeof(dext_pattern) - 1)) { message(MESS_ERROR, "Date format %s is too long\n", log->dateformat); hasErrors = 1; break; } dformat[i++] = *(dext++); dformat[i] = *dext; break; default: dformat[i++] = *dext; dformat[i] = '%'; dext_pattern[j++] = *dext; break; } } else { dformat[i] = *dext; dext_pattern[j++] = *dext; } ++i; ++dext; } dformat[i] = '\0'; message(MESS_DEBUG, "Converted '%s' -> '%s'\n", log->dateformat, dformat); strftime(dext_str, sizeof(dext_str), dformat, &now); } else { if (log->criterium == ROT_HOURLY) { /* hourly adds another two digits */ strftime(dext_str, sizeof(dext_str), "-%Y%m%d%H", &now); strncpy(dext_pattern, "-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]", sizeof(dext_pattern)); } else { /* The default dateformat and glob pattern */ strftime(dext_str, sizeof(dext_str), "-%Y%m%d", &now); strncpy(dext_pattern, "-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]", sizeof(dext_pattern)); } dext_pattern[PATTERN_LEN - 1] = '\0'; } message(MESS_DEBUG, "dateext suffix '%s'\n", dext_str); message(MESS_DEBUG, "glob pattern '%s'\n", dext_pattern); #ifdef WITH_SELINUX if (selinux_enabled) { security_context_t oldContext = NULL; if (getfilecon_raw(log->files[logNum], &oldContext) > 0) { if (getfscreatecon_raw(&prev_context) < 0) { message(MESS_ERROR, "getting default context: %s\n", strerror(errno)); if (selinux_enforce) { freecon(oldContext); return 1; } } if (setfscreatecon_raw(oldContext) < 0) { message(MESS_ERROR, "setting file context %s to %s: %s\n", log->files[logNum], oldContext, strerror(errno)); if (selinux_enforce) { freecon(oldContext); return 1; } } freecon(oldContext); } else { if (errno != ENOENT && errno != ENOTSUP) { message(MESS_ERROR, "getting file context %s: %s\n", log->files[logNum], strerror(errno)); if (selinux_enforce) { return 1; } } } } #endif /* 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 */ if (asprintf(&glob_pattern, "%s/%s%s%s", rotNames->dirName, rotNames->baseName, dext_pattern, fileext) < 0) { message(MESS_FATAL, "could not allocate glob pattern memory\n"); } 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; if (asprintf(&oldName, "%s", (globResult.gl_pathv)[i]) < 0) { message(MESS_FATAL, "could not allocate glob result memory\n"); } if (stat(oldName, &sbprev)) { message(MESS_DEBUG, "previous log %s does not exist\n", oldName); } else { hasErrors = compressLogFile(oldName, log, &sbprev); } free(oldName); } } else { message(MESS_DEBUG, "glob finding logs to compress failed\n"); /* fallback to old behaviour */ if (asprintf(&oldName, "%s/%s.%d%s", rotNames->dirName, rotNames->baseName, logStart, fileext) < 0) { message(MESS_FATAL, "could not allocate oldName memory\n"); } free(oldName); } globfree(&globResult); free(glob_pattern); } else { struct stat sbprev; if (asprintf(&oldName, "%s/%s.%d%s", rotNames->dirName, rotNames->baseName, logStart, fileext) < 0) { message(MESS_FATAL, "could not allocate oldName memory\n"); } if (stat(oldName, &sbprev)) { message(MESS_DEBUG, "previous log %s does not exist\n", oldName); } else { hasErrors = compressLogFile(oldName, log, &sbprev); } free(oldName); } } rotNames->firstRotated = malloc(strlen(rotNames->dirName) + strlen(rotNames->baseName) + strlen(fileext) + strlen(compext) + 30); if (log->flags & LOG_FLAG_DATEEXT) { /* glob for compressed files with our pattern * and compress ext */ if (asprintf(&glob_pattern, "%s/%s%s%s%s", rotNames->dirName, rotNames->baseName, dext_pattern, fileext, compext) < 0) { message(MESS_ERROR, "could not allocate glob pattern memory\n"); } 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) { char *mailFilename = (globResult.gl_pathv)[mail_out]; if (!hasErrors && log->logAddress) hasErrors = mailLogWrapper(mailFilename, mailCommand, logNum, log); if (!hasErrors) { message(MESS_DEBUG, "removing %s\n", mailFilename); hasErrors = removeLogFile(mailFilename, log); } } mail_out = i; } } } if (mail_out != -1) { /* oldName is oldest Backup found (for unlink later) */ if (asprintf(&oldName, "%s", (globResult.gl_pathv)[mail_out]) < 0) { message(MESS_FATAL, "could not allocate mailout memory\n"); } rotNames->disposeName = malloc(strlen(oldName)+1); strcpy(rotNames->disposeName, oldName); free(oldName); } else { free(rotNames->disposeName); rotNames->disposeName = NULL; } } else { message(MESS_DEBUG, "glob finding old rotated logs failed\n"); free(rotNames->disposeName); rotNames->disposeName = NULL; } /* firstRotated is most recently created/compressed rotated log */ sprintf(rotNames->firstRotated, "%s/%s%s%s%s", rotNames->dirName, rotNames->baseName, dext_str, fileext, (log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext); globfree(&globResult); free(glob_pattern); } else { if (log->rotateAge) { struct stat fst_buf; for (i = 1; i <= rotateCount + 1; i++) { if (asprintf(&oldName, "%s/%s.%d%s%s", rotNames->dirName, rotNames->baseName, i, fileext, compext) < 0) { message(MESS_FATAL, "could not allocate mailFilename memory\n"); } if (!stat(oldName, &fst_buf) && (((nowSecs - fst_buf.st_mtime) / 60 / 60 / 24) > log->rotateAge)) { char *mailFilename = oldName; if (!hasErrors && log->logAddress) hasErrors = mailLogWrapper(mailFilename, mailCommand, logNum, log); if (!hasErrors) hasErrors = removeLogFile(mailFilename, log); } free(oldName); } } if (asprintf(&oldName, "%s/%s.%d%s%s", rotNames->dirName, rotNames->baseName, logStart + rotateCount, fileext, compext) < 0) { message(MESS_FATAL, "could not allocate disposeName memory\n"); } newName = strdup(oldName); rotNames->disposeName = strdup(oldName); sprintf(rotNames->firstRotated, "%s/%s.%d%s%s", rotNames->dirName, rotNames->baseName, logStart, fileext, (log->flags & LOG_FLAG_DELAYCOMPRESS) ? "" : compext); for (i = rotateCount + logStart - 1; (i >= 0) && !hasErrors; i--) { free(newName); newName = oldName; if (asprintf(&oldName, "%s/%s.%d%s%s", rotNames->dirName, rotNames->baseName, i, fileext, compext) < 0) { message(MESS_FATAL, "could not allocate oldName memory\n"); } 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; } } if (hasErrors || i - 1 < 0) free(oldName); } free(newName); } /* !LOG_FLAG_DATEEXT */ if (log->flags & LOG_FLAG_DATEEXT) { char *destFile; struct stat fst_buf; if (asprintf(&(rotNames->finalName), "%s/%s%s%s", rotNames->dirName, rotNames->baseName, dext_str, fileext) < 0) { message(MESS_FATAL, "could not allocate finalName memory\n"); } if (asprintf(&destFile, "%s%s", rotNames->finalName, compext) < 0) { message(MESS_FATAL, "could not allocate destFile memory\n"); } if (!stat(destFile, &fst_buf)) { message(MESS_DEBUG, "destination %s already exists, skipping rotation\n", rotNames->firstRotated); hasErrors = 1; } free(destFile); } else { /* note: the gzip extension is *not* used here! */ if (asprintf(&(rotNames->finalName), "%s/%s.%d%s", rotNames->dirName, rotNames->baseName, logStart, fileext) < 0) { message(MESS_ERROR, "could not allocate finalName memory\n"); } } /* if the last rotation doesn't exist, that's okay */ if (!debug && rotNames->disposeName && access(rotNames->disposeName, F_OK)) { message(MESS_DEBUG, "log %s doesn't exist -- won't try to " "dispose of it\n", rotNames->disposeName); free(rotNames->disposeName); rotNames->disposeName = NULL; } return hasErrors; } int rotateSingleLog(struct logInfo *log, int logNum, struct logState *state, struct logNames *rotNames) { int hasErrors = 0; struct stat sb; int fd; #ifdef WITH_SELINUX security_context_t savedContext = NULL; #endif if (!state->doRotate) return 0; if (!hasErrors) { if (!(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) { #ifdef WITH_SELINUX if (selinux_enabled) { security_context_t oldContext = NULL; int fdcurr = -1; if ((fdcurr = open(log->files[logNum], O_RDWR | O_NOFOLLOW)) < 0) { message(MESS_ERROR, "error opening %s: %s\n", log->files[logNum], strerror(errno)); return 1; } if (fgetfilecon_raw(fdcurr, &oldContext) >= 0) { if (getfscreatecon_raw(&savedContext) < 0) { message(MESS_ERROR, "getting default context: %s\n", strerror(errno)); if (selinux_enforce) { freecon(oldContext); if (close(fdcurr) < 0) message(MESS_ERROR, "error closing file %s", log->files[logNum]); return 1; } } if (setfscreatecon_raw(oldContext) < 0) { message(MESS_ERROR, "setting file context %s to %s: %s\n", log->files[logNum], oldContext, strerror(errno)); if (selinux_enforce) { freecon(oldContext); if (close(fdcurr) < 0) message(MESS_ERROR, "error closing file %s", log->files[logNum]); return 1; } } message(MESS_DEBUG, "fscreate context set to %s\n", oldContext); freecon(oldContext); } else { if (errno != ENOTSUP) { message(MESS_ERROR, "getting file context %s: %s\n", log->files[logNum], strerror(errno)); if (selinux_enforce) { if (close(fdcurr) < 0) message(MESS_ERROR, "error closing file %s", log->files[logNum]); return 1; } } } if (close(fdcurr) < 0) message(MESS_ERROR, "error closing file %s", log->files[logNum]); } #endif #ifdef WITH_ACL if ((prev_acl = acl_get_file(log->files[logNum], ACL_TYPE_ACCESS)) == NULL) { if (!ACL_NOT_WELL_SUPPORTED(errno)) { message(MESS_ERROR, "getting file ACL %s: %s\n", log->files[logNum], strerror(errno)); hasErrors = 1; } } #endif /* WITH_ACL */ message(MESS_DEBUG, "renaming %s to %s\n", log->files[logNum], rotNames->finalName); if (!debug && !hasErrors && rename(log->files[logNum], rotNames->finalName)) { message(MESS_ERROR, "failed to rename %s to %s: %s\n", log->files[logNum], rotNames->finalName, strerror(errno)); hasErrors = 1; } if (!log->rotateCount) { rotNames->disposeName = realloc(rotNames->disposeName, strlen(rotNames->dirName) + strlen(rotNames->baseName) + strlen(log->files[logNum]) + 10); sprintf(rotNames->disposeName, "%s%s", rotNames->finalName, (log->compress_ext && (log->flags & LOG_FLAG_COMPRESS)) ? log->compress_ext : ""); message(MESS_DEBUG, "disposeName will be %s\n", rotNames->disposeName); } } if (!hasErrors && log->flags & LOG_FLAG_CREATE && !(log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY))) { if (log->createUid == NO_UID) sb.st_uid = state->sb.st_uid; else sb.st_uid = log->createUid; if (log->createGid == NO_GID) sb.st_gid = state->sb.st_gid; else sb.st_gid = log->createGid; int have_create_mode = 0; if (log->createMode == NO_MODE) sb.st_mode = state->sb.st_mode & 0777; else { sb.st_mode = log->createMode; have_create_mode = 1; } message(MESS_DEBUG, "creating new %s mode = 0%o uid = %d " "gid = %d\n", log->files[logNum], (unsigned int) sb.st_mode, (int) sb.st_uid, (int) sb.st_gid); if (!debug) { if (!hasErrors) { fd = createOutputFile(log->files[logNum], O_CREAT | O_RDWR, &sb, prev_acl, have_create_mode); #ifdef WITH_ACL if (prev_acl) { acl_free(prev_acl); prev_acl = NULL; } #endif if (fd < 0) hasErrors = 1; else { close(fd); } } } } #ifdef WITH_SELINUX if (selinux_enabled) { setfscreatecon_raw(savedContext); freecon(savedContext); savedContext = NULL; } #endif if (!hasErrors && log->flags & (LOG_FLAG_COPYTRUNCATE | LOG_FLAG_COPY)) { hasErrors = copyTruncate(log->files[logNum], rotNames->finalName, &state->sb, log->flags); } #ifdef WITH_ACL if (prev_acl) { acl_free(prev_acl); prev_acl = NULL; } #endif /* WITH_ACL */ } return hasErrors; } int postrotateSingleLog(struct logInfo *log, int logNum, struct logState *state, struct logNames *rotNames) { int hasErrors = 0; if (!state->doRotate) return 0; if ((log->flags & LOG_FLAG_COMPRESS) && !(log->flags & LOG_FLAG_DELAYCOMPRESS)) { hasErrors = compressLogFile(rotNames->finalName, log, &state->sb); } if (!hasErrors && log->logAddress) { char *mailFilename; if (log->flags & LOG_FLAG_MAILFIRST) mailFilename = rotNames->firstRotated; else mailFilename = rotNames->disposeName; if (mailFilename) hasErrors = mailLogWrapper(mailFilename, mailCommand, logNum, log); } if (!hasErrors && rotNames->disposeName) hasErrors = removeLogFile(rotNames->disposeName, log); #ifdef WITH_SELINUX if (selinux_enabled) { setfscreatecon_raw(prev_context); freecon(prev_context); prev_context = NULL; } #endif return hasErrors; } int rotateLogSet(struct logInfo *log, int force) { int i, j; int hasErrors = 0; int logHasErrors[log->numFiles]; int numRotated = 0; struct logState **state; struct logNames **rotNames; message(MESS_DEBUG, "\nrotating pattern: %s ", log->pattern); if (force) { message(MESS_DEBUG, "forced from command line "); } else { switch (log->criterium) { case ROT_HOURLY: message(MESS_DEBUG, "hourly "); break; case ROT_DAYS: message(MESS_DEBUG, "after %llu days ", log->threshhold); break; case ROT_WEEKLY: message(MESS_DEBUG, "weekly "); break; case ROT_MONTHLY: message(MESS_DEBUG, "monthly "); break; case ROT_YEARLY: message(MESS_DEBUG, "yearly "); break; case ROT_SIZE: message(MESS_DEBUG, "%llu bytes ", log->threshhold); 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->minsize) message(MESS_DEBUG, "only log files >= %llu bytes are rotated, ", log->minsize); if (log->maxsize) message(MESS_DEBUG, "log files >= %llu are rotated earlier, ", log->maxsize); if (log->logAddress) { message(MESS_DEBUG, "old logs mailed to %s\n", log->logAddress); } else { message(MESS_DEBUG, "old logs are removed\n"); } if (log->numFiles == 0) { message(MESS_DEBUG, "No logs found. Rotation not needed.\n"); return 0; } if (log->flags & LOG_FLAG_SU) { if (switch_user(log->suUid, log->suGid) != 0) { return 1; } } for (i = 0; i < log->numFiles; i++) { logHasErrors[i] = findNeedRotating(log, i, force); hasErrors |= logHasErrors[i]; /* sure is a lot of findStating going on .. */ if ((findState(log->files[i]))->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, log->pattern, log->first)) { message(MESS_ERROR, "error running first action script " "for %s\n", log->pattern); hasErrors = 1; if (log->flags & LOG_FLAG_SU) { if (switch_user_back() != 0) { return 1; } } /* finish early, firstaction failed, affects all logs in set */ return hasErrors; } } } state = malloc(log->numFiles * sizeof(struct logState *)); rotNames = malloc(log->numFiles * sizeof(struct logNames *)); for (j = 0; (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < log->numFiles) || ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && j < 1); j++) { for (i = j; ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles) || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) { state[i] = findState(log->files[i]); rotNames[i] = malloc(sizeof(struct logNames)); memset(rotNames[i], 0, sizeof(struct logNames)); logHasErrors[i] |= prerotateSingleLog(log, i, state[i], rotNames[i]); hasErrors |= logHasErrors[i]; } if (log->pre && (!( ((logHasErrors[j] || !state[j]->doRotate) && !(log->flags & LOG_FLAG_SHAREDSCRIPTS)) || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) )) ) { if (!numRotated) { message(MESS_DEBUG, "not running prerotate script, " "since no logs will be rotated\n"); } else { message(MESS_DEBUG, "running prerotate script\n"); if (runScript(log, log->flags & LOG_FLAG_SHAREDSCRIPTS ? log->pattern : log->files[j], log->pre)) { if (log->flags & LOG_FLAG_SHAREDSCRIPTS) message(MESS_ERROR, "error running shared prerotate script " "for '%s'\n", log->pattern); else { message(MESS_ERROR, "error running non-shared prerotate script " "for %s of '%s'\n", log->files[j], log->pattern); } logHasErrors[j] = 1; hasErrors = 1; } } } for (i = j; ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles) || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) { if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS)) || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) { logHasErrors[i] |= rotateSingleLog(log, i, state[i], rotNames[i]); hasErrors |= logHasErrors[i]; } } if (log->post && (!( ((logHasErrors[j] || !state[j]->doRotate) && !(log->flags & LOG_FLAG_SHAREDSCRIPTS)) || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) )) ) { if (!numRotated) { message(MESS_DEBUG, "not running postrotate script, " "since no logs were rotated\n"); } else { message(MESS_DEBUG, "running postrotate script\n"); if (runScript(log, log->flags & LOG_FLAG_SHAREDSCRIPTS ? log->pattern : log->files[j], log->post)) { if (log->flags & LOG_FLAG_SHAREDSCRIPTS) message(MESS_ERROR, "error running shared postrotate script " "for '%s'\n", log->pattern); else { message(MESS_ERROR, "error running non-shared postrotate script " "for %s of '%s'\n", log->files[j], log->pattern); } logHasErrors[j] = 1; hasErrors = 1; } } } for (i = j; ((log->flags & LOG_FLAG_SHAREDSCRIPTS) && i < log->numFiles) || (!(log->flags & LOG_FLAG_SHAREDSCRIPTS) && i == j); i++) { if (! ( (logHasErrors[i] && !(log->flags & LOG_FLAG_SHAREDSCRIPTS)) || (hasErrors && (log->flags & LOG_FLAG_SHAREDSCRIPTS)) ) ) { logHasErrors[i] |= postrotateSingleLog(log, i, state[i], rotNames[i]); hasErrors |= logHasErrors[i]; } } } for (i = 0; i < log->numFiles; i++) { free(rotNames[i]->firstRotated); free(rotNames[i]->disposeName); free(rotNames[i]->finalName); free(rotNames[i]->dirName); free(rotNames[i]->baseName); free(rotNames[i]); } free(rotNames); free(state); 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, log->pattern, log->last)) { message(MESS_ERROR, "error running last action script " "for %s\n", log->pattern); hasErrors = 1; } } } if (log->flags & LOG_FLAG_SU) { if (switch_user_back() != 0) { return 1; } } return hasErrors; } static int writeState(char *stateFilename) { struct logState *p; FILE *f; char *chptr; int i; int error = 0; int bytes = 0; int fdcurr; int fdsave; struct stat sb; char *tmpFilename = NULL; tmpFilename = malloc(strlen(stateFilename) + 5 ); if (tmpFilename == NULL) { message(MESS_ERROR, "could not allocate memory for " "tmp state filename\n"); return 1; } strcpy(tmpFilename, stateFilename); strcat(tmpFilename, ".tmp"); /* Remove possible tmp state file from previous run */ unlink(tmpFilename); if ((fdcurr = open(stateFilename, O_RDONLY)) < 0) { message(MESS_ERROR, "error opening %s: %s\n", stateFilename, strerror(errno)); free(tmpFilename); return 1; } #ifdef WITH_SELINUX if (selinux_enabled) { security_context_t oldContext; if (fgetfilecon_raw(fdcurr, &oldContext) >= 0) { if (getfscreatecon_raw(&prev_context) < 0) { message(MESS_ERROR, "getting default context: %s\n", strerror(errno)); if (selinux_enforce) { freecon(oldContext); free(tmpFilename); return 1; } } if (setfscreatecon_raw(oldContext) < 0) { message(MESS_ERROR, "setting file context %s to %s: %s\n", tmpFilename, oldContext, strerror(errno)); if (selinux_enforce) { freecon(oldContext); free(tmpFilename); return 1; } } message(MESS_DEBUG, "set default create context\n"); freecon(oldContext); } else { if (errno != ENOTSUP) { message(MESS_ERROR, "getting file context %s: %s\n", tmpFilename, strerror(errno)); if (selinux_enforce) { free(tmpFilename); return 1; } } } } #endif #ifdef WITH_ACL if ((prev_acl = acl_get_fd(fdcurr)) == NULL) { if (!ACL_NOT_WELL_SUPPORTED(errno)) { message(MESS_ERROR, "getting file ACL %s: %s\n", stateFilename, strerror(errno)); close(fdcurr); return 1; } } #endif close(fdcurr); stat(stateFilename, &sb); fdsave = createOutputFile(tmpFilename, O_RDWR | O_CREAT | O_TRUNC, &sb, prev_acl, 0); #ifdef WITH_ACL if (prev_acl) { acl_free(prev_acl); prev_acl = NULL; } #endif #ifdef WITH_SELINUX if (selinux_enabled) { setfscreatecon_raw(prev_context); freecon(prev_context); prev_context = NULL; } #endif if (fdsave < 0) { free(tmpFilename); return 1; } f = fdopen(fdsave, "w"); if (!f) { message(MESS_ERROR, "error creating temp state file %s: %s\n", tmpFilename, strerror(errno)); free(tmpFilename); return 1; } bytes = fprintf(f, "logrotate state -- version 2\n"); if (bytes < 0) error = bytes; for (i = 0; i < hashSize && error == 0; i++) { for (p = states[i]->head.lh_first; p != NULL && error == 0; p = p->list.le_next) { error = fputc('"', f) == EOF; for (chptr = p->fn; *chptr && error == 0; chptr++) { switch (*chptr) { case '"': case '\\': error = fputc('\\', f) == EOF; break; case '\n': error = fputc('\\', f) == EOF; if (error == 0) { error = fputc('n', f) == EOF; } continue; } if (error == 0 && fputc(*chptr, f) == EOF) { error = 1; } } if (error == 0 && fputc('"', f) == EOF) error = 1; if (error == 0) { bytes = fprintf(f, " %d-%d-%d-%d:%d:%d\n", p->lastRotated.tm_year + 1900, p->lastRotated.tm_mon + 1, p->lastRotated.tm_mday, p->lastRotated.tm_hour, p->lastRotated.tm_min, p->lastRotated.tm_sec); if (bytes < 0) error = bytes; } } } if (error == 0) error = fsync(fdsave); if (error == 0) error = fclose(f); else fclose(f); if (error == 0) { if (rename(tmpFilename, stateFilename)) { unlink(tmpFilename); error = 1; message(MESS_ERROR, "error renaming temp state file %s\n", tmpFilename); } } else { unlink(tmpFilename); if (errno) message(MESS_ERROR, "error creating temp state file %s: %s\n", tmpFilename, strerror(errno)); else message(MESS_ERROR, "error creating temp state file %s%s\n", tmpFilename, error == ENOMEM ? ": Insufficient storage space is available." : "" ); } free(tmpFilename); return error; } static int readState(char *stateFilename) { FILE *f; char buf[STATEFILE_BUFFER_SIZE]; char *filename; const char **argv; int argc; int year, month, day, hour, minute, second; int i; int line = 0; int error; struct 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)) { argv = NULL; line++; i = strlen(buf); if (buf[i - 1] != '\n') { message(MESS_ERROR, "line %d too long in state file %s\n", line, stateFilename); fclose(f); return 1; } buf[i - 1] = '\0'; if (i == 1) continue; year = month = day = hour = minute = second = 0; if (poptParseArgvString(buf, &argc, &argv) || (argc != 2) || (sscanf(argv[1], "%d-%d-%d-%d:%d:%d", &year, &month, &day, &hour, &minute, &second) < 3)) { message(MESS_ERROR, "bad line %d in state file %s\n", line, stateFilename); free(argv); 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); free(argv); 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); free(argv); 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); free(argv); fclose(f); return 1; } if (hour < 0 || hour > 23) { message(MESS_ERROR, "bad hour %d for file %s in state file %s\n", hour, argv[0], stateFilename); free(argv); fclose(f); return 1; } if (minute < 0 || minute > 59) { message(MESS_ERROR, "bad minute %d for file %s in state file %s\n", minute, argv[0], stateFilename); free(argv); fclose(f); return 1; } if (second < 0 || second > 59) { message(MESS_ERROR, "bad second %d for file %s in state file %s\n", second, argv[0], stateFilename); free(argv); fclose(f); return 1; } year -= 1900, month -= 1; filename = strdup(argv[0]); unescape(filename); if ((st = findState(filename)) == NULL) { fclose(f); return 1; } st->lastRotated.tm_year = year; st->lastRotated.tm_mon = month; st->lastRotated.tm_mday = day; st->lastRotated.tm_hour = hour; st->lastRotated.tm_min = minute; st->lastRotated.tm_sec = second; /* fill in the rest of the st->lastRotated fields */ lr_time = mktime(&st->lastRotated); st->lastRotated = *localtime(&lr_time); free(argv); free(filename); } fclose(f); return 0; } int main(int argc, const char **argv) { int force = 0; char *stateFile = STATEFILE; int rc = 0; int arg; const char **files; poptContext optCon; struct logInfo *log; 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"}, {"version", '\0', POPT_ARG_NONE, NULL, 'V', "Display version information"}, POPT_AUTOHELP {0, 0, 0, 0, 0} }; logSetLevel(MESS_NORMAL); setlocale (LC_ALL, ""); optCon = poptGetContext("logrotate", argc, argv, options, 0); poptReadDefaultConfig(optCon, 1); poptSetOtherOptionHelp(optCon, "[OPTION...] "); while ((arg = poptGetNextOpt(optCon)) >= 0) { switch (arg) { case 'd': debug = 1; /* fallthrough */ case 'v': logSetLevel(MESS_DEBUG); break; case 'V': fprintf(stderr, "logrotate %s\n", VERSION); poptFreeContext(optCon); exit(0); } } if (arg < -1) { fprintf(stderr, "logrotate: bad argument %s: %s\n", poptBadOption(optCon, POPT_BADOPTION_NOALIAS), poptStrerror(rc)); poptFreeContext(optCon); 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); poptFreeContext(optCon); exit(1); } #ifdef WITH_SELINUX selinux_enabled = (is_selinux_enabled() > 0); selinux_enforce = security_getenforce(); #endif TAILQ_INIT(&logs); if (readAllConfigPaths(files)) { poptFreeContext(optCon); exit(1); } poptFreeContext(optCon); nowSecs = time(NULL); if (allocateHash() != 0) return 1; if (readState(stateFile)) exit(1); message(MESS_DEBUG, "\nHandling %d logs\n", numLogs); for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next) rc |= rotateLogSet(log, force); if (!debug) rc |= writeState(stateFile); return (rc != 0); } logrotate-3.8.7/test/0000775000076500007650000000000012225464270013576 5ustar hanzzhanzzlogrotate-3.8.7/test/test-config.37.in0000664000076500007650000000054512137672476016617 0ustar hanzzhanzz&DIR&/test.log { size 300x daily firstaction touch scriptout # put some } here, so we check if parser won't break on it. } echo $(cat scriptout) foo > foo } mv foo scriptout endscript rotate 1 } &DIR&/test.log { create daily firstaction touch scriptout echo $(cat scriptout) second > foo mv foo scriptout endscript rotate 1 } logrotate-3.8.7/test/test-config.29.in0000664000076500007650000000011012006205556016566 0ustar hanzzhanzzcreate rotate 1 daily #testing { } on the same line &DIR&/test.log { } logrotate-3.8.7/test/test-config.16.in0000664000076500007650000000010112006205556016562 0ustar hanzzhanzzcreate &DIR&/test.log { weekly maxsize 4 rotate 1 } logrotate-3.8.7/test/test-config.38.in0000664000076500007650000000030012137672476016605 0ustar hanzzhanzzcreate &DIR&/test*.log { monthly rotate 0 mail user@myhost.org maillast sharedscripts preremove touch scriptout echo -n $(basename $*) >> scriptout endscript } logrotate-3.8.7/test/test-config.15.in0000664000076500007650000000011712006205556016570 0ustar hanzzhanzzcreate &DIR&/test.log { daily shred shredcycles 20 rotate 1 } logrotate-3.8.7/test/test-config.17.in0000664000076500007650000000015512006205556016574 0ustar hanzzhanzzcreate # missing { is OK, we're testing parsing here &DIR&/test.log weekly maxsize 4 rotate 1 } logrotate-3.8.7/test/test-config.32.in0000664000076500007650000000005612033240657016572 0ustar hanzzhanzzcreate &DIR&/test.log { daily rotate 999 } logrotate-3.8.7/test/test-config.34.in0000664000076500007650000000006612033240657016575 0ustar hanzzhanzzcreate root root &DIR&/test.log { daily rotate 1 } logrotate-3.8.7/test/test-config.51.in0000664000076500007650000000021312176173107016571 0ustar hanzzhanzz/var/log/this_dir_does_not_exist/*log { size 1 missingok sharedscripts prerotate /usr/bin/some-program endscript } logrotate-3.8.7/test/test-config.10.in0000664000076500007650000000020312006205556016557 0ustar hanzzhanzzcreate &DIR&/test.log { daily rotate 3 compress delaycompress create mailfirst mail user@myhost.org } logrotate-3.8.7/test/test-config.5.in0000664000076500007650000000034412006205556016511 0ustar hanzzhanzzcreate &DIR&/test.log &DIR&/anothertest.log { monthly rotate 1 mail user@myhost.org maillast sharedscripts postrotate touch scriptout echo $(cat scriptout) foo > foo mv foo scriptout endscript } logrotate-3.8.7/test/test-config.26.in0000664000076500007650000000010112006205556016563 0ustar hanzzhanzzcreate &DIR&/test.log { waeekly maxsize 4 rotate 1 }logrotate-3.8.7/test/test-config.14.in0000664000076500007650000000012112006205556016562 0ustar hanzzhanzzcreate &DIR&/test.log { daily dateext dateformat .%Y-%m-%d rotate 1 } logrotate-3.8.7/test/test-config.40.in0000664000076500007650000000012512137672476016603 0ustar hanzzhanzzcreate tabooext .v,.sample,.unused,~ tabooext + .x, .y, .z include &DIR&/testingdir logrotate-3.8.7/test/test-config.46.in0000664000076500007650000000006712155334214016577 0ustar hanzzhanzzcreate &DIR&/test.log { daily rotate 999 dateext } logrotate-3.8.7/test/test-config.44.in0000664000076500007650000000075112155334214016575 0ustar hanzzhanzzcreate &DIR&/test.log &DIR&/test2.log { size 5 rotate 1 nosharedscripts prerotate touch scriptout IFS=$(/bin/echo -en "\n\b") echo -n "\"" >> scriptout for f in "$1" do echo -n $(basename $f) >> scriptout echo -n ";" >> scriptout done endscript postrotate touch scriptout IFS=$(/bin/echo -en "\n\b") for f in "$1" do echo -n $(basename $f) >> scriptout echo -n ";" >> scriptout done echo -n "\"" >> scriptout endscript } logrotate-3.8.7/test/test-config.8.in0000664000076500007650000000033112006205556016510 0ustar hanzzhanzzcreate compress &DIR&/test.log { monthly rotate 3 mail user@myhost.org mailfirst sharedscripts postrotate touch scriptout echo $(cat scriptout) foo > foo mv foo scriptout endscript } logrotate-3.8.7/test/test-config.28.in0000664000076500007650000000013012006205556016567 0ustar hanzzhanzzcreate #testing { on new line &DIR&/test.log { weekly maxsize 4 rotate 1 } logrotate-3.8.7/test/test-config.53.in0000664000076500007650000000007312225464270016576 0ustar hanzzhanzzcreate &DIR&/test.log { rotate 14 size 4096 missingok }logrotate-3.8.7/test/test-config.41.in0000664000076500007650000000045012137672476016605 0ustar hanzzhanzzcreate &DIR&/test*.log { size 5 rotate 1 nosharedscripts prerotate touch scriptout echo -n $(basename $1) >> scriptout echo -n ";" >> scriptout endscript postrotate touch scriptout echo -n $(basename $1) >> scriptout echo -n ";" >> scriptout endscript } logrotate-3.8.7/test/test-config.1.in0000664000076500007650000000020412006205556016500 0ustar hanzzhanzzcreate &DIR&/test.log { daily # note the white space after this line rotate 2 mail user@myhost.org maillast } logrotate-3.8.7/test/test-config.22.in0000664000076500007650000000006312006205556016566 0ustar hanzzhanzzcreate &DIR&/test*.log { daily rotate 1 } logrotate-3.8.7/test/test-config.24.in0000664000076500007650000000010412006205556016564 0ustar hanzzhanzzcreate &DIR&/test*.log { daily copytruncate rotate 1 } logrotate-3.8.7/test/test-config.30.in0000664000076500007650000000006712006205556016571 0ustar hanzzhanzzcreate &DIR&/test.log { daily rotate 999 dateext } logrotate-3.8.7/test/test-config.9.in0000664000076500007650000000033112006205556016511 0ustar hanzzhanzzcreate compress &DIR&/test.log { monthly rotate 0 mail user@myhost.org mailfirst sharedscripts postrotate touch scriptout echo $(cat scriptout) foo > foo mv foo scriptout endscript } logrotate-3.8.7/test/test-config.48.in0000664000076500007650000000006612155334214016600 0ustar hanzzhanzzcreate &DIR&/test.log { daily rotate 999 size 2 } logrotate-3.8.7/test/test0000775000076500007650000006746512225464270014525 0ustar hanzzhanzz#!/bin/bash ACL_TESTS=1 SELINUX_TESTS=0 LOGROTATE=../logrotate M="-m ./mailer" S="-s state" RLR="$LOGROTATE $M $S" # -- ACL - BEGIN -------------------------------- # We test if the ACLs tests should be done or not echo 1 > test.x setfacl -m u:nobody:rwx test.x 2>/dev/null if [ $? != 0 ]; then ACL_TESTS=0 echo "setfacl failed on this system. ACL tests will not be executed." fi rm -f test.x if [ $ACL_TESTS == 1 ]; then # It seems we can run the ACL tests, but was logrotate compiled WITH_ACL=yes ? # See the Makefile, "pretest" part, for more information if [ -f ./test.ACL ]; then ACL_TESTS=`cat ./test.ACL` if [ $ACL_TESTS == 0 ]; then echo "logrotate was NOT compiled with 'WITH_ACL=yes'. ACL tests will not be executed." fi fi fi # -- ACL - END ---------------------------------- # -- SELINUX - BEGIN -------------------------------- # We test if the ACLs tests should be done or not if type "selinuxenabled" > /dev/null && selinuxenabled; then SELINUX_TESTS=1 else echo "SELinux disabled. SELinux tests will not be executed." fi if [ $SELINUX_TESTS == 1 ]; then # It seems we can run the ACL tests, but was logrotate compiled WITH_ACL=yes ? # See the Makefile, "pretest" part, for more information if [ -f ./test.SELINUX ]; then SELINUX_TESTS=`cat ./test.SELINUX` if [ $SELINUX_TESTS == 0 ]; then echo "logrotate was NOT compiled with 'WITH_SELINUX=yes'. SELINUX tests will not be executed." fi fi fi if [ $SELINUX_TESTS == 1 ]; then # if logrotate_tmp_t, we can't continue with SELinux tests... touch .selinuxtest chcon --type=logrotate_tmp_t .selinuxtest 2>/dev/null if [ $? != 0 ]; then SELINUX_TESTS=0 echo "SELinux context 'logrotate_tmp_t' does not exist. SELinux tests will not be executed." fi rm -f .selinuxtest fi # -- SELINUX - END ---------------------------------- cleanup() { rm -f test*.log* anothertest*.log* state test-config. scriptout mail-out compress-args different*.log* rm -f $(ls | egrep '^test-config.[0-9]+$') [ -n "$1" ] && echo "Running test $1" return 0 } genconfig() { input=test-config.$1.in output=test-config.$1 user=$(id -u -n) group=$(id -g -n) sed "s,&DIR&,$PWD,g" < $input | sed "s,&USER&,$user,g" | sed "s,&GROUP&,$group,g" > $output config_crc=$(md5sum $output) } createlog() { num=$1 file=$2 cl_compressed=$3 case $num in 0) what=zero ;; 1) what=first ;; 2) what=second ;; 3) what=third ;; 4) what=fourth ;; 5) what=fifth ;; 6) what=sixth ;; 7) what=seventh ;; 8) what=eight ;; 9) what=ninth ;; *) exit 1 ;; esac echo $what > $file [ -n "$cl_compressed" ] && gzip -9 $file } createlogs() { base=$1 numlogs=$2 cls_compressed=$3 rm -f ${base}* num=0 while [ $num != $numlogs ]; do if [ $num = 0 ]; then createlog 0 $base else createlog $num ${base}.$num $cls_compressed fi num=`expr $num + 1` done } checkmail() { (echo -s $PWD/$1 user@myhost.org; echo $2) | diff -u - mail-out } checkoutput() { while read line; do set $line file=$1 co_compressed=$2 shift 2 fileother=`echo $line | awk '{print $1}'` expected=`echo $line | cut -s -d\ -f3-` if [ $file != $fileother ]; then echo "unexpected file $file'" >&2 exit 2 fi if [ ! -f $file ]; then echo "file $file does not exist" fi if [ -n "$co_compressed" -a "$co_compressed" != 0 ]; then contents=`gunzip -c $file` else contents=`cat $file` fi if [ "$contents" != "$expected" ]; then echo "file $file does not contain expected results (compressed $co_compressed, args $*)" >&2 echo contains: \'$contents\' echo expected: \'$expected\' exit 2 fi echo "$config_crc" | md5sum -c - 2>&1 > /dev/null if [ $? != 0 ]; then echo "config file $output has been altered: MD5 sum mismatch" exit 3 fi done } preptest() { base=$1 confignum=$2 numlogs=$3 pt_compressed=$4 rm -f $base* rm -f state genconfig $confignum createlogs $base $numlogs $pt_compressed } # we don't want any stuff left from previous runs cleanup 1 # ------------------------------- Test 1 ------------------------------------- # Without a log file, no rotations should occur preptest test.log 1 2 $RLR test-config.1 checkoutput < state </dev/null if [ $? != 0 ]; then echo "test.log.0 should have selinux context logrotate_tmp_t." exit 3 fi ls -Z anothertest.log.0|grep logrotate_tmp_t >/dev/null if [ $? == 0 ]; then echo "anothertest.log.0 should not have selinux context logrotate_tmp_t." exit 3 fi fi checkoutput < test.log $RLR test-config.10 --force if [ $SELINUX_TESTS == 1 ]; then ls -Z test.log.2.gz|grep logrotate_tmp_t >/dev/null if [ $? != 0 ]; then echo "test.log.2.gz should have selinux context logrotate_tmp_t." ls -Z test.log.2.gz exit 3 fi ls -Z test.log.1|grep logrotate_tmp_t >/dev/null if [ $? != 0 ]; then echo "test.log.1 should have selinux context logrotate_tmp_t." ls -Z test.log.1 exit 3 fi fi checkoutput < test.log $RLR test-config.16 if [ -f test.log.1 ]; then echo "file $file does exist!" exit 2 fi # log with 4 bytes should be rotated echo "zero" > test.log $RLR test-config.16 checkoutput <error.log grep "unexpected } (missing previous '{')" error.log >/dev/null if [ $? != 0 ]; then echo "No error printed, but there should be one." exit 3 fi rm error.log checkoutput <error.log if [ $? == 0 ]; then echo "Logrotate exited with 0 exit code, but it should not" fi grep "error running non-shared postrotate script for" error.log >/dev/null if [ $? != 0 ]; then echo "No error printed, but there should be one." exit 3 fi cleanup 20 # ------------------------------- Test 20 ------------------------------------ preptest test.log 20 1 $RLR test-config.20 --force 2>error.log if [ $? == 0 ]; then echo "Logrotate exited with 0 exit code, but it should not" fi grep "error running shared postrotate script for" error.log >/dev/null if [ $? != 0 ]; then echo "No error printed, but there should be one." exit 3 fi cleanup 21 # ------------------------------- Test 21 ------------------------------------ # different base name, so it should not find the file preptest differenttest.log 21 1 $RLR test-config.21 --force 2>error.log if [ $? != 0 ]; then echo "Logrotate exited with non-zero exit code, but it should not" fi cat error.log # grep "error running shared postrotate script for" error.log >/dev/null # if [ $? != 0 ]; then # echo "No error printed, but there should be one." # exit 3 # fi cleanup 22 # ------------------------------- Test 22 ------------------------------------ # different base name, so it should not find the file preptest differenttest.log 22 1 $RLR test-config.22 --force 2>error.log if [ $? == 0 ]; then echo "Logrotate exited with zero exit code, but it should not" fi grep "error: stat of" error.log >/dev/null if [ $? != 0 ]; then echo "No error printed, but there should be one." exit 3 fi cleanup 23 # ------------------------------- Test 23 ------------------------------------ # symlinks - symlinks rotation is not allowed for security reasons. preptest test.log.original 23 1 ln -s test.log.original test.log $RLR test-config.23 --force 2>error.log checkoutput </dev/null || true cleanup 24 # ------------------------------- Test 24 ------------------------------------ # symlinks 2 - now copytruncate is used, but symlinks rotation is not allowed for # security reasons. # since logrotate-3.8.2, we don't support symlinks rotation officially. preptest test.log.original 24 1 ln -s test.log.original test.log $RLR test-config.24 --force 2>error.log checkoutput </dev/null || true cleanup 25 # ------------------------------- Test 25 ------------------------------------ # If there is no '{' character after log files definition, error should be printed # and config file should be skipped preptest test.log 25 1 0 # log with 1 byte should not be rotated $RLR test-config.25 2>error.log grep "missing '{' after log files definition" error.log >/dev/null if [ $? != 0 ]; then echo "No error printed, but there should be one." exit 3 fi rm error.log checkoutput <error.log grep "unknown option" error.log >/dev/null if [ $? != 0 ]; then echo "No error printed, but there should be one." exit 3 fi rm error.log checkoutput < test.log-$DATESTRING $RLR test-config.30 --force checkoutput </dev/null if [ $? != 0 ]; then echo "Bad mode of test.log, should be 0600" exit 3 fi checkoutput </dev/null if [ $? != 0 ]; then echo "test.log must have user:nobody:rwx ACL" exit 3 fi getfacl test.log|grep "group::---" >/dev/null if [ $? != 0 ]; then echo "test.log must have group::--- ACL" exit 3 fi getfacl test.log.1|grep "user:nobody:rwx" >/dev/null if [ $? != 0 ]; then echo "test.log.1 must have user:nobody:rwx ACL" exit 3 fi getfacl test.log.1|grep "group::---" >/dev/null if [ $? != 0 ]; then echo "test.log.1 must have group::--- ACL" exit 3 fi checkoutput </dev/null if [ $? == 0 ]; then echo "test.log must not contain user:nobody:rwx" exit 3 fi getfacl test.log.1|grep "user:nobody:rwx" >/dev/null if [ $? != 0 ]; then echo "test.log.1 must contain user:nobody:rwx" exit 3 fi checkoutput <&1|grep "uid = 0 gid = 0" > /dev/null if [ $? != 0 ]; then echo "logrotate output must contain 'uid = 0 gid = 0'" exit 3 fi checkoutput </dev/null if [ $? != 0 ]; then echo "test.log must not contain user:nobody:rwx" exit 3 fi getfacl test.log.1|grep "user:nobody:rwx" >/dev/null if [ $? != 0 ]; then echo "test.log.1 must contain user:nobody:rwx" exit 3 fi checkoutput <error.log grep "unknown unit" error.log >/dev/null if [ $? != 0 ]; then echo "No error 'unknown unit' printed, but there should be one." exit 3 fi checkoutput <error.log grep "skipping" error.log >/dev/null if [ $? != 0 ]; then echo "No error 'skipping' printed, but there should be one." exit 3 fi checkoutput </dev/null if [ $? != 0 ]; then grep "test.log.1test2.log.1" scriptout >/dev/null if [ $? != 0 ]; then echo "ERROR: scriptout should contain 'test2.log.1test.log.1' or 'test.log.1test2.log.1'" exit 3 fi fi rm -f scriptout checkoutput <error.log grep "error running preremove script" error.log >/dev/null if [ $? != 0 ]; then echo "No error 'error running preremove script' printed, but there should be one." exit 3 fi # Check both possible orders grep "test2.log.1" scriptout >/dev/null if [ $? != 0 ]; then grep "test.log.1" scriptout >/dev/null if [ $? != 0 ]; then echo "ERROR: scriptout should contain 'test2.log.1' or 'test.log.1'" exit 3 fi fi rm -f scriptout checkoutput < ./testingdir/config.v echo 2 > ./testingdir/config.x $RLR test-config.40 --force rm -rf testingdir cleanup 41 # ------------------------------- Test 41 ------------------------------------ # Test that prerotate and postrotate scripts are called only when the log files # are actually rotated preptest test.log 41 1 echo x > test2.log $RLR test-config.41 checkoutput < test2.log $RLR test-config.42 checkoutput < test2.log $RLR test-config.43 # Check both possible orders grep "test2.log;test2.log;test.log;test.log;" scriptout >/dev/null if [ $? != 0 ]; then grep "test.log;test.log;test2.log;test2.log;" scriptout >/dev/null if [ $? != 0 ]; then echo "ERROR: scriptout should contain 'test2.log;test2.log;test.log;test.log;' or 'test.log;test.log;test2.log;test2.log;'" exit 3 fi fi rm -f scriptout checkoutput <error.log grep "error: stat of" error.log >/dev/null if [ $? != 0 ]; then echo "No error printed, but there should be one." exit 3 fi rm -f error.log checkoutput <error.log grep "error: stat of" error.log >/dev/null if [ $? != 0 ]; then echo "No error printed, but there should be one." exit 3 fi rm -f error.log checkoutput < state << EOF logrotate state -- version 1 "$PWD/test.log" 2000-1-1 "$PWD/test2.l EOF DATESTRING=$(/bin/date +%Y%m%d) $RLR test-config.46 2>error.log grep "error: bad line 3 in state file state" error.log >/dev/null if [ $? != 0 ]; then echo "No error printed, but there should be one." exit 3 fi rm -f error.log checkoutput < state << EOF logrotate state -- version 2 EOF chcon --type=logrotate_tmp_t state $RLR test-config.47 ls -Z state|grep logrotate_tmp_t >/dev/null if [ $? != 0 ]; then echo "state file should have selinux context logrotate_tmp_t." exit 3 fi checkoutput < state << EOF logrotate state -- version 2 EOF setfacl -m u:nobody:rwx state $RLR test-config.48 getfacl state|grep "user:nobody:rwx" >/dev/null if [ $? != 0 ]; then echo "state file must have acls user:nobody:rwx" exit 3 fi checkoutput < state << EOF logrotate state -- version 2 "test.log" 2012-8-19 EOF $RLR test-config.49 checkoutput </dev/null) HOURAGO=$(/bin/date "+%Y-%-m-%-d-%-H" --date "1 hour ago" 2>/dev/null) GNUDATE=$? # --force to trigger rotation $RLR test-config.50 --force checkoutput < test.log rm -f test.log-$DATESTRING $RLR test-config.50 checkoutput < state << EOF logrotate state -- version 2 "/var/log/httpd/backend_error_log" 2013-6-16 "/var/log/tokyotyrant/*.log" 2011-5-30 "/var/log/mailman/digest" 2011-5-30 "/var/log/piranha/piranha-gui-access" 2011-5-30 "/var/log/boincerr.log" 2011-5-30 "/var/log/btmp" 2013-7-9 "/var/log/httpd/a_log" 2011-11-15 "/var/log/cups/*_log" 2012-7-19 "/var/log/rabbitmq/*.log" 2011-5-30 "/var/log/func/func.log" 2011-11-17 "/var/log/wtmp" 2013-7-9 "/var/log/glusterfs/*glusterd.vol.log" 2011-11-17 "/var/log/imapd.log" 2011-5-30 "/var/log/cobbler/cobbler.log" 2011-11-6 "/var/log/httpd/ssl_access_log" 2013-3-27 "/var/log/mrepo.log" 2011-5-30 EOF $RLR test-config.51 if [ $? != 0 ]; then echo "logrotate ended with non-zero exit code (probably crashed)" exit 3 fi cleanup 52 # ------------------------------- Test 52 ------------------------------------ # sharedscripts are not run if the first log file does not exist preptest test.log 52 1 0 $RLR test-config.52 checkoutput < compress-args gzip $*logrotate-3.8.7/test/test-config.3.in0000664000076500007650000000027512006205556016512 0ustar hanzzhanzzcreate &DIR&/test*.log { monthly rotate 1 mail user@myhost.org maillast postrotate touch scriptout echo $(cat scriptout) foo > foo mv foo scriptout endscript } logrotate-3.8.7/test/test-config.25.in0000664000076500007650000000015312006205556016571 0ustar hanzzhanzzcreate # missing { is OK, we're testing parsing here &DIR&/test.log weekly maxsize 4 rotate 1 logrotate-3.8.7/test/test-config.27.in0000664000076500007650000000017312006205556016575 0ustar hanzzhanzzcreate &DIR&/test.log { daily rotate 999 compress dateext ifempty delaycompress mailfirst mail user@myhost.org } logrotate-3.8.7/test/test-config.45.in0000664000076500007650000000073612137672476016620 0ustar hanzzhanzzcreate &DIR&/test.log &DIR&/test2.log { size 5 rotate 1 sharedscripts prerotate touch scriptout IFS=$(echo -en "\n\b") echo -n "\"" >> scriptout for f in "$1" do echo -n $(basename $f) >> scriptout echo -n ";" >> scriptout done endscript postrotate touch scriptout IFS=$(echo -en "\n\b") for f in "$1" do echo -n $(basename $f) >> scriptout echo -n ";" >> scriptout done echo -n "\"" >> scriptout endscript } logrotate-3.8.7/test/test-config.2.in0000664000076500007650000000012412006205556016502 0ustar hanzzhanzz"&DIR&/test.log" { monthly rotate 2 mail user@myhost.org maillast } logrotate-3.8.7/test/test-config.39.in0000664000076500007650000000033612137672476016617 0ustar hanzzhanzzcreate &DIR&/test*.log { monthly rotate 0 mail user@myhost.org maillast sharedscripts preremove touch scriptout echo -n $(basename $*) >> scriptout this should trigger an error endscript } logrotate-3.8.7/test/test-config.49.in0000664000076500007650000000006412155334214016577 0ustar hanzzhanzzcreate &DIR&/test.log { daily rotate 4 size 2 } logrotate-3.8.7/test/test-config.36.in0000664000076500007650000000007712137672476016616 0ustar hanzzhanzz# bad unit 'x' &DIR&/test.log { daily size 300x rotate 1 } logrotate-3.8.7/test/test-config.31.in0000664000076500007650000000010212033240657016561 0ustar hanzzhanzzcreate 0600 &USER& &GROUP& &DIR&/test.log { daily rotate 999 } logrotate-3.8.7/test/mailer0000775000076500007650000000006212006205556014770 0ustar hanzzhanzz#!/bin/bash echo "$*" > mail-out cat >> mail-out logrotate-3.8.7/test/test-config.19.in0000664000076500007650000000013012006205556016567 0ustar hanzzhanzzcreate &DIR&/test*.log { daily rotate 1 postrotate exit 1 endscript } logrotate-3.8.7/test/test-config.43.in0000664000076500007650000000045012137672476016607 0ustar hanzzhanzzcreate &DIR&/test*.log { size 5 rotate 1 nosharedscripts prerotate touch scriptout echo -n $(basename $1) >> scriptout echo -n ";" >> scriptout endscript postrotate touch scriptout echo -n $(basename $1) >> scriptout echo -n ";" >> scriptout endscript } logrotate-3.8.7/test/test-config.33.in0000664000076500007650000000006312033240657016571 0ustar hanzzhanzzcreate 0600 &DIR&/test.log { daily rotate 999 } logrotate-3.8.7/test/test-config.20.in0000664000076500007650000000015212006205556016563 0ustar hanzzhanzzcreate &DIR&/test*.log { daily rotate 1 sharedscripts postrotate exit 1 endscript } logrotate-3.8.7/test/test-config.4.in0000664000076500007650000000031712006205556016510 0ustar hanzzhanzzcreate &DIR&/test*.log { monthly rotate 1 mail user@myhost.org maillast sharedscripts postrotate touch scriptout echo $(cat scriptout) foo > foo mv foo scriptout endscript } logrotate-3.8.7/test/test-config.11.in0000664000076500007650000000033012006205556016561 0ustar hanzzhanzzcreate compress &DIR&/test.log { monthly rotate 1 mail user@myhost.org maillast sharedscripts postrotate touch scriptout echo $(cat scriptout) foo > foo mv foo scriptout endscript } logrotate-3.8.7/test/test-config.13.in0000664000076500007650000000011512006205556016564 0ustar hanzzhanzzcreate &DIR&/test.log { monthly rotate 1 olddir &DIR&/testdir } logrotate-3.8.7/test/test-config.35.in0000664000076500007650000000007312033240657016574 0ustar hanzzhanzzcreate &USER& &GROUP& &DIR&/test.log { daily rotate 1 } logrotate-3.8.7/test/test-config.7.in0000664000076500007650000000036012006205556016511 0ustar hanzzhanzzcreate &DIR&/test.log &DIR&/anothertest.log { monthly rotate 3 start 6 mail user@myhost.org maillast sharedscripts postrotate touch scriptout echo $(cat scriptout) foo > foo mv foo scriptout endscript } logrotate-3.8.7/test/test-config.52.in0000664000076500007650000000030112176173107016570 0ustar hanzzhanzzcreate /var/log/does_not_exist.log &DIR&/test.log { rotate 14 size 2 missingok sharedscripts postrotate touch scriptout echo $(cat scriptout) foo > foo mv foo scriptout endscript }logrotate-3.8.7/test/test-config.50.in0000664000076500007650000000006612155334214016571 0ustar hanzzhanzzcreate &DIR&/test.log { hourly dateext rotate 4 } logrotate-3.8.7/test/test-config.6.in0000664000076500007650000000036012006205556016510 0ustar hanzzhanzzcreate &DIR&/test.log &DIR&/anothertest.log { monthly rotate 1 start 0 mail user@myhost.org maillast sharedscripts postrotate touch scriptout echo $(cat scriptout) foo > foo mv foo scriptout endscript } logrotate-3.8.7/test/test-config.42.in0000664000076500007650000000073012155334214016570 0ustar hanzzhanzzcreate &DIR&/test*.log { size 5 rotate 1 sharedscripts prerotate touch scriptout IFS=$(/bin/echo -en "\n\b") echo -n "\"" >> scriptout for f in "$1" do echo -n $(basename $f) >> scriptout echo -n ";" >> scriptout done endscript postrotate touch scriptout IFS=$(/bin/echo -en "\n\b") for f in "$1" do echo -n $(basename $f) >> scriptout echo -n ";" >> scriptout done echo -n "\"" >> scriptout endscript } logrotate-3.8.7/test/test-config.23.in0000664000076500007650000000006312006205556016567 0ustar hanzzhanzzcreate &DIR&/test*.log { daily rotate 1 } logrotate-3.8.7/test/test-config.18.in0000664000076500007650000000022212006205556016570 0ustar hanzzhanzzcreate # tests more compressoptions &DIR&/test.log { compress compresscmd ./compress compressoptions -f -9 weekly rotate 1 } logrotate-3.8.7/test/test-config.12.in0000664000076500007650000000010712006205556016564 0ustar hanzzhanzzcreate &DIR&/test.log { monthly rotate 1 olddir testdir } logrotate-3.8.7/test/test-config.21.in0000664000076500007650000000010112006205556016556 0ustar hanzzhanzzcreate &DIR&/test*.log { daily rotate 1 missingok } logrotate-3.8.7/test/test-config.47.in0000664000076500007650000000006712155334214016600 0ustar hanzzhanzzcreate &DIR&/test.log { daily rotate 999 dateext } logrotate-3.8.7/logrotate.conf.50000664000076500007650000000002512006205557015624 0ustar hanzzhanzz.so man8/logrotate.8 logrotate-3.8.7/CHANGES0000664000076500007650000002565612225464270013630 0ustar hanzzhanzz3.8.6 -> 3.8.7 - Fixed --force/-f option handling together with "size" directive (3.8.5 regression). - Use "logrotate_tmp_t" context for SELinux tests and if this context does not exist, skip SELinux related tests. 3.8.5 -> 3.8.6 - Fixed memory corruption caused by rotation directory which does not exist with "sharedscripts" together with "prerotate" script. 3.8.4 -> 3.8.5 - Improved rotation during daylight saving time and between timezone changes. - Fixed ACL setting problem caused by ext3 erroneously reporting ENOSYS instead of ENOSUP. - Do not continue with rotation if state file is corrupted. - Make logrotate.status creation atomic. - Allow "hourly" rotation. See manpage for more information. - Use "/bin/echo" in tests. Fixes tests execution in Dash. - Do no try to parse config files bigger than 16MB. - Improved manpage consistency and formatting. - Fix race condition between acl_set_fd() and fchmod(). 3.8.3 -> 3.8.4 - Added --version command line option - Disable ACL tests if logrotate is not compiled WITH_ACL support or if ACLs are not supported by the system running tests - Disable SELinux tests if logrotate is not compiled WITH_SELINUX support or if SELinux is not supported by the system running tests - Fixed bug which prevented skipping particular log file config if the config contained errors. - Fixed skipping of configs containing firstaction/lastaction scripts with '}' character in case of error before these scripts. - Support also 'K' unit for *size directives. - Added preremove option to let admin to do something with the old logs before they are removed by logrotate. - Fixed possible loop in tabooext parsing. - Move code to set SELinux context before compressLogFile calls to create compressed log files with the proper context. - Call prerotate/postrotate script only for really rotated files in nosharedscripts mode (as stated in man page). 3.8.2 -> 3.8.3 - Fixed setting "size" bigger than 4GB on 32bit architectures - Do not overwrite mode set by "create" option when using ACL. "create" directive is now not mixed up with ACLs. If you use "create" in config file and log file has some ACLs set, ACLs are not kept and are overwritten by the mode set in "create" directive. - Mode argument in "create" directive can be omitted. Only owner and group is set in this case. Check man page for more info. 3.8.1 -> 3.8.2 - show error and ignore config if '{' is not present after log files declaration - support whitespaces in compressoptions directive - support for tilde expansion in config files - 'su' directive does not affect script execution - scripts are executed as a root if 'su' directive is present - fixed mail sending for 'mailfirst', 'dateext' and 'delaycompress' combination - do not use gzip/gunzip from /usr/local on Solaris - add O_NOFOLLOW when opening files as safeguard against symlink tricks. Symlinks rotation is now officially unsupported. It didn't work as expected in the past anyway. - do not run external programs with uid != euid - fixed potential bad-free when ACL is used - Do not include alloca.h on NetBSD, since alloca() is declared in stdlib.h there - 13 new tests added 3.8.0 -> 3.8.1 - fixed 1 memory leak in prerotateSingleLog - another fixes for Solaris - fixed HP-UX compilation and default config - do not redirect logrotate errors to /dev/null in cron script - fixed "size" directive parsing - handle situation when acl_get_fd is supported, but acl_set_fd is not - added "maxsize" directive (see man page) 3.7.9 -> 3.8.0 - added "dateyesterday" option (see man page) - fixed crash when config file had exactly 4096*N bytes - added WITH_ACL make option to link against -lacl and preserve ACLs during rotation - added "su" option to define user/group for rotation. Logrotate now skips directories which are world writable or writable by group which is not "root" unless "su" directive is used. - fixed CVE-2011-1098: race condition by creation of new files - fixed possible shell injection when using "shred" directive (CVE-2011-1154) - fixed escaping of file names within 'write state' action (CVE-2011-1155) - better 'size' directive description - fixed possible buffer-overflow when reading config files - NetBSD/FreeBSD compilation fixes - Solaris compilation fixes 3.7.8 -> 3.7.9 - fix building on Solaris (patch by András Szilárd) - don't copy config files on the stack -- mmap them instead (fixes segfaults with too large/invalid config files) - symlinked conf file man page as requested by Fedora guidelines (thanks to Ivana Hutarova Varekova) - cron script logrotate.cron redirects output to /dev/null - added rotating (copying) non-writable, readable files (patch by Henrique Martins) - fixed missingok problem with globs (taken from the Debian patches by Ted Percival ) - fixed bug when log files could be removed even there was some error in rotation process. - allow setting size greater than 4.2GB in configuration file - pass currently rotated file to postrotate/prerotate script in nosharedscripts mode - added new TabooExts: ".disabled", ".dpkg-old", ".dpkg-dist", ".dpkg-new", ".cfsaved", ".ucf-old", ".ucf-dist", ".ucf-new" (taken from the Debian patches by Paul Martin ) - Don't change utime atime/mtime when compressing files (taken from the Debian patches by Paul Martin ) - Better *rotate scripts parser. (taken from the Debian patches) - Allow 'include' directive in log file definitions 3.7.7 -> 3.7.8 - do not exit on status file errors - limit config file inclusion nesting - use hashes for status file handling (patch by Petr Tesarik and Leonardo Chiquitto) - dateformat to allow unixtime (patch by Sami Kerola ) - manual page corrections (taken from the Debian patches by Paul Martin ) 3.7.6 -> 3.7.7 - dateformat - fix possible buffer overflows in strings handling - various minor bugfixes - change logInfo handling (patches by Leonardo Chiquitto) 3.7.5 -> 3.7.6 - patches from Leonardo Chiquitto that fix compile warnings - examples/logrotate-default: add btmp rotation, dateext - update man page - tabooext honor wildcards - fix selinux support with dateext 3.7.1 -> 3.7.5 - import Fedora patches - add option to use shred for deleting files, patch by Peter Eckersley - ignore .cfsaved files - bugfixes 3.7 -> 3.7.1: - Fix sending mails and running scripts after the system() -> execve() changes - Preserve file attributes when compressing files (original patch by Daniel Himler) 3.6.8 -> 3.7: - always use compressext for the extension for compressed files; before compresscmd and compressext had to agree - moved all compression to one code block - compression, scripts don't use system() anymore - compress and maillast didn't work together properly - delaycompress and mailfirst didn't work properly - don't use system() for mailing (or uncompressing) logs anymore - use "-s" for speciying the subjected of mailed logs 3.5.4 -> 3.6: - See .spec file for changes 3.5.3 -> 3.5.4: - %defattr(-,root,root) in specfile 3.5.2 -> 3.5.3: - patch /tmp file race condition problem, use mkstemp; Thanks go to Solar Designer 3.5 -> 3.5.2: - added .swp and .rpmnew to default taboo list 3.5 -> 3.5.1: - handle state dates in the future a bit more sanely 3.4 -> 3.5: - multiple file names/patterns may be given for a single entry - fixed mistake in when logs were uncompressed before mailing 3.3.2 -> 3.4: - added sharedscripts/nosharedscripts - added simple testbed - quote filenames in state file to allow proper rotation of files with spaces in the name -- this changes the version number of the state file! - ignore white space at end of line 3.3.1 -> 3.3.2: - don't rotate lastlog 3.3 -> 3.3.1: - support gzipped man pages 3.2 -> 3.3: - added "mailfirst" and "maillast" flags (based on Tim Wall's patch) - documented "extension" flag - "rotate 0" gives proper script and mail behavior 3.1 -> 3.2: - create wtmp with correct perms 3.0 -> 3.1: - fixed small alloca() - added missingok flag - use popt to display usage message - handle /some/file { } in config file 2.9 -> 3.0 - updates for glibc 2.1 2.8 -> 2.9: - fixed a bug parsing lines where { immediately follows the filename - allow log file patterns to be placed in double quotes, which allows spaces in names - complain about missing log files (John Van Essen) 2.7 -> 2.8: - changes for glibc 2.1 (Cristian Gafton) 2.6 -> 2.7: - updated man page to include --force (Simon Mudd) - invoke scripts via /bin/sh rather then relying on /tmp execute semantics (Philip Guenther) - added "extension" option for forcing a file extension after rotation (Rob Hagopian) 2.5 -> 2.6: - added nodelaycompress flag (from Jos Vos) - added copytruncate, nocopytruncate flag (from Jos Vos) - removed umask handling; explicitly use fchmod() insteadmoved umask - added --force option (Simon Mudd) - moved /bin/mail to MAIL_COMMAND define (Simon Mudd) - fixed segv caused by overly long filenames - switched from getopt_long to popt 2.4 -> 2.5: - set the umask of the process to 0, letting open() create processes with the proper permissions - added delaycompress flag (from Jos Vos) - fixed how old logs are finally removed when an olddir is specified (Jos Vos) - added nomail option - added mail, nomail documentation to man page - added the tabooext directive - fixed problem in globbing 2.3 -> 2.4: - glob log names in config file - added ,v to taboo list - fixed bug w/ create parsing - use an int rather then a mode_t when parsing create entries as sscanf requires it 2.2 -> 2.3: - fill in all of last rotated structure (this probable isn't really necessary but it's a bit cleaner and will avoid future problems); - fixed .spec file 2.1 -> 2.2: - If a file is rotated and we have no state information for it, right out the current time. - Weekly rotation happens when the current weekday is less then the weekday of the last rotation or more then a week has elapsed between the last rotation and now - Monthly rotation happens when the current month is different from the last month or the current year is different from the last year - (these were contributed and suggested by Ronald Wahl) - added olddir/noolddir options - added ifempty/notifempty options - ignore nonnormal files when reading config files from a directory - (these were suggested and originally implemented by Henning Schmiedehausen) - updated the man page to reflect these changes - made "make install" accept PREFIX argument - added .spec file to tarball 2.0.2 -> 2.1: - Don't output state information for logs that have never been rotated (better then 1900-1-0) - Accept 1900-1-0 as time 0 2.0.1 -> 2.0.2: - I have no idea :-( 2.0 -> 2.0.1: - ignore files in included directories which end with ~, .rpmorig, or .rpmsave logrotate-3.8.7/examples/0000775000076500007650000000000012006205557014433 5ustar hanzzhanzzlogrotate-3.8.7/examples/logrotate.cron0000664000076500007650000000026412006205557017320 0ustar hanzzhanzz#!/bin/sh /usr/sbin/logrotate /etc/logrotate.conf EXITVALUE=$? if [ $EXITVALUE != 0 ]; then /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]" fi exit 0 logrotate-3.8.7/examples/logrotate-default0000664000076500007650000000122612006205557020001 0ustar hanzzhanzz# see "man logrotate" for details # rotate log files weekly weekly # keep 4 weeks worth of backlogs rotate 4 # create new (empty) log files after rotating old ones create # use date as a suffix of the rotated file dateext # uncomment this if you want your log files compressed #compress # RPM packages drop log rotation information into this directory include /etc/logrotate.d # no packages own wtmp and btmp -- we'll rotate them here /var/log/wtmp { monthly create 0664 root utmp minsize 1M rotate 1 } /var/log/btmp { missingok monthly create 0600 root utmp rotate 1 } # system-specific logs may be also be configured here. logrotate-3.8.7/basenames.c0000664000076500007650000000157212006205557014724 0ustar hanzzhanzz#include #include #include "basenames.h" /* Return NAME with any leading path stripped off. */ char *ourBaseName(char *name) { char *base; base = strrchr(name, '/'); return base ? base + 1 : name; } static void stripTrailingSlashes(char *path) { char *last; last = path + strlen(path) - 1; while (last > path && *last == '/') *last-- = '\0'; } char *ourDirName(char *origname) { char *slash; char *name; name = strdup(origname); stripTrailingSlashes(name); slash = strrchr(name, '/'); if (!slash) { /* No slash, must be current directory */ free(name); /* strdup used, as the return value will be free()ed at some point */ return strdup("."); } else { /* Remove any trailing slashes and final element. */ while (slash > name && *slash == '/') --slash; slash[1] = '\0'; return name; } } logrotate-3.8.7/config.c0000664000076500007650000012435212176173107014240 0ustar hanzzhanzz#include /* Alloca is defined in stdlib.h in NetBSD */ #ifndef __NetBSD__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "basenames.h" #include "log.h" #include "logrotate.h" #if !defined(GLOB_ABORTED) && defined(GLOB_ABEND) #define GLOB_ABORTED GLOB_ABEND #endif #define REALLOC_STEP 10 #if defined(SunOS) #include #if !defined(isblank) #define isblank(c) ( (c) == ' ' || (c) == '\t' ) ? 1 : 0 #endif #endif #ifdef __hpux #include "asprintf.c" #endif #if !defined(asprintf) && !defined(_FORTIFY_SOURCE) #include int asprintf(char **string_ptr, const char *format, ...) { va_list arg; char *str; int size; int rv; va_start(arg, format); size = vsnprintf(NULL, 0, format, arg); size++; va_start(arg, format); str = malloc(size); if (str == NULL) { va_end(arg); /* * Strictly speaking, GNU asprintf doesn't do this, * but the caller isn't checking the return value. */ fprintf(stderr, "failed to allocate memory\\n"); exit(1); } rv = vsnprintf(str, size, format, arg); va_end(arg); *string_ptr = str; return (rv); } #endif #if !defined(strndup) char *strndup(const char *s, size_t n) { size_t nAvail; char *p; if(!s) return NULL; /* min() */ nAvail = strlen(s) + 1; if ( (n + 1) < nAvail) nAvail = n + 1; p = malloc(nAvail); if (!p) return NULL; memcpy(p, s, nAvail); p[nAvail - 1] = 0; return p; } #endif enum { STATE_DEFAULT = 2, STATE_SKIP_LINE = 4, STATE_DEFINITION_END = 8, STATE_SKIP_CONFIG = 16, STATE_LOAD_SCRIPT = 32, STATE_ERROR = 64, }; static char *defTabooExts[] = { ".rpmsave", ".rpmorig", "~", ",v", ".disabled", ".dpkg-old", ".dpkg-dist", ".dpkg-new", ".cfsaved", ".ucf-old", ".ucf-dist", ".ucf-new", ".rpmnew", ".swp", ".cfsaved", ".rhn-cfg-tmp-*" }; static int defTabooCount = sizeof(defTabooExts) / sizeof(char *); /* I shouldn't use globals here :-( */ static char **tabooExts = NULL; int tabooCount = 0; static int glob_errno = 0; static int readConfigFile(const char *configFile, struct logInfo *defConfig); static int globerr(const char *pathname, int theerr); static char *isolateLine(char **strt, char **buf, size_t length) { char *endtag, *start, *tmp; start = *strt; endtag = start; while (endtag - *buf < length && *endtag != '\n') { endtag++;} if (endtag - *buf > length) return NULL; tmp = endtag - 1; while (isspace(*endtag)) endtag--; char *key = strndup(start, endtag - start + 1); *strt = tmp; return key; } static char *isolateValue(const char *fileName, int lineNum, char *key, char **startPtr, char **buf, size_t length) { char *chptr = *startPtr; while (chptr - *buf < length && isblank(*chptr)) chptr++; if (chptr - *buf < length && *chptr == '=') { chptr++; while ( chptr - *buf < length && isblank(*chptr)) chptr++; } if (chptr - *buf < length && *chptr == '\n') { message(MESS_ERROR, "%s:%d argument expected after %s\n", fileName, lineNum, key); return NULL; } *startPtr = chptr; return isolateLine(startPtr, buf, length); } static char *isolateWord(char **strt, char **buf, size_t length) { char *endtag, *start; start = *strt; while (start - *buf < length && isblank(*start)) start++; endtag = start; while (endtag - *buf < length && isalpha(*endtag)) { endtag++;} if (endtag - *buf > length) return NULL; char *key = strndup(start, endtag - start); *strt = endtag; return key; } static char *readPath(const char *configFile, int lineNum, char *key, char **startPtr, char **buf, size_t length) { char *chptr; char *start = *startPtr; char *path; wchar_t pwc; size_t len; if ((start = isolateValue(configFile, lineNum, key, startPtr, buf, length)) != NULL) { chptr = start; while( (len = mbrtowc(&pwc, chptr, strlen(chptr), NULL)) != 0 && strlen(chptr) != 0) { if( len == (size_t)(-1) || len == (size_t)(-2) || !iswprint(pwc) || iswblank(pwc) ) { message(MESS_ERROR, "%s:%d bad %s path %s\n", configFile, lineNum, key, start); return NULL; } chptr += len; } /* while (*chptr && isprint(*chptr) && *chptr != ' ') chptr++; if (*chptr) { message(MESS_ERROR, "%s:%d bad %s path %s\n", configFile, lineNum, key, start); return NULL; } */ path = strdup(start); free(start); return path; } else return NULL; } static char *readAddress(const char *configFile, int lineNum, char *key, char **startPtr, char **buf, size_t length) { char *endtag, *chptr; char *start = *startPtr; char *address; if ((endtag = isolateValue(configFile, lineNum, key, startPtr, buf, length)) != NULL) { chptr = endtag; while (*chptr && isprint(*chptr) && *chptr != ' ') { chptr++; } if (*chptr) { message(MESS_ERROR, "%s:%d bad %s address %s\n", configFile, lineNum, key, start); return NULL; } address = strdup(endtag); free(endtag); return address; } else return NULL; } static int checkFile(const char *fname) { int i; char *pattern; /* Check if fname is '.' or '..'; if so, return false */ if (fname[0] == '.' && (!fname[1] || (fname[1] == '.' && !fname[2]))) return 0; /* Check if fname is ending in a taboo-extension; if so, return false */ for (i = 0; i < tabooCount; i++) { if (asprintf(&pattern, "*%s", tabooExts[i]) < 0) { message(MESS_FATAL, "failed to allocate taboo pattern memory\n"); } if (!fnmatch(pattern, fname, 0)) { free(pattern); message(MESS_DEBUG, "Ignoring %s, because of %s ending\n", fname, tabooExts[i]); return 0; } } free(pattern); /* All checks have been passed; return true */ return 1; } /* Used by qsort to sort filelist */ static int compar(const void *p, const void *q) { return strcoll(*((char **) p), *((char **) q)); } /* Free memory blocks pointed to by pointers in a 2d array and the array itself */ static void free_2d_array(char **array, int lines_count) { int i; for (i = 0; i < lines_count; ++i) free(array[i]); free(array); } static void copyLogInfo(struct logInfo *to, struct logInfo *from) { memset(to, 0, sizeof(*to)); if (from->oldDir) to->oldDir = strdup(from->oldDir); to->criterium = from->criterium; to->threshhold = from->threshhold; to->minsize = from->minsize; to->maxsize = from->maxsize; to->rotateCount = from->rotateCount; to->rotateAge = from->rotateAge; to->logStart = from->logStart; if (from->pre) to->pre = strdup(from->pre); if (from->post) to->post = strdup(from->post); if (from->first) to->first = strdup(from->first); if (from->last) to->last = strdup(from->last); if (from->preremove) to->preremove = strdup(from->preremove); if (from->logAddress) to->logAddress = strdup(from->logAddress); if (from->extension) to->extension = strdup(from->extension); if (from->compress_prog) to->compress_prog = strdup(from->compress_prog); if (from->uncompress_prog) to->uncompress_prog = strdup(from->uncompress_prog); if (from->compress_ext) to->compress_ext = strdup(from->compress_ext); to->flags = from->flags; to->shred_cycles = from->shred_cycles; to->createMode = from->createMode; to->createUid = from->createUid; to->createGid = from->createGid; to->suUid = from->suUid; to->suGid = from->suGid; if (from->compress_options_count) { poptDupArgv(from->compress_options_count, from->compress_options_list, &to->compress_options_count, &to->compress_options_list); } if (from->dateformat) to->dateformat = strdup(from->dateformat); } static void freeLogInfo(struct logInfo *log) { free(log->pattern); free_2d_array(log->files, log->numFiles); free(log->oldDir); free(log->pre); free(log->post); free(log->first); free(log->last); free(log->preremove); free(log->logAddress); free(log->extension); free(log->compress_prog); free(log->uncompress_prog); free(log->compress_ext); free(log->compress_options_list); free(log->dateformat); } static struct logInfo *newLogInfo(struct logInfo *template) { struct logInfo *new; if ((new = malloc(sizeof(*new))) == NULL) return NULL; copyLogInfo(new, template); TAILQ_INSERT_TAIL(&logs, new, list); numLogs++; return new; } static void removeLogInfo(struct logInfo *log) { if (log == NULL) return; freeLogInfo(log); TAILQ_REMOVE(&logs, log, list); numLogs--; } static void freeTailLogs(int num) { message(MESS_DEBUG, "removing last %d log configs\n", num); while (num--) removeLogInfo(TAILQ_LAST(&logs, logInfoHead)); } static int readConfigPath(const char *path, struct logInfo *defConfig) { struct stat sb; int here, oldnumlogs, result = 1; struct logInfo defConfigBackup; if (stat(path, &sb)) { message(MESS_ERROR, "cannot stat %s: %s\n", path, strerror(errno)); return 1; } if (S_ISDIR(sb.st_mode)) { char **namelist, **p; struct dirent *dp; int files_count, i; DIR *dirp; here = open(".", O_RDONLY); if ((dirp = opendir(path)) == NULL) { message(MESS_ERROR, "cannot open directory %s: %s\n", path, strerror(errno)); close(here); return 1; } files_count = 0; namelist = NULL; while ((dp = readdir(dirp)) != NULL) { if (checkFile(dp->d_name)) { /* Realloc memory for namelist array if necessary */ if (files_count % REALLOC_STEP == 0) { p = (char **) realloc(namelist, (files_count + REALLOC_STEP) * sizeof(char *)); if (p) { namelist = p; memset(namelist + files_count, '\0', REALLOC_STEP * sizeof(char *)); } else { free_2d_array(namelist, files_count); closedir(dirp); close(here); message(MESS_ERROR, "cannot realloc: %s\n", strerror(errno)); return 1; } } /* Alloc memory for file name */ if ((namelist[files_count] = (char *) malloc(strlen(dp->d_name) + 1))) { strcpy(namelist[files_count], dp->d_name); files_count++; } else { free_2d_array(namelist, files_count); closedir(dirp); close(here); message(MESS_ERROR, "cannot realloc: %s\n", strerror(errno)); return 1; } } } closedir(dirp); if (files_count > 0) { qsort(namelist, files_count, sizeof(char *), compar); } else { close(here); return 0; } if (chdir(path)) { message(MESS_ERROR, "error in chdir(\"%s\"): %s\n", path, strerror(errno)); close(here); free_2d_array(namelist, files_count); return 1; } for (i = 0; i < files_count; ++i) { assert(namelist[i] != NULL); oldnumlogs = numLogs; copyLogInfo(&defConfigBackup, defConfig); if (readConfigFile(namelist[i], defConfig)) { message(MESS_ERROR, "found error in file %s, skipping\n", namelist[i]); freeTailLogs(numLogs - oldnumlogs); freeLogInfo(defConfig); copyLogInfo(defConfig, &defConfigBackup); freeLogInfo(&defConfigBackup); continue; } else { result = 0; } freeLogInfo(&defConfigBackup); } if (fchdir(here) < 0) { message(MESS_ERROR, "could not change directory to '.'"); } close(here); free_2d_array(namelist, files_count); } else { oldnumlogs = numLogs; copyLogInfo(&defConfigBackup, defConfig); if (readConfigFile(path, defConfig)) { freeTailLogs(numLogs - oldnumlogs); freeLogInfo(defConfig); copyLogInfo(defConfig, &defConfigBackup); } else { result = 0; } freeLogInfo(&defConfigBackup); } return result; } int readAllConfigPaths(const char **paths) { int i, result = 0; const char **file; struct logInfo defConfig = { .pattern = NULL, .files = NULL, .numFiles = 0, .oldDir = NULL, .criterium = ROT_SIZE, .threshhold = 1024 * 1024, .minsize = 0, .maxsize = 0, .rotateCount = 0, .rotateAge = 0, .logStart = -1, .pre = NULL, .post = NULL, .first = NULL, .last = NULL, .preremove = NULL, .logAddress = NULL, .extension = NULL, .compress_prog = NULL, .uncompress_prog = NULL, .compress_ext = NULL, .dateformat = NULL, .flags = LOG_FLAG_IFEMPTY, .shred_cycles = 0, .createMode = NO_MODE, .createUid = NO_UID, .createGid = NO_GID, .compress_options_list = NULL, .compress_options_count = 0 }; tabooExts = malloc(sizeof(*tabooExts) * defTabooCount); for (i = 0; i < defTabooCount; i++) { if ((tabooExts[i] = (char *) malloc(strlen(defTabooExts[i]) + 1))) { strcpy(tabooExts[i], defTabooExts[i]); tabooCount++; } else { free_2d_array(tabooExts, tabooCount); message(MESS_ERROR, "cannot malloc: %s\n", strerror(errno)); return 1; } } for (file = paths; *file; file++) { if (readConfigPath(*file, &defConfig)) { result = 1; break; } } free_2d_array(tabooExts, tabooCount); freeLogInfo(&defConfig); return result; } static int globerr(const char *pathname, int theerr) { glob_errno = theerr; /* We want the glob operation to abort on error, so return 1 */ return 1; } #define freeLogItem(what) \ do { \ free(newlog->what); \ newlog->what = NULL; \ } while (0); #define MAX_NESTING 16U static int readConfigFile(const char *configFile, struct logInfo *defConfig) { int fd; char *buf, *endtag, *key = NULL; char foo; off_t length; int lineNum = 1; unsigned long long multiplier; int i, k; char *scriptStart = NULL; char **scriptDest = NULL; struct logInfo *newlog = defConfig; char *start, *chptr; char *dirName; struct group *group; struct passwd *pw = NULL; int rc; char createOwner[200], createGroup[200]; int createMode; struct stat sb, sb2; glob_t globResult; const char **argv; int argc, argNum; int flags; int state = STATE_DEFAULT; int logerror = 0; struct logInfo *log; static unsigned recursion_depth = 0U; char *globerr_msg = NULL; int in_config = 0; struct flock fd_lock = { .l_start = 0, .l_len = 0, .l_whence = SEEK_SET, .l_type = F_RDLCK }; /* FIXME: createOwner and createGroup probably shouldn't be fixed length arrays -- of course, if we aren't run setuid it doesn't matter much */ fd = open(configFile, O_RDONLY); if (fd < 0) { message(MESS_ERROR, "failed to open config file %s: %s\n", configFile, strerror(errno)); return 1; } if ((flags = fcntl(fd, F_GETFD)) == -1) { message(MESS_ERROR, "Could not retrieve flags from file %s\n", configFile); close(fd); return 1; } flags |= FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags) == -1) { message(MESS_ERROR, "Could not set flags on file %s\n", configFile); close(fd); return 1; } /* We don't want anybody to change the file while we parse it, * let's try to lock it for reading. */ if (fcntl(fd, F_SETLK, &fd_lock) == -1) { message(MESS_ERROR, "Could not lock file %s for reading\n", configFile); } if (fstat(fd, &sb)) { message(MESS_ERROR, "fstat of %s failed: %s\n", configFile, strerror(errno)); close(fd); return 1; } if (!S_ISREG(sb.st_mode)) { message(MESS_DEBUG, "Ignoring %s because it's not a regular file.\n", configFile); close(fd); return 0; } if (!(pw = getpwuid(getuid()))) { message(MESS_ERROR, "Logrotate UID is not in passwd file.\n"); close(fd); return 1; } if (getuid() == ROOT_UID) { if ((sb.st_mode & 07533) != 0400) { message(MESS_DEBUG, "Ignoring %s because of bad file mode.\n", configFile); close(fd); return 0; } if ((pw = getpwnam("root")) == NULL) { message(MESS_DEBUG, "Ignoring %s because there's no password entry for the owner.\n", configFile); close(fd); return 0; } if (sb.st_uid != ROOT_UID && (pw == NULL || sb.st_uid != pw->pw_uid || strcmp("root", pw->pw_name) != 0)) { message(MESS_DEBUG, "Ignoring %s because the file owner is wrong (should be root).\n", configFile); close(fd); return 0; } } length = sb.st_size; if (length > 0xffffff) { message(MESS_ERROR, "file %s too large, probably not a config file.\n", configFile); close(fd); return 1; } /* We can't mmap empty file... */ if (length == 0) { message(MESS_DEBUG, "Ignoring %s because it's empty.\n", configFile); close(fd); return 0; } #ifdef MAP_POPULATE buf = mmap(NULL, (size_t) length, PROT_READ, MAP_PRIVATE | MAP_POPULATE, fd, (off_t) 0); #else /* MAP_POPULATE */ buf = mmap(NULL, (size_t) length, PROT_READ, MAP_PRIVATE, fd, (off_t) 0); #endif /* MAP_POPULATE */ if (buf == MAP_FAILED) { message(MESS_ERROR, "Error mapping config file %s: %s\n", configFile, strerror(errno)); close(fd); return 1; } #ifdef MADV_DONTFORK madvise(buf, (size_t)(length + 2), MADV_SEQUENTIAL | MADV_WILLNEED | MADV_DONTFORK); #else /* MADV_DONTFORK */ madvise(buf, (size_t)(length + 2), MADV_SEQUENTIAL | MADV_WILLNEED); #endif /* MADV_DONTFORK */ message(MESS_DEBUG, "reading config file %s\n", configFile); start = buf; for (start = buf; start - buf < length; start++) { if (key) { free(key); key = NULL; } switch (state) { case STATE_DEFAULT: if (isblank(*start)) continue; /* Skip comment */ if (*start == '#') { state = STATE_SKIP_LINE; continue; } if (isalpha(*start)) { if ((key = isolateWord(&start, &buf, length)) == NULL) continue; if (!strcmp(key, "compress")) { newlog->flags |= LOG_FLAG_COMPRESS; } else if (!strcmp(key, "nocompress")) { newlog->flags &= ~LOG_FLAG_COMPRESS; } else if (!strcmp(key, "compress")) { newlog->flags |= LOG_FLAG_COMPRESS; } else if (!strcmp(key, "nocompress")) { newlog->flags &= ~LOG_FLAG_COMPRESS; } else if (!strcmp(key, "delaycompress")) { newlog->flags |= LOG_FLAG_DELAYCOMPRESS; } else if (!strcmp(key, "nodelaycompress")) { newlog->flags &= ~LOG_FLAG_DELAYCOMPRESS; } else if (!strcmp(key, "shred")) { newlog->flags |= LOG_FLAG_SHRED; } else if (!strcmp(key, "noshred")) { newlog->flags &= ~LOG_FLAG_SHRED; } else if (!strcmp(key, "sharedscripts")) { newlog->flags |= LOG_FLAG_SHAREDSCRIPTS; } else if (!strcmp(key, "nosharedscripts")) { newlog->flags &= ~LOG_FLAG_SHAREDSCRIPTS; } else if (!strcmp(key, "copytruncate")) { newlog->flags |= LOG_FLAG_COPYTRUNCATE; } else if (!strcmp(key, "nocopytruncate")) { newlog->flags &= ~LOG_FLAG_COPYTRUNCATE; } else if (!strcmp(key, "copy")) { newlog->flags |= LOG_FLAG_COPY; } else if (!strcmp(key, "nocopy")) { newlog->flags &= ~LOG_FLAG_COPY; } else if (!strcmp(key, "ifempty")) { newlog->flags |= LOG_FLAG_IFEMPTY; } else if (!strcmp(key, "notifempty")) { newlog->flags &= ~LOG_FLAG_IFEMPTY; } else if (!strcmp(key, "dateext")) { newlog->flags |= LOG_FLAG_DATEEXT; } else if (!strcmp(key, "nodateext")) { newlog->flags &= ~LOG_FLAG_DATEEXT; } else if (!strcmp(key, "dateyesterday")) { newlog->flags |= LOG_FLAG_DATEYESTERDAY; } else if (!strcmp(key, "dateformat")) { freeLogItem(dateformat); newlog->dateformat = isolateLine(&start, &buf, length); if (newlog->dateformat == NULL) continue; } else if (!strcmp(key, "noolddir")) { newlog->oldDir = NULL; } else if (!strcmp(key, "mailfirst")) { newlog->flags |= LOG_FLAG_MAILFIRST; } else if (!strcmp(key, "maillast")) { newlog->flags &= ~LOG_FLAG_MAILFIRST; } else if (!strcmp(key, "su")) { free(key); key = isolateLine(&start, &buf, length); if (key == NULL) continue; rc = sscanf(key, "%199s %199s%c", createOwner, createGroup, &foo); if (rc == 3) { message(MESS_ERROR, "%s:%d extra arguments for " "su\n", configFile, lineNum); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } if (rc > 0) { pw = getpwnam(createOwner); if (!pw) { message(MESS_ERROR, "%s:%d unknown user '%s'\n", configFile, lineNum, createOwner); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } newlog->suUid = pw->pw_uid; endpwent(); } if (rc > 1) { group = getgrnam(createGroup); if (!group) { message(MESS_ERROR, "%s:%d unknown group '%s'\n", configFile, lineNum, createGroup); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } newlog->suGid = group->gr_gid; endgrent(); } newlog->flags |= LOG_FLAG_SU; } else if (!strcmp(key, "create")) { free(key); key = isolateLine(&start, &buf, length); if (key == NULL) continue; rc = sscanf(key, "%o %199s %199s%c", &createMode, createOwner, createGroup, &foo); /* We support 'create notation now */ if (rc == 0) { rc = sscanf(key, "%199s %199s%c", createOwner, createGroup, &foo); /* Simulate that we have read createMode and se it * to NO_MODE. */ if (rc > 0) { createMode = NO_MODE; rc += 1; } } if (rc == 4) { message(MESS_ERROR, "%s:%d extra arguments for " "create\n", configFile, lineNum); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } if (rc > 0) newlog->createMode = createMode; if (rc > 1) { pw = getpwnam(createOwner); if (!pw) { message(MESS_ERROR, "%s:%d unknown user '%s'\n", configFile, lineNum, createOwner); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } newlog->createUid = pw->pw_uid; endpwent(); } if (rc > 2) { group = getgrnam(createGroup); if (!group) { message(MESS_ERROR, "%s:%d unknown group '%s'\n", configFile, lineNum, createGroup); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } newlog->createGid = group->gr_gid; endgrent(); } newlog->flags |= LOG_FLAG_CREATE; } else if (!strcmp(key, "nocreate")) { newlog->flags &= ~LOG_FLAG_CREATE; } else if (!strcmp(key, "size") || !strcmp(key, "minsize") || !strcmp(key, "maxsize")) { unsigned long long size = 0; char *opt = key; if ((key = isolateValue(configFile, lineNum, opt, &start, &buf, length)) != NULL) { int l = strlen(key) - 1; if (key[l] == 'k' || key[l] == 'K') { key[l] = '\0'; multiplier = 1024; } else if (key[l] == 'M') { key[l] = '\0'; multiplier = 1024 * 1024; } else if (key[l] == 'G') { key[l] = '\0'; multiplier = 1024 * 1024 * 1024; } else if (!isdigit(key[l])) { free(opt); message(MESS_ERROR, "%s:%d unknown unit '%c'\n", configFile, lineNum, key[l]); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } else { multiplier = 1; } size = multiplier * strtoull(key, &chptr, 0); if (*chptr) { message(MESS_ERROR, "%s:%d bad size '%s'\n", configFile, lineNum, key); free(opt); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } if (!strncmp(opt, "size", 4)) { newlog->criterium = ROT_SIZE; newlog->threshhold = size; } else if (!strncmp(opt, "maxsize", 7)) { newlog->maxsize = size; } else { newlog->minsize = size; } free(opt); } else { free(opt); continue; } } else if (!strcmp(key, "shredcycles")) { free(key); if ((key = isolateValue(configFile, lineNum, "shred cycles", &start, &buf, length)) != NULL) { newlog->shred_cycles = strtoul(key, &chptr, 0); if (*chptr || newlog->shred_cycles < 0) { message(MESS_ERROR, "%s:%d bad shred cycles '%s'\n", configFile, lineNum, key); goto error; } } else continue; } else if (!strcmp(key, "hourly")) { newlog->criterium = ROT_HOURLY; } else if (!strcmp(key, "daily")) { newlog->criterium = ROT_DAYS; newlog->threshhold = 1; } else if (!strcmp(key, "monthly")) { newlog->criterium = ROT_MONTHLY; } else if (!strcmp(key, "weekly")) { newlog->criterium = ROT_WEEKLY; } else if (!strcmp(key, "yearly")) { newlog->criterium = ROT_YEARLY; } else if (!strcmp(key, "rotate")) { free(key); if ((key = isolateValue (configFile, lineNum, "rotate count", &start, &buf, length)) != NULL) { newlog->rotateCount = strtoul(key, &chptr, 0); if (*chptr || newlog->rotateCount < 0) { message(MESS_ERROR, "%s:%d bad rotation count '%s'\n", configFile, lineNum, key); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } } else continue; } else if (!strcmp(key, "start")) { free(key); if ((key = isolateValue (configFile, lineNum, "start count", &start, &buf, length)) != NULL) { newlog->logStart = strtoul(key, &chptr, 0); if (*chptr || newlog->logStart < 0) { message(MESS_ERROR, "%s:%d bad start count '%s'\n", configFile, lineNum, key); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } } else continue; } else if (!strcmp(key, "maxage")) { free(key); if ((key = isolateValue (configFile, lineNum, "maxage count", &start, &buf, length)) != NULL) { newlog->rotateAge = strtoul(key, &chptr, 0); if (*chptr || newlog->rotateAge < 0) { message(MESS_ERROR, "%s:%d bad maximum age '%s'\n", configFile, lineNum, start); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } } else continue; } else if (!strcmp(key, "errors")) { message(MESS_DEBUG, "%s: %d: the errors directive is deprecated and no longer used.\n", configFile, lineNum); } else if (!strcmp(key, "mail")) { freeLogItem(logAddress); if (!(newlog->logAddress = readAddress(configFile, lineNum, "mail", &start, &buf, length))) { if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } else continue; } else if (!strcmp(key, "nomail")) { freeLogItem(logAddress); } else if (!strcmp(key, "missingok")) { newlog->flags |= LOG_FLAG_MISSINGOK; } else if (!strcmp(key, "nomissingok")) { newlog->flags &= ~LOG_FLAG_MISSINGOK; } else if (!strcmp(key, "prerotate")) { freeLogItem (pre); scriptStart = start; scriptDest = &newlog->pre; state = STATE_LOAD_SCRIPT; } else if (!strcmp(key, "firstaction")) { freeLogItem (first); scriptStart = start; scriptDest = &newlog->first; state = STATE_LOAD_SCRIPT; } else if (!strcmp(key, "postrotate")) { freeLogItem (post); scriptStart = start; scriptDest = &newlog->post; state = STATE_LOAD_SCRIPT; } else if (!strcmp(key, "lastaction")) { freeLogItem (last); scriptStart = start; scriptDest = &newlog->last; state = STATE_LOAD_SCRIPT; } else if (!strcmp(key, "preremove")) { freeLogItem (preremove); scriptStart = start; scriptDest = &newlog->preremove; state = STATE_LOAD_SCRIPT; } else if (!strcmp(key, "tabooext")) { if (newlog != defConfig) { message(MESS_ERROR, "%s:%d tabooext may not appear inside " "of log file definition\n", configFile, lineNum); state = STATE_ERROR; continue; } free(key); if ((key = isolateValue(configFile, lineNum, "tabooext", &start, &buf, length)) != NULL) { endtag = key; if (*endtag == '+') { endtag++; while (isspace(*endtag) && *endtag) endtag++; } else { free_2d_array(tabooExts, tabooCount); tabooCount = 0; tabooExts = malloc(1); } while (*endtag) { chptr = endtag; while (!isspace(*chptr) && *chptr != ',' && *chptr) chptr++; tabooExts = realloc(tabooExts, sizeof(*tabooExts) * (tabooCount + 1)); tabooExts[tabooCount] = malloc(chptr - endtag + 1); strncpy(tabooExts[tabooCount], endtag, chptr - endtag); tabooExts[tabooCount][chptr - endtag] = '\0'; tabooCount++; endtag = chptr; if (*endtag == ',') endtag++; while (*endtag && isspace(*endtag)) endtag++; } } else continue; } else if (!strcmp(key, "include")) { free(key); if ((key = isolateValue(configFile, lineNum, "include", &start, &buf, length)) != NULL) { message(MESS_DEBUG, "including %s\n", key); if (++recursion_depth > MAX_NESTING) { message(MESS_ERROR, "%s:%d include nesting too deep\n", configFile, lineNum); --recursion_depth; goto error; } if (readConfigPath(key, newlog)) { --recursion_depth; goto error; } --recursion_depth; } else continue; } else if (!strcmp(key, "olddir")) { freeLogItem (oldDir); if (!(newlog->oldDir = readPath(configFile, lineNum, "olddir", &start, &buf, length))) { if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } #if 0 if (stat(newlog->oldDir, &sb)) { message(MESS_ERROR, "%s:%d error verifying olddir " "path %s: %s\n", configFile, lineNum, newlog->oldDir, strerror(errno)); free(newlog->oldDir); goto error; } if (!S_ISDIR(sb.st_mode)) { message(MESS_ERROR, "%s:%d olddir path %s is not a " "directory\n", configFile, lineNum, newlog->oldDir); free(newlog->oldDir); goto error; } #endif message(MESS_DEBUG, "olddir is now %s\n", newlog->oldDir); } else if (!strcmp(key, "extension")) { if ((key = isolateValue (configFile, lineNum, "extension name", &start, &buf, length)) != NULL) { freeLogItem (extension); newlog->extension = key; key = NULL; } else continue; message(MESS_DEBUG, "extension is now %s\n", newlog->extension); } else if (!strcmp(key, "compresscmd")) { freeLogItem (compress_prog); if (! (newlog->compress_prog = readPath(configFile, lineNum, "compress", &start, &buf, length))) { if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } if (access(newlog->compress_prog, X_OK)) { message(MESS_ERROR, "%s:%d compression program %s is not an executable file\n", configFile, lineNum, newlog->compress_prog); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } message(MESS_DEBUG, "compress_prog is now %s\n", newlog->compress_prog); } else if (!strcmp(key, "uncompresscmd")) { freeLogItem (uncompress_prog); if (! (newlog->uncompress_prog = readPath(configFile, lineNum, "uncompress", &start, &buf, length))) { if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } if (access(newlog->uncompress_prog, X_OK)) { message(MESS_ERROR, "%s:%d uncompression program %s is not an executable file\n", configFile, lineNum, newlog->uncompress_prog); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } message(MESS_DEBUG, "uncompress_prog is now %s\n", newlog->uncompress_prog); } else if (!strcmp(key, "compressoptions")) { char *options; if (newlog->compress_options_list) { free(newlog->compress_options_list); newlog->compress_options_list = NULL; newlog->compress_options_count = 0; } if (!(options = isolateLine(&start, &buf, length))) { if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } if (poptParseArgvString(options, &newlog->compress_options_count, &newlog->compress_options_list)) { message(MESS_ERROR, "%s:%d invalid compression options\n", configFile, lineNum); free(options); if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } message(MESS_DEBUG, "compress_options is now %s\n", options); free(options); } else if (!strcmp(key, "compressext")) { freeLogItem (compress_ext); if (! (newlog->compress_ext = readPath(configFile, lineNum, "compress-ext", &start, &buf, length))) { if (newlog != defConfig) { state = STATE_ERROR; continue; } else { goto error; } } message(MESS_DEBUG, "compress_ext is now %s\n", newlog->compress_ext); } else { message(MESS_ERROR, "%s:%d unknown option '%s' " "-- ignoring line\n", configFile, lineNum, key); if (*start != '\n') state = STATE_SKIP_LINE; } free(key); key = NULL; } else if (*start == '/' || *start == '"' || *start == '\'' #ifdef GLOB_TILDE || *start == '~' #endif ) { in_config = 0; if (newlog != defConfig) { message(MESS_ERROR, "%s:%d unexpected log filename\n", configFile, lineNum); state = STATE_ERROR; continue; } /* If no compression options were found in config file, set default values */ if (!newlog->compress_prog) newlog->compress_prog = strdup(COMPRESS_COMMAND); if (!newlog->uncompress_prog) newlog->uncompress_prog = strdup(UNCOMPRESS_COMMAND); if (!newlog->compress_ext) newlog->compress_ext = strdup(COMPRESS_EXT); /* Allocate a new logInfo structure and insert it into the logs queue, copying the actual values from defConfig */ if ((newlog = newLogInfo(defConfig)) == NULL) goto error; endtag = start; while (endtag - buf < length && *endtag != '{' && *endtag != '}' && *endtag != '\0') { endtag++;} if (endtag - buf > length) continue; if (*endtag == '}') { message(MESS_ERROR, "%s:%d unexpected } (missing previous '{')\n", configFile, lineNum); goto error; } if (*endtag == '{') { in_config = 1; } else { message(MESS_ERROR, "%s:%d missing '{' after log files definition\n", configFile, lineNum); goto error; } char *key = strndup(start, endtag - start); start = endtag; if (poptParseArgvString(key, &argc, &argv)) { message(MESS_ERROR, "%s:%d error parsing filename\n", configFile, lineNum); free(key); goto error; } else if (argc < 1) { message(MESS_ERROR, "%s:%d { expected after log file name(s)\n", configFile, lineNum); free(key); goto error; } newlog->files = NULL; newlog->numFiles = 0; for (argNum = 0; argNum < argc && logerror != 1; argNum++) { if (globerr_msg) { free(globerr_msg); globerr_msg = NULL; } rc = glob(argv[argNum], GLOB_NOCHECK #ifdef GLOB_TILDE | GLOB_TILDE #endif , globerr, &globResult); if (rc == GLOB_ABORTED) { if (newlog->flags & LOG_FLAG_MISSINGOK) { continue; } /* We don't yet know whether this stanza has "missingok" * set, so store the error message for later. */ rc = asprintf(&globerr_msg, "%s:%d glob failed for %s: %s\n", configFile, lineNum, argv[argNum], strerror(glob_errno)); if (rc == -1) globerr_msg = NULL; globResult.gl_pathc = 0; } newlog->files = realloc(newlog->files, sizeof(*newlog->files) * (newlog->numFiles + globResult. gl_pathc)); for (i = 0; i < globResult.gl_pathc; i++) { /* if we glob directories we can get false matches */ if (!lstat(globResult.gl_pathv[i], &sb) && S_ISDIR(sb.st_mode)) { continue; } for (log = logs.tqh_first; log != NULL; log = log->list.tqe_next) { for (k = 0; k < log->numFiles; k++) { if (!strcmp(log->files[k], globResult.gl_pathv[i])) { message(MESS_ERROR, "%s:%d duplicate log entry for %s\n", configFile, lineNum, globResult.gl_pathv[i]); logerror = 1; goto duperror; } } } newlog->files[newlog->numFiles] = strdup(globResult.gl_pathv[i]); newlog->numFiles++; } duperror: globfree(&globResult); } newlog->pattern = key; free(argv); } else if (*start == '}') { if (newlog == defConfig) { message(MESS_ERROR, "%s:%d unexpected }\n", configFile, lineNum); goto error; } if (!in_config) { message(MESS_ERROR, "%s:%d unexpected } (missing previous '{')\n", configFile, lineNum); goto error; } in_config = 0; if (globerr_msg) { if (!(newlog->flags & LOG_FLAG_MISSINGOK)) message(MESS_ERROR, "%s", globerr_msg); free(globerr_msg); globerr_msg = NULL; if (!(newlog->flags & LOG_FLAG_MISSINGOK)) goto error; } if (newlog->oldDir) { for (i = 0; i < newlog->numFiles; i++) { char *ld; dirName = ourDirName(newlog->files[i]); if (stat(dirName, &sb2)) { message(MESS_ERROR, "%s:%d error verifying log file " "path %s: %s\n", configFile, lineNum, dirName, strerror(errno)); free(dirName); goto error; } ld = alloca(strlen(dirName) + strlen(newlog->oldDir) + 2); sprintf(ld, "%s/%s", dirName, newlog->oldDir); free(dirName); if (newlog->oldDir[0] != '/') dirName = ld; else dirName = newlog->oldDir; if (stat(dirName, &sb)) { message(MESS_ERROR, "%s:%d error verifying olddir " "path %s: %s\n", configFile, lineNum, dirName, strerror(errno)); goto error; } if (sb.st_dev != sb2.st_dev) { message(MESS_ERROR, "%s:%d olddir %s and log file %s " "are on different devices\n", configFile, lineNum, newlog->oldDir, newlog->files[i]); goto error; } } } newlog = defConfig; state = STATE_DEFINITION_END; } else if (*start != '\n') { message(MESS_ERROR, "%s:%d lines must begin with a keyword " "or a filename (possibly in double quotes)\n", configFile, lineNum); state = STATE_SKIP_LINE; } break; case STATE_SKIP_LINE: case STATE_SKIP_LINE | STATE_SKIP_CONFIG: if (*start == '\n') state = state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : STATE_DEFAULT; break; case STATE_SKIP_LINE | STATE_LOAD_SCRIPT: if (*start == '\n') state = STATE_LOAD_SCRIPT; break; case STATE_SKIP_LINE | STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG: if (*start == '\n') state = STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG; break; case STATE_DEFINITION_END: case STATE_DEFINITION_END | STATE_SKIP_CONFIG: if (isblank(*start)) continue; if (*start != '\n') { message(MESS_ERROR, "%s:%d, unexpected text after }\n", configFile, lineNum); state = STATE_SKIP_LINE | (state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : 0); } else state = state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : STATE_DEFAULT; break; case STATE_ERROR: assert(newlog != defConfig); message(MESS_ERROR, "found error in %s, skipping\n", newlog->pattern ? newlog->pattern : "log config"); state = STATE_SKIP_CONFIG; break; case STATE_LOAD_SCRIPT: case STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG: if ((key = isolateWord(&start, &buf, length)) == NULL) continue; if (strcmp(key, "endscript") == 0) { if (state & STATE_SKIP_CONFIG) { state = STATE_SKIP_CONFIG; } else { endtag = start - 9; while (*endtag != '\n') endtag--; endtag++; *scriptDest = malloc(endtag - scriptStart + 1); strncpy(*scriptDest, scriptStart, endtag - scriptStart); (*scriptDest)[endtag - scriptStart] = '\0'; scriptDest = NULL; scriptStart = NULL; } state = state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : STATE_DEFAULT; } else { state = (*start == '\n' ? 0 : STATE_SKIP_LINE) | STATE_LOAD_SCRIPT | (state & STATE_SKIP_CONFIG ? STATE_SKIP_CONFIG : 0); } break; case STATE_SKIP_CONFIG: if (*start == '}') { state = STATE_DEFAULT; freeTailLogs(1); newlog = defConfig; } else { if ((key = isolateWord(&start, &buf, length)) == NULL) continue; if ( (strcmp(key, "postrotate") == 0) || (strcmp(key, "prerotate") == 0) || (strcmp(key, "firstaction") == 0) || (strcmp(key, "lastaction") == 0) || (strcmp(key, "preremove") == 0) ) { state = STATE_LOAD_SCRIPT | STATE_SKIP_CONFIG; } else { /* isolateWord moves the "start" pointer. * If we have a line like * rotate 5 * after isolateWord "start" points to "5" and it * is OK to skip the line, but if we have a line * like the following * nocompress * after isolateWord "start" points to "\n". In * this case if we skip a line, we skip the next * line, not the current "nocompress" one, * because in the for cycle the "start" * pointer is increased by one and, after this, * "start" points to the beginning of the next line. */ if (*start != '\n') { state = STATE_SKIP_LINE | STATE_SKIP_CONFIG; } } free(key); key = NULL; } break; } if (key) { free(key); key = NULL; } if (*start == '\n') { lineNum++; } } if (scriptStart) { message(MESS_ERROR, "%s:prerotate, postrotate or preremove without endscript\n", configFile); goto error; } munmap(buf, (size_t) length); close(fd); return 0; error: if (key) free(key); munmap(buf, (size_t) length); close(fd); return 1; } logrotate-3.8.7/config.h0000664000076500007650000000174612006205557014243 0ustar hanzzhanzz/* * OS-specific definitions */ #define ROOT_UID 0 #ifdef __hpux #define DEFAULT_MAIL_COMMAND "/usr/bin/mailx" #define COMPRESS_COMMAND "/usr/contrib/bin/gzip" #define UNCOMPRESS_COMMAND "/usr/contrib/bin/gunzip" #define STATEFILE "/var/run/logrotate.status" #endif #ifdef SunOS #define DEFAULT_MAIL_COMMAND "/usr/bin/mailx" #define STATEFILE "/var/log/logrotate.status" #endif #ifdef __NetBSD__ #define DEFAULT_MAIL_COMMAND "/usr/bin/mail -s" #define COMPRESS_COMMAND "/usr/bin/gzip" #define UNCOMPRESS_COMMAND "/usr/bin/gunzip" #define STATEFILE "/var/log/logrotate.status" #endif /* * Default settings for Linux - leave these last. */ #ifndef DEFAULT_MAIL_COMMAND #define DEFAULT_MAIL_COMMAND "/bin/mail" #endif #ifndef COMPRESS_COMMAND #define COMPRESS_COMMAND "/bin/gzip" #endif #ifndef COMPRESS_EXT #define COMPRESS_EXT ".gz" #endif #ifndef UNCOMPRESS_COMMAND #define UNCOMPRESS_COMMAND "/bin/gunzip" #endif #ifndef STATEFILE #define STATEFILE "/var/lib/logrotate.status" #endif logrotate-3.8.7/log.c0000664000076500007650000000322312006205557013542 0ustar hanzzhanzz#include #include #include #include #include #include #include "log.h" int logLevel = MESS_DEBUG; static FILE *errorFile = NULL; static FILE *messageFile = NULL; int flags = 0; void logSetLevel(int level) { logLevel = level; } void logSetErrorFile(FILE * f) { errorFile = f; } void logSetMessageFile(FILE * f) { messageFile = f; } void logSetFlags(int newFlags) { flags |= newFlags; } void logClearFlags(int newFlags) { flags &= ~newFlags; } #if 0 void log(int fd, char *format, ...) { int i = 0; char *buf = NULL; va_list args; int size; va_start(args, format); do { i += 1000; if (buf) free(buf); buf = malloc(i); size = vsnprintf(buf, i, format, args); } while (size >= i); write(fd, buf, size); free(buf); va_end(args); } #endif void message(int level, char *format, ...) { va_list args; FILE *where = NULL; int showTime = 0; if (errorFile == NULL) errorFile = stderr; if (messageFile == NULL) messageFile = stderr; where = errorFile; if (level >= logLevel) { va_start(args, format); switch (level) { case MESS_DEBUG: where = messageFile; showTime = 1; break; case MESS_NORMAL: case MESS_VERBOSE: where = messageFile; break; default: if (flags & LOG_TIMES) fprintf(where, "%ld: ", (long) time(NULL)); fprintf(errorFile, "error: "); break; } if (showTime && (flags & LOG_TIMES)) { fprintf(where, "%ld:", (long) time(NULL)); } vfprintf(where, format, args); fflush(where); va_end(args); if (level == MESS_FATAL) exit(1); } } logrotate-3.8.7/logrotate.h0000664000076500007650000000401112155334214014760 0ustar hanzzhanzz#ifndef H_LOGROTATE #define H_LOGROTATE #include #include #include #include "config.h" #define LOG_FLAG_COMPRESS (1 << 0) #define LOG_FLAG_CREATE (1 << 1) #define LOG_FLAG_IFEMPTY (1 << 2) #define LOG_FLAG_DELAYCOMPRESS (1 << 3) #define LOG_FLAG_COPYTRUNCATE (1 << 4) #define LOG_FLAG_MISSINGOK (1 << 5) #define LOG_FLAG_MAILFIRST (1 << 6) #define LOG_FLAG_SHAREDSCRIPTS (1 << 7) #define LOG_FLAG_COPY (1 << 8) #define LOG_FLAG_DATEEXT (1 << 9) #define LOG_FLAG_SHRED (1 << 10) #define LOG_FLAG_SU (1 << 11) #define LOG_FLAG_DATEYESTERDAY (1 << 12) #define NO_MODE ((mode_t) -1) #define NO_UID ((uid_t) -1) #define NO_GID ((gid_t) -1) #define NO_FORCE_ROTATE 0 #define FORCE_ROTATE 1 struct logInfo { char *pattern; char **files; int numFiles; char *oldDir; enum { ROT_HOURLY, ROT_DAYS, ROT_WEEKLY, ROT_MONTHLY, ROT_YEARLY, ROT_SIZE } criterium; unsigned long long threshhold; unsigned long long maxsize; unsigned long long minsize; int rotateCount; int rotateAge; int logStart; char *pre, *post, *first, *last, *preremove; char *logAddress; char *extension; char *compress_prog; char *uncompress_prog; char *compress_ext; char *dateformat; /* specify format for strftime (for dateext) */ int flags; int shred_cycles; /* if !=0, pass -n shred_cycles to GNU shred */ mode_t createMode; /* if any/all of these are -1, we use the */ uid_t createUid; /* attributes from the log file just rotated */ gid_t createGid; uid_t suUid; /* switch user to this uid and group to this gid */ gid_t suGid; /* these are at the end so they end up nil */ const char **compress_options_list; int compress_options_count; TAILQ_ENTRY(logInfo) list; }; TAILQ_HEAD(logInfoHead, logInfo) logs; extern int numLogs; extern int debug; int readAllConfigPaths(const char **paths); #if !defined(asprintf) && !defined(_FORTIFY_SOURCE) int asprintf(char **string_ptr, const char *format, ...); #endif #endif logrotate-3.8.7/logrotate.spec0000664000076500007650000002731312225464270015501 0ustar hanzzhanzzSummary: Rotates, compresses, removes and mails system log files Name: logrotate Version: 3.8.7 Release: 1 License: GPL+ Group: System Environment/Base Source: https://fedorahosted.org/releases/l/o/logrotate/logrotate-%{version}.tar.gz Requires: coreutils >= 5.92 libsepol libselinux popt BuildRequires: libselinux-devel popt-devel BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) %description The logrotate utility is designed to simplify the administration of log files on a system which generates a lot of log files. Logrotate allows for the automatic rotation compression, removal and mailing of log files. Logrotate can be set to handle a log file daily, weekly, monthly or when the log file gets to a certain size. Normally, logrotate runs as a daily cron job. Install the logrotate package if you need a utility to deal with the log files on your system. %prep %setup -q %build make %{?_smp_mflags} RPM_OPT_FLAGS="$RPM_OPT_FLAGS" WITH_SELINUX=yes %install rm -rf $RPM_BUILD_ROOT make PREFIX=$RPM_BUILD_ROOT MANDIR=%{_mandir} install mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.d mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}/cron.daily mkdir -p $RPM_BUILD_ROOT/%{_localstatedir}/lib install -p -m 644 examples/logrotate-default $RPM_BUILD_ROOT/%{_sysconfdir}/logrotate.conf install -p -m 755 examples/logrotate.cron $RPM_BUILD_ROOT/%{_sysconfdir}/cron.daily/logrotate touch $RPM_BUILD_ROOT/%{_localstatedir}/lib/logrotate.status %clean rm -rf $RPM_BUILD_ROOT %files %defattr(-,root,root) %doc CHANGES COPYING %attr(0755, root, root) %{_sbindir}/logrotate %attr(0644, root, root) %{_mandir}/man8/logrotate.8* %attr(0644, root, root) %{_mandir}/man5/logrotate.conf.5* %attr(0755, root, root) %{_sysconfdir}/cron.daily/logrotate %attr(0644, root, root) %config(noreplace) %{_sysconfdir}/logrotate.conf %attr(0755, root, root) %dir %{_sysconfdir}/logrotate.d %attr(0644, root, root) %verify(not size md5 mtime) %config(noreplace) %{_localstatedir}/lib/logrotate.status %changelog * Thu Oct 10 2013 Jan Kaluza 3.8.7-1 - new upstream version * Wed Jul 31 2013 Jan Kaluza 3.8.6-1 - new upstream version * Mon Jun 10 2013 Jan Kaluza 3.8.5-1 - new upstream version * Tue Apr 30 2013 Jan Kaluza 3.8.4-1 - new upstream version * Thu Oct 04 2012 Jan Kaluza 3.8.3-1 - new upstream version * Thu Jul 19 2012 Jan Kaluza 3.8.2-1 - new upstream version * Wed Aug 31 2011 Jan Kaluza 3.8.1-1 - new upstream version * Tue Jun 21 2011 Jan Kaluza 3.8.0-1 - new upstream version * Mon Jun 28 2010 Jan Kaluza 3.7.9-1 - new upstream version * Wed Jan 28 2009 Tomas Smetana 3.7.8-1 - new upstream version * Wed May 14 2008 Tomas Smetana 3.7.7-1 - new upstream version * Tue Aug 07 2007 Tomas Smetana 3.7.6-1 - new upstream version * Thu Mar 01 2007 Peter Vrabec 3.7.5-1 - new upstream release. * Fri Feb 09 2007 Peter Vrabec 3.7.4-13 - another spec file fixes (#226104) * Thu Feb 08 2007 Peter Vrabec 3.7.4-12 - fix problem with compress_options_list (#227706) - fix spec file to meet Fedora standards (#226104) * Tue Jan 23 2007 Peter Vrabec 3.7.4-11 - logrotate won't stop if there are some errors in configuration or glob failures (#166510, #182062) * Wed Jan 10 2007 Peter Vrabec 3.7.4-10 - fix some rpmlint issues * Tue Jan 09 2007 Peter Vrabec 3.7.4-9 - allow multibyte characters in readPath() (#122145) * Fri Jan 05 2007 Peter Vrabec 3.7.4-8 - "size" option was ignored in config files (#221341) * Sun Oct 01 2006 Jesse Keating - 3.7.4-7 - rebuilt for unwind info generation, broken in gcc-4.1.1-21 * Tue Sep 26 2006 Peter Vrabec 3.7.4-6 - fix leaking file descriptor (#205072) * Wed Aug 09 2006 Dan Walsh 3.7.4-5 - Use selinux raw functions * Mon Jul 24 2006 Peter Vrabec 3.7.4-4 - make error message, about ignoring certain config files, a debug message instead (#196052) * Wed Jul 12 2006 Jesse Keating - 3.7.4-3.1 - rebuild * Tue Jun 13 2006 Peter Vrabec 3.7.4-3 - rename ENOSUP to ENOTSUP * Tue Jun 13 2006 Peter Vrabec 3.7.4-2 - clean up a couple of SELinux problems. Patch from Daniel J. Walsh. * Wed May 17 2006 Peter Vrabec 3.7.4-1 - add new "minsize" option (#173088) * Tue Mar 28 2006 Peter Vrabec 3.7.3-3 - correct man page "extension" option description (#185318) * Fri Feb 10 2006 Jesse Keating - 3.7.3-2.2.1 - bump again for double-long bug on ppc(64) * Tue Feb 07 2006 Jesse Keating - 3.7.3-2.2 - rebuilt for new gcc4.1 snapshot and glibc changes * Fri Dec 09 2005 Jesse Keating - rebuilt * Sun Nov 13 2005 Peter Vrabec 3.7.3-2 - fix_free_segfaults (#172918) * Sat Nov 12 2005 Peter Vrabec 3.7.3-1 - new upstream release - indent sources * Fri Nov 11 2005 Peter Vrabec 3.7.2-12 - fix_free_segfaults (#172918) * Mon Nov 07 2005 Peter Vrabec 3.7.2-11 - man description for "nodateext" option (#171577) - remove not working "pattern" option (#171577) * Tue Oct 25 2005 Peter Vrabec 3.7.2-10 - some more clean up (#171587) * Thu Oct 20 2005 Peter Vrabec 3.7.2-9 - fix_free_segfaults (#171093) * Tue Oct 18 2005 Peter Vrabec 3.7.2-8 - fix leaks of tabooExts * Sat Oct 15 2005 Peter Vrabec 3.7.2-7 - fix_free_segfaults (#170904) * Wed Oct 12 2005 Peter Vrabec 3.7.2-6 - code clean up (#169885) * Mon Oct 10 2005 Peter Vrabec 3.7.2-5 - fix bug introduced in logrotate 3.7.2-3(#169858) - fix some memory leaks (#169888) * Fri Sep 23 2005 Peter Vrabec 3.7.2-4 - do not run compression program in debug mode (#166912) * Wed Sep 07 2005 Peter Vrabec 3.7.2-3 - even when sharedscript option used, do postrotate script before compress (#167575) * Wed Aug 17 2005 Peter Vrabec 3.7.2-2 - allow yearly rotations(#134612) * Mon Aug 01 2005 Peter Vrabec 3.7.2-1 - new upstream release * Tue Jul 26 2005 Peter Vrabec 3.7.1-14 - fix some "error running script" messages * Tue Jul 26 2005 Peter Vrabec 3.7.1-13 - fix man page (#163458,#163366) * Wed Jun 22 2005 Peter Vrabec 3.7.1-12 - enhance logrotate with "dateext", "maxage" * Thu Mar 31 2005 Dan Walsh 3.7.1-10 - use security_getenforce() instead of selinux_getenforcemode * Thu Mar 17 2005 Dan Walsh 3.7.1-9 - Add selinux_getenforce() calls to work when not in enforcing mode * Thu Mar 17 2005 Peter Vrabec 3.7.1-8 - rebuild * Tue Feb 22 2005 Peter Vrabec - do not use tmpfile to run script anymore (#149270) * Fri Feb 18 2005 Peter Vrabec - remove logrotate-3.7.1-share.patch, it doesn't solve (#140353) * Mon Dec 13 2004 Peter Vrabec - 3.7.1-5 - Add section to logrotate.conf for "/var/log/btmp" (#117844) * Mon Dec 13 2004 Peter Vrabec - 3.7.1-4 - Typo and missing information in man page (#139346) * Mon Dec 06 2004 Peter Vrabec - 3.7.1-3 - compressed logfiles and logrotate (#140353) * Tue Oct 19 2004 Miloslav Trmac - 3.7.1-2 - Fix sending mails (#131583) - Preserve file attributes when compressing files (#121523, original patch by Daniel Himler) * Fri Jul 16 2004 Elliot Lee 3.7.1-1 - Fix #126490 typo * Tue Jun 15 2004 Elliot Lee - rebuilt * Fri Feb 13 2004 Elliot Lee - rebuilt * Mon Jan 26 2004 Dan Walsh 3.6.10-4 - fix is_selinux_enabled call * Fri Sep 5 2003 Dan Walsh 3.6.10-3 - Turn off selinux * Fri Sep 5 2003 Dan Walsh 3.6.10-2.sel - Turn on selinux * Wed Aug 06 2003 Erik Troan - always use compressext for the extension for compressed files; before compresscmd and compressext had to agree - moved all compression to one code block - compression, scripts don't use system() anymore - compress and maillast didn't work together properly - delaycompress and mailfirst didn't work properly - don't use system() for mailing (or uncompressing) logs anymore - use "-s" for speciying the subjected of mailed logs * Thu Jul 24 2003 Elliot Lee 3.6.10-1 - Fix #100546, change selinux port. * Wed Jul 18 2003 Dan Walsh 3.6.9-2 - Port to SELinux 2.5 * Wed Jul 09 2003 Elliot Lee 3.6.9-1 - Fix #90229, #90274, #89458, #91408 * Mon Jan 20 2003 Elliot Lee 3.6.8-1 - Old patch from pm@debian.org * Tue Jan 14 2003 Elliot Lee 3.6.7-1 - Fixes from bugzilla * Fri Nov 15 2002 Elliot Lee 3.6.6-1 - Commit patch from Fidelis Assis * Thu Jun 20 2002 Elliot Lee 3.6.5-1 - Commit fix for #65299 * Mon Apr 15 2002 Elliot Lee 3.6.4-1 - Commit fix for #62560 * Wed Mar 13 2002 Elliot Lee 3.6.3-1 - Apply various bugfix patches from the openwall people * Tue Jan 29 2002 Elliot Lee 3.6.2-1 - Fix bug #55809 (include logrotate.status in "files") - Fix bug #58328 (incorrect error detection when reading state file) - Allow 'G' size specifier from bug #57242 * Mon Dec 10 2001 Preston Brown - noreplace config file * Wed Nov 28 2001 Preston Brown 3.6-1 - patch from Alexander Kourakos to stop the shared postrotate/prerotate scripts from running if none of the log(s) need rotating. All log files are now checked for rotation in one batch, rather than sequentially. - more fixes from Paul Martin * Thu Nov 8 2001 Preston Brown 3.5.10-1 - fix from paul martin for zero-length state files * Tue Sep 4 2001 Preston Brown - fix segfault when logfile is in current directory. * Tue Aug 21 2001 Preston Brown - fix URL for source location * Thu Aug 2 2001 Preston Brown - man page cleanups, check for negative rotation counts * Mon Jul 2 2001 Preston Brown - more minor manpage updates (#45625) * Thu Jun 21 2001 Preston Brown 3.5.6-1 - enable LFS support (debian bug #100810) - quote filenames for running compress commands or pre/postrotate cmds (#21348) - deprecate "errors" directive (see bug #16544 for explanation) - update man page - configurable compression command by Colm Buckley * Fri Jun 1 2001 Preston Brown 3.5.5-1 - be less strict about whitespace near filenames. Patch from Paul Martin . * Thu Jan 4 2001 Bill Nottingham - %%defattr * Wed Jan 03 2001 Preston Brown - see CHANGES * Tue Aug 15 2000 Erik Troan - see CHANGES * Sun Jul 23 2000 Erik Troan - see CHANGES * Tue Jul 11 2000 Erik Troan - support spaces in filenames - added sharedscripts * Sun Jun 18 2000 Matt Wilson - use %%{_mandir} for man pages * Thu Feb 24 2000 Erik Troan - don't rotate lastlog * Thu Feb 03 2000 Erik Troan - gzipped manpages logrotate-3.8.7/Makefile0000664000076500007650000001103112155334214014247 0ustar hanzzhanzzVERSION = $(shell awk '/Version:/ { print $$2 }' logrotate.spec) OS_NAME = $(shell uname -s) LFS = $(shell echo `getconf LFS_CFLAGS 2>/dev/null`) CFLAGS = -Wall -D_GNU_SOURCE -D$(OS_NAME) -DVERSION=\"$(VERSION)\" $(RPM_OPT_FLAGS) $(LFS) PROG = logrotate MAN = logrotate.8 MAN5 = logrotate.conf.5 LOADLIBES = -lpopt SVNURL= svn+ssh://svn.fedorahosted.org/svn/logrotate SVNPUBURL = http://svn.fedorahosted.org/svn/logrotate SVNTAG = r$(subst .,-,$(VERSION)) ifeq ($(WITH_SELINUX),yes) CFLAGS += -DWITH_SELINUX LOADLIBES += -lselinux # See pretest TEST_SELINUX=1 else # See pretest TEST_SELINUX=0 endif ifeq ($(WITH_ACL),yes) CFLAGS += -DWITH_ACL LOADLIBES += -lacl # See pretest TEST_ACL=1 else # See pretest TEST_ACL=0 endif # HP-UX using GCC ifeq ($(OS_NAME),HP-UX) ifeq ($(RPM_OPT_FLAGS),) RPM_OPT_FLAGS = -O2 endif CC = gcc INSTALL = cpset ifeq ($(POPT_DIR),) POPT_DIR = /usr/local endif ifeq ($(HPLX_DIR),) HPLX_DIR = /usr/local/hplx endif LOADLIBES += -lhplx -L$(HPLX_DIR)/lib ifeq ($(BASEDIR),) BASEDIR = /usr/local endif endif # Solaris using gcc ifeq ($(OS_NAME),SunOS) CFLAGS = -D_GNU_SOURCE -D$(OS_NAME) -DVERSION=\"$(VERSION)\" $(RPM_OPT_FLAGS) $(LFS) CC ?= gcc CPP = $(CC) -E -M INSTALL = /usr/ucb/install ifeq ($(CC),cc) CPP = cc -xM endif BASEDIR ?= /usr/local endif # Red Hat Linux ifeq ($(OS_NAME),Linux) INSTALL = install BASEDIR = /usr endif # FreeBSD ifeq ($(OS_NAME),FreeBSD) LOADLIBES += -L${LOCALBASE}/lib CFLAGS += -I${LOCALBASE}/include PREFIX= endif ifeq ($(OS_NAME),NetBSD) CFLAGS += -I/usr/include CFLAGS += -I$(BASEDIR)/include LOADLIBES += -L/usr/lib LOADLIBES += -L$(BASEDIR)/lib -Wl,-R,$(BASEDIR)/lib endif ifneq ($(POPT_DIR),) CFLAGS += -I$(POPT_DIR) LOADLIBES += -L$(POPT_DIR) endif ifneq ($(STATEFILE),) CFLAGS += -DSTATEFILE=\"$(STATEFILE)\" endif BINDIR = $(BASEDIR)/sbin MANDIR ?= $(BASEDIR)/man #-------------------------------------------------------------------------- OBJS = logrotate.o log.o config.o basenames.o SOURCES = $(subst .o,.c,$(OBJS) $(LIBOBJS)) ifeq ($(RPM_OPT_FLAGS),) CFLAGS += -g LDFLAGS = -g endif LDFLAGS += $(EXTRA_LDFLAGS) $(EXTRA_LIBS) CFLAGS += $(EXTRA_CPPFLAGS) $(EXTRA_CFLAGS) ifeq (.depend,$(wildcard .depend)) TARGET=$(PROG) else TARGET=depend $(PROG) endif RCSVERSION = $(subst .,-,$(VERSION)) all: $(TARGET) pretest $(PROG): $(OBJS) clean: rm -f $(OBJS) $(PROG) core* .depend rm -f ./test/test.ACL ./test/test.SELINUX ./test/error.log depend: $(CPP) $(CFLAGS) -M $(SOURCES) > .depend # pretest create the file ./test/test.ACL with # 0 or 1 according to the WITH_ACL=yes presence. # The file will be used by ./test/test to decide # if to do the ACL tests or not. pretest: echo "$(TEST_ACL)" > ./test/test.ACL ; echo "$(TEST_SELINUX)" > ./test/test.SELINUX ; .PHONY : test test: $(TARGET) (cd test; ./test) install: [ -d $(PREFIX)$(BINDIR) ] || mkdir -p $(PREFIX)$(BINDIR) [ -d $(PREFIX)$(MANDIR) ] || mkdir -p $(PREFIX)$(MANDIR) [ -d $(PREFIX)$(MANDIR)/man8 ] || mkdir -p $(PREFIX)$(MANDIR)/man8 [ -d $(PREFIX)$(MANDIR)/man5 ] || mkdir -p $(PREFIX)$(MANDIR)/man5 if [ "$(OS_NAME)" = HP-UX ]; then \ $(INSTALL) $(PROG) $(PREFIX)$(BINDIR) 0755 bin bin; \ $(INSTALL) $(MAN) $(PREFIX)$(MANDIR)/man`echo $(MAN) | sed "s/.*\.//"` 0644 bin bin; \ $(INSTALL) $(MAN5) $(PREFIX)$(MANDIR)/man`echo $(MAN5) | sed "s/.*\.//"` 0644 bin bin; \ else if [ "$(OS_NAME)" = FreeBSD ]; then \ $(BSD_INSTALL_PROGRAM) $(PROG) $(BINDIR); \ $(BSD_INSTALL_MAN) $(MAN) $(MANDIR)/man`echo $(MAN) | sed "s/.*\.//"`/$(MAN); \ $(BSD_INSTALL_MAN) $(MAN5) $(MANDIR)/man`echo $(MAN5) | sed "s/.*\.//"`/$(MAN5); \ else \ $(INSTALL) -m 755 $(PROG) $(PREFIX)$(BINDIR); \ $(INSTALL) -m 644 $(MAN) $(PREFIX)$(MANDIR)/man`echo $(MAN) | sed "s/.*\.//"`/$(MAN); \ $(INSTALL) -m 644 $(MAN5) $(PREFIX)$(MANDIR)/man`echo $(MAN5) | sed "s/.*\.//"`/$(MAN5); \ fi; fi co: co RCS/*,v (cd examples; co RCS/*,v) svntag: svn copy $(SVNURL)/trunk $(SVNURL)/tags/$(SVNTAG) -m "Release $(VERSION)" create-archive: @rm -rf /tmp/logrotate-$(VERSION) /tmp/logrotate @cd /tmp; svn export $(SVNPUBURL)/tags/$(SVNTAG) logrotate-$(VERSION) @cd /tmp/logrotate-$(VERSION) @cd /tmp; tar czSpf logrotate-$(VERSION).tar.gz logrotate-$(VERSION) @rm -rf /tmp/logrotate-$(VERSION) @cp /tmp/logrotate-$(VERSION).tar.gz . @rm -f /tmp/logrotate-$(VERSION).tar.gz @echo " " @echo "The final archive is ./logrotate-$(VERSION).tar.gz." archive: clean svntag create-archive ifeq (.depend,$(wildcard .depend)) include .depend endif logrotate-3.8.7/COPYING0000664000076500007650000004307012006205557013654 0ustar hanzzhanzz GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 675 Mass Ave, Cambridge, MA 02139, USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) 19yy This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. logrotate-3.8.7/INSTALL0000664000076500007650000000162212137672476013664 0ustar hanzzhanzzThe simplest way to compile this package is: 1. `cd' to the directory containing the package's source code 2. Type `make' to compile the package. 3. Optionally, type `make test' to run any self-tests that come with the package. 4. Type `make install' to install the programs and any data files and documentation. 5. You can remove the program binaries and object files from the source code directory by typing `make clean'. If you want to add the Access Control List (ACL) support to the program use `make WITH_ACL=yes' at the point 2 and `make test WITH_ACL=yes' at the point 3. If you want to add the NSA Security-Enhanced Linux (SELinux) support to the program use `make WITH_SELINUX=yes' at the point 2. If you want to add both the ACL and SELinux supports to the program use `make WITH_ACL=yes WITH_SELINUX=yes' at the point 2 and `make test WITH_ACL=yes' at the point 3.