File Coverage

blib/lib/Parse/DMIDecode.pm
Criterion Covered Total %
statement 96 166 57.8
branch 33 86 38.3
condition 4 57 7.0
subroutine 15 24 62.5
pod 11 14 78.5
total 159 347 45.8


line stmt bran cond sub pod time code
1             ############################################################
2             #
3             # $Id: DMIDecode.pm 1004 2007-03-11 12:43:25Z nicolaw $
4             # Parse::DMIDecode - Interface to SMBIOS using dmidecode
5             #
6             # Copyright 2006,2007 Nicola Worthington
7             #
8             # Licensed under the Apache License, Version 2.0 (the "License");
9             # you may not use this file except in compliance with the License.
10             # You may obtain a copy of the License at
11             #
12             # http://www.apache.org/licenses/LICENSE-2.0
13             #
14             # Unless required by applicable law or agreed to in writing, software
15             # distributed under the License is distributed on an "AS IS" BASIS,
16             # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17             # See the License for the specific language governing permissions and
18             # limitations under the License.
19             #
20             ############################################################
21              
22             package Parse::DMIDecode;
23             # vim:ts=4:sw=4:tw=78
24              
25 4     4   66007 use strict;
  4         9  
  4         153  
26             #use Scalar::Util qw(refaddr);
27 4     4   1801 use Parse::DMIDecode::Handle;
  4         11  
  4         150  
28 4     4   28 use Parse::DMIDecode::Constants qw(@TYPES %GROUPS);
  4         5  
  4         601  
29 4     4   23 use Carp qw(croak cluck carp);
  4         8  
  4         731  
30 4     4   23 use vars qw($VERSION $DEBUG);
  4         9  
  4         10468  
31              
32             $VERSION = '0.03' || sprintf('%d', q$Revision: 1004 $ =~ /(\d+)/g);
33             $DEBUG ||= $ENV{DEBUG} ? 1 : 0;
34              
35             my $objstore = {};
36              
37              
38             #
39             # Methods
40             #
41              
42             sub new {
43 2 50   2 1 780 ref(my $class = shift) && croak 'Class name required';
44 2 50       10 croak 'Odd number of elements passed when even was expected' if @_ % 2;
45              
46 2         7 my $self = bless \(my $dummy), $class;
47 2         14 $objstore->{_refaddr($self)} = {@_};
48 2         12 my $stor = $objstore->{_refaddr($self)};
49              
50 2         9 $stor->{commands} = [qw(dmidecode)];
51 2         5 my $validkeys = join('|','nowarnings',@{$stor->{commands}});
  2         11  
52 2         6 my @invalidkeys = grep(!/^$validkeys$/,grep($_ ne 'commands',keys %{$stor}));
  2         66  
53 2         7 delete $stor->{$_} for @invalidkeys;
54 2 0 33     8 cluck('Unrecognised parameters passed: '.join(', ',@invalidkeys))
55             if @invalidkeys && $^W;
56              
57 2         4 for my $command (@{$stor->{commands}}) {
  2         6  
58 2 50 33     13 croak "Command $command '$stor->{$command}'; file not found"
59             if defined $stor->{$command} && !-f $stor->{$command};
60             }
61              
62 2         8 DUMP('$self',$self);
63 2         6 DUMP('$stor',$stor);
64 2         15 return $self;
65             }
66              
67              
68             sub probe {
69 0     0 1 0 my $self = shift;
70 0 0 0     0 croak 'Not called as a method by parent object'
71             unless ref $self && UNIVERSAL::isa($self, __PACKAGE__);
72              
73 0         0 my $stor = $objstore->{_refaddr($self)};
74 0         0 eval {
75 0 0       0 if (!defined $stor->{dmidecode}) {
76 0         0 require File::Which;
77 0         0 for my $command (@{$stor->{commands}}) {
  0         0  
78 0 0       0 $stor->{$command} = File::Which::which($command)
79             if !defined $stor->{$command};
80             }
81             }
82             };
83 0 0       0 croak $@ if $@;
84              
85 0         0 my ($cmd) = $stor->{dmidecode} =~ /^([\/\.\_\-a-zA-Z0-9 ]+)$/;
86 0         0 TRACE($cmd);
87 0 0       0 croak "dmidecode command '$cmd' does not exist; bum!" if !-f $cmd;
88              
89 0         0 my $fh;
90 0         0 local %ENV = %ENV;
91 0         0 delete @ENV{qw(IFS CDPATH ENV BASH_ENV PATH)};
92 0 0       0 open($fh,'-|',$cmd) || croak "Unable to open file handle for command '$cmd': $!";
93 0         0 while (local $_ = <$fh>) {
94 0         0 $stor->{raw} .= $_;
95             }
96 0 0       0 close($fh) || carp "Unable to close file handle for command '$cmd': $!";
97              
98 0         0 return $self->parse($stor->{raw});
99             }
100              
101              
102             sub parse {
103 5     5 1 9 my $self = shift;
104 5 50 33     44 croak 'Not called as a method by parent object'
105             unless ref $self && UNIVERSAL::isa($self, __PACKAGE__);
106              
107 5         14 my $stor = $objstore->{_refaddr($self)};
108 5         22 my %data = (handles => []);
109              
110 5         9 my @lines;
111 5         10 for (@_) {
112 5         1366 push @lines, split(/\n/,$_);
113             }
114              
115 5         9 my $i = 0;
116 5         18 for (; $i < @lines; $i++) {
117 27         45 local $_ = $lines[$i];
118 27 100       209 if (/^Handle [0-9A-Fx]+/) {
    50          
    100          
    100          
    50          
    100          
    100          
119 5         12 last;
120             } elsif (/^SYSID present\.\s*/) {
121             # No-op
122             } elsif (/^# dmidecode ([\d\.]+)\s*$/) {
123 5         27 $data{dmidecode} = $1;
124             } elsif (/^(\d+) structures occupying (\d+) bytes?\.\s*$/) {
125 5         19 $data{structures} = $1;
126 5         25 $data{bytes} = $2;
127             } elsif (/^DMI ([\d\.]+) present\.?\s*$/) {
128 0         0 $data{dmi} = $1;
129             } elsif (/^SMBIOS ([\d\.]+) present\.?\s*$/) {
130 5         23 $data{smbios} = $1;
131             } elsif (/^(?:DMI )?[Tt]able at ([0-9A-Fx]+)\.?\s*$/) {
132 5         24 $data{location} = $1;
133             }
134             }
135              
136 5         14 for (qw(dmidecode structures bytes dmi smbios location)) {
137 30 100       77 $data{$_} = undef if !exists $data{$_};
138             }
139              
140 5         10 my $raw_handle_data = '';
141 5         14 for (; $i < @lines; $i++) {
142 3237 100       6761 if ($lines[$i] =~ /^Handle [0-9A-Fx]+/) {
143 346 100       804 push @{$data{handles}}, Parse::DMIDecode::Handle->new(
  341         1431  
144             raw => $raw_handle_data,
145             nowarnings => $stor->{nowarnings}
146             ) if $raw_handle_data;
147 346         1578 $raw_handle_data = "$lines[$i]\n";
148             } else {
149 2891         10498 $raw_handle_data .= "$lines[$i]\n";
150             }
151             }
152              
153 5 50       29 push @{$data{handles}}, Parse::DMIDecode::Handle->new(
  5         25  
154             raw => $raw_handle_data,
155             nowarnings => $stor->{nowarnings}
156             ) if $raw_handle_data;
157              
158 0         0 carp sprintf("Only parsed %d structures when %d were expected",
159 5         31 scalar(@{$data{handles}}), $data{structures}
160 5 50       12 ) if scalar(@{$data{handles}}) < $data{structures};
161              
162 5         34 $stor->{parsed} = \%data;
163 5         187 DUMP('$stor->{parsed}',$stor->{parsed});
164              
165 5         316 return $stor->{parsed}->{structures};
166             }
167              
168              
169             sub get_handles {
170 0     0 1 0 my $self = shift;
171 0 0 0     0 croak 'Not called as a method by parent object'
172             unless ref $self && UNIVERSAL::isa($self, __PACKAGE__);
173              
174 0 0       0 croak 'Odd number of elements passed when even was expected' if @_ % 2;
175 0         0 my %param = @_;
176 0         0 my $stor = $objstore->{_refaddr($self)};
177 0         0 my @handles;
178 0         0 my $getall = !keys(%param);
179              
180 0         0 for my $handle (@{$stor->{parsed}->{handles}}) {
  0         0  
181 0 0 0     0 if ($getall ||
  0   0     0  
      0        
      0        
      0        
      0        
      0        
182             (defined $param{address} && $handle->address eq $param{address}) ||
183             (defined $param{dmitype} && $handle->dmitype == $param{dmitype}) ||
184             (defined $param{group} && defined $GROUPS{$param{group}} &&
185             grep($_ == $handle->dmitype,@{$GROUPS{$param{group}}}))
186             ) {
187 0         0 push @handles, $handle;
188             }
189             }
190              
191 0         0 return @handles;
192             }
193              
194              
195             sub structures {
196 0     0 1 0 my $self = shift;
197 0 0 0     0 croak 'Not called as a method by parent object'
198             unless ref $self && UNIVERSAL::isa($self, __PACKAGE__);
199 0         0 return $objstore->{_refaddr($self)}->{parsed}->{structures};
200             }
201              
202              
203             sub table_location {
204 0     0 1 0 my $self = shift;
205 0 0 0     0 croak 'Not called as a method by parent object'
206             unless ref $self && UNIVERSAL::isa($self, __PACKAGE__);
207 0         0 return $objstore->{_refaddr($self)}->{parsed}->{location};
208             }
209              
210              
211             sub smbios_version {
212 0     0 1 0 my $self = shift;
213 0 0 0     0 croak 'Not called as a method by parent object'
214             unless ref $self && UNIVERSAL::isa($self, __PACKAGE__);
215 0         0 return $objstore->{_refaddr($self)}->{parsed}->{smbios};
216             }
217              
218              
219             sub dmidecode_version {
220 0     0 1 0 my $self = shift;
221 0 0 0     0 croak 'Not called as a method by parent object'
222             unless ref $self && UNIVERSAL::isa($self, __PACKAGE__);
223 0         0 return $objstore->{_refaddr($self)}->{parsed}->{dmidecode};
224             }
225              
226              
227             sub handle_addresses {
228 0     0 1 0 my $self = shift;
229 0 0 0     0 croak 'Not called as a method by parent object'
230             unless ref $self && UNIVERSAL::isa($self, __PACKAGE__);
231 0         0 return map { $_->handle }
  0         0  
232 0         0 @{$objstore->{_refaddr($self)}->{parsed}->{handles}};
233             }
234              
235              
236             sub keywords {
237 0     0 1 0 my $self = shift;
238 0 0 0     0 croak 'Not called as a method by parent object'
239             unless ref $self && UNIVERSAL::isa($self, __PACKAGE__);
240              
241 0         0 my %keywords;
242 0         0 my $stor = $objstore->{_refaddr($self)};
243 0         0 for my $handle (@{$stor->{parsed}->{handles}}) {
  0         0  
244 0         0 for my $keyword ($handle->keywords) {
245 0         0 $keywords{$keyword} = 1;
246             }
247             }
248              
249 0         0 return sort(keys(%keywords));
250             }
251              
252              
253             sub keyword {
254 6     6 1 13 my $self = shift;
255 6 50 33     50 croak 'Not called as a method by parent object'
256             unless ref $self && UNIVERSAL::isa($self, __PACKAGE__);
257 6 0       17 croak sprintf('%s elements passed when one was expected',
    50          
258             (@_ > 1 ? 'Multiple' : 'No')) if @_ != 1;
259              
260 6         19 my $stor = $objstore->{_refaddr($self)};
261 6         10 for my $handle (@{$stor->{parsed}->{handles}}) {
  6         17  
262 17 100       52 if (grep($_ eq $_[0],$handle->keywords)) {
263 6         22 return $handle->keyword($_[0]);
264             }
265             }
266             }
267              
268              
269 4     4   77 no warnings 'redefine';
  4         5  
  4         257  
270 740     740 0 4376 sub UNIVERSAL::a_sub_not_likely_to_be_here { ref($_[0]) }
271 4     4   19 use warnings 'redefine';
  4         14  
  4         1946  
272              
273              
274             sub _blessed ($) {
275 17     17   91 local($@, $SIG{__DIE__}, $SIG{__WARN__});
276             return length(ref($_[0]))
277 17 50       61 ? eval { $_[0]->a_sub_not_likely_to_be_here }
  17         58  
278             : undef
279             }
280              
281              
282             sub _refaddr($) {
283 17 50   17   61 my $pkg = ref($_[0]) or return undef;
284 17 50       44 if (_blessed($_[0])) {
285 17         58 bless $_[0], 'Scalar::Util::Fake';
286             } else {
287 0         0 $pkg = undef;
288             }
289 17         185 "$_[0]" =~ /0x(\w+)/;
290 17         40 my $i = do { local $^W; hex $1 };
  17         46  
  17         60  
291 17 50       56 bless $_[0], $pkg if defined $pkg;
292 17         323 return $i;
293             }
294              
295              
296             sub DESTROY {
297 2     2   7 my $self = shift;
298 2         22 delete $objstore->{_refaddr($self)};
299             }
300              
301              
302             sub TRACE {
303 0 0   0 0 0 return unless $DEBUG;
304 0         0 carp(shift());
305             }
306              
307              
308             sub DUMP {
309 9 50   9 0 28 return unless $DEBUG;
310 0           eval {
311 0           require Data::Dumper;
312 0           local $Data::Dumper::Indent = 2;
313 0           local $Data::Dumper::Terse = 1;
314 0           carp(shift().': '.Data::Dumper::Dumper(shift()));
315             }
316             }
317              
318             1;
319              
320              
321              
322             =pod
323              
324             =head1 NAME
325              
326             Parse::DMIDecode - Interface to SMBIOS using dmidecode
327              
328             =head1 SYNOPSIS
329              
330             use strict;
331             use Parse::DMIDecode ();
332            
333             my $decoder = new Parse::DMIDecode;
334             $decoder->probe; # Actively probe using dmidecode
335            
336             # Manually supply your own dmidecode output to be parsed
337             # $decoder->parse(qx(sudo /usr/sbin/dmidecode));
338            
339             printf("System: %s, %s",
340             $decoder->keyword("system-manufacturer"),
341             $decoder->keyword("system-product-name"),
342             );
343              
344             =head1 DESCRIPTION
345              
346             This module provides an OO interface to SMBIOS information through
347             the I command which is known to work under a number of
348             Linux, BSD and BeOS variants.
349              
350             =head1 METHODS
351              
352             =head2 new
353              
354             my $decoder = Parse::DMIDecode->new(
355             dmidecode => "/usr/sbin/dmidecode",
356             nowarnings => 1,
357             );
358              
359             This is the constructor method to create a Parse::DMIDeocde
360             object. It accepts two optional arguments; C and
361             C.
362              
363             The C argument specifies the full path and filename
364             of the I command that should used by the C
365             method.
366              
367             The C argument instructs Parse::DMIDecode not to
368             emit any parser warnings.
369              
370             =head2 probe
371              
372             $decoder->probe;
373              
374             This method executes an active probe to gather information using the
375             I command. It does not accept any arguments.
376              
377             =head2 parse
378              
379             my $raw = qx(sudo /usr/sbin/dmidecode);
380             $decoder->prase($raw);
381              
382             This method is a passive alternative to the C method. It
383             accepts a single string argument which should contain output from
384             the I command, which it will parse.
385              
386             =head2 keyword
387              
388             my $serial_number = $decoder->keyword("system-serial-number");
389              
390             =head2 keywords
391              
392             my @keywords = $decoder->keywords;
393             my @bios_keywords = $decoder->keywords("bios");
394            
395             for my $keyword (@bios_keywords) {
396             printf("%s => %s\n",
397             $keyword,
398             $decoder->keyword($keyword)
399             );
400             }
401              
402             =head2 handle_addresses
403              
404             my @addresses = $decoder->handle_addresses;
405              
406             =head2 get_handles
407              
408             use Parse::DMIDecode::Constants qw(@TYPES);
409            
410             # Available groups to query: bios, system, baseboard,
411             # chassis, processor, memory, cache, connector, slot
412             for my $handle ($decoder->get_handles( group => "memory" )) {
413             printf(">> Found handle at %s (%s):\n%s\n",
414             $handle->address,
415             $TYPES[$handle->dmitype],
416             $handle->raw
417             );
418             }
419              
420             See L for accessor method documentation
421             for handle objects.
422              
423             =head2 smbios_version
424              
425             my $smbios_version = $decoder->smbios_version;
426              
427             Returns the SMBIOS version number.
428              
429             =head2 dmidecode_version
430              
431             my $dmidecode_version = $decoder->dmidecode_version;
432              
433             Returns the version number of the copy of I that was used
434             to create the source data that was parsed. This value may not be available
435             when using older versions of I.
436              
437             =head2 table_location
438              
439             my $memory_address = $decoder->table_location;
440              
441             =head2 structures
442              
443             my $total_structures = $decoder->structures;
444              
445             =head1 SEE ALSO
446              
447             L,
448             L,
449             L,
450             examples/*.pl,
451             L,
452             L,
453             L,
454             L,
455             L,
456             L, L, L
457              
458             =head1 VERSION
459              
460             $Id: DMIDecode.pm 1004 2007-03-11 12:43:25Z nicolaw $
461              
462             =head1 AUTHOR
463              
464             Nicola Worthington
465              
466             L
467              
468             If you like this software, why not show your appreciation by sending the
469             author something nice from her
470             L?
471             ( http://www.amazon.co.uk/gp/registry/1VZXC59ESWYK0?sort=priority )
472              
473             =head1 COPYRIGHT
474              
475             Copyright 2006,2007 Nicola Worthington.
476              
477             This software is licensed under The Apache Software License, Version 2.0.
478              
479             L
480              
481             =cut
482              
483              
484             __END__