Logrotate

How logrotate is invoked from cron.

My /etc/crontab file contains the following.

SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root

# run-parts
01 * * * * root run-parts /etc/cron.hourly
02 4 * * * root run-parts /etc/cron.daily
22 4 * * 0 root run-parts /etc/cron.weekly
0 0 1 * * root run-parts /etc/cron.monthly

And the /etc/cron.d directory contains nothing.

So clearly, any logrotate commands are being run out of one or more of the /etc/cron.* files. The manual `man 5 crontab' indicates that the above commands mean that the program `run-parts' will be run as user `root' for each of the scripts.

The command `run-parts' turns out to be the script /usr/bin/run-parts, which is very short, as follows.

#!/bin/bash

# run-parts - concept taken from Debian

# keep going when something fails
set +e

if [ $# -lt 1 ]; then
echo "Usage: run-parts <dir>"
exit 1
fi

if [ ! -d $1 ]; then
echo "Not a directory: $1"
exit 1
fi

for i in $1/* ; do
[ -d $i ] && continue
if [ -x $i ]; then
$i
fi
done

exit 0

In essence, this just runs all of the scripts in the specified directory. E.g. each hour, the executable plain files in /etc/cron.hourly are run.

It turns out that the `logrotate' program is invoked from /etc/cron.daily. This is the contents of the file /etc/cron.daily/logrotate.

#!/bin/sh

/usr/sbin/logrotate /etc/logrotate.conf

This means that the only file that `logrotate' gets its instructions from directly is /etc/logrotate.conf, which contains the following lines.

# see "man logrotate" for details
# rotate log files weekly
weekly

# keep 8 weeks worth of backlogs
rotate 8

# send errors to root
errors root

# create new (empty) log files after rotating old ones
create

# 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 lastlog or wtmp -- we'll rotate them here
/var/log/wtmp {
monthly
rotate 1
}

# system-specific logs may be configured here

This is all explained in the manual man logrotate.

Operation of logrotate.

The directory /etc/logrotate.d contains the following files.

-rw-r--r--   1 root     root          354 Oct 13  1998 apache
-rw-r--r-- 1 root root 108 Aug 28 1999 cron
-rw-r--r-- 1 root root 188 Oct 14 1998 linuxconf
-rw-r--r-- 1 root root 156 Oct 13 1998 mgetty
-rw-r--r-- 1 root root 327 Aug 12 1998 syslog
-rw-r--r-- 1 root root 457 Sep 10 1998 uucp

The file /etc/logrotate.d/apache is the one I'm interested in for this exercise. This file contains the following.

/var/log/httpd/access_log {
postrotate
/usr/bin/killall -HUP httpd
endscript
}

/var/log/httpd/agent_log {
postrotate
/usr/bin/killall -HUP httpd
endscript
}

/var/log/httpd/error_log {
postrotate
/usr/bin/killall -HUP httpd
endscript
}

/var/log/httpd/referer_log {
postrotate
/usr/bin/killall -HUP httpd
endscript
}

When I installed the latest version of Apache to get the PHP3 and PostgreSQL to work (around 20 March 2000) on my web server machine, I installed Apache so that the log files were in /home2/apache/logs instead of /var/log/httpd.

Therefore what I need to do now is to modify the /etc/logrotate.d/apache file so that the files referred to are all in the directory /home2/apache/logs instead. My new /etc/logrotate.d/apache script is as follows, and I saved the old one in directory /etc/logrotate.d/old1.

# The new improved logrotate script for apache on fox.

/home2/apache/logs/*-access_log {
rotate 9
monthly
errors ak@fox.txpxlxgy.org
create
ifempty
olddir /home2/apache/logs/oldlogs
postrotate
/usr/bin/killall -HUP httpd
endscript
}

/home2/apache/logs/*-combref_log {
rotate 9
monthly
errors ak@fox.txpxlxgy.org
create
ifempty
olddir /home2/apache/logs/oldlogs
postrotate
/usr/bin/killall -HUP httpd
endscript
}

/home2/apache/logs/*-error_log {
rotate 9
monthly
errors ak@fox.txpxlxgy.org
create
ifempty
olddir /home2/apache/logs/oldlogs
postrotate
/usr/bin/killall -HUP httpd
endscript }
 

On my SuSE 7.1 machine `dog', the manual says that after reading the per-user crontab files in /var/spool/cron/tabs on start-up, the cron process reads the file /etc/crontab. On my machine as configured, I find the following.

root@dog# more /etc/crontab
SHELL=/bin/sh
PATH=/usr/bin:/usr/sbin:/sbin:/bin:/usr/lib/news/bin
MAILTO=root

#-* * * * * root test -x /usr/sbin/atrun && /usr/sbin/atrun
0 21 * * * root test -x /usr/sbin/faxqclean && /usr/sbin/faxqclean
5 22 * * * root test -x /usr/sbin/texpire && /usr/sbin/texpire
25 23 * * * root test -e /usr/sbin/faxcron && sh /usr/sbin/faxcron | mail FaxMaster

#
# check scripts in cron.hourly, cron.daily, cron.weekly, and cron.monthly
#
-*/15 * * * * root test -x /usr/lib/cron/run-crons && /usr/lib/cron/run-crons
59 * * * * root rm -f /var/spool/cron/lastrun/cron.hourly
14 0 * * * root rm -f /var/spool/cron/lastrun/cron.daily
29 0 * * 6 root rm -f /var/spool/cron/lastrun/cron.weekly

44 0 1 * * root rm -f /var/spool/cron/lastrun/cron.monthly
 

There's nothing here to help me with initiating my daily script. (By the way, the fax commands are a bit worrying. I'll get rid of those when I understand exactly what they do. They obviously produce many meaningless message which root receives every day!)

So my next step is to look at the files in /etc/cron.d, because the cron manual says that all scripts in this directory are read next. On my machine the only file in /etc/cron.d is a script `seccheck', which produces copious useless messages to root every day and week. (I'll see if I can get rid of that some day too!)

The directory /etc/cron.daily contains a script `aaa_base_rotate_logs' which contains a complex set of rotation rules, but how are the scripts in this directory invoked? Hmmm... Maybe they're invoked from that `/usr/lib/cron/run-crons' script. Yes!! That's where it's invoked from. Yet another big, incomprehensible script. The core of that script is the following Bourne-shell loop.

SPOOL=/var/spool/cron/lastrun
for CRONDIR in /etc/cron.{hourly,daily,weekly,monthly} ; do
test -d $CRONDIR || continue
BASE=${CRONDIR##*/}
test -e $SPOOL/$BASE && {
case $BASE in
cron.hourly) TIME="-cmin +60 -or -cmin 60" ;;
cron.daily) TIME="-ctime +1 -or -ctime 1" ;;
cron.weekly) TIME="-ctime +7 -or -ctime 7" ;;
cron.monthly) TIME="-ctime +`date -u +%d`" ;;
esac
eval find $SPOOL/$BASE $TIME | xargs -r rm -f
}
if test ! -e $SPOOL/$BASE ; then
touch $SPOOL/$BASE

# keep going when something fails
set +e
for SCRIPT in $CRONDIR/*[^~,] ; do
test -d $SCRIPT && continue
test -x $SCRIPT || continue
case "$SCRIPT" in
*.rpm*) continue ;;
*.swap) continue ;;
*.bak) continue ;;
*.orig) continue ;;
\#*) continue ;;
esac
/sbin/checkproc $SCRIPT && continue
nice -15 $SCRIPT
done
fi

done

Now what does this mean?
The command `BASE=${CRONDIR##*/}' means that BASE is set to the `longest substring of $CRONDIR which matches pattern "*/"', according to my Bash reference card. This just means that the leading path components are removed. (This is done more simply in C-shell!) In the case of the daily cron job, if there is a file /var/spool/cron/lastrun/cron.daily (which is true), then the following command is run.

eval find /var/spool/cron/lastrun/cron.daily -ctime +1 -or -ctime 1 | xargs -r rm -f 

The `xargs' command (which I have never seen before) builds and executes a ommand line from standard input. Weird! The `xargs' manual says this.

If  the  standard  input  does not contain any non-blanks,
do not run the command. Normally, the command is run once
even if there is no input.

So in this case, the command `rm -f' is executed for each of the files with modification times within 24 hours of the current time. I don't really understand this.

It looks like the file removal commands in the file /etc/crontab are designed to synchronise the operation of the quarter-hour operations. The commands are only executed if the files in /var/spool/cron/lastrun have been removed. What a convoluted way of achieving a simple objective.

Next any file in the directory /etc/cron.daily which do not end in the characters `~' or `,' (presumed to be edited files) are executed if they are executable and do not have the endings .rpm, .swap, .bak or .orig as follows.

/sbin/checkproc $SCRIPT && continue
nice -15 $SCRIPT

The loop continues is the process is already running. Otherwise it is run with nice level 15.

What this all means finally is that any script in the directory /etc/cron.daily will be run at 00:00 on each day. More to the point, the /etc/cron.monthly scripts are run at 00:00 at the beginning of each month. It all looks a bit dodgy because the `date' test is `date -u', which gives the current UTC day. But this should work, although the motivation for the `-u' is not quite clear.

All I have to do now is write a script and put it in /etc/cron.monthly.