pkg://chklogs-2.0-3.tar.gz:163018/
chklogs-2.0-3/
bin/chklogs-dist
downloads
#!/usr/bin/perl
# RCS: $Id: chklogs,v 2.0 1997/09/28 19:19:38 grimaldo Exp $
#------------------------------------------------------------------------
# chklogs (c)1995,1996,1997 D. Emilio Grimaldo Tunon
#------------------------------------------------------------------------
# AUTHOR: D. Emilio Grimaldo T. grimaldo@panama.iaehv.nl
# USAGE: chklogs [[-t | -c] | [-w | -a | -m]
# DESCRIPTION:
# Checks the sizes of system Log files and reports the results.
# The full pathname of the log(s) together with the maximum
# allowed size and default action are specified in `ConfFile'.
# Default actions can be `archive' or `truncate'. When
# archiving a shuffling mechanism is used to save disk space.
#
# Options:
# none Process logs according to default action
# -a Archive all overgrown/aged logs regardless of default action
# -m Mail summary to administrator
# -c Only produce a listing of the (archived) logs
# -t Check correctness of Index indicating registered action
# -w Warn about overgrown/aged logs without taking action
# -v Shows version of Chklogs and its library
#
# NOTE: mail is sent to admin ONLY if one or more logs are overgrown
# ********** I N C L U D E S **********
require 5.003;
use Getopt::Long;
use lib "/usr/local/lib/chklogs";
use Chklogs;
use Smtp;
use DegtUtils;
use strict;
# ********* ********************* *********
# ********* CONFIGURATION SECTION *********
# Most likely to adapt to your site
my $zipper ='/bin/gzip'; # Compress program
my $zipext ='gz'; # Extension given by zipper command
my $syslogF ='syslog.pid'; # Location of syslog.pid file
# Least likely to be modified
my $maxlogs=5; # Shuffle after maxlogs archives
my $mailer ='/usr/sbin/sendmail -ep -i '; # only if MiniMail is 'no'
# ********* ********************* *********
# ********** LOCAL DATA SECTION **********
my $cvsId = '$Revision: 2.0 $';
my ($vBanner, $version );
my (%cfg, %cLog, %cGroup);
my ($opWarn, $opArchive, $opMail, $opCheck, $opTest, $opt_default);
my ($global_cnt, $local_cnt);
my ($AgeFlag, $notify, $Today, $exec_pgm, $Repository, $REPORT);
my ($DC, $ArchiveSuffix, $mailout);
my (@Report, @Xheader);
# ********* ********************* *********
#------------------------------------------------
# FUNCTION : initialize
# PROTOTYPE: initialize()
# INPUT : -
# OUTPUT : -
# GLOBALS : %cfg %cLog %cGroup
# DESCRIPTION
# Global initialization
sub initialize {
my @tmStamp = ();
#
# Global Configuration Settings
#
%cfg = (
'global' => "", # Alternate Repository Feature
'local' => $RelativePath,
'reptype'=> "", # Repository Type (global|local)
'options'=> ""
);
#
# Current Log
#
%cLog = (
'name' => "", # Fully qualified name
'size' => 0, # Current file size (stat)
'mode' => 0640, # Current file mode (stat)
'uid' => 0, # File Owner user (stat)
'gid' => 0, # File Owner group (stat)
'threshold' => 0,
'action' => "",
'max' => 0,
'params' => ""
);
#
# Current Group
#
%cGroup = (
'name' => 'common',
'pre' => '',
'post' => '',
'each' => ''
);
$vBanner ="** c h k l o g s V $version **";
$mailout="$ENV{'HOME'}/.chklogs.out"; # Temporary file for -m option
$global_cnt = 0;
$local_cnt = 0;
@tmStamp = localtime(time);
$ArchiveSuffix = sprintf(".%2d%02d%02d",$tmStamp[5],
$tmStamp[4] + 1,
$tmStamp[3]);
@Report = ();
@Xheader = ();
sPrint \@Xheader, "X-Degt-Product: Chklogs $version\n";
sPrint \@Xheader, "X-Degt-Action: cleanup\n";
}
#------------------------------------------------
# FUNCTION : ExecuteAction
# PROTOTYPE: ExecuteAction($FullLogName, $ActionType)
# INPUT :
# ActionType ::= archive | truncate | execute
# RETURNS : -
# GLOBALS : AgeFlag notify Today exec_pgm %cLog Repository admin
# DESCRIPTION
# Processes the log according to it's associated action. Every
# log can be processed according to 'ActionType'
# WARN Only warn administrator that this log is too big
# ARCHIVE Archive log, check if overriding truncate from command line
# TRUNCATE Truncate the log if it is the default action
# EXECUTE Execute external program, If %L was present it is replaced
# by absolute-log-name in the main function.
sub ExecuteAction {
my ($logname, $p_action) = @_;
# If the entry is time-oriented we have to adjust the meaning
# of cLog{'size'}, cLog{'threshold'} was already done in the main body.
if ($AgeFlag) { # Age (days) since last action
$cLog{'size'} = &DayCount($Today) -
&DayCount(&TimeLogStamp($logname,$p_action));
}
if ( $cLog{'size'} >= $cLog{'threshold'} ) {# Has it grown/aged too much?
$notify = 1; # Mail ONLY IF at least one is too big
if (defined($opWarn)) { # Warn only!
$p_action = $p_action . "!";
&ConsoleOutput($p_action);
}
if (defined($opArchive) || # Override default action!
$p_action eq 'archive') { # Default: archive
&ShuffleLogs($logname);
&ArchiveLog($logname);
}
elsif ($p_action eq 'execute') { # Execute external program
$ENV{'CDKROOT'} = $Repository;
$ENV{'CDKLOG'} = $logname;
$ENV{'CDKMAILTO'} = $admin;
system("$exec_pgm $cLog{'param'}");
&ConsoleOutput($p_action);
}
elsif ($p_action eq 'truncate') { # Default: truncate
# &ShuffleLogs($logname);
&TruncateLog($logname);
}
&ModifyTimeLog($logname,$Today,$p_action);
}
else { # Size is within limits
&ConsoleOutput(" ok"); # Size is ok
}
}
#------------------------------------------------
# FUNCTION : ProcessLog
# PROTOTYPE: ProcessLog($FullLogName)
# INPUT :
# RETURNS : -
# GLOBALS : %cLog DC exec_pgm
# DESCRIPTION
# Determines what to do with the log based
# on the options given and then dispatching
# the right action. Alternatively it processes
# non-action command line parameters
sub ProcessLog {
my $LogName = shift;
my ($dev,$ino,$nlink,$rdev);
my ($atime,$mtime,$ctime,$blksize,$blocks);
($dev,$ino, $cLog{'mode'}, $nlink, $cLog{'uid'}, $cLog{'gid'},
$rdev, $cLog{'size'}, $atime, $mtime,$ctime,
$blksize,$blocks) = stat($LogName);
#
# Now do whatever we want with the logs
# ARCHIVE option overrides TRUNCATE if given in the command line (-a)
#
if ((defined($opMail) || defined($opt_default) ||
defined($opArchive) || defined($opWarn)) && ($LogName ne "")) {
&ExecuteAction($LogName, $cLog{'action'});
}
elsif (defined($opCheck)) { # Just list the archives and logs
&ListLogs($LogName,$cLog{'action'});
}
elsif (defined($opTest)) { # Sanity check of index file
if (-r $LogName) { # Check if it exists/readable
if ($cLog{'action'} eq 'execute') {
printf "%-50s %+7s%s execute %s %s\n", $LogName,
$cLog{'threshold'},
$DC, $exec_pgm,
$cLog{'param'};
}
elsif ($cLog{'action'} eq 'truncate') {
printf "%-50s %+7s%s truncate -\n", $LogName,
$cLog{'threshold'}, $DC;
}
elsif ($LogName ne "") {
printf "%-50s %+7s%s %-8s %d\n", $LogName,
$cLog{'threshold'},
$DC, $cLog{'action'},
$cLog{'max'};
}
}
else {
printf"%50s %7s (does not exist!)\n", $LogName, $cLog{'threshold'};
}
}
}
#------------------------------------------------
# FUNCTION : ArchiveLog
# PROTOTYPE: ArchiveLog($FullLogName)
# INPUT :
# RETURNS : -
# GLOBALS : Repository ArchiveSuffix %cfg %cLog
# DESCRIPTION
# Archives the log by compressing it and adding a timestamp in
# the filename. No new log is created if it already exists.
# At this point Repository already contains a valid path for
# both local and global provided user has followed the rules.
sub ArchiveLog { # PROTO($)
my $logname = shift;
my ($archiveName, $zipArchive);
my $to;
$archiveName = $logname . $ArchiveSuffix;
$zipArchive = $archiveName . ".$zipext";
if ( ! -e $zipArchive) {
if (! system("$zipper < $logname > $zipArchive && : > $logname")) {
# Set correct ownership
chown $cLog{'uid'}, $cLog{'gid'}, $zipArchive;
chmod $cLog{'mode'}, $zipArchive; # Set correct permissions
&ConsoleOutput('archived');
# if ( $cfg{'reptype'} eq 'global' ) {
# $to = "$Repository/" . &basename($zipArchive);
# rename($zipArchive,$to);
# }
# elsif ( $cfg{'reptype'} eq 'local' ) {
# $to = $cfg{'local'} . '/' . &basename($zipArchive);
# rename($zipArchive,$to);
# }
# Already set in main taking into consideration
# global or local.
$to = "$Repository/" . &basename($zipArchive);
xdevRename($zipArchive, $to);
$global_cnt++ if ($cfg{'reptype'} eq 'global');
$local_cnt++ if ($cfg{'reptype'} eq 'local');
}
else {
warn "Could not create $zipArchive\n";
&ConsoleOutput('archive!'); # give a warning
}
}
else {
&ConsoleOutput('archive!'); # give a warning
}
}
#-----------------------------------------
# TruncateLog
# Truncates the log to zero length using system call
sub TruncateLog {
my $logname = shift;
truncate($logname,0);
&ConsoleOutput("truncated");
}
#-----------------------------------------
# ConsoleOutput
# Output to either console or mail file
sub ConsoleOutput {
my $act = shift;
my $tmp;
$tmp = sprintf "%-50s %7d%s %7d %s\n", $cLog{'name'},
$cLog{'size'}, $DC,
$cLog{'threshold'}, $act;
sPrint $REPORT, $tmp;
}
#-----------------------------------------
# ReportHeader
# Sets up the header to be displayed or mailed out
sub ReportHeader {
my ($tDay,$tMon,$tYear,$hostname,$tmp);
$hostname = `hostname`;
($tDay,$tMon,$tYear) = unpack('C A3 I',&GetCurrentDate);
$Today = "$tDay $tMon $tYear";
sPrint($REPORT, "\t\t\t$vBanner\n\n");
if (defined($opMail)) {
# sPrint \@Report, "\t\t\t$vBanner\n\n";
$tmp = sprintf "Configuration: %s Date: %s\nHost:%s%s\n",
$ConfFile, $Today, ' 'x8, $hostname;
sPrint \@Report, $tmp;
$tmp = sprintf "%-50s Current - Allowed Action\n", 'Log Name';
sPrint \@Report, $tmp;
Print \@Report, "-" x 50," ","-" x 7," ","-" x 7," ","-" x 6,"\n";
$notify = 0;
}
elsif (!defined($opCheck)) {
printf "Configuration: %s Date: %s\nHost:%s%s\n", $ConfFile,
$Today, ' 'x8, $hostname;
if (defined($opTest)) {
printf "%-50s Allowed Action Arch.\n","Log Name";
print "-" x 50," ","-" x 7," ","-" x 6," ","-----\n";
}
else {
printf "%-50s Current - Allowed Action\n","Log Name";
print "-" x 50," ","-" x 7," ","-" x 7," ","-" x 6,"\n";
}
}
}
#------------------------------------------------
# FUNCTION : ListLogs
# PROTOTYPE: ListLogs($Directory, $defaultActionType)
# INPUT :
# RETURNS : -
# GLOBALS : %cGroup %cfg
# DESCRIPTION
# Lists all logs matching the name, including a full path
sub ListLogs { # PROTO($,$)
my ($fullpath, $default_action) = @_;
my ($dname,$bname,$ldir);
$dname = &dirname($fullpath);
$bname = &basename($fullpath);
$ldir = "$dname/$cfg{'local'}";
print "\n\n====================================================\n";
print "Log: $bname(s) in: $dname [$default_action] Group: $cGroup{'name'}\n";
if (-d $ldir) {
chdir($ldir);
print "\t\t\tLOCAL REPOSITORY\n";
system("/bin/ls $bname* 2> /dev/null");
}
if ($cGroup{'name'} eq '' || $cGroup{'name'} eq 'common') {
$ldir = "$cfg{'global'}/common";
}
else {
$ldir = "$cfg{'global'}/$cGroup{'name'}";
}
if (-d $ldir) {
chdir($ldir);
print "\t\t\tGLOBAL REPOSITORY\n";
system("/bin/ls $bname* 2> /dev/null");
}
}
#-----------------------------------------
# Shuffles logs, when the maximum number of archived logs (per log name)
# is reached we remove the oldest so that we don't waste disk space with
# too many archived logs
#
sub ShuffleLogs {
my $fullpath = shift;
my ($logcnt,@LogList);
my ($dname,$bname);
$logcnt = 0;
$dname = $Repository;
$bname = &basename($fullpath);
chdir($dname);
open(ARCHLIST,"ls $bname.*.$zipext 2> /dev/null |") || warn "Could not pipe shuffle";
while (<ARCHLIST>) {
chomp($_);
@LogList[$logcnt] = $_;
$logcnt += 1;
}
close(ARCHLIST);
if ($logcnt >= $cLog{'max'}) {
unlink(@LogList[0]); # Remove oldest archived log
}
}
#-----------------------------------------
# Sanity Check of Configuration Parameters
#
sub ExecCheck {
my $which_prog = shift;
if (! -e $which_prog) {
print "Does not exist!\n";
}
elsif (! -x $which_prog) {
print "Is not executable!\n";
}
else { print "ok\n"; }
}
sub SanityCheck {
my ($mailer_prog, $remainder);
print "\nChecking configuration parameters...\n";
print "\tArchiver: $zipper -> ";
&ExecCheck($zipper);
($mailer_prog, $remainder) = split(/\s/,$mailer);
print "\tMailer: $mailer_prog -> ";
&ExecCheck($mailer_prog);
}
sub ReportTrailer {
my $MyOpt;
sPrint $REPORT, "\n\tArchives as : <basename>$ArchiveSuffix.$zipext\n";
sPrint $REPORT, "\tGlobal Repository: $cfg{'global'} ($global_cnt)\n";
sPrint $REPORT, "\tLocal Repository: <logDirname>/$cfg{'local'}" .
" ($local_cnt)\n";
sPrint $REPORT, "\tGlobal resource : $globalResource\n";
sPrint $REPORT, "\tPersonal resource: $personalResource\n";
sPrint $REPORT, "\tOptions:\n\t\t$cfg{'reptype'}\n";
sPrint $REPORT, "\t\tuseMiniMail=$useMiniMail\n";
}
sub ProcessDirectory {
my $Dir = shift;
my ($one_file);
$Dir =~ s|/$||;
if ( opendir(DIRHANDLE,$Dir) ) {
while ($one_file = readdir(DIRHANDLE)) {
if ( $one_file !~ /^\.{1,2}/ ) {
# Not the dot or dot-dot directory entries.
if ( $one_file !~ /\.$zipext$/ ) {
# Not a zipped file, as archives are.
if ( -f "$Dir/$one_file" ) {
# Is a normal file.
&ProcessLog("$Dir/$one_file");
}
}
}
}
closedir DIRHANDLE;
}
}
sub ShowVersion {
print "\tChkLogs version $version\n";
print "\tCopyright (c)1995-1997 D.Emilio Grimaldo T.\n";
print "\tFreely distributable\n";
printf "\t\tChklogs.pm\tv%s\n", &Chklogs::GetLibVersion;
printf "\t\tSmtp.pm\t\tv%s\n", &Smtp::GetVersion;
printf "\t\tDegtUtils.pm\tv%s\n", &DegtUtils::GetVersion;
printf "\t\tInterpret.pm\tv%s\n", &Chklogs::GetInterpretVersion;
exit 0;
}
#-----------------------------------------------------
# mailViaUserAgent
# A last resort for mailing the report. The SMTP
# socket is the preferred way but if your system
# doesn't have the latest Socket.pm (>= 1.5) which
# is platform independent then you will have to
# resort to dump to a temporary file and use the
# User Mail Agent such as Sendmail/Mailx/Smail.
sub mailViaUserAgent {
my $mailcmd="$mailer $admin"; # Command for mail option
my $i;
open(MAIL,"> $mailout") || die 'chklogs: Could not create mail file';
print MAIL "Subject: Chklogs Summary\n";
foreach $i (0 .. $#Xheader) {
print MAIL $Xheader[$i];
}
print MAIL "\n\n";
foreach $i (0 .. $#Report) {
print MAIL $Report[$i];
}
close(MAIL);
system("cat $mailout | $mailcmd");
# unlink($mailout);
}
#=====================================================
# MAIN
#=====================================================
$cvsId =~ m/Revision:\s+(\d+\.\d+\.*\d*\.*\d*)/;
$version = $1;
if ($#ARGV == -1) {
$opt_default = 1;
}
else {
if ($ARGV[0] eq "init" || $ARGV[0] eq "initrepos" || $ARGV[0] eq "newconf" ||
$ARGV[0] eq "newgroup" || $ARGV[0] eq "rmgroup" ||
$ARGV[0] eq "del" || $ARGV[0] eq "gadd" || $ARGV[0] eq "sync" ||
$ARGV[0] eq "when" || $ARGV[0] eq "syslog") {
exec 'chklogsadm', @ARGV;
}
my $i;
foreach $i (0 .. $#ARGV) {
if ($ARGV[$i] eq "-a") { $opArchive = 1; }
if ($ARGV[$i] eq "-m") { $opMail = 1; }
if ($ARGV[$i] eq "-c") { $opCheck = 1; }
if ($ARGV[$i] eq "-t") { $opTest = 1; }
if ($ARGV[$i] eq "-w") { $opWarn = 1; }
if ($ARGV[$i] eq "-v") { &ShowVersion }
}
}
&BeginChklogs;
&initialize; # Must be after interpreter
if (defined($opWarn) || defined($opCheck) || defined($opTest)) {
&DisableGroups;
&DisableTimed;
}
my ($BuiltinMethod, $pid, $valid);
open(CONF,$ConfFile) || die "Cannot open $ConfFile\n";
# Feature: TIMED LOGS
#
&ReadTimeLog; # Read in memory cache
&IdentifySyslog(1); # Be truly syslog aware...
$BuiltinMethod = 0;
$REPORT = \*STDOUT;
$REPORT = \@Report if ($opMail);
&ReportHeader();
while (<CONF>) {
if (/^#:options\s+/i || /^-options\s+/i) {
&ReadOptions($', \%cfg);
next;
}
if (/^#:global\s+/i || /^-global\s+/i) {
$cfg{'global'} = $';
chomp($cfg{'global'});
next;
}
if (/^#:group\s+/i || /^-group\s+/i) {
$cGroup{'name'} = $';
chomp $cGroup{'name'};
&ReadGroupConfiguration(\*CONF, \%cGroup);
`$cGroup{'pre'}` if $cGroup{'pre'} ne '';
next;
}
if (/^#/ || /^\s*$/) { # ignore comment line
`$cGroup{'post'}` if $cGroup{'post'} ne '';
$cGroup{'name'} = ""; # Reset group handling
$cGroup{'pre'} = "";
$cGroup{'post'} = "";
next;
}
$AgeFlag = 0; # Default is size-oriented
$DC = " ";
# Logname MaximumSize DefaultAction MaxNrLogs [ExecParameters]
($cLog{'name'}, $cLog{'threshold'}, $cLog{'action'},
$cLog{'max'}, $cLog{'param'}) = split(' ',$_,5);
# External handler?
undef($exec_pgm);
if ($cLog{'action'} eq 'execute') {
$exec_pgm = $cLog{'max'};
chomp($cLog{'param'});
$cLog{'param'} =~ s/%L/$cLog{'name'}/;
}
# Use the default minimum if not specified in index file
if ($cLog{'max'} == 0) { $cLog{'max'} = $maxlogs; }
if (&IsSyslogMember($cLog{'name'}) == 1) {
if ($BuiltinMethod == 0) {
$pid = &StopProcess($syslogF); # send SIGSTOP to syslogd
$BuiltinMethod = 1;
}
} else {
if ($BuiltinMethod == 1) {
&ContProcess($pid); # send SIGCONT to syslogd
$BuiltinMethod = 0; # Let it run free again
}
}
# Check if user means (kilo)bytes, days or months
if ($cLog{'threshold'} =~ /[kK]$/) {
$cLog{'threshold'} =~ s/[kK]$//;
$cLog{'threshold'} *= 1000;
}
elsif ($cLog{'threshold'} =~ /[dD]$/) {
$cLog{'threshold'} =~ s/[Dd]$//;
$AgeFlag = 1;
$DC = "d";
}
elsif ($cLog{'threshold'} =~ /[mM]$/) {
$cLog{'threshold'} =~ s/[mM]$//;
$cLog{'threshold'} *= 30;
$AgeFlag = 1;
$DC = "d";
}
#
# Check if the Action field is valid: `archive' or `truncate'
#
if ($cLog{'action'} ne 'archive' && $cLog{'action'} ne 'truncate' &&
$cLog{'action'} ne 'execute') {
$cLog{'action'} = '????';
}
# Feature: Alternate Repository
#
if ($cLog{'action'} eq 'archive') { # Not needed if execute or truncate
$valid = &ValidateRepository(\$Repository, \%cfg, \%cGroup,
&dirname($cLog{'name'}));
if ($valid != 0) {
# Leave it where it is... this assumption may
# change in the future.
$Repository = &dirname($cLog{'name'});
# Forgive but do not forget
print "Configuration line $. : Need to run chklogsadm initrepos\n";
}
}
if ( -d $cLog{'name'} ) {
&ProcessDirectory($cLog{'name'});
}
else {
&ProcessLog($cLog{'name'});
}
}
# Watchcat for syslog handling in case the last log was syslog member.
if ($BuiltinMethod == 1) {
&ContProcess($pid); # Meouw !!!
$BuiltinMethod = 0;
}
&WriteTimeLog;
#
# We are almost done...
#
close(CONF);
&SanityCheck() if defined($opTest);
&ReportTrailer() unless defined($opTest);
if (defined($opMail) && $notify) {
my $SMTP_S; # Handle to SMTP port socket
my $myself;
if ($useMiniMail eq 'yes') {
&OpenSMTP(\*SMTP_S, $mailhost);
$myself = $ENV{'USER'} || $ENV{'LOGNAME'} || 'nobody';
$myself .= '@' . &GethostSMTP;
&Mail(\*SMTP_S, # Socket handle
$admin, # To:
$myself, # From:
'Chklogs Summary', # Subject:
\@Report, # Message content
\@Xheader); # Custom headers
&CloseConnection(\*SMTP_S);
} else {
&mailViaUserAgent;
}
}