1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
|
#!/usr/local/bin/perl5 -s
# $Header: /usr/local/src/dhcp_probe-latest/extras/RCS/rotate_logs,v 1.1 2011/12/14 20:32:21 root Exp root $
# rotate_logs [-logdir="logdirname"] [-logname="filename"] [-max="-maxlogs"] [-daily] [-nochown]
# [-dailydir="dailydirname"] [-filter="filterprog"] [-compress="compressprog"] [-compressopts="compressprog options"] [-debug]
# [-dailyflagdir="dailyflagdirname1 dailyflagdirname2..."]
# where:
# logdirname is name of directory containing the logfiles, defaults to /c/var/log
# filename is base name of logfile, defaults to syslog.listproc
# maxlogs is the number of old logs to keep, defaults to -8
# if you specify -max, remember to include the dash before the number.
# If you specify dailydir, that directory is used to archive old logfiles. We copy the current logfile
# to $dailydir/$filename.YYMMDD, optionally run it through filterprog, then compress it.
# Note that we still perform the normal rotation in logdirname of the current and up to maxlogs old logs.
# Note that if dailydir is specified, we ignore daily.
# If you specify daily (and don't specify dailydir), we don't actually rotate old files at all, (we
# ignore any value for -max). Instead, we assume today's log is named $filename.today, and we
# rename $filename.today to $filename.YYMMDD.
# When dailydir is specified, the compress program we use is /usr/bin/compress. You can override
# that with compressprog. This is ignored when dailydir isn't specified.
# If compression is performed, compressopts are the options passed to the compress program; default is ''.
# When dailydir is specified, you may specify an optional filterprog. That filterprog will be
# handed a filename, and is expected to write to stdout. The result is what gets compressed.
# If you specify nochown, we won't try to call chown to set the uid/gid of the new log file
# or a file put in dailydir. That's useful if we're not being run by root.
# When dailyflagdirs is specified, it is a listof whitespace delimited directory names.
# (Don't use dir names with embedded whitespace.)
# We create an empty file in each specified directory; the filename is the same name
# as the file we created in dailydirname. (If no file was created in dailydirname,
# then dailyflagdirname is ignored.) This is allow other independent programs that each needs to be alerted to new
# files in dailydirname to learn about them without having to maintain their own state.
# Presumably each time each of those other programs sees a flag file in its dailyflagdir, it will
# process the corresponding file in dailydirname, then erase the flag file in dailyflagdirname.
# (Used a separate dailyflagdir for each of these consumer programs.)
# If you specify debug, we produce some diagnostic output.
#
# We create a new empty logfile, copying the uid/gid/mode from the previous one (or the most-recent
# previous one, the case of rotation), defaulting to 0600/0/0.
#
# Typical use:
# rotate_logs -logdir="/var/adm" -logname="wtmpx" -max="-12"
# rotate_logs -logdir="/usr/local/etc/httpd" -logname="access_log" -daily
# rotate_logs -logdir="/var/local/logs" -logname="local7.log" -max="-3" \
# -dailydir="/var/local/logs/long_term" -filter="/usr/local/etc/prune-local7" \
# -compress="/usr/local/bin/gzip" -compressopts="--best --force"
#
# For rotation, we rename the old files, so processes that have them open will still be able to use them.
# (It's up to you to figure out how to get processes that had the old logfile open to close them
# and re-open them (e.g. so they stop appending to the old file and start appending to the new one).)
# For archiving to dailydir, we copy the current log, so that's a new file.
use 5.006; use 5.6.0;
use File::Temp qw(tempfile); # standard starting in perl 5.6.1; else get from CPAN
$TMPDIR = '/tmp';
# defaults for new file if none found from old file
$mode=0600;
$uid=0;
$gid=0;
$compress = "/usr/bin/compress" unless defined($compress);
$compressopts = "" unless defined($compressopts);
$logdir = "/c/var/log" unless defined($logdir);
$logname="syslog.listproc" unless defined($logname);
$dailydir = "" unless defined($dailydir);
$dailyflagdir = "" unless (defined($dailyflagdir) && defined($dailydir));
$daily = 0 unless (defined($daily) && !$dailydir);
$filter = "" unless defined($filter);
$max="-8" unless defined($max);
$nochown = 0 unless defined($nochown);
$debug = 0 unless defined($debug);
my @dailyflagdirs = ();
if ($dailyflagdir) {
@dailyflagdirs = split(' ', $dailyflagdir);
}
($prog = $0) =~ s/.*\///g;
File::Temp->safe_level(File::Temp::HIGH);
chdir("$logdir") || die "$prog: cannot cd $logdir: $!\n";
&rotate();
exit(0);
sub rotate {
$timenow=time;
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst)=localtime($timenow);
$year -= 100 if $year > 99;
$today=sprintf("%02d%02d%02d",$year, $mon+1, $mday);
if ($daily) {
if (-e "$logname.today") {
($mode, $uid, $gid) = (stat("$logname.today"))[2,4,5];
warn("rename $logname.today -> $logname.$today\n") if $debug;
rename("$logname.today","$logname.$today") || die "$prog: can't rename $logname.today $logname.$today: $!\n";
}
&create_file("$logname.today", $mode, $uid, $gid);
exit(0);
}
if ($dailydir) { # we do this before rotation
warn("Copying today's log for archival\n") if $debug;
system("/bin/cp -p $logname $dailydir/$logname.today");
}
# rotation
unlink("$logname$max"); # may not exist
foreach ( $max .. -2 ) { # recall $max starts with a dash
$current = sprintf("%s%s", $logname, $_);
$prev = sprintf("%s%s", $logname, $_+1);
if (-e "$prev") {
($mode, $uid, $gid) = (stat("$prev"))[2,4,5];
warn("rename $prev -> $current\n") if $debug;
rename("$prev", "$current"); # $prev may not exist
}
}
if (-e "$logname") {
($mode, $uid, $gid) = (stat("$logname"))[2,4,5];
rename("$logname","$prev");
}
&create_file("$logname", $mode, $uid, $gid);
if ($dailydir) {
chdir("$dailydir") || die "$prog: can't cd $dailydir: $!\n";
if (-e "$logname.today") {
if ($filter ne "") {
# we don't also check (-x $filter), since $filter might not be fully-qualified
warn("Running $filter $logname.today -> $logname.$today\n") if $debug;
unlink("$logname.$today", "$logname.$today.Z"); # WHY?
system("/bin/date") if $debug;
($mode, $uid, $gid) = (stat("$logname.today"))[2,4,5];
($tmp_fh, $tmp_fn) = tempfile("${prog}.XXXXXXXXXX", DIR => $TMPDIR);
system("$filter $logname.today >> $tmp_fn"); # XXX we are ignoring filter's rval, pray!
system("/bin/cp $tmp_fn $logname.today"); # XXX we are ignoring cp's rval - pray!
unlink($tmp_fn);
(chown($uid, $gid, "$logname.today") || die "$prog: can't chown $logname.today: $!\n") unless $nochown;
chmod($mode, "$logname.today") || die "$prog: can't chmod $logname.today: $!\n";
system("/bin/date") if $debug;
}
rename("$logname.today","$logname.$today") || die "$prog: can't rename $dailydir/$logname.today $dailydir/$logname.$today: $!\n";
foreach my $dir (@dailyflagdirs) {
open(FLAGFILE, ">${dir}/${logname}.$today") || warn "$prog: can't create flag file ${dir}/${logname}.${today}: $!\n";
close(FLAGFILE);
}
if (-x $compress) {
system("$compress $compressopts $logname.$today"); # note we are ignoring rval
# BUG: There's no guarantee that the compress program will preserve the owner/group and perms of
# the file. And since we don't know what the output filename will be, we can't fix it.
# Fortunately, 'gzip' seems to preserve owner/group if it can.
foreach my $dir (@dailyflagdirs) {
# Although the flag file is empty, we still run the compress program on it,
# to cause the filename to be undergo the same transformation that the file in dailydir underwent.
# (We can't simply rename the flag file, since we don't know the filename transformation performed
# by the compress program.)
system("$compress $compressopts ${dir}/${logname}.$today"); # note we are ignoring rval
# BUG: There's no guarantee that the compress program will preserve the owner/group and perms of
# the file. And since we don't know what the output filename will be, we can't fix it.
# Fortunately, 'gzip' seems to preserve owner/group if it can.
}
} else {
warn("$prog: can't find $compress - won't compress ${dailydir}/$logname.$today\n");
}
}
}
}
sub create_file {
# Create new empty log. It may already exist (due to some other process writing after after
# we just renamed it), so first just see if we can update its times. If that fails, try to
# create a new one, else die.
#
# Note that we assume that the directory in which the file is to be created is only writable
# by folks with the appropriate privilege; if it is writable by others, they can exploit the
# usual race conditions. Now, we're always called in such a way that the directory we'll
# write in is the same as the directory that contained the original log file. So you'll be
# safe as long as you store your log files in directories that are only writable by
# folks with appropriate privs.
local($newfile, $mode, $uid, $gid) = @_;
utime($timenow, $timenow, "$newfile") || open(TMP,">>$newfile") || die "$prog: can't create $newfile: $!\n";
(chown($uid, $gid, "$newfile") || die "$prog: can't chown $newfile: $!\n") unless $nochown;
chmod($mode, "$newfile") || die "$prog: can't chmod $newfile: $!\n";
return 0;
}
|