695 lines
15 KiB
Perl
Executable File
695 lines
15 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
#
|
|
# fixproc [-min n] [-max n] [-check | -kill | -restart | -exist | -fix] proc ...
|
|
#
|
|
# fixproc exit code:
|
|
# 0 ok
|
|
# 1 check failed
|
|
# 2 cannot restart
|
|
# 3 cannot kill
|
|
# 4 fix failed if fix is defined as kill or restart, then
|
|
# cannot kill or cannot restart is return instead
|
|
# 10 fixproc error
|
|
#
|
|
#
|
|
# Fixes a process named "proc" by performing the specified action. The
|
|
# actions can be check, kill, restart, exist, or fix. The action is specified
|
|
# on the command line or is read from a default database, which describes
|
|
# the default action to take for each process. The database format and
|
|
# the meaning of each action are described below.
|
|
#
|
|
# database format
|
|
# ---------------
|
|
#
|
|
# name foo required
|
|
# cmd /a/b/name args required
|
|
# min number optional, defaults to 1
|
|
# max number optional, defaults to 1
|
|
#
|
|
# check {null, exist, shell} optional, defaults to exist if not defined
|
|
# [shell command shell commands needed only if check=shell
|
|
# ...
|
|
# shell command
|
|
# end_shell] keyword end_shell marks end of shell commands
|
|
# fix {kill, restart, shell} required
|
|
# [shell command shell commands needed only if fix=shell
|
|
# ...
|
|
# shell command
|
|
# end_shell] keyword end_shell marks end of shell commands
|
|
#
|
|
# Blank lines and lines beginning with "#" are ignored.
|
|
#
|
|
#
|
|
# Example:
|
|
#
|
|
# name test1
|
|
# cmd nice /home/kong/z/test1 > /dev/null &
|
|
# max 2
|
|
# fix shell
|
|
# xterm&
|
|
# nice /home/kong/z/test1 > /dev/null &
|
|
# end_shell
|
|
#
|
|
#
|
|
# actions
|
|
# -------
|
|
# There are 5 possible actions: kill, restart, fix, exist, check. Fix is
|
|
# defined to be the kill action, the restart action, or a series of shell
|
|
# commands. Check is optionally defined in the database. If check is not
|
|
# defined, it defaults to exist.
|
|
#
|
|
# If the action is specified on the cmd line, it is executed regardless of
|
|
# check. The commands executed for each action type is as follow:
|
|
#
|
|
# switch action:
|
|
# kill:
|
|
# kill process, wait 5 seconds, kill -9 if still exist
|
|
# if still exist
|
|
# return "cannot kill"
|
|
# else
|
|
# return "ok"
|
|
#
|
|
# restart:
|
|
# execute kill
|
|
# if kill returned "cannot kill"
|
|
# return "cannot kill"
|
|
# restart by issuing cmd to shell
|
|
# if check defined
|
|
# execute check
|
|
# if check succeeds
|
|
# return "ok"
|
|
# else
|
|
# return "cannot restart"
|
|
#
|
|
# fix:
|
|
# if fix=kill
|
|
# execute kill
|
|
# else if fix=restart
|
|
# execute restart
|
|
# else
|
|
# execute shell commands
|
|
# execute check
|
|
#
|
|
# check:
|
|
# if check defined as null
|
|
# return "fixproc error"
|
|
# else
|
|
# execute check
|
|
# if check succeeds
|
|
# return (execute exist)
|
|
# return "check failed"
|
|
#
|
|
# exist:
|
|
# if proc exists in ps && (min <= num. of processes <= max)
|
|
# return "ok"
|
|
# else
|
|
# return "check failed"
|
|
#
|
|
#
|
|
# If the action is not specified on the cmd line, the default action is the
|
|
# fix action defined in the database. Fix is only executed if check fails:
|
|
#
|
|
# if fix defined
|
|
# if check is not defined as null
|
|
# execute check
|
|
# if check succeeds
|
|
# return "ok"
|
|
# execute action defined for fix
|
|
# else
|
|
# return "fixproc error"
|
|
#
|
|
#
|
|
# If proc is not specified on the command line, return "fixproc error."
|
|
# Multiple proc's can be defined on the cmd line. When an error occurs
|
|
# when multiple proc's are specified, the first error encountered halts the
|
|
# script.
|
|
#
|
|
# For check shell scripts, any non-zero exit code means the check has failed.
|
|
#
|
|
#
|
|
# Timothy Kong 3/1995
|
|
|
|
use File::Temp qw(tempfile);
|
|
|
|
$database_file = '/local/etc/fixproc.conf';
|
|
|
|
$debug = 0; # specify debug level using -dN
|
|
# currently defined: -d1
|
|
|
|
$no_error = 0;
|
|
$check_failed_error = 1;
|
|
$cannot_restart_error = 2;
|
|
$cannot_kill_error = 3;
|
|
$cannot_fix_error = 4;
|
|
$fixproc_error = 10;
|
|
|
|
$min = 1;
|
|
$max = 1;
|
|
$cmd_line_action = '';
|
|
%min = ();
|
|
%max = ();
|
|
%cmd = ();
|
|
%check = ();
|
|
%fix = ();
|
|
$shell_lines = ();
|
|
@proc_list = ();
|
|
|
|
$shell_header = "#!/bin/sh\n";
|
|
$shell_end_marker = 'shell_end_marker';
|
|
|
|
&read_args();
|
|
&read_database();
|
|
# &dump_database(); # debug only
|
|
|
|
# change the default min. and max. number of processes allowed
|
|
if ($min != 1)
|
|
{
|
|
for $name ( keys (%min) )
|
|
{
|
|
$min{$name} = $min;
|
|
}
|
|
}
|
|
if ($max != 1)
|
|
{
|
|
for $name ( keys (%max) )
|
|
{
|
|
$max{$name} = $max;
|
|
}
|
|
}
|
|
|
|
# work on one process at a time
|
|
for $proc ( @proc_list )
|
|
{
|
|
$error_code = &work_on_proc ($proc);
|
|
|
|
############# uncomment next line when fully working ############
|
|
# exit $error_code if ($error_code);
|
|
|
|
die "error_code = $error_code\n" if ($error_code);
|
|
}
|
|
|
|
|
|
# create an executable shell script file
|
|
sub create_sh_script
|
|
{
|
|
local ($file) = pop (@_);
|
|
local ($fh) = pop (@_);
|
|
local ($i) = pop (@_);
|
|
|
|
printf (STDERR "create_sh_script\n") if ($debug > 0);
|
|
|
|
$! = $fixproc_error;
|
|
while ( $shell_lines[$i] ne $shell_end_marker )
|
|
{
|
|
printf ($fh "%s", $shell_lines[$i]);
|
|
$i++;
|
|
}
|
|
close ($fh);
|
|
chmod 0755, $file;
|
|
}
|
|
|
|
|
|
sub do_fix
|
|
{
|
|
local ($proc) = pop(@_);
|
|
|
|
printf (STDERR "do_fix\n") if ($debug > 0);
|
|
|
|
if ($fix{$proc} eq '')
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: internal error 4\n";
|
|
}
|
|
if ($fix{$proc} eq 'kill')
|
|
{
|
|
return &do_kill ($proc);
|
|
}
|
|
elsif ($fix{$proc} eq 'restart')
|
|
{
|
|
return &do_restart ($proc);
|
|
}
|
|
else
|
|
{
|
|
# it must be "shell", so execute the shell script defined in database
|
|
local ($tmpfh, $tmpfile) = tempfile("fix_XXXXXXXX", DIR => "/tmp");
|
|
|
|
&create_sh_script ($fix{$proc}, $tmpfh, $tmpfile);
|
|
|
|
# return code is number divided by 256
|
|
$error_code = (system "$tmpfile") / 256;
|
|
unlink($tmpfile);
|
|
return ($fix_failed_error) if ($error_code != 0);
|
|
# sleep needed here?
|
|
return &do_exist ($proc);
|
|
}
|
|
}
|
|
|
|
|
|
sub do_check
|
|
{
|
|
local ($proc) = pop(@_);
|
|
|
|
printf (STDERR "do_check\n") if ($debug > 0);
|
|
|
|
if ($check{$proc} eq '')
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: internal error 2\n";
|
|
}
|
|
|
|
if ($check{$proc} ne 'exist')
|
|
{
|
|
# if not "exist", then it must be "shell", so execute the shell script
|
|
# defined in database
|
|
|
|
local ($tmpfh, $tmpfile) = tempfile("check_XXXXXXXX", DIR => "/tmp");
|
|
|
|
&create_sh_script ($fix{$proc}, $tmpfh, $tmpfile);
|
|
|
|
# return code is number divided by 256
|
|
$error_code = (system "$tmpfile") / 256;
|
|
unlink($tmpfile);
|
|
return ($check_failed_error) if ($error_code != 0);
|
|
|
|
# check passed, continue
|
|
}
|
|
return &do_exist ($proc);
|
|
}
|
|
|
|
|
|
sub do_exist
|
|
{
|
|
local ($proc) = pop(@_);
|
|
|
|
printf (STDERR "do_exist\n") if ($debug > 0);
|
|
|
|
# do ps, check to see if min <= no. of processes <= max
|
|
$! = $fixproc_error;
|
|
open (COMMAND, "/bin/ps -e | /bin/grep $proc | /bin/wc -l |")
|
|
|| die "$0: can't run ps-grep-wc command\n";
|
|
$proc_count = <COMMAND>;
|
|
if (($proc_count < $min{$proc}) || ($proc_count > $max{$proc}))
|
|
{
|
|
return $check_failed_error;
|
|
}
|
|
return $no_error;
|
|
}
|
|
|
|
|
|
sub do_kill
|
|
{
|
|
local ($proc) = pop(@_);
|
|
local ($second_kill_needed);
|
|
|
|
printf (STDERR "do_kill\n") if ($debug > 0);
|
|
|
|
# first try kill
|
|
$! = $fixproc_error;
|
|
open (COMMAND, "/bin/ps -e | /bin/grep $proc |")
|
|
|| die "$0: can't run ps-grep-awk command\n";
|
|
while (<COMMAND>)
|
|
{
|
|
# match the first field of ps -e
|
|
$! = $fixproc_error;
|
|
/^\s*(\d+)\s/ || die "$0: can't match ps -e output\n";
|
|
system "kill $1";
|
|
}
|
|
|
|
# if process still exist, try kill -9
|
|
sleep 2;
|
|
$! = $fixproc_error;
|
|
open (COMMAND, "/bin/ps -e | /bin/grep $proc |")
|
|
|| die "$0: can't run ps-grep-awk command\n";
|
|
$second_kill_needed = 0;
|
|
while (<COMMAND>)
|
|
{
|
|
# match the first field of ps -e
|
|
$! = $fixproc_error;
|
|
/^\s*(\d+)\s/ || die "$0: can't match ps -e output\n";
|
|
system "kill -9 $1";
|
|
$second_kill_needed = 1;
|
|
}
|
|
return ($no_error) if ($second_kill_needed == 0);
|
|
|
|
# see if kill -9 worked
|
|
sleep 2;
|
|
$! = $fixproc_error;
|
|
open (COMMAND, "/bin/ps -e | /bin/grep $proc |")
|
|
|| die "$0: can't run ps-grep-awk command\n";
|
|
while (<COMMAND>)
|
|
{ # a process still exist, return error
|
|
return $cannot_kill_error;
|
|
}
|
|
return $no_error; # good, all dead
|
|
}
|
|
|
|
|
|
sub do_restart
|
|
{
|
|
local ($proc) = pop(@_);
|
|
local ($error_code);
|
|
|
|
printf (STDERR "do_restart\n") if ($debug > 0);
|
|
|
|
$error_code = &do_kill ($proc);
|
|
return $error_code if ($error_code != $no_error);
|
|
die "$0: internal error 3\n" if ($cmd{$proc} eq '');
|
|
system "$cmd{$proc}";
|
|
# sleep needed here?
|
|
if ($check{$proc} ne 'null')
|
|
{
|
|
return $no_error if (&do_check($proc) == $no_error);
|
|
return $cannot_restart_error;
|
|
}
|
|
}
|
|
|
|
|
|
sub work_on_proc
|
|
{
|
|
local ($proc) = pop(@_);
|
|
local ($error_code);
|
|
|
|
printf (STDERR "work_on_proc\n") if ($debug > 0);
|
|
|
|
if ($cmd_line_action eq '')
|
|
{
|
|
# perform action from database
|
|
|
|
if ($check{$proc} ne 'null')
|
|
{
|
|
$error_code = &do_check ($proc);
|
|
if ($error_code != $check_failed_error)
|
|
{
|
|
return $error_code;
|
|
}
|
|
}
|
|
return &do_fix ($proc);
|
|
}
|
|
else
|
|
{
|
|
# perform action from command line
|
|
|
|
$error_code = $no_error;
|
|
if ($cmd_line_action eq 'kill')
|
|
{
|
|
$error_code = &do_kill ($proc);
|
|
}
|
|
elsif ($cmd_line_action eq 'restart')
|
|
{
|
|
$error_code = &do_restart ($proc);
|
|
}
|
|
elsif ($cmd_line_action eq 'fix')
|
|
{
|
|
$error_code = &do_fix ($proc);
|
|
}
|
|
elsif ($cmd_line_action eq 'check')
|
|
{
|
|
if ( $check{$proc} eq 'null' )
|
|
{
|
|
exit $fixproc_error;
|
|
}
|
|
$error_code = &do_check ($proc);
|
|
}
|
|
elsif ($cmd_line_action eq 'exist')
|
|
{
|
|
$error_code = &do_exist ($proc);
|
|
}
|
|
else
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: internal error 1\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
sub dump_database
|
|
{
|
|
local ($name);
|
|
|
|
for $name (keys(%cmd))
|
|
{
|
|
printf ("name\t%s\n", $name);
|
|
printf ("cmd\t%s\n", $cmd{$name});
|
|
printf ("min\t%s\n", $min{$name});
|
|
printf ("max\t%s\n", $max{$name});
|
|
if ( $check{$name} =~ /[0-9]+/ )
|
|
{
|
|
printf ("check\tshell\n");
|
|
$i = $check{$name};
|
|
while ( $shell_lines[$i] ne $shell_end_marker )
|
|
{
|
|
printf ("%s", $shell_lines[$i]);
|
|
$i++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf ("check\t%s\n", $check{$name});
|
|
}
|
|
if ( $fix{$name} =~ /[0-9]+/ )
|
|
{
|
|
printf ("fix\tshell\n");
|
|
$i = $fix{$name};
|
|
while ( $shell_lines[$i] ne $shell_end_marker )
|
|
{
|
|
printf ("%s", $shell_lines[$i]);
|
|
$i++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf ("fix\t%s\n", $fix{$name});
|
|
}
|
|
printf ("\n");
|
|
}
|
|
}
|
|
|
|
|
|
sub read_database
|
|
{
|
|
local ($in_check_shell_lines) = 0;
|
|
local ($in_fix_shell_lines) = 0;
|
|
local ($name) = '';
|
|
local ($str1);
|
|
local ($str2);
|
|
|
|
$! = $fixproc_error;
|
|
open (DB, $database_file) || die 'cannot open database file $database_file\n';
|
|
while (<DB>)
|
|
{
|
|
if ((! /\S/) || (/^[ \t]*#.*$/))
|
|
{
|
|
# ignore blank lines or lines beginning with "#"
|
|
}
|
|
elsif ($in_check_shell_lines)
|
|
{
|
|
if ( /^\s*end_shell\s*$/ )
|
|
{
|
|
$in_check_shell_lines = 0;
|
|
push (@shell_lines, $shell_end_marker);
|
|
}
|
|
else
|
|
{
|
|
push (@shell_lines, $_);
|
|
}
|
|
}
|
|
elsif ($in_fix_shell_lines)
|
|
{
|
|
if ( /^\s*end_shell\s*$/ )
|
|
{
|
|
$in_fix_shell_lines = 0;
|
|
push (@shell_lines, $shell_end_marker);
|
|
}
|
|
else
|
|
{
|
|
push (@shell_lines, $_);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( ! /^\s*(\S+)\s+(\S.*)\s*$/ )
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: syntax error in database\n$_";
|
|
}
|
|
$str1 = $1;
|
|
$str2 = $2;
|
|
if ($str1 eq 'name')
|
|
{
|
|
&finish_db_entry($name);
|
|
$name = $str2;
|
|
}
|
|
elsif ($str1 eq 'cmd')
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: cmd specified before name in database\n$_\n"
|
|
if ($name eq '');
|
|
die "$0: cmd specified multiple times for $name in database\n"
|
|
if ($cmd{$name} ne '');
|
|
$cmd{$name} = $str2;
|
|
}
|
|
elsif ($str1 eq 'min')
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: min specified before name in database\n$_\n"
|
|
if ($name eq '');
|
|
die "$0: min specified multiple times in database\n$_\n"
|
|
if ($min{$name} ne '');
|
|
die "$0: non-numeric min value in database\n$_\n"
|
|
if ( ! ($str2 =~ /[0-9]+/ ));
|
|
$min{$name} = $str2;
|
|
}
|
|
elsif ($str1 eq 'max')
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: max specified before name in database\n$_\n"
|
|
if ($name eq '');
|
|
die "$0: max specified multiple times in database\n$_\n"
|
|
if ($max{$name} ne '');
|
|
die "$0: non-numeric max value in database\n$_\n"
|
|
if ( ! ($str2 =~ /[0-9]+/ ));
|
|
$max{$name} = $str2;
|
|
}
|
|
elsif ($str1 eq 'check')
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: check specified before name in database\n$_\n"
|
|
if ($name eq '');
|
|
die "$0: check specified multiple times in database\n$_\n"
|
|
if ($check{$name} ne '');
|
|
if ( $str2 eq 'shell' )
|
|
{
|
|
# if $check{$name} is a number, it is a pointer into
|
|
# $shell_lines[] where the shell commands are kept
|
|
$shell_lines[$#shell_lines+1] = $shell_header;
|
|
$check{$name} = $#shell_lines;
|
|
$in_check_shell_lines = 1;
|
|
}
|
|
else
|
|
{
|
|
$check{$name} = $str2;
|
|
}
|
|
}
|
|
elsif ($str1 eq 'fix')
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: fix specified before name in database\n$_\n"
|
|
if ($name eq '');
|
|
die "$0: fix specified multiple times in database\n$_\n"
|
|
if ($fix{$name} ne '');
|
|
if ( $str2 eq 'shell' )
|
|
{
|
|
# if $fix{$name} is a number, it is a pointer into
|
|
# $shell_lines[] where the shell commands are kept
|
|
$shell_lines[$#shell_lines+1] = $shell_header;
|
|
$fix{$name} = $#shell_lines;
|
|
$in_fix_shell_lines = 1;
|
|
}
|
|
else
|
|
{
|
|
$fix{$name} = $str2;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
&finish_db_entry($name);
|
|
}
|
|
|
|
|
|
sub finish_db_entry
|
|
{
|
|
local ($name) = pop(@_);
|
|
|
|
if ($name ne '')
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: fix not defined for $name in database\n"
|
|
if ($fix{$name} eq '');
|
|
die "$0: cmd not defined for $name in database\n"
|
|
if ($cmd{$name} eq '');
|
|
$check{$name} = 'exist' if ($check{$name} eq '');
|
|
$max{$name} = 1 if ($max{$name} eq '');
|
|
$min{$name} = 1 if ($min{$name} eq '');
|
|
}
|
|
}
|
|
|
|
|
|
sub read_args
|
|
{
|
|
local ($i) = 0;
|
|
local ($arg);
|
|
local ($action_arg_count) = 0;
|
|
|
|
while ( $i <= $#ARGV )
|
|
{
|
|
$arg = $ARGV[$i];
|
|
if (($arg eq '-min') || ($arg eq '-max'))
|
|
{
|
|
if (($i == $#ARGV - 1) || ($ARGV[$i+1] =~ /\D/)) # \D is non-numeric
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: numeric arg missing after -min or -max\n";
|
|
}
|
|
if ($arg eq '-min')
|
|
{
|
|
$min = $ARGV[$i+1];
|
|
}
|
|
else
|
|
{
|
|
$max = $ARGV[$i+1];
|
|
}
|
|
$i += 2;
|
|
}
|
|
elsif ($arg eq '-kill')
|
|
{
|
|
$cmd_line_action = 'kill';
|
|
$action_arg_count++;
|
|
$i++;
|
|
}
|
|
elsif ($arg eq '-check')
|
|
{
|
|
$cmd_line_action = 'check';
|
|
$action_arg_count++;
|
|
$i++;
|
|
}
|
|
elsif ($arg eq '-restart')
|
|
{
|
|
$cmd_line_action = 'restart';
|
|
$action_arg_count++;
|
|
$i++;
|
|
}
|
|
elsif ($arg eq '-exist')
|
|
{
|
|
$cmd_line_action = 'exist';
|
|
$action_arg_count++;
|
|
$i++;
|
|
}
|
|
elsif ($arg eq '-fix')
|
|
{
|
|
$cmd_line_action = 'fix';
|
|
$action_arg_count++;
|
|
$i++;
|
|
}
|
|
elsif ($arg =~ /-d(\d)$/)
|
|
{
|
|
$debug = $1;
|
|
$i++;
|
|
}
|
|
elsif ($arg =~ /^-/)
|
|
{
|
|
$! = $fixproc_error;
|
|
die "$0: unknown switch $arg\n";
|
|
}
|
|
else
|
|
{
|
|
push (@proc_list, $arg);
|
|
$i++;
|
|
}
|
|
}
|
|
$! = $fixproc_error;
|
|
die "$0: no process specified\n" if ($#proc_list == -1);
|
|
die "$0: more than one action specified\n" if ($action_arg_count > 1);
|
|
}
|
|
|