#!/usr/bin/env perl

=head1 NAME

timeout - Run a command and kill it after the given period.

=cut


=head1 SYNOPSIS

  General Options:

   --timeout   Specify the maximum execution time.
   --help      Show the help information for this script.
   --verbose   Show useful debugging information.

=cut


=head1 ABOUT

This command will allow you to run a subcommand and terminate
that after the given time.

You may specify the timeout period in either seconds, minutes and
seconds, or hours, minutes and seconds.  For example:

=for example begin

    # kill after 32 seconds
    timeout -t 32 top

    # kill after two minutes, five seconds
    timeout -t 2:5 top

    # kill after six hour, five minutes, and two seconds.
    timeout -t 6:5:2 top

=for example end

The timeout period is mandatory and has no default.

=cut

=head1 AUTHOR

 Steve
 --
 http://www.steve.org.uk/

=cut


=head1 LICENSE

Copyright (c) 2013 by Steve Kemp.  All rights reserved.

This script is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.

The LICENSE file contains the full text of the license.

=cut

use strict;
use warnings;

use Getopt::Long;
use Pod::Usage;



#
#  Get the options, either defaults or from the command line.
#
my %config = parsedOptions();

#
#  Ensure we have a timeotut
#
if ( !$config{ 'timeout' } )
{
    print "Please specify a timeout.\n";
    exit(2);
}

#
#  If we're being verbose..
#
if ( $config{ 'verbose' } )
{
    print "Running command: " . join( " ", @ARGV ) . "\n";
    print "Max executiong time " . $config{ 'timeout' } . " seconds\n";

}

#
#  Call the command we were given, with our timeout.
#
timed_system( $config{ 'timeout' }, @ARGV );

#
# All done.
#
exit(0);


sub timed_system
{
    my $time = shift;
    my $pid;

    local $SIG{ ALRM } = sub {
        kill 15, $pid or die "kill: $!";
        die "Timeout!";
    };    # Just SIGTERM.
    eval {
        $pid = fork;
        die "Fork failed: $!" unless defined $pid;
        unless ($pid)
        {
            exec @_;
            die "Exec failed: $!";
        }
        alarm $time;
        waitpid $pid => 0;
    };
    die $@ if $@ && $@ !~ /^Timeout!/;
}



=begin doc

Parse the command line options (minimal!)

=end doc

=cut

sub parsedOptions
{
    my %vars;

    exit
      if (
           !GetOptions( "help"      => \$vars{ 'help' },
                        "timeout=s" => \$vars{ 'timeout' },
                        "verbose"   => \$vars{ 'verbose' } ) );

    pod2usage(1) if ( $vars{ 'help' } );

    if ( !$vars{ 'timeout' } )
    {
        print "Please specify a timeout\n";
        exit(2);
    }


    #
    #  HH:MM:SS
    #
    if ( $vars{ 'timeout' } =~ /^([0-9]+):([0-9]+):([0-9]+)$/ )
    {
        my $sec = $3 + ( 60 * $2 ) + ( 60 * 60 * $1 );
        $vars{ 'timeout' } = $sec;
    }
    elsif ( $vars{ 'timeout' } =~ /^([0-9]+):([0-9]+)$/ )
    {

        #
        # MM:SS
        #
        my $sec = $2 + ( 60 * $1 );
        $vars{ 'timeout' } = $sec;
    }
    elsif ( $vars{ 'timeout' } =~ /^([0-9]+)$/ )
    {

        #
        #  SS
        #
    }
    else
    {
        print "Invalid timeout specification\n";
        exit(2);
    }

    return (%vars);

}
