File Coverage

blib/lib/FusionInventory/Agent/Tools/Linux.pm
Criterion Covered Total %
statement 178 231 77.0
branch 102 142 71.8
condition 8 11 72.7
subroutine 20 23 86.9
pod 7 7 100.0
total 315 414 76.0


line stmt bran cond sub pod time code
1             package FusionInventory::Agent::Tools::Linux;
2              
3 22     22   9294056 use strict;
  22         34  
  22         544  
4 22     22   74 use warnings;
  22         34  
  22         534  
5 22     22   77 use base 'Exporter';
  22         56  
  22         1660  
6              
7             # Constant for ethtool system call
8 22     22   91 use constant SIOCETHTOOL => 0x8946 ; # See linux/sockios.h
  22         31  
  22         1252  
9 22     22   74 use constant ETHTOOL_GSET => 0x00000001 ; # See linux/ethtool.h
  22         28  
  22         923  
10 22     22   78 use constant SPEED_UNKNOWN => 65535 ; # See linux/ethtool.h, to be read as -1
  22         20  
  22         918  
11              
12 22     22   730 use English qw(-no_match_vars);
  22         4073  
  22         233  
13 22     22   9099 use Memoize;
  22         6406  
  22         852  
14 22     22   3131 use Socket qw(PF_INET SOCK_DGRAM);
  22         16560  
  22         1856  
15              
16 22     22   1455 use FusionInventory::Agent::Tools;
  22         30  
  22         2627  
17 22     22   7787 use FusionInventory::Agent::Tools::Unix;
  22         47  
  22         1499  
18 22     22   111 use FusionInventory::Agent::Tools::Network;
  22         26  
  22         61634  
19              
20             our @EXPORT = qw(
21             getDevicesFromUdev
22             getDevicesFromHal
23             getDevicesFromProc
24             getCPUsFromProc
25             getInfoFromSmartctl
26             getInterfacesFromIfconfig
27             getInterfacesFromIp
28             getInterfacesInfosFromIoctl
29             );
30              
31             memoize('getDevicesFromUdev');
32              
33             sub getDevicesFromUdev {
34             my (%params) = @_;
35              
36             my @devices;
37              
38             foreach my $file (glob ("/dev/.udev/db/*")) {
39             my $device = getFirstMatch(
40             file => $file,
41             pattern => qr/^N:(\S+)/
42             );
43             next unless $device;
44             next unless $device =~ /([hsv]d[a-z]|sr\d+)$/;
45             push (@devices, _parseUdevEntry(
46             logger => $params{logger}, file => $file, device => $device
47             ));
48             }
49              
50             foreach my $device (@devices) {
51             next if $device->{TYPE} && $device->{TYPE} eq 'cd';
52             $device->{DISKSIZE} = getDeviceCapacity(device => '/dev/' . $device->{NAME})
53             }
54              
55             return @devices;
56             }
57              
58             sub _parseUdevEntry {
59 1     1   681 my (%params) = @_;
60              
61 1         6 my $handle = getFileHandle(%params);
62 1 50       3 return unless $handle;
63              
64 1         2 my ($result, $serial);
65 1         8 while (my $line = <$handle>) {
66 49 100       237 if ($line =~ /^S:.*-scsi-(\d+):(\d+):(\d+):(\d+)/) {
    50          
    100          
    100          
    100          
    100          
    100          
    100          
67 1         4 $result->{SCSI_COID} = $1;
68 1         2 $result->{SCSI_CHID} = $2;
69 1         3 $result->{SCSI_UNID} = $3;
70 1         19 $result->{SCSI_LUN} = $4;
71             } elsif ($line =~ /^E:ID_VENDOR=(.*)/) {
72 0         0 $result->{MANUFACTURER} = $1;
73             } elsif ($line =~ /^E:ID_MODEL=(.*)/) {
74 1         5 $result->{MODEL} = $1;
75             } elsif ($line =~ /^E:ID_REVISION=(.*)/) {
76 1         4 $result->{FIRMWARE} = $1;
77             } elsif ($line =~ /^E:ID_SERIAL=(.*)/) {
78 1         3 $serial = $1;
79             } elsif ($line =~ /^E:ID_SERIAL_SHORT=(.*)/) {
80 1         4 $result->{SERIALNUMBER} = $1;
81             } elsif ($line =~ /^E:ID_TYPE=(.*)/) {
82 1         4 $result->{TYPE} = $1;
83             } elsif ($line =~ /^E:ID_BUS=(.*)/) {
84 1         5 $result->{DESCRIPTION} = $1;
85             }
86             }
87 1         6 close $handle;
88              
89 1 50       3 if (!$result->{SERIALNUMBER}) {
90 0         0 $result->{SERIALNUMBER} = $serial;
91             }
92              
93 1         2 $result->{NAME} = $params{device};
94              
95 1         5 return $result;
96             }
97              
98             sub getCPUsFromProc {
99 31     31 1 52327 my (%params) = (
100             file => '/proc/cpuinfo',
101             @_
102             );
103              
104 31         134 my $handle = getFileHandle(%params);
105              
106 31         46 my (@cpus, $cpu);
107              
108 31         513 while (my $line = <$handle>) {
109 1375 100       3969 if ($line =~ /^([^:]+\S) \s* : \s (.+)/x) {
    100          
110 1279         1991 $cpu->{lc($1)} = trimWhitespace($2);
111             } elsif ($line =~ /^$/) {
112             # an empty line marks the end of a cpu section
113             # push to the list, but only if it is a valid cpu
114 66 100 100     173 push @cpus, $cpu if $cpu && _isValidCPU($cpu);
115 66         248 undef $cpu;
116             }
117             }
118 31         231 close $handle;
119              
120             # push remaining cpu to the list, if it is valid cpu
121 31 100 100     89 push @cpus, $cpu if $cpu && _isValidCPU($cpu);
122              
123 31         216 return @cpus;
124             }
125              
126             sub _isValidCPU {
127 74     74   83 my ($cpu) = @_;
128              
129 74   66     392 return exists $cpu->{processor} || exists $cpu->{cpu};
130             }
131              
132              
133             sub getDevicesFromHal {
134 1     1 1 4039 my (%params) = (
135             command => '/usr/bin/lshal',
136             @_
137             );
138 1         21 my $handle = getFileHandle(%params);
139              
140 1         4 my (@devices, $device);
141              
142 1         35 while (my $line = <$handle>) {
143 2904         1620 chomp $line;
144 2904 100       3211 if ($line =~ m{^udi = '/org/freedesktop/Hal/devices/(storage|legacy_floppy|block)}) {
145 1         5 $device = {};
146 1         3 next;
147             }
148              
149 2903 100       5328 next unless defined $device;
150              
151 32 100       214 if ($line =~ /^$/) {
    100          
    100          
    100          
    100          
    100          
    100          
    100          
152 1         2 push(@devices, $device);
153 1         3 undef $device;
154             } elsif ($line =~ /^\s+ storage.serial \s = \s '([^']+)'/x) {
155 1         5 $device->{SERIALNUMBER} = $1;
156             } elsif ($line =~ /^\s+ storage.firmware_version \s = \s '([^']+)'/x) {
157 1         4 $device->{FIRMWARE} = $1;
158             } elsif ($line =~ /^\s+ block.device \s = \s '([^']+)'/x) {
159 1         5 my $value = $1;
160 1         11 ($device->{NAME}) = $value =~ m{/dev/(\S+)};
161             } elsif ($line =~ /^\s+ info.vendor \s = \s '([^']+)'/x) {
162 1         6 $device->{MANUFACTURER} = $1;
163             } elsif ($line =~ /^\s+ storage.model \s = \s '([^']+)'/x) {
164 1         4 $device->{MODEL} = $1;
165             } elsif ($line =~ /^\s+ storage.drive_type \s = \s '([^']+)'/x) {
166 1         4 $device->{TYPE} = $1;
167             } elsif ($line =~ /^\s+ storage.size \s = \s (\S+)/x) {
168 1         3 my $value = $1;
169 1         9 $device->{DISKSIZE} = int($value/(1024*1024) + 0.5);
170             }
171             }
172 1         19 close $handle;
173              
174 1         16 return @devices;
175             }
176              
177             sub getDevicesFromProc {
178 0     0 1 0 my (%params) = @_;
179              
180 0         0 my $logger = $params{logger};
181              
182             # compute list of devices
183 0         0 my @names;
184              
185 0         0 foreach my $file (glob ("/sys/block/*")) {
186 0 0       0 next unless $file =~ /([shv]d[a-z]|fd\d)$/;
187 0         0 push @names, $1;
188             }
189              
190 0 0       0 my $command = getFirstLine(command => '/sbin/fdisk -v') =~ '^GNU' ?
191             "/sbin/fdisk -p -l" :
192             "/sbin/fdisk -l" ;
193              
194 0         0 my $handle = getFileHandle(
195             command => $command,
196             logger => $logger
197             );
198              
199 0 0       0 return unless $handle;
200              
201 0         0 while (my $line = <$handle>) {
202 0 0       0 next unless $line =~ m{^/dev/([shv]d[a-z])};
203 0         0 push @names, $1;
204             }
205 0         0 close $handle;
206              
207             # filter duplicates
208 0         0 my %seen;
209 0         0 @names = grep { !$seen{$_}++ } @names;
  0         0  
210              
211             # extract information
212 0         0 my @devices;
213 0         0 foreach my $name (@names) {
214 0 0       0 my $device = {
215             NAME => $name,
216             MANUFACTURER => _getValueFromSysProc($logger, $name, 'vendor'),
217             MODEL => _getValueFromSysProc($logger, $name, 'model'),
218             FIRMWARE => _getValueFromSysProc($logger, $name, 'rev'),
219             SERIALNUMBER => _getValueFromSysProc($logger, $name, 'serial'),
220             TYPE =>
221             _getValueFromSysProc($logger, $name, 'removable') ?
222             'removable' : 'disk'
223             };
224 0         0 push @devices, $device;
225             }
226              
227 0         0 return @devices;
228             }
229              
230             sub _getValueFromSysProc {
231 0     0   0 my ($logger, $device, $key) = @_;
232              
233             ## no critic (ExplicitReturnUndef)
234              
235 0 0       0 my $file =
    0          
236             -f "/sys/block/$device/device/$key" ? "/sys/block/$device/device/$key" :
237             -f "/proc/ide/$device/$key" ? "/proc/ide/$device/$key" :
238             undef;
239              
240 0 0       0 return undef unless $file;
241              
242 0         0 my $handle = getFileHandle(file => $file, logger => $logger);
243 0 0       0 return undef unless $handle;
244              
245 0         0 my $value = <$handle>;
246 0         0 close $handle;
247              
248 0         0 chomp $value;
249 0         0 $value =~ s/^(\w+)\W*/$1/;
250              
251 0         0 return $value;
252             }
253              
254             sub getInfoFromSmartctl {
255 4     4 1 4537 my (%params) = @_;
256              
257             my $handle = getFileHandle(
258             %params,
259 4 50       25 command => $params{device} ? "smartctl -i $params{device}" : undef,
260             );
261 4 50       13 return unless $handle;
262              
263 4         10 my $info = {
264             TYPE => 'disk',
265             DESCRIPTION => 'SATA',
266             };
267              
268 4         64 while (my $line = <$handle>) {
269 39 50       55 if ($line =~ /^Vendor: +(\S+)/i) {
270 0         0 $info->{MANUFACTURER} = getCanonicalManufacturer($1);
271 0         0 next;
272             }
273              
274 39 50       50 if ($line =~ /^Product: +(\S+)/i) {
275 0         0 $info->{MODEL} = $1;
276 0         0 next;
277             }
278              
279 39 50       49 if ($line =~ /^Revision: +(\S+)/i) {
280 0         0 $info->{FIRMWARE} = $1;
281 0         0 next;
282             }
283              
284 39 100       46 if ($line =~ /^User Capacity: +(\S.+\S)/i) {
285 1         6 $info->{DISKSIZE} = getCanonicalSize($1, 1024);
286 1         3 next;
287             }
288              
289 38 100       46 if ($line =~ /^Transport protocol: +(\S+)/i) {
290 1         2 $info->{DESCRIPTION} = $1;
291 1         3 next;
292             }
293              
294 37 100       77 if ($line =~ /^Device type: +(\S+)/i) {
295 2         7 $info->{TYPE} = $1;
296 2         4 next;
297             }
298              
299 35 100       88 if ($line =~ /^Serial number: +(\S+)/i) {
300 2         8 $info->{SERIALNUMBER} = $1;
301 2         5 next;
302             }
303             }
304 4         23 close $handle;
305              
306 4         15 return $info;
307             }
308              
309             sub getInterfacesFromIfconfig {
310 4     4 1 10954 my (%params) = (
311             command => '/sbin/ifconfig -a',
312             @_
313             );
314 4         22 my $handle = getFileHandle(%params);
315 4 50       16 return unless $handle;
316              
317 4         6 my @interfaces;
318             my $interface;
319              
320 4         15 my %types = (
321             Ethernet => 'ethernet',
322             );
323              
324 4         93 while (my $line = <$handle>) {
325 166 100       335 if ($line =~ /^$/) {
326             # end of interface section
327 18 50       39 push @interfaces, $interface if $interface;
328 18         54 next;
329             }
330              
331 148 100       282 if ($line =~ /^([\w\d.]+)/) {
332             # new interface
333              
334 20         71 $interface = {
335             STATUS => 'Down',
336             DESCRIPTION => $1
337             }
338              
339             }
340 148 100       691 if ($line =~ /
341             inet \s ($ip_address_pattern) \s+
342             netmask \s ($ip_address_pattern) \s+
343             broadcast \s $ip_address_pattern
344             /x) {
345 1         5 $interface->{IPADDRESS} = $1;
346 1         4 $interface->{IPMASK} = $2;
347             }
348              
349 148 100       477 if ($line =~ /
350             ether \s ($mac_address_pattern)
351             .+
352             \( Ethernet \)
353             /x) {
354 1         5 $interface->{MACADDR} = $1;
355 1         3 $interface->{TYPE} = 'ethernet';
356             }
357              
358 148 100       247 if ($line =~ /inet6 \s (\S+)/x) {
359 16         35 $interface->{IPADDRESS6} = $1;
360             }
361              
362 148 100       414 if ($line =~ /inet addr:($ip_address_pattern)/i) {
363 8         18 $interface->{IPADDRESS} = $1;
364             }
365              
366 148 100       371 if ($line =~ /Mask:($ip_address_pattern)/) {
367 8         16 $interface->{IPMASK} = $1;
368             }
369              
370 148 100       295 if ($line =~ /inet6 addr: (\S+)/i) {
371 15         32 $interface->{IPADDRESS6} = $1;
372             }
373              
374 148 100       517 if ($line =~ /hwadd?r\s+($mac_address_pattern)/i) {
375 14         31 $interface->{MACADDR} = $1;
376             }
377              
378 148 100       272 if ($line =~ /^\s+UP\s/) {
379 16         21 $interface->{STATUS} = 'Up';
380             }
381              
382 148 100       216 if ($line =~ /flags=.*[<,]UP[>,]/) {
383 1         5 $interface->{STATUS} = 'Up';
384             }
385              
386 148 100       539 if ($line =~ /Link encap:(\S+)/) {
387 19         87 $interface->{TYPE} = $types{$1};
388             }
389              
390             }
391 4         40 close $handle;
392              
393 4         38 return @interfaces;
394             }
395              
396             sub getInterfacesInfosFromIoctl {
397 0     0 1 0 my (%params) = (
398             interface => 'eth0',
399             @_
400             );
401              
402 0 0       0 return unless $params{interface};
403              
404 0         0 my $logger = $params{logger};
405              
406 0 0       0 socket(my $socket, PF_INET, SOCK_DGRAM, 0)
407             or return ;
408              
409             # Pack command in ethtool_cmd struct
410 0         0 my $cmd = pack("L3SC6L2SC2L3", ETHTOOL_GSET);
411              
412             # Pack request for ioctl
413 0         0 my $request = pack("a16p", $params{interface}, $cmd);
414              
415 0   0     0 my $retval = ioctl($socket, SIOCETHTOOL, $request) || -1;
416 0 0       0 return if ($retval < 0);
417              
418             # Unpack returned datas
419 0         0 my @datas = unpack("L3SC6L2SC2L3", $cmd);
420              
421             # Actually only speed value is requested and extracted
422 0         0 my $datas = {
423             SPEED => $datas[3]|$datas[12]<<16
424             };
425              
426             # Forget speed value if got unknown speed special value
427 0 0       0 if ($datas->{SPEED} == SPEED_UNKNOWN) {
428 0         0 delete $datas->{SPEED};
429             $params{logger}->debug2("Unknown speed found on $params{interface}")
430 0 0       0 if $params{logger};
431             }
432              
433 0         0 return $datas;
434             }
435              
436             sub getInterfacesFromIp {
437 5     5 1 28685 my (%params) = (
438             command => '/sbin/ip addr show',
439             @_
440             );
441              
442 5         33 my $handle = getFileHandle(%params);
443 5 50       20 return unless $handle;
444              
445 5         6 my (@interfaces, @addresses, $interface);
446              
447 5         136 while (my $line = <$handle>) {
448 79 100       899 if ($line =~ /^\d+:\s+(\S+): <([^>]+)>/) {
    100          
    100          
    100          
449              
450 20 100       50 if (@addresses) {
    100          
451 11         17 push @interfaces, @addresses;
452 11         19 undef @addresses;
453             } elsif ($interface) {
454 4         7 push @interfaces, $interface;
455             }
456              
457 20         63 my ($name, $flags) = ($1, $2);
458             my $status =
459 20 100   57   133 (any { $_ eq 'UP' } split(/,/, $flags)) ? 'Up' : 'Down';
  57         206  
460              
461 20         151 $interface = {
462             DESCRIPTION => $name,
463             STATUS => $status
464             };
465             } elsif ($line =~ /link\/\S+ ($any_mac_address_pattern)?/) {
466 20         122 $interface->{MACADDR} = $1;
467             } elsif ($line =~ /inet6 (\S+)\/(\d{1,2})/) {
468 10         24 my $address = $1;
469 10         27 my $mask = getNetworkMaskIPv6($2);
470 10         1033 my $subnet = getSubnetAddressIPv6($address, $mask);
471              
472             push @addresses, {
473             IPADDRESS6 => $address,
474             IPMASK6 => $mask,
475             IPSUBNET6 => $subnet,
476             STATUS => $interface->{STATUS},
477             DESCRIPTION => $interface->{DESCRIPTION},
478             MACADDR => $interface->{MACADDR}
479 10         981 };
480             } elsif ($line =~ /
481             inet \s
482             ($ip_address_pattern)(?:\/(\d{1,3}))? \s
483             .* \s
484             (\S+)$
485             /x) {
486 15         31 my $address = $1;
487 15         55 my $mask = getNetworkMask($2);
488 15         359 my $subnet = getSubnetAddress($address, $mask);
489 15         174 my $name = $3;
490              
491             # the name associated with the address differs from the current
492             # interface if the address is actually attached to an alias
493             push @addresses, {
494             IPADDRESS => $address,
495             IPMASK => $mask,
496             IPSUBNET => $subnet,
497             STATUS => $interface->{STATUS},
498             DESCRIPTION => $name,
499             MACADDR => $interface->{MACADDR}
500 15         127 };
501             }
502             }
503              
504 5 100       18 if (@addresses) {
    50          
505 2         4 push @interfaces, @addresses;
506 2         5 undef @addresses;
507             } elsif ($interface) {
508 3         6 push @interfaces, $interface;
509             }
510              
511 5         85 return @interfaces;
512             }
513              
514             1;
515             __END__