????

Your IP : 3.22.79.179


Current Path : /proc/322176/root/scripts/
Upload File :
Current File : //proc/322176/root/scripts/try-later

#!/usr/local/cpanel/3rdparty/bin/perl

# cpanel - scripts/try-later                       Copyright 2022 cPanel, L.L.C.
#                                                           All rights reserved.
# copyright@cpanel.net                                         http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited

use strict;
use warnings;

use Cpanel::Alarm           ();
use Cpanel::Binaries        ();
use Cpanel::SafeRun::Errors ();
use Cpanel::SafeRun::Object ();
use Cpanel::Usage           ();
use DateTime                ();
use IPC::Open3              ();
use Cpanel::Version::Full   ();

my $act_finally;
my $action_command;
my $at_args;
my $check_command;
my $delay = 5;
my $max_retries;
my $skip_first;
my $has_jobs;
my $at_cmd  = Cpanel::Binaries::path('at');
my $atd_cmd = Cpanel::Binaries::path('atd');

if ( !-x $at_cmd || !-x $atd_cmd ) {
    print_usage_and_exit('System "at" command required to run this utility.');
}

Cpanel::Usage::wrap_options(
    \@ARGV,
    \&print_usage_and_exit,
    {
        'act-finally' => \$act_finally,
        'action'      => \$action_command,
        'at'          => \$at_args,
        'check'       => \$check_command,
        'delay'       => \$delay,
        'max-retries' => \$max_retries,
        'skip-first'  => \$skip_first,
        'has-jobs'    => \$has_jobs,
    },
);

if ($has_jobs) {

    # exit 0 : queue is empty
    # exit 1 : queue has at least one job
    exit try_later_has_jobs();
}

if ( !$action_command ) {
    print_usage_and_exit('An action command is required.');
}

if ( !$check_command ) {
    print_usage_and_exit('A check command is required');
}

# The extra parens are necessary.
if ( $max_retries && ( $max_retries !~ m/^\d+$/ || $max_retries < 1 ) ) {
    print_usage_and_exit('Invalid value for --max-retries');
}

# if we're skipping running the check immediately, then
# we need to add a retry as it is decremented during
# do_later
if ( $skip_first && $max_retries ) {
    ++$max_retries;
}

if ( $delay && $delay =~ m/^\d+$/ && $delay > 0 ) {

    # at seems to subtract a minute from the now +, so adding
    # an extra minute seems to make it more understandable
    ++$delay;
    $at_args = "now + $delay minutes";
}
elsif ($delay) {
    print_usage_and_exit('Invalid value for --delay');
}

check() unless $skip_first;

if ( $max_retries == 1 ) {
    if ($act_finally) {
        exit run_command($action_command);
    }
    exit;
}

if ( !start_atd() ) {
    print "Unable to start 'atd', which is required to run this utility.\n";
    exit 1;
}

do_later();

sub check {
    if ( run_command($check_command) ) {
        return;
    }
    exit run_command($action_command);
}

sub do_later {
    my %arg_for_name = (
        '--act-finally' => $act_finally,
        '--action'      => $action_command,
        '--at'          => $at_args,
        '--check'       => $check_command,
    );
    my $me = '/usr/local/cpanel/scripts/try-later';

    # during a fast upgrade / downgrade we could disappear
    # we should empty the at queue before upgrading or downgrading
    exit unless -x $me;
    my @self_command = ($me);

    if ($max_retries) {
        --$max_retries;
        push @self_command, '--max-retries', $max_retries;
    }

    while ( my ( $name, $arg ) = each %arg_for_name ) {
        next if !length $arg;
        push @self_command, $name, "'$arg'";
    }

    # _job_tag() is used to identify jobs in queue
    # could be used to clean the at queue when launching an upgrade
    my $stdin = _job_tag() . "\nif [ -x $me ]; then \n" . join( ' ', @self_command ) . "\nfi\n";

    my $result = Cpanel::SafeRun::Object->new(
        'program' => $at_cmd,
        'args'    => [$at_args],
        'stdin'   => $stdin,
    );

    exit( $result->error_code() // 0 );
}

sub start_atd {

    # Before we start atd, we need to check for stale jobs so that if atd has
    # been disabled, we don't unleash an angry horde of ancient jobs on the
    # system when we re-enable it.

    my $alarm    = Cpanel::Alarm->new( 60, sub { print "Unable to start 'atd' (required for try-later)\n"; exit 1; } );
    my $atq_cmd  = Cpanel::Binaries::path('atq');
    my $atrm_cmd = Cpanel::Binaries::path('atrm');
    my @jobs;
    my @check_cmd = (
        '/usr/local/cpanel/scripts/cpservice',
        'atd',
        'status'
    );
    return if !-x $check_cmd[0];

    # Don't bother starting atd if it's already running.
    Cpanel::SafeRun::Errors::saferunnoerror(@check_cmd);
    return 1 unless $?;

    return unless -x $atq_cmd;

    open( my $fh, '-|', $atq_cmd );

    while ( defined( my $line = <$fh> ) ) {
        next unless $line =~ m/(\d+)\s+(\d{4})-(\d{2})-(\d{2})\s+(\d{2}):(\d{2})/;
        push @jobs, $1 if DateTime->new( year => $2, month => $3, day => $4, hour => $5, minute => $6 ) <= DateTime->now();
    }

    close($fh);

    if (@jobs) {
        return unless -x $atrm_cmd;
        return if system( $atrm_cmd, @jobs );
    }

    my @enable_cmd = (
        '/usr/local/cpanel/scripts/cpservice',
        'atd',
        'enable'
    );
    return if !-x $enable_cmd[0];

    # Sometimes if atd exits uncleanly (e.g. with kill -9), simply trying to
    # start it won't work. This is true of CentOS 5, but not CentOS 6. So
    # instead, we call restart to stop it first to make sure that all the
    # appropriate state is cleaned up, and then start it again.
    my @start_cmd = (
        '/usr/local/cpanel/scripts/cpservice',
        'atd',
        'restart'
    );
    return if !-x $start_cmd[0];

    return if system @enable_cmd;

    return !system @start_cmd;
}

sub _job_tag {
    return "# cPanel try-later version " . Cpanel::Version::Full::getversion();
}

sub try_later_has_jobs {
    my @results = Cpanel::SafeRun::Errors::saferunallerrors( Cpanel::Binaries::path('atq') );

    foreach (@results) {
        next unless $_ =~ /^(\d+)/;
        my $jid    = $1;
        my $job    = Cpanel::SafeRun::Errors::saferunallerrors( $at_cmd, '-c', $jid );
        my $tag    = _job_tag();
        my $regexp = qr{$tag};
        return 1 if $job =~ /^$regexp/m;
    }

    return 0;
}

# This function exists because we may be running under atd.  If we are, and we
# produce output of any sort, the system administrator will receive an email
# entitled "Output from your job", which will only serve to confuse them.
# Consequently, we suppress all output here.
sub run_command {
    my ($command) = @_;

    local *STDOUT = *STDOUT;
    local *STDERR = *STDERR;
    open( STDOUT, ">", "/dev/null" ) or die;
    open( STDERR, ">", "/dev/null" ) or die;
    return system($command);
}

sub print_usage_and_exit {
    my ($error) = @_;

    my %options = (
        'act-finally' => 'Perform action when retries run out',
        'action'      => 'Command to run when a check succeeds',
        'at'          => 'Args to specify when the at command will retry the check',
        'check'       => 'Command to run to check whether or not to run the action',
        'delay'       => 'Specify a delay in minutes after which to check and act (default 5)',
        'help'        => 'Brief help message',
        'max-retries' => 'Maximum attempts to retry before giving up (default infinite)',
        'skip-first'  => 'Skip the first check command',
        'has-jobs'    => 'Check if the try-later queue is empty or not ( exit with 0 if queue is empty )'
    );

    if ( defined $error ) {
        print $error, "\n\n";
    }

    print "Usage: $0 ";
    print "[options]\n\n";
    print "    Options:\n";

    while ( my ( $opt, $desc ) = each %options ) {
        print "      --$opt";
        my $space = 12 - length $opt;
        ( 0 < $space ) ? print ' ' x $space : print '  ';
        print "$desc\n";
    }

    print "\n";
    print "This utility will execute a check command at the configured interval.  If the\n";
    print "check command returns in error, it will be retried later as often as allowed by\n";
    print "max-retries.  When the check succeeds, the action command will be run.";
    print "\n";

    exit 1 if defined $error;
    exit;
}