#!/usr/local/bin/perl
# New_log.pl
# Preserves current logs by moving them and appending a date stamp.
#
# Expected input:
# A text file where the first line indicates a directory
# and subsequent lines list patterns for target log files.
# Prompts for a this configuration file if not specified.
#
# Expected output:
# Diagnostic messages
#
#
# 12/05/1997 Version 1.2.1
# This script is free, public domain, no strings attached
# Igor S. Livshits <mailto:igorl@ayradyss.org>
# Define some global variables
#
$true= (1==1);
$false= (1==0);
$newLine= "\n";
$space= " ";
$suffixDelimiter= ".";
$configFilePrompt= "Please specify your configuration file:";
$oldSubdirectory= "old/"; # where to dump old logs
$directoryPath= "DIRECTORY+PATH"; # reserved token
$controlFileTag= "rc"; # denotes a special control file for a process
$syslogTag= "syslog"; # denotes log maintained via syslog
$syslogDaemon= "syslogd"; # the name of the syslogd process
$startCommand= "start"; # commands for the control file
$stopCommand= "stop";
@controlFileDirectories= ("/etc/rc2.d/", "/etc/rc3.d/");
$psCommand= "/usr/bin/ps -eaf -o pid,user,time,stime,comm |";
# Grab our input
#
$configFile= shift; # Only grab the first command line argument
$configFile= &AskForFilePath($configFilePrompt) unless $configFile;
# Find and rename specified files
#
%matchedPatterns= &RenameMatchingFiles($configFile);
# Appropriately signal affected processes
#
&SignalAffectedProcesses($matchedPatterns);
# Terminate gracefully
#
exit(0);
#
# Subroutines
#
# AskForFilePath($promptString)
# Asks for a path to a file, returning it
#
# $promptString is just a string displayed to the user
#
sub AskForFilePath
{
print "$newLine";
print (shift);
print "$newLine>";
while (<STDIN>)
{ # read a line from standard input
chop; # kill the terminating newline
last if -e $_; # make sure the file exists
print $newLine, $newLine;
print "The file you just specified does not seem to exist.$newLine";
print "Please re-enter the path:$newLine";
print ">";
}
return $_; # return the file path
}
# RenameMatchingFiles($configFile)
# Renames files in a given directory that match provided patterns
#
# $configFile specifies the path to our input file
#
sub RenameMatchingFiles
{
local($configFile)= shift; # path to the configuration file
local(%matchPatterns); # patterns which specify target files
local($targetDirectory); # where the targte logs reside
local($dateStamp); # a unique suffix for renamed logs
local(%matchedPatterns); # patterns that were successfully matched
local($logDirectory, @logs); # path to and contents of the log directory
local($matchPattern, $candidate);
%matchPatterns= &ReadInput($configFile);
$logDirectory= $matchPatterns{$directoryPath};
undef($matchPatterns{$directoryPath});
$dateStamp= &CurrentDateStamp();
# List our log directory
opendir(LOGS, $logDirectory)
|| die "Could not open log directory <$logDirectory>.$newLine";
@logs= readdir(LOGS);
closedir(LOGS);
# Find matches and rename them
foreach $candidate (@logs)
{
next unless -f $logDirectory.$candidate; # Only check files
foreach $matchPattern (keys(%matchPatterns))
{ # See if this file matches our pattern
if ($candidate=~ /$matchPattern/)
{
print "[$matchPattern]: ";
if (rename($logDirectory.$candidate,
$logDirectory.$oldSubdirectory.$candidate.$dateStamp))
{
print "Successfully renamed <$logDirectory$candidate> to ",
"<$logDirectory$oldSubdirectory$candidate$dateStamp>.$newLine";
open(DUMMY, ">$logDirectory$candidate")
|| warn "Could not create empty <$logDirectory$candidate>.",
$newLine;
close(DUMMY);
$matchedPatterns{$matchPattern}= $matchPatterns{$matchPattern};
}
else
{
print "Could not move <$logDirectory$candidate>...$newLine";
}
last; # Only process each candidate once
}
}
}
return %matchedPatterns;
}
# ReadInput($inputFile)
# Read lines of the input file into an array
#
# $inputFile specifies the path to our input file
#
sub ReadInput
{
local($inputFile)= shift; # path to the input file
local(%patterns); # match patterns and signal hints
local(@patternSpec); # words of the pattern specification line
open(CONFIG, $inputFile)
|| die "Could not open configuration file <$inputFile>.$newLine";
# Grab the directory path from the first line
$patterns{$directoryPath}= <CONFIG>;
chop($patterns{$directoryPath});
while (<CONFIG>)
{ # process each subsequent line
@patternSpec= split;
$patterns{@patternSpec[0]}= @patternSpec[1];
}
close(CONFIG);
return %patterns;
}
# CurrentDateStamp()
# Returns a current date stamp as a string ".yymmdd"
#
sub CurrentDateStamp
{
local($second, $minute, $hour, $day, $month, $year)= localtime(time());
$month++; # correct for 0 based count
# and pad single digits and incomplete years
$month= "0".$month if ($month < 10);
$day= "0".$day if ($day < 10);
return $suffixDelimiter.$year.$month.$day;
}
# SignalAffectedProcesses($matchedPatterns)
# Send a hangup signal (default) or kill and restart affected processes
#
# %matchedPatterns lists process matching patterns and signal specification
#
sub SignalAffectedProcesses
{
local($matchedPatterns)= shift; # patterns of the log files we moved
local($pattern); # a given match pattern
local($currentFileHandle); # just a place holder
local($restartSyslogDaemon); # if we troubled any logs maintained by syslogd
$restartSyslogDaemon= $false;
# Separate previous messages from upcoming
print $newLine, "-=-=-=-", $newLine, $newLine;
# Set up buffering in case we call system() below
$currentFileHandle= select(STDOUT);
$|= $true;
select($currentFileHandle);
# Dispatch restart duties
foreach $pattern (keys(%matchedPatterns))
{
if ($matchedPatterns{$pattern} eq $controlFileTag)
{
&RestartViaControlFile($pattern);
}
elsif ($matchedPatterns{$pattern} eq $syslogTag)
{
$restartSyslogDaemon= $true;
}
else
{
&RestartViaHangupSignal($pattern);
}
}
if ($restartSyslogDaemon)
{ # Signal syslogd to notice new log files
&RestartViaHangupSignal($syslogDaemon);
}
}
# RestartViaControlFile($pattern)
# Find an appropriate control file and restart the process
#
# $pattern specifies how to match our process
#
sub RestartViaControlFile
{
local($pattern)= shift;
local($directory, @items, @candidates, $candidate);
# First, find all possible control files
foreach $directory (@controlFileDirectories)
{ # search for a control file
opendir(DIRECTORY, $directory) || next;
@items= readdir(DIRECTORY);
closedir(DIRECTORY);
foreach $candidate (@items)
{
next unless -f $directory.$candidate; # only check files
if ($candidate=~ /$pattern/)
{ # found a matching control file
push (@candidates, $directory.$candidate);
}
}
}
# Now, make sure it is unique and executable, and invoke it
if (@candidates > 1)
{ # ambiguous match pattern
print "[$pattern]: More than one matching control files found. ",
"Skipping...$newLine";
}
elsif (@candidates < 1)
{ # make sure we have at least one match
print "[$pattern]: No matching control files found. ",
"Skipping...$newLine";
}
else
{
if (-x @candidates[0])
{ # invoke it to stop then start our process
print "[$pattern]: "; # control file should write more :)
system(@candidates[0], $stopCommand);
system(@candidates[0], $startCommand);
}
else
{ # cannot execute this file
print "[$pattern]: Cannot execute @candidates[0]. ",
"Skipping...$newLine";
}
}
}
# RestartViaHangupSignal($pattern)
# Find an appropriate process and send it a hangup signal
#
# $pattern specifies how to match our process
#
sub RestartViaHangupSignal
{
local($pattern)= shift; # will match our target process
local($candidatePID, $result);
local(@candidates);
open(PROCESSES, $psCommand) || die "Cannot read current processes.";
while (<PROCESSES>)
{
push(@candidates, $_) if (/$pattern/);
}
close(PROCESSES);
if (@candidates)
{
if (@candidates > 1)
{
print "[$pattern]: Multiple instances running! Skipping...$newLine";
}
else
{
$candidates[0]=~ s/^$space+//; # kill leading spaces
($candidatePID)= split($space, $candidates[0]);
$result= kill 'HUP', $candidatePID;
if ($result)
{
print "[$pattern]: Process #$candidatePID restarted.$newLine";
}
else
{
print "[$pattern]: Failed to restart process #$candidatePID.$newLine";
}
}
}
else
{
print "[$pattern]: No matching active processes.$newLine";
}
}