#!/usr/bin/perl

$sendmailpath = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:'
	      . '/usr/lib:/sbin:/bin';

# safe - run a command but kill or report it after the given amount of time
# Steve Kinzler, steve@kinzler.com, Jun 08/Mar 11
# ported/enhanced from safe(1) in C by Mike Blackwell, ihnp4!aicchi!mdb, Aug 86
# https://kinzler.com/me/home.html#unix

# Tests:	safe -d 3 -v /bin/true	&& echo ok
#		safe -d 3 -v sleep 1	&& echo ok
#		safe -d 3 -v sleep 6	|| echo ok
#		safe -d 3 -v -n sleep 1	&& echo ok
#		safe -d 3 -v -n sleep 6	&& echo ok

# NOTE: This seems to mostly work, but I'm not entirely confident with it
#	since I've seen it fail strangely on occasion on RH9 and RHEL3.
# NOTE: See also now timeout(1)

$dflt_d = 60;

use Getopt::Std;	$Getopt::Std::STANDARD_HELP_VERSION = 1;

$usage = <<EOF;
usage: $0 [ -d seconds ] [ -w seconds ] [ -b ] [ -q ] [ -v ]
       [ -n ] [ -m address ] command
	-d	seconds to delay before killing command (default $dflt_d)
	-w	seconds before killing to give a warning message
	-b	beep instead of printing the warning message
	-q	quiet mode, don't print the killing message
	-v	verbose mode, print a message if the command finishes itself
	-n	don't kill the command, just print message at delay and finish
	-m	don't print but email messages to the given address
Also, signal INT is trapped and will kill the command.
EOF
die $usage if ! getopts('d:w:bqvnm:h') || $opt_h || $opt_d < 0 || $opt_w < 0;

$opt_d = $opt_d || $dflt_d;
warn("$0: delay ($opt_d) less than warning ($opt_w),"
   . " no warning will be given\n"), $opt_w = 0 if $opt_d < $opt_w;

###############################################################################

$start = time;
$pid   = fork;
die "$0: aborting, cannot fork ($!)\n" unless defined $pid;
if ($pid == 0) {
	exec(@ARGV) || die "$0: cannot exec command ($!)\n";
} else {
	$ENV{'PATH'} = $sendmailpath;
	($proc, $cmd) = ("process $pid", " (@ARGV @ $start)");
	@SIG{'CHLD', 'INT'} = ('done', 'maim');
	sleep $opt_d - $opt_w;
	if ($opt_w > 0) {
		$opt_b ? print(STDERR "\a\a")
		       : &msg($opt_n ? "$0: $proc overruns in $opt_w sec$cmd"
				     : "$0: killing $proc in $opt_w sec$cmd");
		sleep $opt_w;
	}
	$opt_n ? &over() : &maim();
}

sub maim {
	$SIG{'CHLD'} = 'IGNORE';
	kill(15, $pid);
	sleep(2)	if kill(0, $pid);
	kill( 9, $pid)	if kill(0, $pid);
	sleep(2)	if kill(0, $pid);
	if (kill(0, $pid)) {
		&msg("$0: $proc will not die after $opt_d sec$cmd");
	} else {
		&msg("$0: $proc killed after $opt_d sec$cmd") unless $opt_q;
	}
	exit 1;
}

sub over {
	&msg("$0: $proc has run $opt_d sec$cmd");
	$opt_v = 1;
	wait;
}

sub done {
	&msg("$0: $proc self-terminated after ", time - $start, " sec$cmd")
		if $opt_v;
	exit 0;
}

###############################################################################

sub msg {
	warn(@_, "\n"), return unless defined $opt_m;

	warn("$0: cannot open sendmail ($!)\n"), return
		unless open(SENDMAIL, '| sendmail -t -oi');
	print SENDMAIL 'To: ', $opt_m || $ENV{'USER'} || $ENV{'LOGNAME'}, "\n",
		       "Subject: $0 message\n\n", @_, "\n";
	$SIG{'CHLD'} = 'IGNORE';
	close SENDMAIL;
	$SIG{'CHLD'} = 'done';
}
