#!/usr/local/bin/perl
;#
;# Copyright (c) 1995-1999
;#	Ikuo Nakagawa. All rights reserved.
;#
;# Redistribution and use in source and binary forms, with or without
;# modification, are permitted provided that the following conditions
;# are met:
;#
;# 1. Redistributions of source code must retain the above copyright
;#    notice unmodified, this list of conditions, and the following
;#    disclaimer.
;# 2. Redistributions in binary form must reproduce the above copyright
;#    notice, this list of conditions and the following disclaimer in the
;#    documentation and/or other materials provided with the distribution.
;#
;# THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
;# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
;# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
;# PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
;# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
;# OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
;# OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
;# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
;# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
;# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
;# EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
;#
;# $Id: rotate,v 1.6 1999/11/10 23:12:28 ikuo Exp $
;#
;# How to use `rotate' program:
;#
;#   To rotate "/var/log/xxx.log" to "/var/log/xxx.log.old", and
;#   create a new file "/var/log/xxx.log":
;#	rotate /var/log/xxx.log
;#
;#   If you want to rotate files with suffixes, try additional
;#   argument for `rotate' command.
;#	rotate /var/log/xxx.log 2 1 0
;#
;#   You can specify the owner/group or file permission mode for
;#   the new file (like `install' command):
;#	rotate -o root -g wheel -m 644 /var/log/messages 2 1 0
;#
;#   You can also compress rotated file with `gzip':
;#	rotate -z /var/log/access.log 2 1 0
;#
;#   or with `compress':
;#	rotate -Z /var/log/access.log 2 1 0
;#
;# This is because we supports perl version 4.
require 'getopts.pl';

;# Get program name
($program) = ($0 =~ m%([^/]+)$%);

;# For zero based index.
$[ = 0;

;# Show debug log to STDOUT.
sub debug {
	local($_); # used in grep.
	grep((print "$_\n"), @_) if $opt_v;
}

;# Initialize options (for "perl -cw").
undef $opt_N;
undef $opt_T;
undef $opt_Z;
undef $opt_g;
undef $opt_m;
undef $opt_n;
undef $opt_o;
undef $opt_t;
undef $opt_v;
undef $opt_z;

;# Parsing options
unless (&Getopts("NTZg:m:no:tvz") && defined($target = shift)) {
	die <<"END";
Usage: $program [options] path [suffix suffix ...]
Options:
	-v	verbose mode.
	-n	do not real work. only show processing.
	-N	do not create a new file.
	-z	compress with `gzip'.
	-Z	compress with `compress'.
	-o	specify owner.
	-g	specify group.
	-m	specify mode.
	-T	use `YYYY-MM-DD' (given by current time) as the default
		suffix, instead of `old'.
	-t	use `YYYY-MM-DD' (from last modified time of the target)
		as the default suffix, instead of `old'.
END
}

;# Test mode requires verbose option
$opt_v++ if $opt_n;

;# If no suffix was given, we generate default one.
unless (@ARGV) {
	if ($opt_T || $opt_t) {
		if ($opt_t && ! -e $target) {
			die("$target must exist if -t flag is specified.\n");
		}
		$t = $opt_t ? (stat($target))[9] : time;
		@t = reverse((localtime($t))[0..5]);
		$t[0] += 1900;
		$t[1]++;
		@ARGV = (sprintf("%04d-%02d-%02d", @t));
	} else {
		@ARGV = ('old');
	}
}

;# Rotate the target file.
&safe_rotate($target, @ARGV);

;# Touch the new one.
&safe_create($target) unless $opt_N;

;# Normal termination.
exit;

;# Touch a file. Create a new one if it does not exist.
sub touch {
	local($a) = @_;
	local(*FILE);

	$a ne '' && open(FILE, '>>'.$a) && close(FILE) && -e $a;
}

;#
sub safe_unlink {
	local($a) = @_;

	if (-e $a) {
		&debug("unlink \"$a\"");
		$opt_n || unlink($a) || die("unlink($a): $!");
	}
}

;#
sub safe_rename {
	local($a, $b) = @_; # from, to

	if (-e $a) {
		&debug("rename \"$a\" to \"$b\"");
		$opt_n || rename($a, $b) || die("rename($a, $b): $!");
	}
}

;#
sub safe_compress {
	local($a) = @_;

	if (-z $a) { # compress will fail in this case
		&debug("we won't compress zero-sized file: \"$a\"");
	} else {
		&debug("compress \"$a\"");
		$opt_n	|| system('compress', $a) == 0
			|| die("system(compress, $a): failure.\n");
	}
}

;#
sub safe_gzip {
	local($a) = @_;

	&debug("gzip \"$a\"");
	$opt_n	|| system('gzip', $a) == 0
		|| die("system(gzip, $a): failure.\n");
}

;# Create a new one
sub safe_create {
	local($a) = shift;

	&debug("touch \"$a\"");
	$opt_n || &touch($a) || die("touch($a): $!");

	# set owner and group
	if (defined($opt_o) || defined($opt_g)) {
		local($uid, $gid) = (stat($a))[4, 5];
		!defined($opt_o)
			|| (($uid = $opt_o) =~ /^\d+$/)
			|| defined($uid = getpwnam($opt_o))
			|| die("getpwnam($opt_o): $!");
		!defined($opt_g)
			|| (($gid = $opt_g) =~ /^\d+$/)
			|| defined($gid = getgrnam($opt_g))
			|| die("getgrnam($opt_g): $!");
		&debug("chown($uid, $gid, \"$a\")");
		$opt_n	|| chown($uid, $gid, $a)
			|| die("chown($a): $!");
	}

	# set file mode
	if (defined($opt_m)) {
		$opt_m =~ /^\d+$/ || die "illegal mode: $opt_m\n";
		$opt_m = oct($opt_m);
		&debug("chmod ".sprintf("%04o", $opt_m).", \"$a\"");
		$opt_n || chmod($opt_m, $a) || die("chmod($a): $!");
	}

	# success.
	1;
}

;# Rotate - do real work.
sub safe_rotate {
	local($a) = shift;

	# check existence, and suffixes
	return 0 unless $a ne '' && -e $a && @_;

	# log message
	&debug("rotating \"$a\"");

	# remove oldest one
	local($b) = $a.'.'.shift;
	&safe_unlink($b);
	&safe_unlink($b.'.Z');
	&safe_unlink($b.'.gz');

	# loop to rotate files
	while (@_) {
		local($x) = $a.'.'.shift;
		&safe_rename($x, $b);
		&safe_rename($x.'.Z', $b.'.Z');
		&safe_rename($x.'.gz', $b.'.gz');
		$b = $x;
	}

	# rotate last one
	&safe_rename($a, $b);

	# shall we compress rotated one?
	$opt_z ? &safe_gzip($b) : $opt_Z ? &safe_compress($b) : 1;
}
