package Ocsinventory::Agent::XML::Inventory; # TODO: resort the functions use strict; use warnings; =head1 NAME Ocsinventory::Agent::XML::Inventory - the XML abstraction layer =head1 DESCRIPTION OCS Inventory uses XML for the data transmition. The module is the abstraction layer. It's mostly used in the backend module where it called $inventory in general. =cut use XML::Simple; use Digest::MD5 qw(md5_base64); use Config; use Ocsinventory::Agent::Backend; =over 4 =item new() The usual constructor. =cut sub new { my (undef, $params) = @_; my $self = {}; $self->{accountinfo} = $params->{context}->{accountinfo}; $self->{accountconfig} = $params->{context}->{accountconfig}; $self->{backend} = $params->{backend}; $self->{common} = $params->{context}->{common}; my $logger = $self->{logger} = $params->{context}->{logger}; $self->{config} = $params->{context}->{config}; if (!($self->{config}{deviceid})) { $logger->fault ('deviceid unititalised!'); } $self->{xmlroot}{QUERY} = ['INVENTORY']; $self->{xmlroot}{DEVICEID} = [$self->{config}->{deviceid}]; #$self->{xmlroot}{CONTENT}{HARDWARE} = { # TODO move that in a backend module # ARCHNAME => [$Config{archname}] #}; # Is the XML centent initialised? $self->{isInitialised} = undef; bless $self; } =item initialise() Runs the backend modules to initilise the data. =cut sub initialise { my ($self) = @_; return if $self->{isInitialised}; $self->{backend}->feedInventory ({inventory => $self}); $self->{isInitialised} = 1; } =item getContent() Return the inventory as a XML string. =cut sub getContent { my ($self, $args) = @_; my $logger = $self->{logger}; my $common = $self->{common}; if ($self->{isInitialised}) { $self->processChecksum(); # checks for MAC, NAME and SSN presence my $macaddr = $self->{xmlroot}->{CONTENT}->{NETWORKS}->[0]->{MACADDR}->[0]; my $ssn = $self->{xmlroot}->{CONTENT}->{BIOS}->{SSN}->[0]; my $name = $self->{xmlroot}->{CONTENT}->{HARDWARE}->{NAME}->[0]; my $missing; $missing .= "MAC-address " unless $macaddr; $missing .= "SSN " unless $ssn; $missing .= "HOSTNAME " unless $name; if ($missing) { $logger->debug('Missing value(s): '.$missing.'. I will send this inventory to the server BUT important value(s) to identify the computer are missing'); } $self->{accountinfo}->setAccountInfo($self); my $content = XMLout( $self->{xmlroot}, RootName => 'REQUEST', XMLDecl => '', SuppressEmpty => undef ); #Cleaning XML to delete unprintable characters my $clean_content=$common->cleanXml($content); #Cleaning xmltags content after adding it o inventory $common->flushXMLTags(); return $clean_content; } } =item printXML() Only for debugging purpose. Print the inventory on STDOUT. =cut sub printXML { my ($self, $args) = @_; if ($self->{isInitialised}) { print $self->getContent(); } } =item writeXML() Save the generated inventory as an XML file. The 'local' key of the config is used to know where the file as to be saved. =cut sub writeXML { my ($self, $args) = @_; my $logger = $self->{logger}; if ($self->{config}{local} =~ /^$/) { $logger->fault ('local path unititalised!'); } if ($self->{isInitialised}) { my $localfile = $self->{config}{local}."/".$self->{config}{deviceid}.'.ocs'; $localfile =~ s!(//){1,}!/!; # Convert perl data structure into xml strings if (open OUT, ">$localfile") { print OUT $self->getContent(); close OUT or warn; $logger->info("Inventory saved in $localfile"); } else { warn "Can't open `$localfile': $!" } } } =item processChecksum() Compute the field. This information is used by the server to know which parts of the XML have changed since the last inventory. The is done thank to the last_file file. It has MD5 prints of the previous inventory. =cut sub processChecksum { my $self = shift; my $logger = $self->{logger}; my $common = $self->{common}; #To apply to $checksum with an OR my %mask = ( 'HARDWARE' => 1, 'BIOS' => 2, 'MEMORIES' => 4, 'SLOTS' => 8, 'REGISTRY' => 16, 'CONTROLLERS' => 32, 'MONITORS' => 64, 'PORTS' => 128, 'STORAGES' => 256, 'DRIVES' => 512, 'INPUT' => 1024, 'MODEMS' => 2048, 'NETWORKS' => 4096, 'PRINTERS' => 8192, 'SOUNDS' => 16384, 'VIDEOS' => 32768, 'SOFTWARES' => 65536, 'VIRTUALMACHINES' => 131072, ); # TODO CPUS is not in the list if (!$self->{config}->{vardir}) { $logger->fault ("vardir uninitialised!"); } my $checksum = 0; if (!$self->{config}{local} && $self->{config}->{last_statefile}) { if (-f $self->{config}->{last_statefile}) { # TODO: avoid a violant death in case of problem with XML $self->{last_state_content} = XML::Simple::XMLin( $self->{config}->{last_statefile}, SuppressEmpty => undef, ForceArray => 1 ); } else { $logger->debug ('last_state file: `'. $self->{config}->{last_statefile}. "' doesn't exist (yet)."); } } foreach my $section (keys %mask) { #If the checksum has changed... my $hash = md5_base64(XML::Simple::XMLout($self->{xmlroot}{'CONTENT'}{$section})); if (!$self->{last_state_content}->{$section}[0] || $self->{last_state_content}->{$section}[0] ne $hash ) { $logger->debug ("Section $section has changed since last inventory"); #We make OR on $checksum with the mask of the current section $checksum |= $mask{$section}; # Finally I store the new value. $self->{last_state_content}->{$section}[0] = $hash; } } $common->setHardware({CHECKSUM => $checksum}); } =item saveLastState() At the end of the process IF the inventory was saved correctly, the last_state is saved. =cut sub saveLastState { my ($self, $args) = @_; my $logger = $self->{logger}; if (!defined($self->{last_state_content})) { $self->processChecksum(); } if (!defined ($self->{config}->{last_statefile})) { $logger->debug ("Can't save the last_state file. File path is not initialised."); return; } if (open LAST_STATE, ">".$self->{config}->{last_statefile}) { print LAST_STATE my $string = XML::Simple::XMLout( $self->{last_state_content}, RootName => 'LAST_STATE' );; close LAST_STATE or warn; } else { $logger->debug ("Cannot save the checksum values in ".$self->{config}->{last_statefile}.":$!"); } } =item addSection() A generic way to save a section in the inventory. Please avoid this solution. =cut sub addSection { my ($self, $args) = @_; my $logger = $self->{logger}; my $multi = $args->{multi}; my $tagname = $args->{tagname}; for( keys %{$self->{xmlroot}{CONTENT}} ){ if( $tagname eq $_ ){ $logger->debug("Tag name `$tagname` already exists - Don't add it"); return 0; } } if($multi){ $self->{xmlroot}{CONTENT}{$tagname} = []; } else{ $self->{xmlroot}{CONTENT}{$tagname} = {}; } return 1; } =item feedSection() Add information in inventory. =back =cut # Q: is that really useful()? Can't we merge with addSection()? sub feedSection{ my ($self, $args) = @_; my $tagname = $args->{tagname}; my $values = $args->{data}; my $logger = $self->{logger}; my $found=0; for( keys %{$self->{xmlroot}{CONTENT}} ){ $found = 1 if $tagname eq $_; } if(!$found){ $logger->debug("Tag name `$tagname` doesn't exist - Cannot feed it"); return 0; } if( $self->{xmlroot}{CONTENT}{$tagname} =~ /ARRAY/ ){ push @{$self->{xmlroot}{CONTENT}{$tagname}}, $args->{data}; } else{ $self->{xmlroot}{CONTENT}{$tagname} = $values; } return 1; } 1;