File: rotate_logs

package info (click to toggle)
dhcp-probe 1.3.1-1.1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,316 kB
  • sloc: sh: 4,783; ansic: 2,400; perl: 381; xml: 51; makefile: 50
file content (197 lines) | stat: -rwxr-xr-x 9,620 bytes parent folder | download | duplicates (2)
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;
}