Beta Shell
v2.0 ยท web2.us.cloudlogin.co
[FM]
[CMD]
[PHP]
[DB]
[INFO]
[SEC]
File Manager
~
/
usr
/
share
/
fusioninventory
/
lib
/
FusionInventory
Upload
4 items
Name
Size
Perms
Modified
Actions
[ .. / .. ]
Agent
-
drwxr-xr-x
2022-02-23 11:31:06
Del
Agent.pm
16.25 KB
-rw-r--r--
2020-08-04 07:36:42
Edit
Del
Editing: Agent.pm
(16.25 KB)
Path: /usr/share/fusioninventory/lib/FusionInventory/Agent.pm
Back
package FusionInventory::Agent; use strict; use warnings; use English qw(-no_match_vars); use UNIVERSAL::require; use File::Glob; use IO::Handle; use Storable 'dclone'; use FusionInventory::Agent::Version; use FusionInventory::Agent::Config; use FusionInventory::Agent::Logger; use FusionInventory::Agent::Storage; use FusionInventory::Agent::Target::Local; use FusionInventory::Agent::Target::Server; use FusionInventory::Agent::Tools; use FusionInventory::Agent::Tools::Hostname; our $VERSION = $FusionInventory::Agent::Version::VERSION; my $PROVIDER = $FusionInventory::Agent::Version::PROVIDER; our $COMMENTS = $FusionInventory::Agent::Version::COMMENTS || []; our $VERSION_STRING = _versionString($VERSION); our $AGENT_STRING = "$PROVIDER-Agent_v$VERSION"; our $CONTINUE_WORD = "..."; sub _versionString { my ($VERSION) = @_; my $string = "$PROVIDER Agent ($VERSION)"; if ($VERSION =~ /^\d+\.\d+\.(99\d\d|\d+-dev|.*-build-?\d+)$/) { unshift @{$COMMENTS}, "** THIS IS A DEVELOPMENT RELEASE **"; } return $string; } sub new { my ($class, %params) = @_; my $self = { status => 'unknown', datadir => $params{datadir}, libdir => $params{libdir}, vardir => $params{vardir}, targets => [], }; bless $self, $class; return $self; } sub init { my ($self, %params) = @_; # Skip create object if still defined (re-init case) my $config = $self->{config} || FusionInventory::Agent::Config->new( options => $params{options}, vardir => $self->{vardir}, ); $self->{config} = $config; my $logger = FusionInventory::Agent::Logger->new(config => $config); $self->{logger} = $logger; $logger->debug("Configuration directory: ".$config->confdir()); $logger->debug("Data directory: $self->{datadir}"); $logger->debug("Storage directory: $self->{vardir}"); $logger->debug("Lib directory: $self->{libdir}"); $self->_handlePersistentState(); # Persistent data can store a set "forcerun" value to be handled during start # Mainly used by win32 service installer my $forced_run = delete $self->{_forced_run}; # Always reset targets to handle re-init case $self->{targets} = $config->getTargets( logger => $self->{logger}, deviceid => $self->{deviceid}, vardir => $self->{vardir} ); if (!$self->getTargets()) { $logger->error("No target defined, aborting"); exit 1; } # Keep program name for Provider inventory as it will be reset in setStatus() FusionInventory::Agent::Task::Inventory::Provider->require(); $FusionInventory::Agent::Task::Inventory::Provider::PROGRAM = "$PROGRAM_NAME"; # compute list of allowed tasks my %available = $self->getAvailableTasks(disabledTasks => $config->{'no-task'}); my @tasks = keys %available; my @plannedTasks = $self->computeTaskExecutionPlan(\@tasks); my %available_lc = map { (lc $_) => $_ } keys %available; if (!@tasks) { $logger->error("No tasks available, aborting"); exit 1; } $logger->debug("Available tasks:"); foreach my $task (keys %available) { $logger->debug("- $task: $available{$task}"); } foreach my $target ($self->getTargets()) { if ($target->isType('local') || $target->isType('server')) { $logger->debug("target $target->{id}: " . $target->getType() . " " . $target->getName()); # Handle forced run request $target->setNextRunDateFromNow() if $forced_run; } else { $logger->debug("target $target->{id}: " . $target->getType()); } # Register planned tasks by target my @planned = $target->plannedTasks(@plannedTasks); if (@planned) { $logger->debug("Planned tasks for $target->{id}:"); foreach my $task (@planned) { my $task_lc = lc $task; $logger->debug("- $task: " . $available{$available_lc{$task_lc}}); } } else { $logger->debug("No planned task"); } } $logger->info("Options 'no-task' and 'tasks' are both used. Be careful that 'no-task' always excludes tasks.") if ($self->{config}->isParamArrayAndFilled('no-task') && $self->{config}->isParamArrayAndFilled('tasks')); # install signal handler to handle graceful exit $SIG{INT} = sub { $self->terminate(); exit 0; }; $SIG{TERM} = sub { $self->terminate(); exit 0; }; if ($params{options}) { foreach my $comment (@{$COMMENTS}) { $self->{logger}->debug($comment); } } } sub run { my ($self) = @_; # API overrided in daemon or service mode $self->setStatus('waiting'); my @targets = $self->getTargets(); $self->{logger}->debug("Running in foreground mode"); # foreground mode: check each targets once my $time = time(); while ($self->getTargets() && @targets) { my $target = shift @targets; if ($self->{config}->{lazy} && $time < $target->getNextRunDate()) { $self->{logger}->info( "$target->{id} is not ready yet, next server contact " . "planned for " . localtime($target->getNextRunDate()) ); next; } eval { $self->runTarget($target); }; $self->{logger}->error($EVAL_ERROR) if $EVAL_ERROR; # Reset next run date to support --lazy option with foreground mode $target->resetNextRunDate(); } } sub terminate { my ($self) = @_; # Forget our targets $self->{targets} = []; # Abort realtask running in that forked process or thread $self->{current_task}->abort() if ($self->{current_task}); } sub runTarget { my ($self, $target) = @_; if ($target->isType('local') || $target->isType('server')) { $self->{logger}->info("target $target->{id}: " . $target->getType() . " " . $target->getName()); } # the prolog dialog must be done once for all tasks, # but only for server targets my $response; if ($target->isType('server')) { return unless FusionInventory::Agent::HTTP::Client::OCS->require(); my $client = FusionInventory::Agent::HTTP::Client::OCS->new( logger => $self->{logger}, timeout => $self->{config}->{timeout}, user => $self->{config}->{user}, password => $self->{config}->{password}, proxy => $self->{config}->{proxy}, ca_cert_file => $self->{config}->{'ca-cert-file'}, ca_cert_dir => $self->{config}->{'ca-cert-dir'}, no_ssl_check => $self->{config}->{'no-ssl-check'}, no_compress => $self->{config}->{'no-compression'}, ); return unless FusionInventory::Agent::XML::Query::Prolog->require(); my $prolog = FusionInventory::Agent::XML::Query::Prolog->new( deviceid => $self->{deviceid}, ); $self->{logger}->info("sending prolog request to $target->{id}"); $response = $client->send( url => $target->getUrl(), message => $prolog ); unless ($response) { $self->{logger}->error("No answer from server at ".$target->getUrl()); # Return true on net error return 1; } # update target my $content = $response->getContent(); if (defined($content->{PROLOG_FREQ})) { $target->setMaxDelay($content->{PROLOG_FREQ} * 3600); } } foreach my $name ($target->plannedTasks()) { eval { $self->runTask($target, $name, $response); }; $self->{logger}->error($EVAL_ERROR) if $EVAL_ERROR; $self->setStatus($target->paused() ? 'paused' : 'waiting'); # Leave earlier while requested last unless $self->getTargets(); last if $target->paused(); } return 0; } sub runTask { my ($self, $target, $name, $response) = @_; # API overrided in daemon or service mode $self->setStatus("running task $name"); # standalone mode: run each task directly $self->runTaskReal($target, $name, $response); } sub runTaskReal { my ($self, $target, $name, $response) = @_; my $class = "FusionInventory::Agent::Task::$name"; if (!$class->require()) { $self->{logger}->debug2("$name task module does not compile: $@") if $self->{logger}; return; } my $task = $class->new( config => $self->{config}, datadir => $self->{datadir}, logger => $self->{logger}, target => $target, deviceid => $self->{deviceid}, ); return if !$task->isEnabled($response); $self->{logger}->info("running task $name"); $self->{current_task} = $task; $task->run( user => $self->{config}->{user}, password => $self->{config}->{password}, proxy => $self->{config}->{proxy}, ca_cert_file => $self->{config}->{'ca-cert-file'}, ca_cert_dir => $self->{config}->{'ca-cert-dir'}, no_ssl_check => $self->{config}->{'no-ssl-check'}, no_compress => $self->{config}->{'no-compression'}, ); delete $self->{current_task}; } sub getStatus { my ($self) = @_; return $self->{status}; } sub setStatus { my ($self, $status) = @_; my $config = $self->{config}; # Rename process including status, for unix platforms $0 = lc($PROVIDER) . "-agent"; $0 .= " (tag $config->{tag})" if $config->{tag}; if ($status) { $self->{status} = $status; # Show set status in process name on unix platforms $0 .= ": $status"; } } sub getTargets { my ($self) = @_; return @{$self->{targets}}; } sub getAvailableTasks { my ($self, %params) = @_; my $logger = $self->{logger}; my %tasks; my %disabled = map { lc($_) => 1 } @{$params{disabledTasks}}; # tasks may be located only in agent libdir my $directory = $self->{libdir}; $directory =~ s,\\,/,g; my $subdirectory = "FusionInventory/Agent/Task"; # look for all Version perl modules around here foreach my $file (File::Glob::bsd_glob("$directory/$subdirectory/*/Version.pm")) { next unless $file =~ m{($subdirectory/(\S+)/Version\.pm)$}; my $module = file2module($1); my $name = file2module($2); next if $disabled{lc($name)}; my $version; if (!$module->require()) { $logger->debug2("module $module does not compile: $@") if $logger; # Don't keep trace of module, only really needed to fix perl 5.8 issue delete $INC{module2file($module)}; next; } { no strict 'refs'; ## no critic $version = &{$module . '::VERSION'}; } # no version means non-functionning task next unless $version; $tasks{$name} = $version; $logger->debug2("getAvailableTasks() : add of task $name version $version") if $logger; } return %tasks; } sub _handlePersistentState { my ($self) = @_; # Only create storage at first call unless ($self->{storage}) { $self->{storage} = FusionInventory::Agent::Storage->new( logger => $self->{logger}, directory => $self->{vardir} ); } # Load current agent state my $data = $self->{storage}->restore(name => "$PROVIDER-Agent"); if (!$self->{deviceid} && !$data->{deviceid}) { # compute an unique agent identifier, based on host name and current time my $hostname = getHostname(); my ($year, $month , $day, $hour, $min, $sec) = (localtime (time))[5, 4, 3, 2, 1, 0]; $data->{deviceid} = sprintf "%s-%02d-%02d-%02d-%02d-%02d-%02d", $hostname, $year + 1900, $month + 1, $day, $hour, $min, $sec; } elsif (!$data->{deviceid}) { $data->{deviceid} = $self->{deviceid}; } $self->{deviceid} = $data->{deviceid}; # Handle the option to force a run during start/init/reinit if "forcerun" has # been set in storage datas $self->{_forced_run} = delete $data->{forcerun} || 0; # Always save agent state $self->{storage}->save( name => "$PROVIDER-Agent", data => $data ); } sub setForceRun { my ($self, $forcerun) = @_; my $storage = FusionInventory::Agent::Storage->new( logger => $self->{logger}, directory => $self->{vardir} ); my $data = $storage->restore(name => "$PROVIDER-Agent"); $data->{forcerun} = defined($forcerun) ? $forcerun : 1; $storage->save( name => "$PROVIDER-Agent", data => $data ); } sub _appendElementsNotAlreadyInList { my ($list, $elements, $logger) = @_; if (! UNIVERSAL::isa($list, 'ARRAY')) { $logger->error('_appendElementsNotAlreadyInList(): first argument is not an ARRAY ref') if defined $logger; return $list; } if (UNIVERSAL::isa($elements, 'HASH')) { @$elements = keys %$elements; } elsif (! UNIVERSAL::isa($elements, 'ARRAY')) { $logger->error('_appendElementsNotAlreadyInList(): second argument is neither an ARRAY ref nor a HASH ref') if defined $logger; return $list; } my %list = map { $_ => $_ } @$list; # we want to add elements only once, so we ensure that there are no duplicates my %elements = map { $_ => 1 } @$elements; @$elements = keys %elements; # union of list AND elements which are NOT in list my @newList = (@$list, grep( !defined($list{$_}), @$elements)); return @newList; } sub computeTaskExecutionPlan { my ($self, $availableTasksNames) = @_; if (! defined($self->{config}) || !(UNIVERSAL::isa($self->{config}, 'FusionInventory::Agent::Config'))) { $self->{logger}->error( "no config found in agent. Can't compute tasks execution plan" ) if (defined $self->{logger}); return; } my @executionPlan = (); if ($self->{config}->isParamArrayAndFilled('tasks')) { $self->{logger}->debug2('isParamArrayAndFilled(\'tasks\') : true') if (defined $self->{logger}); @executionPlan = _makeExecutionPlan($self->{config}->{'tasks'}, $availableTasksNames, $self->{logger}); } else { $self->{logger}->debug2('isParamArrayAndFilled(\'tasks\') : false') if (defined $self->{logger}); @executionPlan = @$availableTasksNames; } return @executionPlan; } sub _makeExecutionPlan { my ($sortedTasks, $availableTasksNames, $logger) = @_; my $sortedTasksCloned = dclone $sortedTasks; my @executionPlan = (); my %available = map { (lc $_) => $_ } @$availableTasksNames; my $task = shift @$sortedTasksCloned; while (defined $task) { if ($task eq $CONTINUE_WORD) { last; } $task = lc $task; if ( defined($available{$task})) { push @executionPlan, $available{$task}; } $task = shift @$sortedTasksCloned; } if ( defined($task) && $task eq $CONTINUE_WORD) { # we append all other available tasks @executionPlan = _appendElementsNotAlreadyInList(\@executionPlan, $availableTasksNames, $logger); } return @executionPlan; } 1; __END__ =head1 NAME FusionInventory::Agent - FusionInventory agent =head1 DESCRIPTION This is the agent object. =head1 METHODS =head2 new(%params) The constructor. The following parameters are allowed, as keys of the %params hash: =over =item I<datadir> the read-only data directory. =item I<vardir> the read-write data directory. =item I<options> the options to use. =back =head2 init() Initialize the agent. =head2 run() Run the agent. =head2 terminate() Terminate the agent. =head2 getStatus() Get the current agent status. =head2 setStatus() Set new agent status, also updates process name on unix platforms. =head2 getTargets() Get all targets. =head2 getAvailableTasks() Get all available tasks found on the system, as a list of module / version pairs: %tasks = ( 'Foo' => x, 'Bar' => y, ); =head2 setForceRun($forcerun) Set "forcerun" option to 1 (by default) in persistent state storage. This option is only read during start, init or reinit. If set to true, the next run is planned to be started as soon as possible. =head1 LICENSE This is free software, licensed under: The GNU General Public License, Version 2, June 1991 See LICENSE file for details.