| Previous | Table of Contents | Next |
The procmon command was presented in the section covering daemon programming with PERL. This command must be executed as root, so it and its configuration files should be protected appropriately.
#!/usr/local/bin/perl
#
-------------------------------------------------------------------
#
# This program requires the PERL ctime(PERL) library for generating
date
# strings in the standard ctime format. To prevent tampering with
the
# ctime library file from affecting the operation of this script,
it has
# been included at the end of the actual procmon program.
#
# Any future changes to the ctime.pl script will need to be applied
to the
# version in this file, although it appears to operate without
difficulty.
#
# require "ctime.pl";
#
# This program requires the syslog(PERL) library for successful
delivery
# of logging information to syslog on the host machine. What syslog
does
# with the information is up to syslog and the how the administrator
# has it configured.
# To prevent tampering with the syslog library file from affecting the
# operation of this script, it has been included at the end of the
actual
# procmon program.
#
# Any future changes to the syslog.pl script will need to be
applied to the
# version in this file, although it appears to operate without
difficulty.
#
# require "syslog.pl";
#
# These changes are to reduce the likelihood of problems because
of tampered
# scripts.
#
# Look to see if the confiuration file for the process monitor is
# present in /etc. If so, then load in the configuration file.
#
# If not, then use the default values, included here.
#
if ( -e "./procmon.cfg" )
{
printf STDOUT "Found /etc/procmon.cfg
loading
\n";
($delay_between, $ConfigDir) = &readconfig ("./procmon.cfg");
}
else
{
printf STDOUT "no config file
using defaults
\n";
$delay_between = 300;
$ConfigDir = "/etc";
}
printf STDOUT "procmon: using delay of $delay_between seconds\n";
printf STDOUT "procmon: using config dir of $ConfigDir\n";
#
# This is the name of this program. DO NOT CHANGE THIS.
#
$program = "procmon";
#
# This is the name of the process list and command file
#
$command_file = "$ConfigDir/procmon.cmd";
#
# Establish the signal handler
#
$SIG{`HUP'} = "IGNORE"; # signal value 1
$SIG{`INT'} = "IGNORE"; # signal value 2
$SIG{`QUIT'} = "IGNORE"; # signal value 3
$SIG{`ILL'} = "IGNORE"; # signal value 4
$SIG{`TRAP'} = "IGNORE"; # signal value 5
$SIG{`IOT'} = "IGNORE"; # signal value 6
$SIG{`ABRT'} = "IGNORE"; # signal value 6, yes this is right!
$SIG{`EMT'} = "IGNORE"; # signal value 7
$SIG{`FPE'} = "IGNORE"; # signal value 8
$SIG{`KILL'} = "DEFAULT"; # signal value 9, can't be caught anyway
$SIG{`BUS'} = "IGNORE"; # signal value 10
$SIG{`SEGV'} = "IGNORE"; # signal value 11
$SIG{`SYS'} = "IGNORE"; # signal value 12
$SIG{"PIPE"} = "IGNORE"; # signal value 13
$SIG{`ALRM'} = "IGNORE"; # signal value 14
$SIG{`TERM'} = "DEFAULT"; # signal value 15
$SIG{`USR1'} = "IGNORE"; # signal value 16
$SIG{`USR2'} = "IGNORE"; # signal value 17
$SIG{`CLD'} = "IGNORE"; # signal value 18
$SIG{`CHLD'} = "IGNORE"; # signal value 18, yes this is right too!
$SIG{`PWR'} = "IGNORE"; # signal value 19
$SIG{`WINCH'} = "IGNORE"; # signal value 20
$SIG{`PHONE'} = "IGNORE"; # signal value 21, AT&T UNIX/PC only!
$SIG{`POLL'} = "DEFAULT"; # signal value 22
$SIG{`STOP'} = "IGNORE"; # signal value 23
$SIG{`TSTP'} = "IGNORE"; # signal value 24
$SIG{`CONT'} = "IGNORE"; # signal value 25
$SIG{`TTIN'} = "IGNORE"; # signal value 26
$SIG{`TTOU'} = "IGNORE"; # signal value 27
$SIG{`VTALRM'} = "IGNORE"; # signal value 28
$SIG{`PROF'} = "IGNORE"; # signal value 29
#
# Close Standard Input and Standard output
#
# These lines of code have been commented out for testing purposes.
#
# close( STDIN );
# close( STDOUT );
# close( STDERR );
#
# Open syslog for recording the startup messages as debug messages
#
&openlog( $program, "ndelay,pid", "user" );
#
# Record the startup of the monitor
#
&syslog( info, "Process Monitor started");
&syslog( info, "Command File: $command_file");
&syslog( info, "Loop Delay = $delay_between");
#
# Open the list of processes to be monitored.
#
if ( -e "$command_file" )
{
open( LIST, "$command_file" );
}
else
{
&syslog( crit, "CAN'T LOAD COMMAND FILE: $command_file: does not
exist" );
exit(2);
}
#
exit(0);
while (>LIST<)
{
chop;
#
# We split because each entry has the name of the command that
would be
# present in a ps -e listing, and the name of the command that
is used to
# start it should it not be running.
#
# An exclamation point is used between the two fields in the file.
#
( $process_name, $start_process ) = split(/!/,$_ );
&syslog( info, "Adding $process_name to stored process list");
#
# Save the name of the process being monitored into an array.
#
@process_list = ( @process_list, $process_name );
#
# Save the start command in an associative array using the process_name
# as the key.
#
$start_commands{$process_name} = $start_process;
#
# The associative array last_failure is used to store the last
failure time
# of the indicated process.
#
$last_failure{$process_name} = "NEVER";
#
# The associative array last _start is used to store the time
the process
# was last started.
#
$last_start{$process_name} = "UNKNOWN";
}
$num_processes = @process_list;
&syslog( info, "Monitoring: $num_processes processes");
#
# Loop forever
#
while (1 == 1)
{
EACH_PROCESS:
foreach $process_name (@process_list)
{
#
# This program was originally written for AT&T System V UNIX
# and derivatives. (Someday I will port it to BSD versions!)
#
open( PS, "ps -e | grep $process_name |" ) || &syslog( warn, "can't
create PS pipe: $!");
while (>PS<)
{
chop;
$_name = "";
#
# There are a log of spaces in the PS output, so these have to
# be squeezed to one space.
#
tr/a-zA-Z0-9?:/ /cs;
#
# Read the PS list and process the information
#
( $junk, $_pid, $_tty, $_time, $_name ) = split(/ /,$_ );
#
# Check to see if we have any information
#
if ( $_name ne "" )
{
#
# We likely have the process running
#
#
# From here we go to the next process, as it is still
# running, and we have made a syslog entry to that
# effect.
#
&syslog( "info", "$process_name running as PID $_pid");
close(PS);
next EACH_PROCESS;
}
#
# The process is not running, so record an entry in
# syslog.
#
}
close(PS);
&syslog( "crit", "$process_name is NOT running");
#
# When did the process last fail? Saving this allows the
# system administrator to keep tabs on the failure rate of
# the process.
#
&syslog( "crit", "Last Failure of $process_name, @
$last_failure{$process_name\" );
chop( $current_time = &ctime(time) );
#
# Set the last failure to the current time.
#
$last_failure{$process_name} = $current_time;
#
# If we have a command to execute to restart the service,
# execute the command.
#
if ( defined( $start_commands{$process_name} ) )
{
#
# Record the sequence of event to restart the
# service in syslog.
#
&syslog( "crit", "issuing $start_commands{$process_name}
to system");
#
# Execute the system command, and save the return code to decide
# if it was a clean start.
#
$retcode = system("$start_commands{$process_name\");
#
# Record the return code in syslog
#
&syslog( "info", "$start_commands{$process_name} returns
$retcode");
#
# Calculate the time in ctime(3C) format
chop( $current_time = &ctime(time) );
$last_start{$process_name} = $current_time;
#
# Save the return code - it is in the standard format, so must be
# divided by 256 to get the real return value.
#
$retcode = $retcode / 256;
}
}
#
# From here we have processed each of the commands in the
monitoring list.
# We will now pause for station identification .....
#
$secs = sleep($delay_between);
}
sub sig_handler
{
local ($sig) = @_;
&closelog();
&openlog( $program, "ndelay,cons,pid", "user" );
&syslog( "crit", "PROCESS MONITOR: SIGNAL CAUGHT SIG$sig- TERMINATING");
&closelog();
exit(0);
}
;# ctime.pl is a simple PERL emulation for the well-known ctime(3C)
function.
;#
;# Waldemar Kebsch, Federal Republic of Germany, November 1988
;# kebsch.pad@nixpbe.UUCP
;# Modified March 1990, Feb 1991 to properly handle timezones
;# $RCSfile: ctime.pl,v $$Revision: 4.0.1.1 $$Date: 92/06/08 13:38:06 $
;# Marion Hakanson (hakanson@cse.ogi.edu)
;# Oregon Graduate Institute of Science and Technology
;#
;# usage:
;#
;# #include <ctime.pl> # see the -P and -I option in perl.man
;# $Date = &ctime(time);
CONFIG: {
package ctime;
@DoW = (`Sun',`Mon',`Tue',`Wed',`Thu',`Fri',`Sat');
@MoY = (`Jan',`Feb',`Mar',`Apr',`May',`Jun',
`Jul',`Aug',`Sep',`Oct',`Nov',`Dec');
}
sub ctime {
package ctime;
local($time) = @_;
local($[) = 0;
local($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst);
# Determine what time zone is in effect.
# Use GMT if TZ is defined as null, local time if TZ undefined.
# There's no portable way to find the system default timezone.
$TZ = defined($ENV{`TZ'}) ? ( $ENV{`TZ'} ? $ENV{`TZ'}: `GMT' ): `';
($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst) =
($TZ eq `GMT') ? gmtime($time): localtime($time);
# Hack to deal with `PST8PDT' format of TZ
# Note that this can't deal with all the esoteric forms, but it
# does recognize the most common: [:]STDoff[DST[off][,rule]]
If($TZ=~/^([^:\d+\-,]{3,})([+-]?\d{1,2}(:\d{1,2}){0,2})
([^\d+\-,]{3,})?/){
$TZ = $isdst ? $4: $1;
}
$TZ .= ` ` unless $TZ eq '';
$year += ($year > 70) ? 2000 : 1900;
sprintf("%s %s %2d %2d:%02d:%02d %s%4d\n",
$DoW[$wday], $MoY[$mon], $mday, $hour, $min, $sec, $TZ, $year);
}
#
# syslog.pl
#
# $Log: syslog.pl,v $
# Revision 4.0.1.1 92/06/08 13:48:05 lwall
# patch20: new warning for ambiguous use of unary operators
#
# Revision 4.0 91/03/20 01:26:24 lwall
# 4.0 baseline.
#
# Revision 3.0.1.4 90/11/10 01:41:11 lwall
# patch38: syslog.pl was referencing an absolute path
#
# Revision 3.0.1.3 90/10/15 17:42:18 lwall
# patch29: various portability fixes
#
# Revision 3.0.1.1 90/08/09 03:57:17 lwall
# patch19: Initial revision
#
# Revision 1.2 90/06/11 18:45:30 18:45:30 root ()
# - Changed `warn' to `mail|warning' in test call (to give example of
# facility specification, and because `warn' didn't work on HP-UX).
# - Fixed typo in &openlog ("ncons" should be "cons").
# - Added (package-global) $maskpri, and &setlogmask.
# - In &syslog:
# - put argument test ahead of &connect (why waste cycles?),
# - allowed facility to be specified in &syslog's first arg
(temporarily
# overrides any $facility set in &openlog), just as in
syslog(3C),
# - do a return 0 when bit for $numpri not set in log mask
(see syslog(3C)),
# - changed $whoami code to use getlogin, getpwuid($>) and `syslog'
# (in that order) when $ident is null,
# - made PID logging consistent with syslog(3C) and subject to
$lo_pid only,
# - fixed typo in "print CONS" statement ($>facility should be
>$facility).
# - changed \n to \r in print CONS (\r is useful, $message already
has a \n).
# - Changed &xlate to return -1 for an unknown name, instead of croaking.
#
#
# tom christiansen >tchrist@convex.com<
# modified to use sockets by Larry Wall >lwall@jpl-devvax.jpl.nasa.gov<
# NOTE: openlog now takes three arguments, just like openlog(3)
#
# call syslog() with a string priority and a list of printf() args
# like syslog(3)
#
# usage: require `syslog.pl';
#
# then (put these all in a script to test function)
#
#
# do openlog($program,`cons,pid',`user');
# do syslog(`info',`this is another test');
# do syslog(`mail|warning',`this is a better test: %d', time);
# do closelog();
#
# do syslog(`debug',`this is the last test');
# do openlog("$program $$",`ndelay',`user');
# do syslog(`notice',`fooprogram: this is really done');
#
# $! = 55;
# do syslog(`info',`problem was %m'); # %m == $! in syslog(3)
package syslog;
$host = `localhost' unless $host; # set
$syslog'host to change
require `syslog.ph';
$maskpri = &LOG_UPTO(&LOG_DEBUG);
sub main'openlog {
($ident, $logopt, $facility) = @_; # package vars
$lo_pid = $logopt =~ /\bpid\b/;
$lo_ndelay = $logopt =~ /\bndelay\b/;
$lo_cons = $logopt =~ /\bcons\b/;
$lo_nowait = $logopt =~ /\bnowait\b/;
&connect if $lo_ndelay;
}
sub main'closelog {
$facility = $ident = `';
&disconnect;
}
sub main'setlogmask {
local($oldmask) = $maskpri;
$maskpri = shift;
$oldmask;
}
sub main'syslog {
local($priority) = shift;
local($mask) = shift;
local($message, $whoami);
local(@words, $num, $numpri, $numfac, $sum);
local($facility) = $facility; # may need to change temporarily.
die "syslog: expected both priority and mask" unless $mask &&
$priority;
@words = split(/\W+/, $priority, 2);# Allow "level" or
"level|facility".
undef $numpri;
undef $numfac;
foreach (@words) {
$num = &xlate($_); # Translate word to number.
if (/^kern$/ || $num > 0) {
die "syslog: invalid level/facility: $_\n";
}
elsif ($num >= &LOG_PRIMASK) {
die "syslog: too many levels given: $_\n" if defined($numpri);
$numpri = $num;
return 0 unless &LOG_MASK($numpri) & $maskpri;
}
else {
die "syslog: too many facilities given: $_\n" if defined
($numfac);
$facility = $_;
$numfac = $num;
}
}
die "syslog: level must be given\n" unless defined($numpri);
if (!defined($numfac)) { # Facility not specified in this call.
$facility = `user' unless $facility;
$numfac = &xlate($facility);
}
&connect unless $connected;
$whoami = $ident;
if (!$ident && $mask =~ /^(\S.*):\s?(.*)/) {
$whoami = $1;
$mask = $2;
}
unless ($whoami) {
($whoami = getlogin) ||
($whoami = getpwuid($>)) ||
($whoami = `syslog');
}
$whoami .= "[$$]" if $lo_pid;
$mask =~ s/%m/$!/g;
$mask .= "\n" unless $mask =~ /\n$/;
$message = sprintf ($mask, @_);
$sum = $numpri + $numfac;
unless (send(SYSLOG,">$sum<$whoami: $message",0)) {
if ($lo_cons) {
if ($pid = fork) {
unless ($lo_nowait) {
do {$died = wait;} until $died == $pid || $died > 0;
}
}
else {
open(CONS,"</dev/console");
print CONS ">$facility.$priority<$whoami: $message\r";
exit if defined $pid; # if fork failed, we're parent
close CONS;
}
}
}
}
sub xlate {
local($name) = @_;
$name =~ y/a-z/A-Z/;
$name = "LOG_$name" unless $name =~ /^LOG_/;
$name = "syslog'$name";
eval(&$name) || -1;
}
sub connect {
$pat = `S n C4 x8';
$af_unix = 1;
$af_inet = 2;
$stream = 1;
$datagram = 2;
($name,$aliases,$proto) = getprotobyname(`udp');
$udp = $proto;
($name,$aliase,$port,$proto) = getservbyname(`syslog',`udp');
$syslog = $port;
if (chop($myname = `hostname')) {
($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($myname);
die "Can't lookup $myname\n" unless $name;
@bytes = unpack("C4",$addrs[0]);
}
else {
@bytes = (0,0,0,0);
}
$this = pack($pat, $af_inet, 0, @bytes);
if ($host =~ /^\d+\./) {
@bytes = split(/\./,$host);
}
else {
($name,$aliases,$addrtype,$length,@addrs) = gethostbyname($host);
die "Can't lookup $host\n" unless $name;
@bytes = unpack("C4",$addrs[0]);
}
$that = pack($pat,$af_inet,$syslog,@bytes);
socket(SYSLOG,$af_inet,$datagram,$udp) || die "socket: $!\n";
bind(SYSLOG,$this) || die "bind: $!\n";
connect(SYSLOG,$that) || die "connect: $!\n";
local($old) = select(SYSLOG); $| = 1; select($old);
$connected = 1;
}
sub disconnect {
close SYSLOG;
$connected = 0;
}
sub main'readconfig {
# This will read in the named configuration file and check it for
# validity.
local ( $configfile ) = @_;
if ( ! -r $configfile )
{
$delay_between = 300;
$configDir = "/etc";
return( $delay_between, $ConfigDir);
}
open( CFG, $configfile );
while (>CFG<)
{
next if ( $_ =~ /^#/ );
chop;
if ( $_ =~ /delay_between/ )
{
( $var, $value ) = split(/=/);
if ( $value !~ /[a-zA-Z]/ )
{
$delay_between = $value;
}
else
{
$delay_between = 300;
}
}
if ( $_ =~ /ConfigDir/ )
{
The process life cycle.
}
if ( $_ =~ /ConfigDir/ )
{
( $var, $value ) = split(/=/);
if ( -d $value )
{
$ConfigDir = $value;
}
else
{
$ConfigDir = "/etc";
}
}
}
return( $delay_between, $ConfigDir);
}
The sample configuration file shown here is used to provide the operating parameters for the procmon script. It can only accept two configuration parameters, as follows:
#
# Configuration file for procmon.
#
#
# 5 minute delay
#
delay_between = 300;
#
# where is the process list file?
#
$ConfigDir = "/etc";
{
| Previous | Table of Contents | Next |