mirror of
https://github.com/pandorafms/pandorafms.git
synced 2025-07-29 16:55:05 +02:00
WIP Omnishell common libraries
This commit is contained in:
parent
1a2f14a35b
commit
8d5e3adf6a
@ -33,18 +33,6 @@ use IO::Socket;
|
|||||||
use Sys::Syslog;
|
use Sys::Syslog;
|
||||||
use Time::Local;
|
use Time::Local;
|
||||||
|
|
||||||
my $YAML = 0;
|
|
||||||
# Dynamic load. Avoid unwanted behaviour.
|
|
||||||
eval {
|
|
||||||
eval 'require YAML::Tiny;1' or die('YAML::Tiny lib not found, commands feature won\'t be available');
|
|
||||||
};
|
|
||||||
if ($@) {
|
|
||||||
$YAML = 0;
|
|
||||||
print STDERR $@;
|
|
||||||
} else {
|
|
||||||
$YAML = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Agent XML data
|
# Agent XML data
|
||||||
my $Xml;
|
my $Xml;
|
||||||
|
|
||||||
@ -1488,336 +1476,6 @@ sub check_collections () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Check for remote commands defined.
|
|
||||||
################################################################################
|
|
||||||
sub prepare_commands {
|
|
||||||
if ($YAML == 0) {
|
|
||||||
log_message(
|
|
||||||
'error',
|
|
||||||
'Cannot use commands without YAML dependency, please install it.'
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Force configuration file read.
|
|
||||||
my @commands = read_config('cmd_file');
|
|
||||||
|
|
||||||
if (empty(\@commands)) {
|
|
||||||
$Conf{'commands'} = {};
|
|
||||||
} else {
|
|
||||||
foreach my $rcmd (@commands) {
|
|
||||||
$Conf{'commands'}->{trim($rcmd)} = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Cleanup old commands. Not registered.
|
|
||||||
cleanup_old_commands();
|
|
||||||
|
|
||||||
foreach my $ref (keys %{$Conf{'commands'}}) {
|
|
||||||
my $file_content;
|
|
||||||
my $download = 0;
|
|
||||||
my $rcmd_file = $ConfDir.'/commands/'.$ref.'.rcmd';
|
|
||||||
|
|
||||||
# Check for local .rcmd.done files
|
|
||||||
if (-e $rcmd_file.'.done') {
|
|
||||||
# Ignore.
|
|
||||||
delete $Conf{'commands'}->{$ref};
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Search for local .rcmd file
|
|
||||||
if (-e $rcmd_file) {
|
|
||||||
my $remote_md5_file = $Conf{'temporal'}.'/'.$ref.'.md5';
|
|
||||||
|
|
||||||
$file_content = read_file($rcmd_file);
|
|
||||||
if (recv_file($ref.'.md5', $remote_md5_file) != 0) {
|
|
||||||
# Remote file could not be retrieved, skip.
|
|
||||||
delete $Conf{'commands'}->{$ref};
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $local_md5 = md5($file_content);
|
|
||||||
my $remote_md5 = md5(read_file($remote_md5_file));
|
|
||||||
|
|
||||||
if ($local_md5 ne $remote_md5) {
|
|
||||||
# Must be downloaded again.
|
|
||||||
$download = 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$download = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Search for remote .rcmd file
|
|
||||||
if ($download == 1) {
|
|
||||||
# Download .rcmd file
|
|
||||||
if (recv_file($ref.'.rcmd') != 0) {
|
|
||||||
# Remote file could not be retrieved, skip.
|
|
||||||
delete $Conf{'commands'}->{$ref};
|
|
||||||
next;
|
|
||||||
} else {
|
|
||||||
# Success
|
|
||||||
move($Conf{'temporal'}.'/'.$ref.'.rcmd', $rcmd_file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Parse and prepare in memory skel.
|
|
||||||
eval {
|
|
||||||
$Conf{'commands'}->{$ref} = YAML::Tiny->read($rcmd_file);
|
|
||||||
};
|
|
||||||
if ($@) {
|
|
||||||
# Failed.
|
|
||||||
log_message('error', 'Failed to decode command. ' . "\n".$@);
|
|
||||||
delete $Conf{'commands'}->{$ref};
|
|
||||||
next;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Command report.
|
|
||||||
################################################################################
|
|
||||||
sub report_command {
|
|
||||||
my ($ref, $err_level) = @_;
|
|
||||||
|
|
||||||
# Retrieve content from .stdout and .stderr
|
|
||||||
my $stdout_file = $Conf{'temporal'}.'/'.$ref.'.stdout';
|
|
||||||
my $stderr_file = $Conf{'temporal'}.'/'.$ref.'.stderr';
|
|
||||||
|
|
||||||
my $return;
|
|
||||||
eval {
|
|
||||||
$return = {
|
|
||||||
'error_level' => $err_level,
|
|
||||||
'stdout' => read_file($stdout_file),
|
|
||||||
'stderr' => read_file($stderr_file),
|
|
||||||
};
|
|
||||||
|
|
||||||
$return->{'name'} = $Conf{'commands'}->{$ref}->[0]->{'name'};
|
|
||||||
};
|
|
||||||
if ($@) {
|
|
||||||
log_message('error', 'Failed to report command output. ' . $@);
|
|
||||||
}
|
|
||||||
|
|
||||||
# Cleanup
|
|
||||||
unlink($stdout_file) if (-e $stdout_file);
|
|
||||||
unlink($stderr_file) if (-e $stderr_file);
|
|
||||||
|
|
||||||
# Mark command as done.
|
|
||||||
open (my $R_FILE, '> '.$ConfDir.'/commands/'.$ref.'.rcmd.done');
|
|
||||||
print $R_FILE $err_level;
|
|
||||||
close($R_FILE);
|
|
||||||
|
|
||||||
|
|
||||||
$return->{'stdout'} = '' unless defined ($return->{'stdout'});
|
|
||||||
$return->{'stderr'} = '' unless defined ($return->{'stderr'});
|
|
||||||
|
|
||||||
return $return;
|
|
||||||
}
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Cleanup unreferenced rcmd and rcmd.done files.
|
|
||||||
################################################################################
|
|
||||||
sub cleanup_old_commands {
|
|
||||||
# Cleanup old .rcmd and .rcmd.done files.
|
|
||||||
my %registered = map { $_.'.rcmd' => 1 } keys %{$Conf{'commands'}};
|
|
||||||
if(opendir(my $dir, $ConfDir.'/commands/')) {
|
|
||||||
while (my $item = readdir($dir)) {
|
|
||||||
|
|
||||||
# Skip other files.
|
|
||||||
next if ($item !~ /\.rcmd$/);
|
|
||||||
|
|
||||||
# Clean .rcmd.done file if its command is not referenced in conf.
|
|
||||||
if (!defined($registered{$item})) {
|
|
||||||
if (-e $ConfDir.'/commands/'.$item) {
|
|
||||||
unlink($ConfDir.'/commands/'.$item);
|
|
||||||
}
|
|
||||||
if (-e $ConfDir.'/commands/'.$item.'.done') {
|
|
||||||
unlink($ConfDir.'/commands/'.$item.'.done');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Close dir.
|
|
||||||
closedir($dir);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Executes a command using defined timeout.
|
|
||||||
################################################################################
|
|
||||||
sub execute_command_timeout {
|
|
||||||
my ($cmd, $timeout) = @_;
|
|
||||||
|
|
||||||
if (!defined($timeout)
|
|
||||||
|| !looks_like_number($timeout)
|
|
||||||
|| $timeout <= 0
|
|
||||||
) {
|
|
||||||
`$cmd`;
|
|
||||||
return $?>>8;
|
|
||||||
}
|
|
||||||
|
|
||||||
my $remaining_timeout = $timeout;
|
|
||||||
|
|
||||||
my $RET;
|
|
||||||
my $output;
|
|
||||||
|
|
||||||
my $pid = open ($RET, "-|");
|
|
||||||
if (!defined($pid)) {
|
|
||||||
# Failed to fork.
|
|
||||||
log_message('error', '[command] Failed to fork.');
|
|
||||||
return undef;
|
|
||||||
}
|
|
||||||
if ($pid == 0) {
|
|
||||||
# Child.
|
|
||||||
my $ret;
|
|
||||||
eval {
|
|
||||||
local $SIG{ALRM} = sub { die "timeout\n" };
|
|
||||||
alarm $timeout;
|
|
||||||
`$cmd`;
|
|
||||||
alarm 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
my $result = ($?>>8);
|
|
||||||
return $result;
|
|
||||||
|
|
||||||
# Exit child.
|
|
||||||
# Child finishes.
|
|
||||||
exit;
|
|
||||||
|
|
||||||
} else {
|
|
||||||
# Parent waiting.
|
|
||||||
while( --$remaining_timeout > 0 ){
|
|
||||||
if (wait == -1) {
|
|
||||||
last;
|
|
||||||
}
|
|
||||||
# Wait child up to timeout seconds.
|
|
||||||
sleep 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($remaining_timeout > 0) {
|
|
||||||
# Retrieve output from child.
|
|
||||||
$output = do { local $/; <$RET> };
|
|
||||||
$output = $output>>8;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
# Timeout expired.
|
|
||||||
return 124;
|
|
||||||
}
|
|
||||||
|
|
||||||
close($RET);
|
|
||||||
|
|
||||||
return $output;
|
|
||||||
}
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Executes a block of commands, returns error level, leaves output in
|
|
||||||
# redirection set by $std_files. E.g:
|
|
||||||
# $std_files = ' >> /tmp/stdout 2>> /tmp/stderr
|
|
||||||
################################################################################
|
|
||||||
sub execute_command_block {
|
|
||||||
my ($commands, $std_files, $timeout, $retry) = @_;
|
|
||||||
|
|
||||||
return 0 unless defined($commands);
|
|
||||||
|
|
||||||
my $retries = $retry;
|
|
||||||
|
|
||||||
$retries = 1 unless looks_like_number($retries) && $retries > 0;
|
|
||||||
|
|
||||||
my $err_level = 0;
|
|
||||||
$std_files = '' unless defined ($std_files);
|
|
||||||
|
|
||||||
if (ref($commands) ne "ARRAY") {
|
|
||||||
return 0 if $commands eq '';
|
|
||||||
|
|
||||||
do {
|
|
||||||
$err_level = execute_command_timeout(
|
|
||||||
"($commands) $std_files",
|
|
||||||
$timeout
|
|
||||||
);
|
|
||||||
|
|
||||||
# Do not retry if success.
|
|
||||||
last if looks_like_number($err_level) && $err_level == 0;
|
|
||||||
} while ((--$retries) > 0);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
foreach my $comm (@{$commands}) {
|
|
||||||
next unless defined($comm);
|
|
||||||
$retries = $retry;
|
|
||||||
$retries = 1 unless looks_like_number($retries) && $retries > 0;
|
|
||||||
|
|
||||||
do {
|
|
||||||
$err_level = execute_command_timeout(
|
|
||||||
"($comm) $std_files",
|
|
||||||
$timeout
|
|
||||||
);
|
|
||||||
|
|
||||||
# Do not retry if success.
|
|
||||||
$retries = 0 if looks_like_number($err_level) && $err_level == 0;
|
|
||||||
|
|
||||||
} while ((--$retries) > 0);
|
|
||||||
|
|
||||||
# Do not continue evaluating block if failed.
|
|
||||||
last unless ($err_level == 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $err_level;
|
|
||||||
}
|
|
||||||
|
|
||||||
################################################################################
|
|
||||||
# Evalate given command.
|
|
||||||
################################################################################
|
|
||||||
sub evaluate_command {
|
|
||||||
my ($ref) = @_;
|
|
||||||
|
|
||||||
# Not found.
|
|
||||||
return unless defined $Conf{'commands'}->{$ref};
|
|
||||||
|
|
||||||
# Already completed.
|
|
||||||
return if (-e $ConfDir.'/commands/'.$ref.'.rcmd.done');
|
|
||||||
|
|
||||||
# [0] because how library works.
|
|
||||||
my $cmd = $Conf{'commands'}->{$ref}->[0];
|
|
||||||
|
|
||||||
my $std_files = ' >> '.$Conf{'temporal'}.'/'.$ref.'.stdout ';
|
|
||||||
$std_files .= ' 2>> '.$Conf{'temporal'}.'/'.$ref.'.stderr ';
|
|
||||||
|
|
||||||
# Check preconditions
|
|
||||||
my $err_level;
|
|
||||||
|
|
||||||
$err_level = execute_command_block(
|
|
||||||
$cmd->{'preconditions'},
|
|
||||||
$std_files,
|
|
||||||
$cmd->{'timeout'}
|
|
||||||
);
|
|
||||||
|
|
||||||
# Precondition not satisfied.
|
|
||||||
return report_command($ref, $err_level) unless ($err_level == 0);
|
|
||||||
|
|
||||||
# Main run.
|
|
||||||
$err_level = execute_command_block(
|
|
||||||
$cmd->{'script'},
|
|
||||||
$std_files,
|
|
||||||
$cmd->{'timeout'}
|
|
||||||
);
|
|
||||||
|
|
||||||
# Script not success.
|
|
||||||
return report_command($ref, $err_level) unless ($err_level == 0);
|
|
||||||
|
|
||||||
# Check postconditions
|
|
||||||
$err_level = execute_command_block(
|
|
||||||
$cmd->{'postconditions'},
|
|
||||||
$std_files,
|
|
||||||
$cmd->{'timeout'}
|
|
||||||
);
|
|
||||||
|
|
||||||
# Return results.
|
|
||||||
return report_command($ref, $err_level);
|
|
||||||
}
|
|
||||||
|
|
||||||
################################################################################
|
################################################################################
|
||||||
# Sleep function
|
# Sleep function
|
||||||
################################################################################
|
################################################################################
|
||||||
@ -3453,8 +3111,11 @@ while (1) {
|
|||||||
# Check file collections
|
# Check file collections
|
||||||
check_collections () unless ($Conf{'debug'} eq '1');
|
check_collections () unless ($Conf{'debug'} eq '1');
|
||||||
|
|
||||||
# Check scheduled commands
|
if ($Conf{'debug'} ne '1') {
|
||||||
prepare_commands() unless ($Conf{'debug'} eq '1');
|
# Check scheduled commands
|
||||||
|
my $omni = new PandoraFMS::Omnishell(\%Conf);
|
||||||
|
$omni->prepare_commands();
|
||||||
|
}
|
||||||
|
|
||||||
# Launch broker agents
|
# Launch broker agents
|
||||||
@BrokerPid = ();
|
@BrokerPid = ();
|
||||||
|
21
pandora_agents/win32/omnishell/omnishell_client.pl
Normal file
21
pandora_agents/win32/omnishell/omnishell_client.pl
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
################################################################################
|
||||||
|
# Pandora FMS Omnishell client.
|
||||||
|
#
|
||||||
|
# (c) Fco de Borja Sánchez <fborja.sanchez@pandorafms.com>
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use lib '/usr/lib/perl5';
|
||||||
|
use PandoraFMS::Tools;
|
||||||
|
use PandoraFMS::Omnishell;
|
||||||
|
|
||||||
|
my %Conf;
|
||||||
|
|
||||||
|
if ($Conf{'debug'} ne '1') {
|
||||||
|
# Check scheduled commands
|
||||||
|
my $omni = new PandoraFMS::Omnishell(\%Conf);
|
||||||
|
$omni->prepare_commands();
|
||||||
|
}
|
400
pandora_server/lib/PandoraFMS/Omnishell.pm
Normal file
400
pandora_server/lib/PandoraFMS/Omnishell.pm
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
package PandoraFMS::Omnishell;
|
||||||
|
################################################################################
|
||||||
|
# Pandora FMS Omnishell common functions.
|
||||||
|
#
|
||||||
|
# (c) Fco de Borja Sánchez <fborja.sanchez@pandorafms.com>
|
||||||
|
#
|
||||||
|
################################################################################
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
my $YAML = 0;
|
||||||
|
# Dynamic load. Avoid unwanted behaviour.
|
||||||
|
eval {
|
||||||
|
eval 'require YAML::Tiny;1' or die('YAML::Tiny lib not found, commands feature won\'t be available');
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
$YAML = 0;
|
||||||
|
} else {
|
||||||
|
$YAML = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
use lib '/usr/lib/perl5';
|
||||||
|
|
||||||
|
our @ISA = ("Exporter");
|
||||||
|
our %EXPORT_TAGS = ( 'all' => [ qw( ) ] );
|
||||||
|
our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
|
||||||
|
our @EXPORT = qw();
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# return last error.
|
||||||
|
################################################################################
|
||||||
|
sub get_last_error {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
if (!is_empty($self->{'last_error'})) {
|
||||||
|
return $self->{'last_error'};
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Create new omnishell handler.
|
||||||
|
################################################################################
|
||||||
|
sub new {
|
||||||
|
my ($class,$args) = @_;
|
||||||
|
|
||||||
|
if (ref($args) ne 'HASH') {
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $self = {
|
||||||
|
'last_error' => undef,
|
||||||
|
%{$args}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
$self = bless($self, $class);
|
||||||
|
|
||||||
|
return $self;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Check for remote commands defined.
|
||||||
|
################################################################################
|
||||||
|
sub prepare_commands {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
if ($YAML == 0) {
|
||||||
|
log_message(
|
||||||
|
'error',
|
||||||
|
'Cannot use commands without YAML dependency, please install it.'
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Force configuration file read.
|
||||||
|
my @commands = read_config('cmd_file');
|
||||||
|
|
||||||
|
if (empty(\@commands)) {
|
||||||
|
$self->{'commands'} = {};
|
||||||
|
} else {
|
||||||
|
foreach my $rcmd (@commands) {
|
||||||
|
$self->{'commands'}->{trim($rcmd)} = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup old commands. Not registered.
|
||||||
|
cleanup_old_commands();
|
||||||
|
|
||||||
|
foreach my $ref (keys %{$self->{'commands'}}) {
|
||||||
|
my $file_content;
|
||||||
|
my $download = 0;
|
||||||
|
my $rcmd_file = $self->{'ConfDir'}.'/commands/'.$ref.'.rcmd';
|
||||||
|
|
||||||
|
# Check for local .rcmd.done files
|
||||||
|
if (-e $rcmd_file.'.done') {
|
||||||
|
# Ignore.
|
||||||
|
delete $self->{'commands'}->{$ref};
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Search for local .rcmd file
|
||||||
|
if (-e $rcmd_file) {
|
||||||
|
my $remote_md5_file = $self->{'temporal'}.'/'.$ref.'.md5';
|
||||||
|
|
||||||
|
$file_content = read_file($rcmd_file);
|
||||||
|
if (recv_file($ref.'.md5', $remote_md5_file) != 0) {
|
||||||
|
# Remote file could not be retrieved, skip.
|
||||||
|
delete $self->{'commands'}->{$ref};
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $local_md5 = md5($file_content);
|
||||||
|
my $remote_md5 = md5(read_file($remote_md5_file));
|
||||||
|
|
||||||
|
if ($local_md5 ne $remote_md5) {
|
||||||
|
# Must be downloaded again.
|
||||||
|
$download = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$download = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Search for remote .rcmd file
|
||||||
|
if ($download == 1) {
|
||||||
|
# Download .rcmd file
|
||||||
|
if (recv_file($ref.'.rcmd') != 0) {
|
||||||
|
# Remote file could not be retrieved, skip.
|
||||||
|
delete $self->{'commands'}->{$ref};
|
||||||
|
next;
|
||||||
|
} else {
|
||||||
|
# Success
|
||||||
|
move($self->{'temporal'}.'/'.$ref.'.rcmd', $rcmd_file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parse and prepare in memory skel.
|
||||||
|
eval {
|
||||||
|
$self->{'commands'}->{$ref} = YAML::Tiny->read($rcmd_file);
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
# Failed.
|
||||||
|
log_message('error', 'Failed to decode command. ' . "\n".$@);
|
||||||
|
delete $self->{'commands'}->{$ref};
|
||||||
|
next;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Command report.
|
||||||
|
################################################################################
|
||||||
|
sub report_command {
|
||||||
|
my ($self, $ref, $err_level) = @_;
|
||||||
|
|
||||||
|
# Retrieve content from .stdout and .stderr
|
||||||
|
my $stdout_file = $self->{'temporal'}.'/'.$ref.'.stdout';
|
||||||
|
my $stderr_file = $self->{'temporal'}.'/'.$ref.'.stderr';
|
||||||
|
|
||||||
|
my $return;
|
||||||
|
eval {
|
||||||
|
$return = {
|
||||||
|
'error_level' => $err_level,
|
||||||
|
'stdout' => read_file($stdout_file),
|
||||||
|
'stderr' => read_file($stderr_file),
|
||||||
|
};
|
||||||
|
|
||||||
|
$return->{'name'} = $self->{'commands'}->{$ref}->[0]->{'name'};
|
||||||
|
};
|
||||||
|
if ($@) {
|
||||||
|
log_message('error', 'Failed to report command output. ' . $@);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cleanup
|
||||||
|
unlink($stdout_file) if (-e $stdout_file);
|
||||||
|
unlink($stderr_file) if (-e $stderr_file);
|
||||||
|
|
||||||
|
# Mark command as done.
|
||||||
|
open (my $R_FILE, '> '.$self->{'ConfDir'}.'/commands/'.$ref.'.rcmd.done');
|
||||||
|
print $R_FILE $err_level;
|
||||||
|
close($R_FILE);
|
||||||
|
|
||||||
|
|
||||||
|
$return->{'stdout'} = '' unless defined ($return->{'stdout'});
|
||||||
|
$return->{'stderr'} = '' unless defined ($return->{'stderr'});
|
||||||
|
|
||||||
|
return $return;
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Cleanup unreferenced rcmd and rcmd.done files.
|
||||||
|
################################################################################
|
||||||
|
sub cleanup_old_commands {
|
||||||
|
my ($self) = @_;
|
||||||
|
|
||||||
|
# Cleanup old .rcmd and .rcmd.done files.
|
||||||
|
my %registered = map { $_.'.rcmd' => 1 } keys %{$self->{'commands'}};
|
||||||
|
if(opendir(my $dir, $self->{'ConfDir'}.'/commands/')) {
|
||||||
|
while (my $item = readdir($dir)) {
|
||||||
|
|
||||||
|
# Skip other files.
|
||||||
|
next if ($item !~ /\.rcmd$/);
|
||||||
|
|
||||||
|
# Clean .rcmd.done file if its command is not referenced in conf.
|
||||||
|
if (!defined($registered{$item})) {
|
||||||
|
if (-e $self->{'ConfDir'}.'/commands/'.$item) {
|
||||||
|
unlink($self->{'ConfDir'}.'/commands/'.$item);
|
||||||
|
}
|
||||||
|
if (-e $self->{'ConfDir'}.'/commands/'.$item.'.done') {
|
||||||
|
unlink($self->{'ConfDir'}.'/commands/'.$item.'.done');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Close dir.
|
||||||
|
closedir($dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Executes a command using defined timeout.
|
||||||
|
################################################################################
|
||||||
|
sub execute_command_timeout {
|
||||||
|
my ($cmd, $timeout) = @_;
|
||||||
|
|
||||||
|
if (!defined($timeout)
|
||||||
|
|| !looks_like_number($timeout)
|
||||||
|
|| $timeout <= 0
|
||||||
|
) {
|
||||||
|
`$cmd`;
|
||||||
|
return $?>>8;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $remaining_timeout = $timeout;
|
||||||
|
|
||||||
|
my $RET;
|
||||||
|
my $output;
|
||||||
|
|
||||||
|
my $pid = open ($RET, "-|");
|
||||||
|
if (!defined($pid)) {
|
||||||
|
# Failed to fork.
|
||||||
|
log_message('error', '[command] Failed to fork.');
|
||||||
|
return undef;
|
||||||
|
}
|
||||||
|
if ($pid == 0) {
|
||||||
|
# Child.
|
||||||
|
my $ret;
|
||||||
|
eval {
|
||||||
|
local $SIG{ALRM} = sub { die "timeout\n" };
|
||||||
|
alarm $timeout;
|
||||||
|
`$cmd`;
|
||||||
|
alarm 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
my $result = ($?>>8);
|
||||||
|
return $result;
|
||||||
|
|
||||||
|
# Exit child.
|
||||||
|
# Child finishes.
|
||||||
|
exit;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
# Parent waiting.
|
||||||
|
while( --$remaining_timeout > 0 ){
|
||||||
|
if (wait == -1) {
|
||||||
|
last;
|
||||||
|
}
|
||||||
|
# Wait child up to timeout seconds.
|
||||||
|
sleep 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($remaining_timeout > 0) {
|
||||||
|
# Retrieve output from child.
|
||||||
|
$output = do { local $/; <$RET> };
|
||||||
|
$output = $output>>8;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
# Timeout expired.
|
||||||
|
return 124;
|
||||||
|
}
|
||||||
|
|
||||||
|
close($RET);
|
||||||
|
|
||||||
|
return $output;
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Executes a block of commands, returns error level, leaves output in
|
||||||
|
# redirection set by $std_files. E.g:
|
||||||
|
# $std_files = ' >> /tmp/stdout 2>> /tmp/stderr
|
||||||
|
################################################################################
|
||||||
|
sub execute_command_block {
|
||||||
|
my ($commands, $std_files, $timeout, $retry) = @_;
|
||||||
|
|
||||||
|
return 0 unless defined($commands);
|
||||||
|
|
||||||
|
my $retries = $retry;
|
||||||
|
|
||||||
|
$retries = 1 unless looks_like_number($retries) && $retries > 0;
|
||||||
|
|
||||||
|
my $err_level = 0;
|
||||||
|
$std_files = '' unless defined ($std_files);
|
||||||
|
|
||||||
|
if (ref($commands) ne "ARRAY") {
|
||||||
|
return 0 if $commands eq '';
|
||||||
|
|
||||||
|
do {
|
||||||
|
$err_level = execute_command_timeout(
|
||||||
|
"($commands) $std_files",
|
||||||
|
$timeout
|
||||||
|
);
|
||||||
|
|
||||||
|
# Do not retry if success.
|
||||||
|
last if looks_like_number($err_level) && $err_level == 0;
|
||||||
|
} while ((--$retries) > 0);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
foreach my $comm (@{$commands}) {
|
||||||
|
next unless defined($comm);
|
||||||
|
$retries = $retry;
|
||||||
|
$retries = 1 unless looks_like_number($retries) && $retries > 0;
|
||||||
|
|
||||||
|
do {
|
||||||
|
$err_level = execute_command_timeout(
|
||||||
|
"($comm) $std_files",
|
||||||
|
$timeout
|
||||||
|
);
|
||||||
|
|
||||||
|
# Do not retry if success.
|
||||||
|
$retries = 0 if looks_like_number($err_level) && $err_level == 0;
|
||||||
|
|
||||||
|
} while ((--$retries) > 0);
|
||||||
|
|
||||||
|
# Do not continue evaluating block if failed.
|
||||||
|
last unless ($err_level == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $err_level;
|
||||||
|
}
|
||||||
|
|
||||||
|
################################################################################
|
||||||
|
# Evalate given command.
|
||||||
|
################################################################################
|
||||||
|
sub evaluate_command {
|
||||||
|
my ($self, $ref) = @_;
|
||||||
|
|
||||||
|
# Not found.
|
||||||
|
return unless defined $self->{'commands'}->{$ref};
|
||||||
|
|
||||||
|
# Already completed.
|
||||||
|
return if (-e $self->{'ConfDir'}.'/commands/'.$ref.'.rcmd.done');
|
||||||
|
|
||||||
|
# [0] because how library works.
|
||||||
|
my $cmd = $self->{'commands'}->{$ref}->[0];
|
||||||
|
|
||||||
|
my $std_files = ' >> '.$self->{'temporal'}.'/'.$ref.'.stdout ';
|
||||||
|
$std_files .= ' 2>> '.$self->{'temporal'}.'/'.$ref.'.stderr ';
|
||||||
|
|
||||||
|
# Check preconditions
|
||||||
|
my $err_level;
|
||||||
|
|
||||||
|
$err_level = execute_command_block(
|
||||||
|
$cmd->{'preconditions'},
|
||||||
|
$std_files,
|
||||||
|
$cmd->{'timeout'}
|
||||||
|
);
|
||||||
|
|
||||||
|
# Precondition not satisfied.
|
||||||
|
return report_command($ref, $err_level) unless ($err_level == 0);
|
||||||
|
|
||||||
|
# Main run.
|
||||||
|
$err_level = execute_command_block(
|
||||||
|
$cmd->{'script'},
|
||||||
|
$std_files,
|
||||||
|
$cmd->{'timeout'}
|
||||||
|
);
|
||||||
|
|
||||||
|
# Script not success.
|
||||||
|
return report_command($ref, $err_level) unless ($err_level == 0);
|
||||||
|
|
||||||
|
# Check postconditions
|
||||||
|
$err_level = execute_command_block(
|
||||||
|
$cmd->{'postconditions'},
|
||||||
|
$std_files,
|
||||||
|
$cmd->{'timeout'}
|
||||||
|
);
|
||||||
|
|
||||||
|
# Return results.
|
||||||
|
return report_command($ref, $err_level);
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
Loading…
x
Reference in New Issue
Block a user