File Coverage

blib/lib/FusionInventory/Agent/Inventory.pm
Criterion Covered Total %
statement 24 153 15.6
branch 0 64 0.0
condition 0 3 0.0
subroutine 8 20 40.0
pod 12 12 100.0
total 44 252 17.4


line stmt bran cond sub pod time code
1             package FusionInventory::Agent::Inventory;
2              
3 3     3   2127788 use strict;
  3         9  
  3         167  
4 3     3   25 use warnings;
  3         5  
  3         117  
5              
6 3     3   14 use Config;
  3         50  
  3         159  
7 3     3   2278 use Data::Dumper;
  3         20695  
  3         231  
8 3     3   22 use Digest::MD5 qw(md5_base64);
  3         4  
  3         248  
9 3     3   15 use English qw(-no_match_vars);
  3         3  
  3         33  
10 3     3   2290 use XML::TreePP;
  3         13178  
  3         43  
11              
12 3     3   589 use FusionInventory::Agent::Tools;
  3         8  
  3         5782  
13              
14             my %fields = (
15             BIOS => [ qw/SMODEL SMANUFACTURER SSN BDATE BVERSION
16             BMANUFACTURER MMANUFACTURER MSN MMODEL ASSETTAG
17             ENCLOSURESERIAL BIOSSERIAL
18             TYPE SKUNUMBER/ ],
19             HARDWARE => [ qw/USERID OSVERSION PROCESSORN OSCOMMENTS CHECKSUM
20             PROCESSORT NAME PROCESSORS SWAP ETIME TYPE OSNAME
21             IPADDR WORKGROUP DESCRIPTION MEMORY UUID VMID DNS
22             LASTLOGGEDUSER USERDOMAIN DATELASTLOGGEDUSER
23             DEFAULTGATEWAY VMSYSTEM WINOWNER WINPRODID
24             WINPRODKEY WINCOMPANY WINLANG CHASSIS_TYPE VMID
25             VMNAME VMHOSTSERIAL/ ],
26             OPERATINGSYSTEM => [ qw/KERNEL_NAME KERNEL_VERSION NAME VERSION FULL_NAME
27             SERVICE_PACK INSTALL_DATE FQDN DNS_DOMAIN
28             SSH_KEY ARCH BOOT_TIME/ ],
29             ACCESSLOG => [ qw/USERID LOGDATE/ ],
30              
31             ANTIVIRUS => [ qw/COMPANY ENABLED GUID NAME UPTODATE VERSION/ ],
32             BATTERIES => [ qw/CAPACITY CHEMISTRY DATE NAME SERIAL MANUFACTURER
33             VOLTAGE/ ],
34             CONTROLLERS => [ qw/CAPTION DRIVER NAME MANUFACTURER PCICLASS VENDORID
35             PRODUCTID PCISUBSYSTEMID PCISLOT TYPE REV/ ],
36             CPUS => [ qw/CACHE CORE DESCRIPTION MANUFACTURER NAME THREAD
37             SERIAL STEPPING FAMILYNAME FAMILYNUMBER MODEL
38             SPEED ID EXTERNAL_CLOCK ARCH/ ],
39             DRIVES => [ qw/CREATEDATE DESCRIPTION FREE FILESYSTEM LABEL
40             LETTER SERIAL SYSTEMDRIVE TOTAL TYPE VOLUMN/ ],
41             ENVS => [ qw/KEY VAL/ ],
42             INPUTS => [ qw/NAME MANUFACTURER CAPTION DESCRIPTION INTERFACE
43             LAYOUT POINTINGTYPE TYPE/ ],
44             LICENSEINFOS => [ qw/NAME FULLNAME KEY COMPONENTS TRIAL UPDATE OEM
45             ACTIVATION_DATE PRODUCTID/ ],
46             LOCAL_GROUPS => [ qw/ID MEMBER NAME/ ],
47             LOCAL_USERS => [ qw/HOME ID LOGIN NAME SHELL/ ],
48             LOGICAL_VOLUMES => [ qw/LV_NAME VG_NAME ATTR SIZE LV_UUID SEG_COUNT
49             VG_UUID/ ],
50             MEMORIES => [ qw/CAPACITY CAPTION FORMFACTOR REMOVABLE PURPOSE
51             SPEED SERIALNUMBER TYPE DESCRIPTION NUMSLOTS
52             MEMORYCORRECTION MANUFACTURER/ ],
53             MODEMS => [ qw/DESCRIPTION NAME TYPE MODEL/ ],
54             MONITORS => [ qw/BASE64 CAPTION DESCRIPTION MANUFACTURER SERIAL
55             UUENCODE NAME TYPE/ ],
56             NETWORKS => [ qw/DESCRIPTION MANUFACTURER MODEL MANAGEMENT TYPE
57             VIRTUALDEV MACADDR WWN DRIVER FIRMWARE PCIID
58             PCISLOT PNPDEVICEID MTU SPEED STATUS SLAVES BASE
59             IPADDRESS IPSUBNET IPMASK IPDHCP IPGATEWAY
60             IPADDRESS6 IPSUBNET6 IPMASK6 WIFI_BSSID WIFI_SSID
61             WIFI_MODE WIFI_VERSION/ ],
62             PHYSICAL_VOLUMES => [ qw/DEVICE PV_PE_COUNT PV_UUID FORMAT ATTR
63             SIZE FREE PE_SIZE VG_UUID/ ],
64             PORTS => [ qw/CAPTION DESCRIPTION NAME TYPE/ ],
65             PRINTERS => [ qw/COMMENT DESCRIPTION DRIVER NAME NETWORK PORT
66             RESOLUTION SHARED STATUS ERRSTATUS SERVERNAME
67             SHARENAME PRINTPROCESSOR SERIAL/ ],
68             PROCESSES => [ qw/USER PID CPUUSAGE MEM VIRTUALMEMORY TTY STARTED
69             CMD/ ],
70             REGISTRY => [ qw/NAME REGVALUE HIVE/ ],
71             RUDDER => [ qw/AGENT UUID HOSTNAME/ ],
72             SLOTS => [ qw/DESCRIPTION DESIGNATION NAME STATUS/ ],
73             SOFTWARES => [ qw/COMMENTS FILESIZE FOLDER FROM HELPLINK INSTALLDATE
74             NAME NO_REMOVE RELEASE_TYPE PUBLISHER
75             UNINSTALL_STRING URL_INFO_ABOUT VERSION
76             VERSION_MINOR VERSION_MAJOR GUID ARCH USERNAME
77             USERID/ ],
78             SOUNDS => [ qw/CAPTION DESCRIPTION MANUFACTURER NAME/ ],
79             STORAGES => [ qw/DESCRIPTION DISKSIZE INTERFACE MANUFACTURER MODEL
80             NAME TYPE SERIAL SERIALNUMBER FIRMWARE SCSI_COID
81             SCSI_CHID SCSI_UNID SCSI_LUN WWN/ ],
82             VIDEOS => [ qw/CHIPSET MEMORY NAME RESOLUTION PCISLOT PCIID/ ],
83             USBDEVICES => [ qw/VENDORID PRODUCTID MANUFACTURER CAPTION SERIAL
84             CLASS SUBCLASS NAME/ ],
85             USERS => [ qw/LOGIN DOMAIN/ ],
86             VIRTUALMACHINES => [ qw/MEMORY NAME UUID STATUS SUBSYSTEM VMTYPE VCPU
87             VMID MAC COMMENT OWNER SERIAL/ ],
88             VOLUME_GROUPS => [ qw/VG_NAME PV_COUNT LV_COUNT ATTR SIZE FREE VG_UUID
89             VG_EXTENT_SIZE/ ],
90             );
91              
92             my %checks = (
93             STORAGES => {
94             INTERFACE => qr/^(SCSI|HDC|IDE|USB|1394|Serial-ATA|SAS)$/
95             },
96             VIRTUALMACHINES => {
97             STATUS => qr/^(running|blocked|idle|paused|shutdown|crashed|dying|off)$/
98             },
99             SLOTS => {
100             STATUS => qr/^(free|used)$/
101             },
102             NETWORKS => {
103             TYPE => qr/^(ethernet|wifi|aggregate|alias|dialup|loopback|bridge|fibrechannel)$/
104             },
105             CPUS => {
106             ARCH => qr/^(MIPS|MIPS64|Alpha|SPARC|SPARC64|m68k|i386|x86_64|PowerPC|PowerPC64|ARM|AArch64)$/
107             }
108             );
109              
110             # convert fields list into fields hashes, for fast lookup
111             foreach my $section (keys %fields) {
112             $fields{$section} = { map { $_ => 1 } @{$fields{$section}} };
113             }
114              
115             sub new {
116 0     0 1   my ($class, %params) = @_;
117              
118 0           my $self = {
119             logger => $params{logger},
120             fields => \%fields,
121             content => {
122             HARDWARE => {
123             ARCHNAME => $Config{archname},
124             VMSYSTEM => "Physical" # Default value
125             },
126             VERSIONCLIENT => $FusionInventory::Agent::AGENT_STRING
127             }
128             };
129 0           bless $self, $class;
130              
131 0           $self->setTag($params{tag});
132 0 0         $self->{last_state_file} = $params{statedir} . '/last_state'
133             if $params{statedir};
134              
135 0           return $self;
136             }
137              
138             sub getContent {
139 0     0 1   my ($self) = @_;
140              
141 0           return $self->{content};
142             }
143              
144             sub mergeContent {
145 0     0 1   my ($self, $content) = @_;
146              
147 0 0         die "no content" unless $content;
148              
149 0           foreach my $section (keys %$content) {
150 0 0         if (ref $content->{$section} eq 'ARRAY') {
151             # a list of entry
152 0           foreach my $entry (@{$content->{$section}}) {
  0            
153 0           $self->addEntry(section => $section, entry => $entry);
154             }
155             } else {
156             # single entry
157             SWITCH: {
158 0 0         if ($section eq 'HARDWARE') {
  0            
159 0           $self->setHardware($content->{$section});
160 0           last SWITCH;
161             }
162 0 0         if ($section eq 'BIOS') {
163 0           $self->setBios($content->{$section});
164 0           last SWITCH;
165             }
166 0 0         if ($section eq 'ACCESSLOG') {
167 0           $self->setAccessLog($content->{$section});
168 0           last SWITCH;
169             }
170             $self->addEntry(
171 0           section => $section, entry => $content->{$section}
172             );
173             }
174             }
175             }
176             }
177              
178             sub addEntry {
179 0     0 1   my ($self, %params) = @_;
180              
181 0           my $entry = $params{entry};
182 0 0         die "no entry" unless $entry;
183              
184 0           my $section = $params{section};
185 0           my $fields = $fields{$section};
186 0           my $checks = $checks{$section};
187 0 0         die "unknown section $section" unless $fields;
188              
189 0           foreach my $field (keys %$entry) {
190 0 0         if (!$fields->{$field}) {
191             # unvalid field, log error and remove
192 0           $self->{logger}->debug("unknown field $field for section $section");
193 0           delete $entry->{$field};
194 0           next;
195             }
196 0 0         if (!defined $entry->{$field}) {
197             # undefined value, remove
198 0           delete $entry->{$field};
199 0           next;
200             }
201             # sanitize value
202 0           my $value = getSanitizedString($entry->{$field});
203             # check value if appliable
204 0 0         if ($checks->{$field}) {
205 0 0         $self->{logger}->debug(
206             "invalid value $value for field $field for section $section"
207             ) unless $value =~ $checks->{$field};
208             }
209 0           $entry->{$field} = $value;
210             }
211              
212             # avoid duplicate entries
213 0 0         if ($params{noDuplicated}) {
214 0           my $md5 = md5_base64(Dumper($entry));
215 0 0         return if $self->{seen}->{$section}->{$md5};
216 0           $self->{seen}->{$section}->{$md5} = 1;
217             }
218              
219 0 0         if ($section eq 'STORAGES') {
220 0 0         $entry->{SERIALNUMBER} = $entry->{SERIAL} if !$entry->{SERIALNUMBER}
221             }
222              
223 0           push @{$self->{content}{$section}}, $entry;
  0            
224             }
225              
226             sub computeLegacyValues {
227 0     0 1   my ($self) = @_;
228              
229             # CPU-related values
230 0           my $cpus = $self->{content}->{CPUS};
231 0 0         if ($cpus) {
232 0           my $cpu = $cpus->[0];
233              
234 0           $self->setHardware({
235             PROCESSORN => scalar @$cpus,
236             PROCESSORS => $cpu->{SPEED},
237             PROCESSORT => $cpu->{NAME},
238             });
239             }
240              
241             # network related values
242 0           my $interfaces = $self->{content}->{NETWORKS};
243 0 0         if ($interfaces) {
244 0           my @ip_addresses =
245 0           grep { ! /^127/ }
246 0           grep { $_ }
247 0           map { $_->{IPADDRESS} }
248             @$interfaces;
249              
250 0           $self->setHardware({
251             IPADDR => join('/', @ip_addresses),
252             });
253             }
254              
255             # user-related values
256 0           my $users = $self->{content}->{USERS};
257 0 0         if ($users) {
258 0           my $user = $users->[-1];
259              
260 0           my ($domain, $id);
261 0 0         if ($user->{LOGIN} =~ /(\S+)\\(\S+)/) {
262             # Windows fully qualified username: domain\user
263 0           $domain = $1;
264 0           $id = $2;
265             } else {
266             # simple username: user
267 0           $id = $user->{LOGIN};
268             }
269              
270 0           $self->setHardware({
271             USERID => $id,
272             USERDOMAIN => $domain,
273             });
274             }
275             }
276              
277             sub setHardware {
278 0     0 1   my ($self, $args) = @_;
279              
280 0           foreach my $field (keys %$args) {
281 0 0         if (!$fields{HARDWARE}->{$field}) {
282 0           $self->{logger}->debug("unknown field $field for section HARDWARE");
283             next
284 0           }
285              
286             # Do not overwrite existing value with undef
287 0 0         next unless $args->{$field};
288              
289 0           $self->{content}->{HARDWARE}->{$field} =
290             getSanitizedString($args->{$field});
291             }
292             }
293              
294             sub setOperatingSystem {
295 0     0 1   my ($self, $args) = @_;
296              
297 0           foreach my $field (keys %$args) {
298 0 0         if (!$fields{OPERATINGSYSTEM}->{$field}) {
299 0           $self->{logger}->debug(
300             "unknown field $field for section OPERATINGSYSTEM"
301             );
302             next
303 0           }
304 0           $self->{content}->{OPERATINGSYSTEM}->{$field} =
305             getSanitizedString($args->{$field});
306             }
307             }
308              
309             sub setBios {
310 0     0 1   my ($self, $args) = @_;
311              
312 0           foreach my $field (keys %$args) {
313 0 0         if (!$fields{BIOS}->{$field}) {
314 0           $self->{logger}->debug("unknown field $field for section BIOS");
315             next
316 0           }
317              
318 0           $self->{content}->{BIOS}->{$field} =
319             getSanitizedString($args->{$field});
320             }
321             }
322              
323             sub setAccessLog {
324 0     0 1   my ($self, $args) = @_;
325              
326 0           foreach my $field (keys %$args) {
327 0 0         if (!$fields{ACCESSLOG}->{$field}) {
328 0           $self->{logger}->debug(
329             "unknown field $field for section ACCESSLOG"
330             );
331             next
332 0           }
333              
334 0           $self->{content}->{ACCESSLOG}->{$field} =
335             getSanitizedString($args->{$field});
336             }
337             }
338              
339             sub setTag {
340 0     0 1   my ($self, $tag) = @_;
341              
342 0 0         return unless $tag;
343              
344 0           $self->{content}{ACCOUNTINFO} = [{
345             KEYNAME => "TAG",
346             KEYVALUE => $tag
347             }];
348              
349             }
350              
351             sub computeChecksum {
352 0     0 1   my ($self) = @_;
353              
354 0           my $logger = $self->{logger};
355              
356             # to apply to $checksum with an OR
357 0           my %mask = (
358             HARDWARE => 1,
359             BIOS => 2,
360             MEMORIES => 4,
361             SLOTS => 8,
362             REGISTRY => 16,
363             CONTROLLERS => 32,
364             MONITORS => 64,
365             PORTS => 128,
366             STORAGES => 256,
367             DRIVES => 512,
368             INPUT => 1024,
369             MODEMS => 2048,
370             NETWORKS => 4096,
371             PRINTERS => 8192,
372             SOUNDS => 16384,
373             VIDEOS => 32768,
374             SOFTWARES => 65536,
375             );
376             # TODO CPUS is not in the list
377              
378 0 0         if ($self->{last_state_file}) {
379 0 0         if (-f $self->{last_state_file}) {
380 0           eval {
381 0           $self->{last_state_content} = XML::TreePP->new()->parsefile(
382             $self->{last_state_file}
383             );
384             };
385 0 0         if (ref($self->{last_state_content}) ne 'HASH') {
386 0           $self->{last_state_file} = {};
387             }
388             } else {
389 0           $logger->debug(
390             "last state file '$self->{last_state_file}' doesn't exist"
391             );
392             }
393             }
394              
395 0           my $checksum = 0;
396 0           foreach my $section (keys %mask) {
397 0           my $hash =
398             md5_base64(Dumper($self->{content}->{$section}));
399              
400             # check if the section did change since the last run
401             next if
402 0 0 0       $self->{last_state_content}->{$section} &&
403             $self->{last_state_content}->{$section} eq $hash;
404              
405 0           $logger->debug("Section $section has changed since last inventory");
406              
407             # add the mask of the current section to the checksum
408 0           $checksum |= $mask{$section}; ## no critic (ProhibitBitwise)
409              
410             # store the new value.
411 0           $self->{last_state_content}->{$section} = $hash;
412             }
413              
414              
415 0           $self->setHardware({CHECKSUM => $checksum});
416             }
417              
418             sub saveLastState {
419 0     0 1   my ($self) = @_;
420              
421 0           my $logger = $self->{logger};
422              
423 0 0         if (!defined($self->{last_state_content})) {
424 0           $self->processChecksum();
425             }
426 0 0         if ($self->{last_state_file}) {
427 0           eval {
428 0           XML::TreePP->new()->writefile(
429             $self->{last_state_file}, $self->{last_state_content}
430             );
431             }
432             } else {
433 0           $logger->debug(
434             "last state file is not defined, last state not saved"
435             );
436             }
437              
438 0           my $tpp = XML::TreePP->new();
439             }
440              
441             1;
442             __END__