File Coverage

blib/lib/FusionInventory/Agent/Inventory.pm
Criterion Covered Total %
statement 95 163 58.2
branch 31 72 43.0
condition 3 3 100.0
subroutine 16 24 66.6
pod 16 16 100.0
total 161 278 57.9


line stmt bran cond sub pod time code
1             package FusionInventory::Agent::Inventory;
2              
3 69     69   5367286 use strict;
  69         165  
  69         1807  
4 69     69   363 use warnings;
  69         152  
  69         1736  
5              
6 69     69   339 use Config;
  69         249  
  69         2751  
7 69     69   69116 use Data::Dumper;
  69         521113  
  69         4418  
8 69     69   537 use Digest::MD5 qw(md5_base64);
  69         126  
  69         3380  
9 69     69   42036 use English qw(-no_match_vars);
  69         236159  
  69         442  
10 69     69   95522 use XML::TreePP;
  69         587377  
  69         2435  
11              
12 69     69   43076 use FusionInventory::Agent::Tools;
  69         274  
  69         188939  
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 DNS
22             LASTLOGGEDUSER USERDOMAIN DATELASTLOGGEDUSER
23             DEFAULTGATEWAY VMSYSTEM WINOWNER WINPRODID
24             WINPRODKEY WINCOMPANY WINLANG CHASSIS_TYPE
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             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|infiniband|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 61     61 1 7222 my ($class, %params) = @_;
117              
118             my $self = {
119             logger => $params{logger},
120             fields => \%fields,
121             content => {
122             HARDWARE => {
123             ARCHNAME => $Config{archname},
124 61         1403 VMSYSTEM => "Physical" # Default value
125             },
126             VERSIONCLIENT => $FusionInventory::Agent::AGENT_STRING
127             }
128             };
129 61         229 bless $self, $class;
130              
131 61         583 $self->setTag($params{tag});
132             $self->{last_state_file} = $params{statedir} . '/last_state'
133 61 50       286 if $params{statedir};
134              
135 61         265 return $self;
136             }
137              
138             sub getContent {
139 2     2 1 32 my ($self) = @_;
140              
141 2         16 return $self->{content};
142             }
143              
144             sub getSection {
145 0     0 1 0 my ($self, $section) = @_;
146             ## no critic (ExplicitReturnUndef)
147 0 0       0 my $content = $self->getContent() or return undef;
148 0 0       0 return exists($content->{$section}) ? $content->{$section} : undef ;
149             }
150              
151             sub getField {
152 0     0 1 0 my ($self, $section, $field) = @_;
153             ## no critic (ExplicitReturnUndef)
154 0 0       0 $section = $self->getSection($section) or return undef;
155 0 0       0 return exists($section->{$field}) ? $section->{$field} : undef ;
156             }
157              
158             sub mergeContent {
159 1     1 1 2342 my ($self, $content) = @_;
160              
161 1 50       5 die "no content" unless $content;
162              
163 1         5 foreach my $section (keys %$content) {
164 3 100       12 if (ref $content->{$section} eq 'ARRAY') {
165             # a list of entry
166 2         2 foreach my $entry (@{$content->{$section}}) {
  2         4  
167 3         8 $self->addEntry(section => $section, entry => $entry);
168             }
169             } else {
170             # single entry
171             SWITCH: {
172 1 50       3 if ($section eq 'HARDWARE') {
  1         4  
173 1         5 $self->setHardware($content->{$section});
174 1         5 last SWITCH;
175             }
176 0 0       0 if ($section eq 'BIOS') {
177 0         0 $self->setBios($content->{$section});
178 0         0 last SWITCH;
179             }
180 0 0       0 if ($section eq 'ACCESSLOG') {
181 0         0 $self->setAccessLog($content->{$section});
182 0         0 last SWITCH;
183             }
184             $self->addEntry(
185 0         0 section => $section, entry => $content->{$section}
186             );
187             }
188             }
189             }
190             }
191              
192             sub addEntry {
193 5298     5298 1 5979172 my ($self, %params) = @_;
194              
195 5298         7700 my $entry = $params{entry};
196 5298 100       11120 die "no entry" unless $entry;
197              
198 5297         7313 my $section = $params{section};
199 5297         7611 my $fields = $fields{$section};
200 5297         7943 my $checks = $checks{$section};
201 5297 100       10066 die "unknown section $section" unless $fields;
202              
203 5296         13505 foreach my $field (keys %$entry) {
204 28010 100       56527 if (!$fields->{$field}) {
205             # unvalid field, log error and remove
206 1         8 $self->{logger}->debug("unknown field $field for section $section");
207 1         10 delete $entry->{$field};
208 1         3 next;
209             }
210 28009 100       56283 if (!defined $entry->{$field}) {
211             # undefined value, remove
212 3927         5191 delete $entry->{$field};
213 3927         5563 next;
214             }
215             # sanitize value
216 24082         61648 my $value = getSanitizedString($entry->{$field});
217             # check value if appliable
218 24082 100       53207 if ($checks->{$field}) {
219             $self->{logger}->debug(
220             "invalid value $value for field $field for section $section"
221 312 100       1754 ) unless $value =~ $checks->{$field};
222             }
223 24082         43628 $entry->{$field} = $value;
224             }
225              
226             # avoid duplicate entries
227 5296 50       13065 if ($params{noDuplicated}) {
228 0         0 my $md5 = md5_base64(Dumper($entry));
229 0 0       0 return if $self->{seen}->{$section}->{$md5};
230 0         0 $self->{seen}->{$section}->{$md5} = 1;
231             }
232              
233 5296 100       11123 if ($section eq 'STORAGES') {
234             $entry->{SERIALNUMBER} = $entry->{SERIAL} if !$entry->{SERIALNUMBER}
235 368 100       1012 }
236              
237 5296         6115 push @{$self->{content}{$section}}, $entry;
  5296         21887  
238             }
239              
240             sub computeLegacyValues {
241 1     1 1 2022 my ($self) = @_;
242              
243             # CPU-related values
244 1         4 my $cpus = $self->{content}->{CPUS};
245 1 50       4 if ($cpus) {
246 1         3 my $cpu = $cpus->[0];
247              
248             $self->setHardware({
249             PROCESSORN => scalar @$cpus,
250             PROCESSORS => $cpu->{SPEED},
251             PROCESSORT => $cpu->{NAME},
252 1         9 });
253             }
254              
255             # network related values
256 1         4 my $interfaces = $self->{content}->{NETWORKS};
257 1 50       4 if ($interfaces) {
258             my @ip_addresses =
259 0         0 grep { ! /^127/ }
260 0         0 grep { $_ }
261 0         0 map { $_->{IPADDRESS} }
  0         0  
262             @$interfaces;
263              
264 0         0 $self->setHardware({
265             IPADDR => join('/', @ip_addresses),
266             });
267             }
268              
269             # user-related values
270 1         3 my $users = $self->{content}->{USERS};
271 1 50       5 if ($users) {
272 0         0 my $user = $users->[-1];
273              
274 0         0 my ($domain, $id);
275 0 0       0 if ($user->{LOGIN} =~ /(\S+)\\(\S+)/) {
276             # Windows fully qualified username: domain\user
277 0         0 $domain = $1;
278 0         0 $id = $2;
279             } else {
280             # simple username: user
281 0         0 $id = $user->{LOGIN};
282             }
283              
284 0         0 $self->setHardware({
285             USERID => $id,
286             USERDOMAIN => $domain,
287             });
288             }
289             }
290              
291             sub getHardware {
292 0     0 1 0 my ($self, $field) = @_;
293 0         0 return $self->getField('HARDWARE', $field);
294             }
295              
296             sub setHardware {
297 5     5 1 11 my ($self, $args) = @_;
298              
299 5         15 foreach my $field (keys %$args) {
300 7 50       23 if (!$fields{HARDWARE}->{$field}) {
301 0         0 $self->{logger}->debug("unknown field $field for section HARDWARE");
302             next
303 0         0 }
304              
305             # Do not overwrite existing value with undef
306 7 50       20 next unless $args->{$field};
307              
308             $self->{content}->{HARDWARE}->{$field} =
309 7         24 getSanitizedString($args->{$field});
310             }
311             }
312              
313             sub setOperatingSystem {
314 0     0 1 0 my ($self, $args) = @_;
315              
316 0         0 foreach my $field (keys %$args) {
317 0 0       0 if (!$fields{OPERATINGSYSTEM}->{$field}) {
318             $self->{logger}->debug(
319 0         0 "unknown field $field for section OPERATINGSYSTEM"
320             );
321             next
322 0         0 }
323             $self->{content}->{OPERATINGSYSTEM}->{$field} =
324 0         0 getSanitizedString($args->{$field});
325             }
326             }
327              
328             sub getBios {
329 0     0 1 0 my ($self, $field) = @_;
330 0         0 return $self->getField('BIOS', $field);
331             }
332              
333             sub setBios {
334 0     0 1 0 my ($self, $args) = @_;
335              
336 0         0 foreach my $field (keys %$args) {
337 0 0       0 if (!$fields{BIOS}->{$field}) {
338 0         0 $self->{logger}->debug("unknown field $field for section BIOS");
339             next
340 0         0 }
341              
342             $self->{content}->{BIOS}->{$field} =
343 0         0 getSanitizedString($args->{$field});
344             }
345             }
346              
347             sub setAccessLog {
348 0     0 1 0 my ($self, $args) = @_;
349              
350 0         0 foreach my $field (keys %$args) {
351 0 0       0 if (!$fields{ACCESSLOG}->{$field}) {
352             $self->{logger}->debug(
353 0         0 "unknown field $field for section ACCESSLOG"
354             );
355             next
356 0         0 }
357              
358             $self->{content}->{ACCESSLOG}->{$field} =
359 0         0 getSanitizedString($args->{$field});
360             }
361             }
362              
363             sub setTag {
364 61     61 1 255 my ($self, $tag) = @_;
365              
366 61 50       268 return unless $tag;
367              
368             $self->{content}{ACCOUNTINFO} = [{
369 0         0 KEYNAME => "TAG",
370             KEYVALUE => $tag
371             }];
372              
373             }
374              
375             sub computeChecksum {
376 3     3 1 17304 my ($self) = @_;
377              
378 3         9 my $logger = $self->{logger};
379              
380             # to apply to $checksum with an OR
381 3         31 my %mask = (
382             HARDWARE => 1,
383             BIOS => 2,
384             MEMORIES => 4,
385             SLOTS => 8,
386             REGISTRY => 16,
387             CONTROLLERS => 32,
388             MONITORS => 64,
389             PORTS => 128,
390             STORAGES => 256,
391             DRIVES => 512,
392             INPUT => 1024,
393             MODEMS => 2048,
394             NETWORKS => 4096,
395             PRINTERS => 8192,
396             SOUNDS => 16384,
397             VIDEOS => 32768,
398             SOFTWARES => 65536,
399             );
400             # TODO CPUS is not in the list
401              
402 3 50       12 if ($self->{last_state_file}) {
403 0 0       0 if (-f $self->{last_state_file}) {
404 0         0 eval {
405             $self->{last_state_content} = XML::TreePP->new()->parsefile(
406             $self->{last_state_file}
407 0         0 );
408             };
409 0 0       0 if (ref($self->{last_state_content}) ne 'HASH') {
410 0         0 $self->{last_state_file} = {};
411             }
412             } else {
413 0         0 $logger->debug(
414             "last state file '$self->{last_state_file}' doesn't exist"
415             );
416             }
417             }
418              
419 3         6 my $checksum = 0;
420 3         13 foreach my $section (keys %mask) {
421             my $hash =
422 51         184 md5_base64(Dumper($self->{content}->{$section}));
423              
424             # check if the section did change since the last run
425             next if
426             $self->{last_state_content}->{$section} &&
427 51 100 100     2488 $self->{last_state_content}->{$section} eq $hash;
428              
429 20         79 $logger->debug("Section $section has changed since last inventory");
430              
431             # add the mask of the current section to the checksum
432 20         228 $checksum |= $mask{$section}; ## no critic (ProhibitBitwise)
433              
434             # store the new value.
435 20         48 $self->{last_state_content}->{$section} = $hash;
436             }
437              
438              
439 3         17 $self->setHardware({CHECKSUM => $checksum});
440             }
441              
442             sub saveLastState {
443 0     0 1   my ($self) = @_;
444              
445 0           my $logger = $self->{logger};
446              
447 0 0         if (!defined($self->{last_state_content})) {
448 0           $self->processChecksum();
449             }
450 0 0         if ($self->{last_state_file}) {
451 0           eval {
452             XML::TreePP->new()->writefile(
453             $self->{last_state_file}, $self->{last_state_content}
454 0           );
455             }
456             } else {
457 0           $logger->debug(
458             "last state file is not defined, last state not saved"
459             );
460             }
461              
462 0           my $tpp = XML::TreePP->new();
463             }
464              
465             1;
466             __END__