File Coverage

blib/lib/FusionInventory/Agent/Inventory.pm
Criterion Covered Total %
statement 104 166 62.6
branch 35 74 47.3
condition 3 3 100.0
subroutine 17 24 70.8
pod 16 16 100.0
total 175 283 61.8


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