#!/usr/bin/perl -w # (c) copyright 2015 Kim Holburn # GPLv3 use strict; use Getopt::Long; my $verbose = 0; my $help = 0; my $myname = ""; my $mydir = "/var/run/once"; my $PID = 0; my $time = 0; my $timeout = 120; Getopt::Long::Configure('require_order'); GetOptions ('verbose+' => \$verbose, 'directory=s' => \$mydir, 'name=s' => \$myname, 'timeout=i' => \$timeout, 'help|?' => \$help); if ($help) { print < [ARGS] options: -h|-?|--help - print this screen -d|--directory directory to store run files. Default is /var/run/once -t|--timeout time in seconds that a program is allowed to run before being considered hung and will be killed -t=0 means no timeout. The running process will be left untouched. -v|--verbose - print extra messages -n|--name name of process or resource to be run once The default is the name of the program. EOM exit; } if ($timeout < 0) { die "$0 ERROR: specified timeout less than zero ($timeout)"; } if (10000 < $timeout) { die "$0 ERROR: ($timeout)i too large"; } if ($mydir !~ m{^/}) { die "$0 ERROR: directory ($mydir) is not an absolute path!"; } if (! -d $mydir) { die "$0 ERROR: directory ($mydir) does not exist!"; } # first argument is command to run my $path = shift; if (!$path) { die "$0 ERROR: No command "; } my $program = $path; # command must be a full absolute path. my $dir = "."; my $args=""; if ($program =~ m#/#) { $program =~ s#^.*/##; $dir =~ s#/[^/]*$##; } if (!$program) { die "$0 ERROR: program name invalid. Must be a filename"; } if (! -e $path) { die "$0 ERROR: program ($path) does not exist"; } if (-d $path) { die "$0 ERROR: program ($path) is a directory"; } if (! -x $path) { die "$0 ERROR: program ($path) not executable"; } if (!$myname) { $myname=$program; } my $run1 = "$mydir/$myname"; sub deleterun { if ( -e $run1 ) { unlink $run1; if ( -e $run1 ) { die "$0 ERROR: cannot delete file ($run1)"; } } } if ($verbose) { print "debug timeout=($timeout) dir=($mydir) name=($myname) \n"; print "debug v=($verbose) 1=($run1) prog=($program) args=("; print join (")(",@ARGV); print ") \n"; } if (open(my $fh, '<', "$run1")) { while (<$fh>) { chomp ; if (/^\s*$/) { next; } if (/^PID (\d+)$/i) { $PID = $1; } elsif (/^TIME (\d+)$/i) { $time = $1; } } close $fh; # no PID or time # this shouldn't happen and probably means we have the wrong file if (!$PID or !$time) { die "$0 ERROR: run file ($run1) has no PID or time"; } chomp (my $proc = `ps hp $PID -o %c`); # orphaned run file, delete if ($proc eq "") { deleterun; } else { # another instance running my $timediff = time - $time; if (0 == $timeout or $timediff <= $timeout) { die "$0 ERROR: another instance of $myname ($proc) running"; } print STDERR "$0 ERROR: another instance of $myname ($proc) has probably hung\n"; # another process has probably hung # grab its children my @PIDS = (`ps h --ppid $PID -o %p`, $PID); kill ('TERM', @PIDS); chomp ($proc = `ps hp $PID -o %c`); # couldn't kill it. Give up. if ($proc) { die "$0 ERROR: can't kill instance ($myname) ($proc) PID ($PID)"; } # it died OK. Delete run file if possible. deleterun; } } if (1 < $verbose) { print STDERR "creating run file ($run1) ... \n"; } open(my $fh, '>', "$run1") or die "$0 ERROR: opening file ($run1) ($!)" ; print $fh "PID $$\n"; print $fh "time ", time, "\n"; close $fh; #If something bad happens delete the run file sub cleanup { if (-e $run1) { if (1 < $verbose) { print STDERR "Deleting run file...\n"; } unlink $run1; } exit; }; $SIG{'INT'}=\&cleanup; $SIG{'TERM'}=\&cleanup; $SIG{'QUIT'}=\&cleanup; if ($verbose) { print STDERR "Starting ....\n"; } system ($path, @ARGV); cleanup;