#!/usr/bin/perl -wT
# $Ringlet: perl/sys/stalepid/stalepid.pl,v 1.2 2003/12/04 14:09:09 roam Exp $

use strict;

use Errno;
use Error qw(:try);
use Getopt::Std;

sub usage()
{
	die("Usage: stalepid [-5bhV] pidfile processname\n".
	    "\t-5\tuse a SysV-like ps(1) syntax (not implemented yet);\n".
	    "\t-b\tuse a BSD-like ps(1) syntax (default);\n".
	    "\t-d\tdisplay debug information;\n".
	    "\t-h\tdisplay this help text and exit;\n".
	    "\t-V\tdisplay version information and exit.\n");
}

sub version()
{
	print("stalepid 1.0pre1\n");
}

sub readpid($)
{
	my $cfg = $_[0];
	my ($fname) = $cfg->{'pidfile'};
	my ($line);

	if (!defined($fname)) {
		die("PID file name not specified");
	}
	if (!open(PIDFILE, "< $fname")) {
		if ($!{ENOENT}) {
			print STDERR
			    "stalepid: the pid file $fname does not exist\n"
			    if $cfg->{'debug'};
			exit(0);
		}
		die("Opening $fname: $!\n");
	}
	if (!defined($line = <PIDFILE>)) {
		die("Reading from $fname: $!\n");
	}
	close(PIDFILE);
	$line =~ s/[\r\n]*$//;
	if ($line !~ /^(\d+)$/) {
		die("Invalid process ID format: $line\n");
	}
	$cfg->{'pid'} = $1;
}

sub checkproc($)
{
	my $cfg = $_[0];
	my ($pid, $procname) = ($cfg->{'pid'}, $cfg->{'procname'});
	my ($childpid, @cmd, @output);

	if (!defined($pid)) {
		die("Undefined process id in checkproc\n");
	}
	if (!defined($procname)) {
		die("Undefined process name in checkproc\n");
	}
	if ($cfg->{'bsdps'}) {
		@cmd = ('/bin/ps', '-axc', '-p', $pid, '-o', 'command');
	} else {
		die("SysV ps not supported yet!\n");
	}
	print STDERR 'stalepid: attempting to execute '.join(' ', @cmd)."\n"
	    if $cfg->{'debug'};
	if (!defined($childpid = open(PS, "-|"))) {
		die("Creating a child process failed: $!\n");
	} elsif ($childpid == 0) {
		$ENV{'PATH'} = '/usr/bin:/bin';
		exec(@cmd);
		die("Executing '".join(' ', @cmd)." failed: $!\n");
	}
	@output = <PS>;
	close(PS);
	if ($#output == 0) {
		print STDERR "No process with pid $pid\n" if $cfg->{'debug'};
		$cfg->{'exists'} = undef;
		return;
	}
	if ($#output != 1) {
		die('The ps(1) output should contain 2 lines, not '.
		    ($#output + 1)."\n");
	}
	map { s/[\r\n]*$// } @output;
	print STDERR "stalepid: got output:\n".join("\n", @output)."---\n"
	    if $cfg->{'debug'};
	print STDERR "stalepid: procname is $procname\n" if $cfg->{'debug'};
	$cfg->{'exists'} = $output[1] eq $procname;
	print STDERR "stalepid: exists is ".$cfg->{'exists'}.", pid is ".
	    $cfg->{'pid'}."\n" if $cfg->{'debug'};
}

sub killproc($)
{
	my $cfg = $_[0];
	my ($pid, $fname) = ($cfg->{'pid'}, $cfg->{'pidfile'});

	if (!defined($pid)) {
		die("Undefined process id at killproc\n");
	}
	if (!defined($fname)) {
		die("Undefined pidfile name at killproc\n");
	}
	if ($cfg->{'exists'}) {
		if (kill(0, $pid) == 1) {
			print STDERR 'stalepid: the '.$cfg->{'procname'}.
			    " process is alive and running as $pid\n"
			    if $cfg->{'debug'};
			return;
		}
		if (!$!{ENOENT}) {
			die("Attempting to signal process $pid: $!\n");
		}
	}
	unlink($fname) or die("Removing pid file $fname: $!\n");
}

MAIN:
{
	my %opts;
	my %config = (
		'bsdps'		=> 1,
		'debug'		=> 0,
		'exists'	=> 0,
		'pid'		=> undef,
		'pidfile'	=> undef,
		'procname'	=> undef,
	);

	getopts("5bdhV", \%opts) or usage();

	if (defined($opts{'V'})) {
		version();
		exit(0);
	}
	usage() if (defined($opts{'h'}));
	if (defined($opts{'b'})) {
		usage() if (defined($opts{'5'}));
		$config{'bsdps'} = 1;
	} elsif (defined($opts{'5'})) {
		$config{'bsdps'} = 0;
	}
	$config{'debug'} = 1 if (defined($opts{'d'}));

	usage() if ($#ARGV < 1);
	# Anything goes for the pidfile syntax..
	if ($ARGV[0] =~ '^(.*)$') {
		$config{'pidfile'} = $1;
	}
	# ...and for the procname syntax, too.
	if ($ARGV[1] =~ '^(.*)$') {
		$config{'procname'} = $1;
	}

	try {
		&readpid(\%config);
		&checkproc(\%config);
		&killproc(\%config);
	} catch Error with {
		my $e = shift;
		chomp $e;
		die("stalepid: $e\n");
	}
}


syntax highlighted by Code2HTML, v. 0.9.1