#! /usr/bin/perl -w

use strict;
use Fcntl qw{:flock};
use Getopt::Std qw{getopts};
use POSIX qw{ WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG WIFSTOPPED WSTOPSIG };

# IMPORTANT: DO NOT CHANGE THE CONTENT OF ANY MESSAGES SENT TO STDOUT,
# AND DO NOT ADD ANY NEW TEXT TO STDOUT.

my %opts;
getopts('m:s:r:vn',\%opts);

my $sleeptime=defined($opts{s}) ? 0+$opts{s} : 1;
my $maxwait=defined($opts{m}) ? 0+$opts{m} : 10;
my $nolockret=defined($opts{r}) ? 0+$opts{r} : 1;
my $nocommand=defined($opts{n});
my $fewarg=( $#ARGV == 0 );
my $verbose=defined($opts{v});

if($nocommand) {
    $SIG{__DIE__}=sub {
        warn @_;
        print "DIE EXIT 2"; # must go to stdout.  Do not change that message.
        close(STDOUT); # should fail.
        exit 2;
    };
}

if($#ARGV<0 || ($fewarg && !$nocommand) || (!$fewarg && $nocommand)) {
    die<<EOT
Error: Syntax:

  $0 [options] lock-file-name.lock command [ arg1 [ arg2 [ ... ] ] ]
OR
  $0 -n [options] lock-file-name.lock 

In the first form, this script will lock the lockfile, then run the
specified command, and then unlock the lock file.  In the second form,
this script will lock the lock file, write stuff to STDOUT until
SIGPIPE is received, and then release the lock file and exit.
EOT
}

my $lockfile=shift;

sub verbosely {
    if($verbose) {
        print STDERR @_;
    }
}

verbosely("Open lock file \"$lockfile\"...\n");
open(FILE,"+> $lockfile")
    or die "cannot open \"$lockfile\" for read and write: $!.  Aborting";

verbosely("Try to grab lock...\n");
my $start=time();
while(!flock(FILE,LOCK_EX | LOCK_NB)) {
    if(time()-$start>=$maxwait) {
        warn "Flock \"$lockfile\" took more than $maxwait seconds: $!.  Returning with status $nolockret";
	if($nocommand) {
	    print "TIME OUT $maxwait\n"; # must go to stdout
	}
        exit $nolockret;
    }
    verbosely("Could not get lock.  Sleep $sleeptime seconds...\n");
    sleep 1;
    verbosely("Try to grab lock...\n");
}

if($nocommand) {
    $|=1;
    print "HAVE LOCK ON \"$lockfile\"\n"; # must go to stdout
    $SIG{'PIPE'}=sub {
        verbosely "Sigpipe caught: child process has finished.  Close stdout and exit.\n";
        close(STDOUT); # should fail.
        exit 0;
    };
    $SIG{'HUP'}=sub {
        verbosely "SIGHUP CAUGHT.  SHOULD NOT HAPPEN.  CLOSE STDOUT AND EXIT.\n";
        close(STDOUT) or die "ERROR CLOSING STDOUT AFTER SIGHUP: $!.  Aborting";
        exit 0;
    };
    while(1) {
        verbosely "Write to stdout...\n";
        print("HAVE LOCK ON \"$lockfile\"\n") # must go to stdout
            or exit 0;
        verbosely "Sleep 10 seconds...\n";
        sleep 10;
    }
} else {
    verbosely("Have lock on \"$lockfile\".  Run command...\n");
    verbosely("  Command (unquoted): @ARGV\n");
    my $ret=system(@ARGV);
    verbosely("Command returned.  Close lock file...\n");
    close(FILE)
        or die "Unable to close lock file \"$lockfile\": $!.  Aborting";
    
    # Return value from this script depends on the return value from the command:
    if(WIFEXITED($ret)) {
      # Command exited normally.  Return its exit status:
      exit WEXITSTATUS($ret);
    } elsif(WIFSIGNALED($ret)) {
      # Command exited due to a signal.  Return value will be 128 + signal number
      exit 128+WTERMSIG($ret);
    } elsif(WIFSTOPPED($ret)) {
      # Should not happen.  Command is suspended!?  
      die "Something weird happened: command was stopped (suspended).  Hunh?  Should not be possible (parent process should be suspended too since I did not catch SIGCHLD).  Aborting";
    } else {
      # Cannot get here if perl is functioning correctly, system is
      # POSIX-compliant and command was able to be executed.
      die "Command exited for unknown reason (not a normal exit, not killed by a signal, not suspended).  Aborting";
    }
}
