#!/bin/sh
#!webjob-perl -w
eval 'exec perl -x ${0} ${1+"${@}"} ;'
  if (0);
######################################################################
#
# $Id: queue-worker,v 1.27 2012/01/07 08:01:15 mavrik Exp $
#
######################################################################
#
# Copyright 2007-2012 The WebJob Project, All Rights Reserved.
#
######################################################################
#
# Purpose: Request and execute jobs queued on a WebJob Server.
#
######################################################################

use strict;

######################################################################
#
# Adjust @INC. This must be done before any other modules are loaded.
# Note that special provisions may be required when this script runs
# via WebJob to ensure that downloaded Perl scripts are able to find
# locally installed, WebJob-specific modules.
#
######################################################################

my @aLibs;

BEGIN
{
  use FindBin qw($Bin $RealBin);
  @aLibs =
  (
    "$Bin/../lib/perl5/site_perl",
    "$RealBin/../lib/perl5/site_perl",
    "/usr/local/webjob/lib/perl5/site_perl",
    "/opt/local/webjob/lib/perl5/site_perl",
  );
  if (($ENV{'WEBJOB_TEMP_DIRECTORY'} || "") =~ /^(.+[\/\\][.]?webjob[\/\\](?:profiles[\/\\][^\/\\]+[\/\\])?)run$/)
  {
    my $sLib = $1 . "lib/perl5/site_perl";
    $sLib =~ s,\\,/,g; # According to the documentation, lib.pm only works with UNIX-style paths.
    unshift(@aLibs, $sLib); # This path needs to be first since it's closer to the install point.
  }
}

use lib (@aLibs);

######################################################################
#
# Load any remaining modules needed by this script.
#
######################################################################

use Fcntl qw(:DEFAULT :flock);
use File::Basename;
use Getopt::Std;
use POSIX qw(:sys_wait_h SIGINT);
use WebJob::KvpRoutines 1.029;
use WebJob::Properties 1.028;
use WebJob::VersionRoutines;

BEGIN
{
  ####################################################################
  #
  # The Properties hash is essentially private. Those parts of the
  # program that wish to access or modify the data in this hash need
  # to call GetProperties() to obtain a reference.
  #
  ####################################################################

  my (%hProperties);

  ####################################################################
  #
  # Initialize regex variables.
  #
  ####################################################################

  $hProperties{'CommonRegexes'} = PropertiesGetGlobalRegexes();

  ####################################################################
  #
  # Initialize platform-specific variables.
  #
  ####################################################################

  if ($^O =~ /MSWin(32|64)/i)
  {
    $hProperties{'ExtensionExe'} = ".exe";
    $hProperties{'OsClass'} = "WINX";
    $hProperties{'PathSeparator'} = "\\";
  }
  else
  {
    $hProperties{'ExtensionExe'} = "";
    $hProperties{'OsClass'} = "UNIX";
    $hProperties{'PathSeparator'} = "/";
  }

  ####################################################################
  #
  # Initialize application-specific variables.
  #
  ####################################################################

  # EMPTY

  ####################################################################
  #
  # Define helper routines.
  #
  ####################################################################

  sub GetProperties
  {
    return \%hProperties;
  }
}

######################################################################
#
# Main Routine
#
######################################################################

  ####################################################################
  #
  # Punch in and go to work.
  #
  ####################################################################

  my ($phProperties);

  $phProperties = GetProperties();

  $$phProperties{'Program'} = basename(__FILE__);

  ####################################################################
  #
  # Get Options.
  #
  ####################################################################

  my (%hOptions);

  if (!getopts('c:d:f:H:j:k:l:o:P:q:r:s:T:t:', \%hOptions))
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # A client ID, '-c', is optional.
  #
  ####################################################################

  $$phProperties{'ClientId'} = (exists($hOptions{'c'})) ? $hOptions{'c'} : $ENV{'WEBJOB_CLIENTID'};

  ####################################################################
  #
  # A delay list, '-d', is optional.
  #
  ####################################################################

  $$phProperties{'DelayList'} = (exists($hOptions{'d'})) ? $hOptions{'d'} : "0,300,0";

  if ($$phProperties{'DelayList'} !~ /^(\d+)(?:,(\d+)(?:,(\d+))?)?$/)
  {
    print STDERR "$$phProperties{'Program'}: Error='Delay list ($$phProperties{'DelayList'}) does not pass muster.'\n";
    exit(2);
  }
  $$phProperties{'StartDelay'} = $1;
  $$phProperties{'RequestDelay'} = (defined($2)) ? $2 : 300;
  $$phProperties{'JobDelay'} = (defined($3)) ? $3 : 0;

  ####################################################################
  #
  # A config file, '-f', is optional.
  #
  ####################################################################

  $$phProperties{'WebJobCfg'} = (exists($hOptions{'f'})) ? $hOptions{'f'} : $ENV{'WEBJOB_CFG_FILE'};

  ####################################################################
  #
  # A webjob home directory, '-H', is optional.
  #
  ####################################################################

  if (exists($hOptions{'H'}))
  {
    $$phProperties{'WebJobHome'} = $hOptions{'H'};
  }
  else
  {
    $$phProperties{'WebJobHome'} = ($$phProperties{'OsClass'} eq "WINX") ? "C:/WebJob" : "/usr/local/webjob";
  }

  ####################################################################
  #
  # A job count, '-j', is optional.
  #
  ####################################################################

  $$phProperties{'JobCount'} = (exists($hOptions{'j'})) ? $hOptions{'j'} : 0;

  if ($$phProperties{'JobCount'} !~ /^\d+$/)
  {
    print STDERR "$$phProperties{'Program'}: Error='Job count ($$phProperties{'JobCount'}) does not pass muster.'\n";
    exit(2);
  }

  ####################################################################
  #
  # A kid limit, '-k', is optional.
  #
  ####################################################################

  $$phProperties{'KidLimit'} = (exists($hOptions{'k'})) ? $hOptions{'k'} : 25;

  if ($$phProperties{'KidLimit'} !~ /^\d+$/ || $$phProperties{'KidLimit'} < 1)
  {
    print STDERR "$$phProperties{'Program'}: Error='Kid limit ($$phProperties{'KidLimit'}) must be a number greater than zero.'\n";
    exit(2);
  }

  ####################################################################
  #
  # A lock file, '-l', is optional.
  #
  ####################################################################

  $$phProperties{'LockFile'} = (exists($hOptions{'l'})) ? $hOptions{'l'} : undef;

  ####################################################################
  #
  # The option list, '-o', is optional.
  #
  ####################################################################

  my @sSupportedOptions =
  (
    'BeQuiet',
    'DelayAllRequests',
    'RelaySignals',
    'StorePidInLockFile'
  );
  foreach my $sOption (@sSupportedOptions)
  {
    $$phProperties{$sOption} = 0;
  }

  $$phProperties{'Options'} = (exists($hOptions{'o'})) ? $hOptions{'o'} : undef;

  if (defined($$phProperties{'Options'}))
  {
    foreach my $sOption (split(/,/, $$phProperties{'Options'}))
    {
      foreach my $sSupportedOption (@sSupportedOptions)
      {
        $sOption = $sSupportedOption if ($sOption =~ /^$sSupportedOption$/i);
      }
      if (!exists($$phProperties{$sOption}))
      {
        print STDERR "$$phProperties{'Program'}: Error='Unknown or unsupported option ($sOption).'\n";
        exit(2);
      }
      $$phProperties{$sOption} = 1;
    }
  }

  ####################################################################
  #
  # A priority, '-P', is optional.
  #
  ####################################################################

  $$phProperties{'Priority'} = (exists($hOptions{'P'})) ? $hOptions{'P'} : undef;

  if (defined($$phProperties{'Priority'}) && $$phProperties{'Priority'} =~ /^(?:low|below_normal|normal|above_normal|high)$/i)
  {
    $ENV{'WEBJOB_PRIORITY'} = lc($$phProperties{'Priority'});
  }

  ####################################################################
  #
  # A queue, '-q', is optional.
  #
  ####################################################################

  $$phProperties{'Queue'} = (exists($hOptions{'q'})) ? $hOptions{'q'} : $$phProperties{'ClientId'};

  ####################################################################
  #
  # A repeat count, '-r', is optional.
  #
  ####################################################################

  $$phProperties{'RequestCount'} = (exists($hOptions{'r'})) ? $hOptions{'r'} : 1;

  if ($$phProperties{'RequestCount'} !~ /^(infinite|\d+)$/i)
  {
    print STDERR "$$phProperties{'Program'}: Error='Repeat count ($$phProperties{'RequestCount'}) does not pass muster.'\n";
    exit(2);
  }
  $$phProperties{'RequestCount'} = 0 if ($1 =~ /^infinite$/i);

  ####################################################################
  #
  # A soft stop, '-s', is optional.
  #
  ####################################################################

  $$phProperties{'SoftStop'} = (exists($hOptions{'s'})) ? $hOptions{'s'} : undef;

  if (defined($$phProperties{'SoftStop'}) && $$phProperties{'SoftStop'} !~ /^\d+$/)
  {
    print STDERR "$$phProperties{'Program'}: Error='Soft stop ($$phProperties{'SoftStop'}) does not pass muster.'\n";
    exit(2);
  }

  ####################################################################
  #
  # A timeout signal, '-T', is optional.
  #
  ####################################################################

  $$phProperties{'TimeoutSignal'} = (exists($hOptions{'T'})) ? $hOptions{'T'} : undef;

  if (defined($$phProperties{'TimeoutSignal'}) && $$phProperties{'TimeoutSignal'} !~ /^([0-9]|[12][0-9]|3[0-2])$/)
  {
    print STDERR "$$phProperties{'Program'}: Error='Timeout signal ($$phProperties{'TimeoutSignal'}) does not pass muster.'\n";
    exit(2);
  }

  ####################################################################
  #
  # A job type, '-t', is required.
  #
  ####################################################################

  $$phProperties{'JobType'} = (exists($hOptions{'t'})) ? $hOptions{'t'} : undef;

  if (!defined($$phProperties{'JobType'}))
  {
    Usage($$phProperties{'Program'});
  }

  if ($$phProperties{'JobType'} =~ /^p(?:arallel)?$/i)
  {
    $$phProperties{'JobType'} = "parallel";
  }
  elsif ($$phProperties{'JobType'} =~ /^s(?:erial)?$/i)
  {
    $$phProperties{'JobType'} = "serial";
  }
  else
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # Make final adjustments to the run time environment.
  #
  ####################################################################

  my ($sError);

  if (!AdjustRunTimeEnvironment($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
    exit(2);
  }

  ####################################################################
  #
  # Initialize the request URL.
  #
  ####################################################################

  my (%hKvpMap, %hLArgs, $sUrl);

  %hLArgs =
  (
    'File'           => $$phProperties{'WebJobCfg'},
    'MatchKeyCase'   => 0,
    'Properties'     => \%hKvpMap,
    'RecursionKey'   => "Import",
    'RequiredKeys'   => [ "UrlGetUrl", "UrlPutUrl" ],
    'Template'       =>
    {
      'Import'    => $$phProperties{'CommonRegexes'}{'AnyValueExceptNothing'},
      'UrlGetUrl' => $$phProperties{'CommonRegexes'}{'AnyValueExceptNothing'},
      'UrlPutUrl' => $$phProperties{'CommonRegexes'}{'AnyValueExceptNothing'},
    },
    'VerifyValues'   => 1,
  );
  if (!KvpGetKvps(\%hLArgs))
  {
    print STDERR "$$phProperties{'Program'}: Error='$hLArgs{'Error'}'\n";
    exit(2);
  }
  $sUrl = $hKvpMap{'UrlGetUrl'};
  $sUrl .= "?ClientId=" . $$phProperties{'ClientId'};
  $sUrl .= "&QueueName=" . $$phProperties{'Queue'};
  $sUrl .= "&JobType=" . $$phProperties{'JobType'};
  $sUrl .= "&JobCount=" . $$phProperties{'JobCount'};

  ####################################################################
  #
  # Conditionally create a lock file. This is an exclusive lock.
  #
  ####################################################################

  my (%hLockArgs);

  if (defined($$phProperties{'LockFile'}))
  {
    %hLockArgs =
    (
      'LockFile'   => $$phProperties{'LockFile'},
      'LockFlags'  => LOCK_EX | LOCK_NB,
      'LockRemove' => 1,
    );
    if (!KvpLockFile(\%hLockArgs))
    {
      print STDERR "$$phProperties{'Program'}: Error='$hLockArgs{'Error'}'\n";
      exit(($hLockArgs{'Error'} =~ /could not be locked/) ? 3 : 2);
    }
    if ($$phProperties{'StorePidInLockFile'})
    {
      $hLockArgs{'LockHandle'}->truncate(0);
      $hLockArgs{'LockHandle'}->print("$$\n");
    }
  }

  ####################################################################
  #
  # Sleep for the designated number of seconds before starting.
  #
  ####################################################################

  sleep($$phProperties{'StartDelay'});

  ####################################################################
  #
  # Execute any jobs returned in a parallel or serial loop.
  #
  ####################################################################

  $$phProperties{'StartTime'} = time();

  $$phProperties{'JobCount'} = 0;
  $$phProperties{'JobGroup'} = $$phProperties{'LoopCount'} = 0;
  $$phProperties{'KidCount'} = 0;
  $$phProperties{'KidLimit'} = 1 if ($$phProperties{'JobType'} =~ /^s(?:erial)?$/i);
  $$phProperties{'LoopFlag'} = 1;

  while($$phProperties{'LoopFlag'} && ($$phProperties{'RequestCount'} == 0 || $$phProperties{'LoopCount'} < $$phProperties{'RequestCount'}))
  {
    my (@aJobKvps, $sJobGroup, $sStatus);
    $$phProperties{'JobGroup'} = ++$$phProperties{'LoopCount'};
    if ($$phProperties{'LoopCount'} > 1 && ($$phProperties{'JobCount'} == 0 || $$phProperties{'DelayAllRequests'}))
    {
      sleep($$phProperties{'RequestDelay'});
      last unless ($$phProperties{'LoopFlag'}); # If the signal to quit arrived while we were asleep, we're done.
    }
    if (defined($$phProperties{'SoftStop'}) && (time() - $$phProperties{'StartTime'}) >= $$phProperties{'SoftStop'})
    {
      last;
    }
    $$phProperties{'JobCount'} = 0;
    if (!open(PH, qq("$$phProperties{'WebJobExe'}" -g -f "$$phProperties{'WebJobCfg'}" "$sUrl" |)))
    {
      print STDERR "$$phProperties{'Program'}: Error='Job request failed ($!) for job group $$phProperties{'JobGroup'}.'\n";
      next;
    }
    @aJobKvps = <PH>;
    close(PH);
    $sStatus = ($? >> 8) & 0xff;
    if ($sStatus != 0)
    {
      my $phExitCodes = PropertiesGetGlobalExitCodes();
      my $sFailure = (exists($$phExitCodes{'webjob'}{$sStatus})) ? $$phExitCodes{'webjob'}{$sStatus} : $sStatus;
      print STDERR "$$phProperties{'Program'}: Error='Job request failed ($sFailure) for job group $$phProperties{'JobGroup'}.'\n";
      next;
    }
    foreach my $sJobKvp (@aJobKvps)
    {
      $sJobKvp =~ s/[\r\n]+$//;
      if ($sJobKvp =~ /^($$phProperties{'CommonRegexes'}{'JqdQueueTag'})=(.+)$/)
      {
        my ($sQueueTag, $sJob) = ($1, $2);
        sleep($$phProperties{'JobDelay'}) if ($$phProperties{'JobCount'}++ > 0);
        my $sCommandLine = qq("$$phProperties{'WebJobExe'}" -e -f "$$phProperties{'WebJobCfg'}");
        if (defined($$phProperties{'TimeoutSignal'}) && $$phProperties{'OsClass'} eq "UNIX")
        {
          $sCommandLine .= " --TimeoutSignal $$phProperties{'TimeoutSignal'}";
        }
        $sCommandLine .= " $sJob";
        $ENV{'WEBJOB_QUEUETAG'} = $sQueueTag;
        my $sKidPid = fork();
        if (!defined($sKidPid))
        {
          print STDERR "$$phProperties{'Program'}: Error='Fork failed ($!) job group $$phProperties{'JobGroup'} and queue tag $sQueueTag.'\n";
          next;
        }
        if ($sKidPid == 0)
        {
          exec($sCommandLine);
          exit(-1);
        }
        print STDERR "$$phProperties{'Program'}: JobGroup='$$phProperties{'JobGroup'}'; QueueTag='$sQueueTag'; KidPid='$sKidPid'; CommandLine='$sCommandLine';\n" unless ($$phProperties{'BeQuiet'});
        $$phProperties{'KidPids'}{$sKidPid}{'QueueTag'} = $sQueueTag;
        $$phProperties{'KidPids'}{$sKidPid}{'Command'} = $sCommandLine;
        $$phProperties{'KidPids'}{$sKidPid}{'Signaled'} = 0;
        $$phProperties{'KidPids'}{$sKidPid}{'StartTime'} = time();
        $$phProperties{'KidCount'}++;
        ReapKids($phProperties);
      }
    }
  }

  ####################################################################
  #
  # Shutdown and go home.
  #
  ####################################################################

  $$phProperties{'KidLimit'} = 0; # Wait for all remaining kids.

  ReapKids($phProperties);

  if (defined($$phProperties{'LockFile'}))
  {
    KvpUnlockFile(\%hLockArgs);
  }

  1;


######################################################################
#
# AdjustRunTimeEnvironment
#
######################################################################

sub AdjustRunTimeEnvironment
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Make sure stdout/stderr are in binary mode.
  #
  ####################################################################

  # EMPTY

  ####################################################################
  #
  # Initialize paths that will be used during execution.
  #
  ####################################################################

  my @aPathKeys =
  (
    "WebJobCfg",
    "WebJobExe",
    "WebJobHome",
  );

  if (!defined($$phProperties{'WebJobCfg'}))
  {
    $$phProperties{'WebJobCfg'} = $$phProperties{'WebJobHome'} . "/etc/upload.cfg";
  }
  $$phProperties{'WebJobExe'} = $$phProperties{'WebJobHome'} . "/bin/webjob" . $$phProperties{'ExtensionExe'};

  ####################################################################
  #
  # Adjust path separators if running on a Windows platform.
  #
  ####################################################################

  if ($$phProperties{'OsClass'} eq "WINX")
  {
    foreach my $sKey (@aPathKeys)
    {
      $$phProperties{$sKey} =~ s,/,\\,g;
    }
  }

  ####################################################################
  #
  # Update system PATH.
  #
  ####################################################################

  my (%hLArgs);

  %hLArgs =
  (
    'First' => 1,
    'Path' => $$phProperties{'WebJobHome'} . $$phProperties{'PathSeparator'} . "bin",
    'RemoveDuplicates' => 1,
  );
  if (!PropertiesAddPath(\%hLArgs))
  {
    $$psError = $hLArgs{'Error'};
    return undef;
  }

  ####################################################################
  #
  # Setup a signal handler.
  #
  ####################################################################

  $SIG{'INT'} = \&SigIntHandler;

  ####################################################################
  #
  # Make sure we have the necessary prerequisites.
  #
  ####################################################################

  my ($sStatus, $sVersionString);

  if (!-d $$phProperties{'WebJobHome'})
  {
    $$psError = "Directory ($$phProperties{'WebJobHome'}) does not exist.";
    return undef;
  }

  if (!-f $$phProperties{'WebJobCfg'})
  {
    $$psError = "File ($$phProperties{'WebJobCfg'}) does not exist or is not regular.";
    return undef;
  }

  if (!defined($$phProperties{'ClientId'}))
  {
    $$psError = "Client ID is not defined. This value can be set in the environment or on command line using the WEBJOB_CLIENTID variable or the \"-c\" option, respectively.";
    return undef;
  }

  if ($$phProperties{'ClientId'} !~ /^$$phProperties{'CommonRegexes'}{'ClientId'}$/)
  {
    $$psError = "Client ID ($$phProperties{'ClientId'}) does not pass muster.";
    return undef;
  }

  if ($$phProperties{'Queue'} !~ /^$$phProperties{'CommonRegexes'}{'ClientId'}$/)
  {
    $$psError = "Queue ($$phProperties{'Queue'}) does not pass muster.";
    return undef;
  }

  $sVersionString = qx("$$phProperties{'WebJobExe'}" --version);
  %hLArgs =
  (
    'MinVersion' => VersionStringToNumber({ 'VersionString' => "1.8.0" }),
    'VersionString' => $sVersionString,
  );
  $sStatus = VersionIsInRange(\%hLArgs);
  if (!$sStatus)
  {
    $$psError = (!defined($sStatus)) ? $hLArgs{'Error'} : "WebJob version 1.8.0 or higher is required.";
    return undef;
  }

  1;
}


######################################################################
#
# ReapKids
#
######################################################################

sub ReapKids
{
  my ($phProperties) = @_;

  ####################################################################
  #
  # Reap kids until the count is below the specified kid limit.
  #
  ####################################################################

  my $sKidLimit = ($$phProperties{'KidLimit'} > 0) ? $$phProperties{'KidLimit'} : 1;

  while ($$phProperties{'KidCount'} >= $sKidLimit)
  {
    my $sPid = waitpid(-1, WNOHANG);
    if ($sPid == 0)
    {
      sleep(1); # All kids are alive, sleep for a second.
      next;
    }
    if ($sPid == -1)
    {
      print STDERR "$$phProperties{'Program'}: Error='Kid count is ($$phProperties{'KidCount'}), but there are no kids left to wait for. That should not happen!'\n";
      exit(4);
    }
    my $sKidStatus = ($? >> 8) & 0xff;
    my $sKidSignal = ($?     ) & 0x7f;
    my $sRunTime = time() - $$phProperties{'KidPids'}{$sPid}{'StartTime'};
    print STDERR "$$phProperties{'Program'}: JobGroup='$$phProperties{'JobGroup'}'; QueueTag='$$phProperties{'KidPids'}{$sPid}{'QueueTag'}'; KidPid='$sPid'; CommandLine='$$phProperties{'KidPids'}{$sPid}{'Command'}'; ExitCode='$sKidStatus'; Signal='$sKidSignal'; RunTime='$sRunTime';\n" unless ($$phProperties{'BeQuiet'});
    delete($$phProperties{'KidPids'}{$sPid});
    $$phProperties{'KidCount'}--;
  }

  1;
}


######################################################################
#
# SigIntHandler
#
######################################################################

sub SigIntHandler
{
  my $phProperties = GetProperties();

  print STDERR "$$phProperties{'Program'}: Message='Received SIGINT signal.'\n" unless ($$phProperties{'BeQuiet'});
  $$phProperties{'LoopFlag'} = 0;
  if ($$phProperties{'RelaySignals'})
  {
    foreach my $sKidPid (keys(%{$$phProperties{'KidPids'}}))
    {
      print STDERR "$$phProperties{'Program'}: Message='Relaying SIGINT signal to kid $sKidPid.'\n" unless ($$phProperties{'BeQuiet'});
      kill(SIGINT, $sKidPid);
    }
  }
}


######################################################################
#
# Usage
#
######################################################################

sub Usage
{
  my ($sProgram) = @_;
  print STDERR "\n";
  print STDERR "Usage: $sProgram [-c client-id] [-d delay-list] [-f config-file] [-H webjob-home] [-j job-count] [-k kid-limit] [-l lock-file] [-o option[,option[,...]]] [-P priority] [-q queue] [-r repeat-count] [-s soft-stop] [-T signal] -t {p|parallel|s|serial}\n";
  print STDERR "\n";
  exit(1);
}


=pod

=head1 NAME

queue-worker - Request and execute jobs queued on a WebJob Server.

=head1 SYNOPSIS

B<queue-worker> B<[-c client-id]> B<[-d delay-list]> B<[-f config-file]> B<[-H webjob-home]> B<[-j job-count]> B<[-k kid-limit]> B<[-l lock-file]> B<[-o option[,option[,...]]]> B<[-P priority]> B<[-q queue]> B<[-r repeat-count]> B<[-s soft-stop]> B<[-T signal]> B<-t {p|parallel|s|serial}>

=head1 DESCRIPTION

This utility requests jobs from a specified queue located on a WebJob
Server and executes them in a parallel or serial fashion.

Upon receipt of a SIGINT signal (UNIX platforms), the script will
complete all pending jobs (i.e., jobs already pulled from the queue)
and exit.

=head1 OPTIONS

=over 4

=item B<-c client-id>

Specifies the client ID to use when requesting jobs.  This utility
attempts to obtain the default client ID from the environment by way
of the WEBJOB_CLIENTID variable.

=item B<-d delay-list>

Specifies a comma delimited list of delay times representing the
number of seconds to delay 1) before starting, 2) between requests,
and 3) between jobs.  The default value is '0,300,0'.

=item B<-f config-file>

Specifies the config file to use when requesting jobs.  This utility
attempts to obtain the default config file from the environment by way
of the WEBJOB_CFG_FILE variable.  Failing that, it defaults to a value
of '${WEBJOB_HOME}/etc/upload.cfg'.

=item B<-H webjob-home>

Specifies the location of WebJob components on the client system.  The
default value is 'C:\WebJob' and '/usr/local/webjob' for Windows and
UNIX platforms, respectively.

=item B<-j job-count>

Specifies the number of jobs to request.  The default value is zero,
which means return all jobs in the queue.  The number of jobs actually
returned will depend on how many jobs were in the specified queue and
server-side controls that govern how many and when jobs are let out.

=item B<-k kid-limit>

Specifies the maximum number jobs that may be running on the client
system at a given time.  The default value is 25.  If the specified
job type is serial, then the kid limit is forced to a value of one.

=item B<-l lock-file>

Specifies the name of an advisory lock that will be used to prevent
other instances of this script (seeking the same lock file) from
running concurrently.  Multiple instances of this script can run
concurrently by omitting this option or specifying distinct lock
files.

=item B<-o option,[option[,...]]>

Specifies the list of options to apply.  Currently, the following
options are supported:

=over 4

=item BeQuiet

Don't print informational or warning messages.

=item DelayAllRequests

Impose a delay between each request.  Normally, delays are suppressed
until the queue is empty.

=item RelaySignals

Relay received signals to all kids. Currently, the only signal that
can be relayed is SIGINT.

=item StorePidInLockFile

Store the current Process ID (PID) in the lock file.  This option is
ignored unless a lock file has been requested/created.

=back

=item B<-P priority>

Specifies the priority at which all target processes should run.  This
value is used to set the WEBJOB_PRIORITY environment variable -- refer
to the B<Priority> control in the webjob(1) man page for additional
details.  Currently, the following priorities are supported: 'low',
'below_normal', 'normal', 'above_normal', and 'high'.

Note: This option can not be used to set the priority of this script.

=item B<-q queue>

Specifies the name of the queue, if different than the client ID, from
which jobs will be requested.  This option is typically used to
request jobs from a public queue rather than the client's own private
queue.

=item B<-r repeat-count>

Specifies the number of times to request jobs.  The default value is
one.  A value of 'infinite' or '0' means loop forever.

=item B<-s soft-stop>

Specifies the total number of seconds that a single invocation of this
script is allowed to run.  Once the specified amount of time has
elapsed (not including any start delay), the script will complete all
pending jobs (i.e., jobs already pulled from the queue) and exit.
This value is not defined/used by default.

=item B<-T signal-number>

Specifies the timeout signal to send the target process when the run
timer expires -- refer to the B<RunTimeLimit> control in the webjob(1)
man page for additional details.

Note: This option is only supported on UNIX platforms.

=item B<-t {p|parallel|s|serial}>

Specifies the job/queue type, which can either be parallel or serial.
Serial jobs are executed sequentially, and parallel jobs are executed
concurrently up to the imposed kid limit.  All jobs are executed in
the background.

=back

=head1 RETURN VALUES

Upon successful completion, a value of B<0> (B<XER_OK>) is returned.
Otherwise, one of the following error codes is returned:

=over 5

=item *

B<1 => B<XER_Usage>

=item *

B<2 => B<XER_Abort>

=item *

B<3 => B<XER_Locked>

=item *

B<4 => B<XER_NoKids>

=back

=head1 AUTHOR

Klayton Monroe

=head1 SEE ALSO

webjob(1)

=head1 LICENSE

All documentation and code are distributed under same terms and
conditions as B<WebJob>.

=cut
