File Coverage

blib/lib/Sys/Detect/Virtualization.pm
Criterion Covered Total %
statement 75 116 64.6
branch 16 46 34.7
condition 3 13 23.0
subroutine 13 17 76.4
pod 4 4 100.0
total 111 196 56.6


line stmt bran cond sub pod time code
1             package Sys::Detect::Virtualization;
2              
3 4     4   43416 use warnings;
  4         12  
  4         133  
4 4     4   28 use strict;
  4         8  
  4         122  
5 4     4   507 use 5.008;
  4         33  
  4         455  
6              
7             =head1 NAME
8              
9             Sys::Detect::Virtualization - Detect if a UNIX system is running as a virtual machine
10              
11             =head1 VERSION
12              
13             Version 0.107
14              
15             =cut
16              
17             our $VERSION = '0.107';
18              
19             use constant {
20 4         4734 VIRT_KVM => 'Linux KVM',
21             VIRT_LGUEST => 'Linux lguest',
22             VIRT_OPENVZ => 'OpenVZ',
23             VIRT_QEMU => 'Qemu',
24             VIRT_VIRTUALPC => 'Microsoft Virtual PC',
25             VIRT_VMWARE => 'VMWare',
26             VIRT_VSERVER => 'linux-vserver',
27             VIRT_XEN => 'Xen',
28              
29             VIRT_FREEBSD_JAIL => 'FreeBSD Jail',
30              
31             VIRT_OPENVZ_HOST => 'OpenVZ Host',
32              
33             VIRT_VIRTUALBOX => 'VirtualBox',
34             VIRT_LXC => 'Linux Containers (LXC)',
35 4     4   29 };
  4         9  
36              
37             my %_hosts = (
38             VIRT_OPENVZ_HOST() => VIRT_OPENVZ(),
39             );
40              
41             # used for secondary sort where detector finds similar solutions
42             my %_priorities = (
43             VIRT_OPENVZ_HOST() => '1.2',
44             VIRT_OPENVZ() => '1.1',
45              
46             VIRT_KVM() => '1.1',
47             VIRT_QEMU() => '1.0',
48              
49             VIRT_LGUEST() => '1.0',
50             VIRT_VIRTUALPC() => '1.0',
51             VIRT_VMWARE() => '1.0',
52             VIRT_VSERVER() => '1.0',
53             VIRT_XEN() => '1.0',
54             );
55              
56             =head1 SYNOPSIS
57              
58             use Sys::Detect::Virtualization;
59              
60             my $detector = eval { Sys::Detect::Virtualization->new() };
61             if( $@ ) {
62             print "Detector may not be supported for your platform. Error was: $@\n";
63             }
64              
65             my @found = $detector->detect();
66             if( @found ) {
67             print "Possible virtualized system. May be running under:\n";
68             print "\t$_\n" for @found;
69             }
70              
71             =head1 DESCRIPTION
72              
73             This module attempts to detect whether or not a system is running as a guest
74             under virtualization, using various heuristics.
75              
76             =head1 METHODS
77              
78             =head2 Class Methods
79              
80             =over 4
81              
82             =item new ( $args )
83              
84             Construct a new detector object. On success, returns the object. On failure, dies.
85              
86             This constructor will fail if the system is not running a supported OS.
87             Currently, only Linux is supported.
88              
89             $args is an optional hash reference containing additional arguments for the
90             constructor. Currently supported are:
91              
92             =over 4
93              
94             =item verbose
95              
96             Set to 1 if we should output verbose debugging, 0 otherwise. Defaults to 0.
97              
98             =item ignore_host
99              
100             Set to 1 if we should report no virtualization if a virtualization host (as opposed to guest) is detected. Defaults to 1.
101              
102             =back
103              
104             =back
105              
106             =cut
107              
108             sub new
109             {
110 4     4 1 1225 my ($class, $extra_args) = @_;
111              
112 4 100       26 die q{Perl doesn't know what OS you're on!} unless $^O;
113 3         14 my $submodule = join('::', __PACKAGE__, lc $^O);
114              
115 2     2   1235 eval "use $submodule";
  1     1   2  
  1         18  
  1         772  
  1         4  
  1         27  
  3         218  
116 3         16 my $local_err = $@;
117 3 100       26 if( $local_err =~ m{Can't locate Sys/Detect/Virtualization/.*?\.pm} ) {
    50          
118 1         11 die "Virtualization detection not supported for '$^O' platform";
119             } elsif( $local_err ) {
120 0         0 die "Constructor failure: $local_err";
121             }
122              
123 2         11 my $self = $submodule->new($extra_args);
124              
125 2 50       640 $self->{verbose} = exists $extra_args->{verbose} ? $extra_args->{verbose} : 0;
126 2 50       11 $self->{ignore_host} = exists $extra_args->{ignore_host} ? $extra_args->{ignore_host} : 1;
127              
128 2         15 return $self;
129             }
130              
131             =head2 Instance Methods
132              
133             =over 4
134              
135             =item detect ( )
136              
137             Runs detection heuristics. Returns a list of possible virtualization systems,
138             or an empty list if none were detected.
139              
140             Possible hits are returned in order of most likely to least likely.
141              
142             Note that the failure to detect does NOT mean the system is not virtualized --
143             it simply means we couldn't detect it.
144              
145             =cut
146              
147             sub detect
148             {
149 0     0 1 0 my ($self) = @_;
150              
151 0         0 my $guesses = $self->_detect();
152              
153 0 0       0 if( ! $guesses ) {
154 0         0 return;
155             }
156              
157             return
158 0 0 0     0 sort { ($guesses->{$b} <=> $guesses->{$a}) ||
  0   0     0  
159             (($_priorities{$b} || 1.0) <=> ($_priorities{$a} || 1.0))
160             } keys %$guesses;
161             }
162              
163             sub _detect
164             {
165 0     0   0 my ($self) = @_;
166              
167 0         0 my @detectors = $self->get_detectors();
168              
169 0         0 my %guesses;
170 0         0 for my $name ( @detectors ) {
171 0 0       0 print "Running detector $name\n" if $self->{verbose};
172              
173 0         0 my $found = eval { $self->$name; };
  0         0  
174 0 0 0     0 if( ! $found || !scalar(@$found) ) {
175 0 0       0 if( $@ ) {
176 0 0       0 warn "Callback $name failed: $@" if $self->{verbose};
177             }
178 0 0       0 print "$name callback did not detect virtualization\n" if $self->{verbose};
179 0         0 next;
180             }
181              
182 0         0 for my $guess (@$found) {
183 0         0 $guesses{$guess}++;
184 0 0       0 print "$name callback detected $guess\n" if $self->{verbose};
185             }
186             }
187              
188             # Can't be both host and guest at the same time
189 0         0 foreach my $host_type (keys %_hosts) {
190 0 0       0 if( exists $guesses{$host_type} ) {
191 0         0 delete $guesses{ $_hosts{$host_type} };
192             }
193             }
194              
195 0 0       0 if( $self->{ignore_host} ) {
196 0         0 delete $guesses{$_} for keys %_hosts;
197             }
198              
199 0         0 return \%guesses
200             }
201              
202             =item guess ( )
203              
204             Runs detection heuristics and returns a single answer based on the "best guess" available.
205              
206             Currently, this is defined as the virt platform with the most heuristic hits.
207              
208             =back
209              
210             =cut
211              
212             sub guess
213             {
214 0     0 1 0 my ($self) = @_;
215              
216 0         0 my ($best, @rest) = $self->detect();
217              
218 0         0 return $best;
219             }
220              
221             =head2 Internal Methods
222              
223             You probably shouldn't ever need to call these
224              
225             =over 4
226              
227             =item get_detectors ( )
228              
229             Returns a list of all detector subroutines for the given instance.
230              
231             =cut
232              
233             sub get_detectors
234             {
235 2     2 1 6740 my ($thingy) = @_;
236              
237 2   66     22 my $class = ref $thingy || $thingy;
238              
239 4     4   30 no strict 'refs';
  4         9  
  4         3259  
240              
241             # Note: This does not consider parent classes. This is intentional.
242 2         5 return grep { /^detect_/ } keys %{"${class}::"};
  30         101  
  2         24  
243             }
244              
245             =item _find_bin ( $command )
246              
247             Returns full path to given command by searching $ENV{PATH}. If not present in
248             the path variable, the directories /usr/sbin, /usr/bin, /sbin, and /bin are
249             appended.
250              
251             =cut
252              
253             my @basic_paths = qw( /usr/sbin /usr/bin /sbin /bin );
254             sub _find_bin
255             {
256 12     12   46 my ($self, $command) = @_;
257              
258 12         87 my @paths = split(/:/, $ENV{PATH});
259 12         80 foreach my $path (@basic_paths) {
260 48 50       72 if( ! grep { $_ eq $path } @paths ) {
  120         417  
261 48         190 push @paths, $path;
262             }
263             }
264              
265 12         29 my $cmd = ( grep { -x $_ } map { "$_/$command" } @paths )[0];
  60         1506  
  60         161  
266 12 50 33     266 if (!defined($cmd) || ($cmd eq '')) {
267 0         0 $cmd = '/bin/false';
268             }
269 12         598 return $cmd;
270             }
271              
272             =item _fh_apply_patterns ( $fh, $patterns )
273              
274             Check, linewise, the data from $fh against the patterns in $patterns.
275              
276             $patterns is a listref read pairwise. The first item of each pair is the
277             pattern, and the second item of each pair is a list of names of virt solutions
278             detected by the pattern.
279              
280             =cut
281              
282             sub _fh_apply_patterns
283             {
284 9     9   199 my ($self, $fh, $patterns) = @_;
285              
286 9         37 my @hits;
287              
288 9         351404 while(my $line = <$fh>) {
289 1315         2860 for(my $i = 0; $i < scalar @$patterns; $i+=2) {
290 13115         14855 my ($pattern, $name) = @{$patterns}[$i, $i+1];
  13115         17960  
291 13115 100       62289 if( $line =~ /$pattern/ ) {
292 10         21 push @hits, @{$name};
  10         102  
293             }
294             }
295             }
296              
297 9         82 return \@hits;
298             }
299              
300             =item _check_command_output ( $command, $patterns )
301              
302             Check, linewise, the output of $command against the patterns in $patterns.
303              
304             $patterns is a listref read pairwise. The first item of each pair is the
305             pattern, and the second item of each pair is the name of the virt solution
306             detected by the pattern.
307              
308             =cut
309              
310             sub _check_command_output
311             {
312 8     8   19 my($self, $command, $patterns) = @_;
313              
314             # TODO: open3 or roll our own fork/exec?
315             # TODO: error code of command
316 8 50       38412 open( my $fh, "$command 2>/dev/null |") or die $!;
317 8         417 my $result = $self->_fh_apply_patterns( $fh, $patterns );
318 8         470 close $fh;
319              
320 8         389 return $result;
321             }
322              
323             =item _check_file_contents ( $fileglob, $patterns )
324              
325             Check, linewise, the content of each filename in $fileglob against the
326             patterns in $patterns.
327              
328             $fileglob is a glob that returns zero or more filenames.
329              
330             $patterns is a listref read pairwise. The first item of each pair is the
331             pattern, and the second item of each pair is the name of the virt solution
332             detected by the pattern.
333              
334             =cut
335              
336             sub _check_file_contents
337             {
338 4     4   273 my ($self, $fileglob, $patterns) = @_;
339              
340             # TODO: caller does globbing, passes listref of filenames?
341 4         11 my @hits;
342 4         459 while( my $filename = glob($fileglob) ) {
343 4         31 my $fh;
344 4 100       242 if( ! open( $fh, "<$filename") ) {
345 3 50       36 warn "skipping $filename: $!" if $self->{verbose};
346 3         34 next;
347             }
348 1         15 my $result = $self->_fh_apply_patterns( $fh, $patterns );
349 1         13 close $fh;
350 1 50       7 if( $result ) {
351 1         13 push @hits, @$result;
352             }
353             }
354 4         40 return \@hits;
355             }
356              
357             =item _check_path_exists ( $paths )
358              
359             Checks for the existence of each path in $paths.
360              
361             $paths is a listref read pairwise. The first item of each pair is the path
362             name, and the second item of each pair is the name of the virt solution
363             detected by the existence of that $path.
364              
365             =cut
366              
367             sub _check_path_exists
368             {
369 0     0     my ($self, $paths) = @_;
370              
371 0           my @hits;
372 0           for(my $i = 0; $i < scalar @{$paths}; $i+=2) {
  0            
373 0           my ($path, $name) = @{$paths}[$i, $i+1];
  0            
374 0 0         if( -e $path ) {
375 0           push @hits, @$name;
376             }
377             }
378              
379 0           return \@hits;
380             }
381              
382             =back
383              
384             =head1 AUTHOR
385              
386             Dave O'Neill,
387              
388             =head1 BUGS
389              
390             Known issues:
391              
392             =over 4
393              
394             =item *
395              
396             No support for non-Linux platforms. Feel free to contribute an appropriate
397             Sys::Detect::Virtualization::foo class for your platform.
398              
399             =item *
400              
401             No weighting of tests so that high-confidence checks can be done first.
402             Patches welcome.
403              
404             =back
405              
406             Please report any bugs or feature requests to C
407             at rt.cpan.org>, or through the web interface at
408             L. I
409             will be notified, and then you'll automatically be notified of progress on your
410             bug as I make changes.
411              
412              
413             =head1 SUPPORT
414              
415             You can find documentation for this module with the perldoc command.
416              
417             perldoc Sys::Detect::Virtualization
418              
419              
420             You can also look for information at:
421              
422             =over 4
423              
424             =item * RT: CPAN's request tracker
425              
426             L
427              
428             =item * AnnoCPAN: Annotated CPAN documentation
429              
430             L
431              
432             =item * CPAN Ratings
433              
434             L
435              
436             =item * Search CPAN
437              
438             L
439              
440             =item * The author's blog
441              
442             L
443              
444             =back
445              
446             =head1 LICENSE AND COPYRIGHT
447              
448             Copyright (C) 2009 Roaring Penguin Software Inc.
449              
450             This program is free software; you can redistribute it and/or modify it
451             under the terms of either: the GNU General Public License as published
452             by the Free Software Foundation; or the Artistic License.
453              
454             See http://dev.perl.org/licenses/ for more information.
455              
456              
457             =cut
458              
459             1; # End of Sys::Detect::Virtualization