use strict;
use warnings;
+use Carp qw(croak);
+
use Sys::Guestfs;
use File::Temp qw/tempdir/;
use Locale::TextDomain 'libguestfs';
eval "use Sys::Virt;";
eval "use XML::XPath;";
eval "use XML::XPath::XMLParser;";
+eval "use Win::Hivex;";
=pod
have to call C<$g-E<gt>launch ()>.
The optional C<address> parameter can be added to specify the libvirt
-URI. In addition, L<Sys::Virt(3)> lists other parameters which are
-passed through to C<Sys::Virt-E<gt>new> unchanged.
+URI.
The implicit libvirt handle is closed after this function, I<unless>
you call the function in C<wantarray> context, in which case the
If the C<Sys::Virt> module is not available, then libvirt is bypassed,
and this function can only open disk images.
+The optional C<interface> parameter can be used to open devices with
+C<add_drive{,_ro}_with_if>. See
+L<Sys::Guestfs/guestfs_add_drive_with_if> for more details.
+
=cut
sub open_guest
my $first = shift;
my %params = @_;
- my $readwrite = $params{rw};
+ my $rw = $params{rw};
+ my $address = $params{address};
+ my $interface = $params{interface};
my @images = ();
if (ref ($first) eq "ARRAY") {
} elsif (ref ($first) eq "SCALAR") {
@images = ($first);
} else {
- die __"open_guest: first parameter must be a string or an arrayref"
+ croak __"open_guest: first parameter must be a string or an arrayref"
}
my ($conn, $dom);
if (-e $images[0]) {
foreach (@images) {
- die __x("guest image {imagename} does not exist or is not readable",
+ croak __x("guest image {imagename} does not exist or is not readable",
imagename => $_)
unless -r $_;
}
die __"open_guest: too many domains listed on command line"
if @images > 1;
- $conn = Sys::Virt->new (readonly => 1, @_);
+ my @libvirt_args = ();
+ push @libvirt_args, address => $address if defined $address;
+
+ $conn = Sys::Virt->new (readonly => 1, @libvirt_args);
die __"open_guest: cannot connect to libvirt" unless $conn;
my @doms = $conn->list_defined_domains ();
my $isitinactive = 1;
- unless ($readwrite) {
+ unless ($rw) {
# In the case where we want read-only access to a domain,
# allow the user to specify an active domain too.
push @doms, $conn->list_domains ();
# We've now got the list of @images, so feed them to libguestfs.
my $g = Sys::Guestfs->new ();
foreach (@images) {
- if ($readwrite) {
- $g->add_drive ($_);
+ if ($rw) {
+ if ($interface) {
+ $g->add_drive_with_if ($_, $interface);
+ } else {
+ $g->add_drive ($_);
+ }
} else {
- $g->add_drive_ro ($_);
+ if ($interface) {
+ $g->add_drive_ro_with_if ($_, $interface);
+ } else {
+ $g->add_drive_ro ($_);
+ }
}
}
%fses = inspect_all_partitions ($g, \@partitions);
- %fses = inspect_all_partitions ($g, \@partitions, use_windows_registry => 1);
-
This calls C<inspect_partition> for each partition in the list
C<@partitions>.
The result is a hash which maps partition name to C<\%fs> hashref.
-The contents of the C<%fs> hash and the meaning of the
-C<use_windows_registry> flag are explained below.
+The contents of the C<%fs> hash is explained below.
=cut
my $g = shift;
my $parts = shift;
my @parts = @$parts;
- return map { _canonical_dev ($_) => inspect_partition ($g, $_, @_) } @parts;
+ return map { _canonical_dev ($_) => inspect_partition ($g, $_) } @parts;
}
=head2 inspect_partition
\%fs = inspect_partition ($g, $partition);
- \%fs = inspect_partition ($g, $partition, use_windows_registry => 1);
-
This function inspects the device named C<$partition> in isolation and
tries to determine what it is. It returns information such as whether
the partition is formatted, and with what, whether it is mountable,
and what it appears to contain (eg. a Windows root, or a Linux /usr).
-If C<use_windows_registry> is set to 1, then we will try to download
-and parse the content of the Windows registry (for Windows root
-devices). However since this is an expensive and error-prone
-operation, we don't do this by default. It also requires the external
-program C<reged>, patched to remove numerous crashing bugs in the
-upstream version.
+If the Perl module L<Win::Hivex(3)> is installed, then additional
+information is made available for Windows guests, if we can locate and
+read their registries.
The returned value is a hashref C<\%fs> which may contain the
following top-level keys (any key can be missing):
=item package_format
(For Linux root partitions only)
-The package format used by the guest distribution. One of: "rpm", "dpkg".
+The package format used by the guest distribution. One of: "rpm", "deb".
=item package_management
local $_;
my $g = shift;
my $dev = shift; # LV or partition name.
- my %params = @_;
-
- my $use_windows_registry = $params{use_windows_registry};
my %r; # Result hash.
$r{fsos} = "windows";
$r{content} = "windows-root";
$r{is_root} = 1;
- _check_windows_root ($g, \%r, $use_windows_registry);
+ _check_windows_root ($g, \%r);
goto OUT;
}
}
$_ = $g->cat ("/etc/redhat-release");
if (/Fedora release (\d+)(?:\.(\d+))?/) {
+ chomp; $r->{product_name} = $_;
$r->{osdistro} = "fedora";
$r->{os_major_version} = "$1";
$r->{os_minor_version} = "$2" if(defined($2));
}
elsif (/(Red Hat Enterprise Linux|CentOS|Scientific Linux)/) {
+ chomp; $r->{product_name} = $_;
+
my $distro = $1;
if($distro eq "Red Hat Enterprise Linux") {
$r->{osdistro} = "redhat-based";
}
} elsif ($g->is_file ("/etc/debian_version")) {
- $r->{package_format} = "dpkg";
+ $r->{package_format} = "deb";
$r->{package_management} = "apt";
$_ = $g->cat ("/etc/debian_version");
if (/(\d+)\.(\d+)/) {
+ chomp; $r->{product_name} = $_;
$r->{osdistro} = "debian";
$r->{os_major_version} = "$1";
$r->{os_minor_version} = "$2";
# Windows installations and their %systemroot%s in a simple text
# format.
#
-# XXX We could parse this better. This won't work if /boot.ini is on
-# a different drive from the %systemroot%, and in other unusual cases.
+# XXX We don't handle the case where /boot.ini is on a different
+# partition very well (Windows Vista and later).
sub _check_windows_root
{
local $_;
my $g = shift;
my $r = shift;
- my $use_windows_registry = shift;
my $boot_ini = resolve_windows_path ($g, "/boot.ini");
$r->{boot_ini} = $boot_ini;
+ my $systemroot;
if (defined $r->{boot_ini}) {
$_ = $g->cat ($boot_ini);
my @lines = split /\n/;
my $section;
- my $systemroot;
foreach (@lines) {
if (m/\[.*\]/) {
$section = $1;
last;
}
}
+ }
- if (defined $systemroot) {
- $r->{systemroot} = resolve_windows_path ($g, "/$systemroot");
- if (defined $r->{systemroot}) {
- _check_windows_arch ($g, $r, $r->{systemroot});
- if ($use_windows_registry) {
- _check_windows_registry ($g, $r, $r->{systemroot});
- }
+ if (!defined $systemroot) {
+ # Last ditch ... try to guess %systemroot% location.
+ foreach ("windows", "winnt") {
+ my $dir = resolve_windows_path ($g, "/$_/system32");
+ if (defined $dir) {
+ $systemroot = $_;
+ last;
}
}
}
+
+ if (defined $systemroot) {
+ $r->{systemroot} = resolve_windows_path ($g, "/$systemroot");
+ if (defined $r->{systemroot}) {
+ _check_windows_arch ($g, $r, $r->{systemroot});
+ _check_windows_registry ($g, $r, $r->{systemroot});
+ }
+ }
}
# Find Windows userspace arch.
my $systemroot = shift;
# Download the system registry files. Only download the
- # interesting ones, and we don't bother with user profiles at all.
-
- my $configdir = resolve_windows_path ($g, "$systemroot/system32/config");
- if (defined $configdir) {
- my $softwaredir = resolve_windows_path ($g, "$configdir/software");
- if (defined $softwaredir) {
- _load_windows_registry ($g, $r, $softwaredir,
- "HKEY_LOCAL_MACHINE\\SOFTWARE");
- }
- my $systemdir = resolve_windows_path ($g, "$configdir/system");
- if (defined $systemdir) {
- _load_windows_registry ($g, $r, $systemdir,
- "HKEY_LOCAL_MACHINE\\System");
- }
- }
-}
-
-sub _load_windows_registry
-{
- local $_;
- my $g = shift;
- my $r = shift;
- my $regfile = shift;
- my $prefix = shift;
-
- my $dir = tempdir (CLEANUP => 1);
-
- $g->download ($regfile, "$dir/reg");
+ # interesting ones (SOFTWARE and SYSTEM). We don't bother with
+ # the user ones.
- # 'reged' command is particularly noisy. Redirect stdout and
- # stderr to /dev/null temporarily.
- open SAVEOUT, ">&STDOUT";
- open SAVEERR, ">&STDERR";
- open STDOUT, ">/dev/null";
- open STDERR, ">/dev/null";
+ return unless exists $INC{"Win/Hivex.pm"};
- my @cmd = ("reged", "-x", "$dir/reg", "$prefix", "\\", "$dir/out");
- my $res = system (@cmd);
+ my $configdir = resolve_windows_path ($g, "$systemroot/system32/config");
+ return unless defined $configdir;
- close STDOUT;
- close STDERR;
- open STDOUT, ">&SAVEOUT";
- open STDERR, ">&SAVEERR";
- close SAVEOUT;
- close SAVEERR;
+ my $tmpdir = tempdir (CLEANUP => 1);
- unless ($res == 0) {
- warn __x("reged command failed: {errormsg}", errormsg => $?);
- return;
+ my $software = resolve_windows_path ($g, "$configdir/software");
+ my $software_hive;
+ if (defined $software) {
+ eval {
+ $g->download ($software, "$tmpdir/software");
+ $software_hive = Win::Hivex->open ("$tmpdir/software");
+ };
+ warn "$@\n" if $@;
+ $r->{windows_software_hive} = $software;
}
- # Some versions of reged segfault on inputs. If that happens we
- # may get no / partial output file. Anyway, if it exists, load
- # it.
- my $content;
- unless (open F, "$dir/out") {
- warn __x("no output from reged command: {errormsg}", errormsg => $!);
- return;
+ my $system = resolve_windows_path ($g, "$configdir/system");
+ my $system_hive;
+ if (defined $system) {
+ eval {
+ $g->download ($system, "$tmpdir/system");
+ $system_hive = Win::Hivex->open ("$tmpdir/system");
+ };
+ warn "$@\n" if $@;
+ $r->{windows_system_hive} = $system;
}
- { local $/ = undef; $content = <F>; }
- close F;
- my @registry = ();
- @registry = @{$r->{registry}} if exists $r->{registry};
- push @registry, $content;
- $r->{registry} = \@registry;
+ # Get the ProductName, major and minor version, etc.
+ if (defined $software_hive) {
+ my $cv_node;
+ eval {
+ $cv_node = $software_hive->root;
+ $cv_node = $software_hive->node_get_child ($cv_node, $_)
+ foreach ("Microsoft", "Windows NT", "CurrentVersion");
+ };
+ warn "$@\n" if $@;
+
+ if ($cv_node) {
+ my @values = $software_hive->node_values ($cv_node);
+
+ foreach (@values) {
+ my $k = $software_hive->value_key ($_);
+ if ($k eq "ProductName") {
+ $_ = $software_hive->value_string ($_);
+ $r->{product_name} = $_ if defined $_;
+ } elsif ($k eq "CurrentVersion") {
+ $_ = $software_hive->value_string ($_);
+ if (defined $_ && m/^(\d+)\.(\d+)/) {
+ $r->{os_major_version} = $1;
+ $r->{os_minor_version} = $2;
+ }
+ } elsif ($k eq "CurrentBuild") {
+ $_ = $software_hive->value_string ($_);
+ $r->{windows_current_build} = $_ if defined $_;
+ } elsif ($k eq "SoftwareType") {
+ $_ = $software_hive->value_string ($_);
+ $r->{windows_software_type} = $_ if defined $_;
+ } elsif ($k eq "CurrentType") {
+ $_ = $software_hive->value_string ($_);
+ $r->{windows_current_type} = $_ if defined $_;
+ } elsif ($k eq "RegisteredOwner") {
+ $_ = $software_hive->value_string ($_);
+ $r->{windows_registered_owner} = $_ if defined $_;
+ } elsif ($k eq "RegisteredOrganization") {
+ $_ = $software_hive->value_string ($_);
+ $r->{windows_registered_organization} = $_ if defined $_;
+ } elsif ($k eq "InstallationType") {
+ $_ = $software_hive->value_string ($_);
+ $r->{windows_installation_type} = $_ if defined $_;
+ } elsif ($k eq "EditionID") {
+ $_ = $software_hive->value_string ($_);
+ $r->{windows_edition_id} = $_ if defined $_;
+ } elsif ($k eq "ProductID") {
+ $_ = $software_hive->value_string ($_);
+ $r->{windows_product_id} = $_ if defined $_;
+ }
+ }
+ }
+ }
}
sub _check_grub
Operating system distribution, eg. "debian".
+=item product_name
+
+Free text product name.
+
=item major_version
Operating system major version, eg. "4".
my $r = shift;
$r->{os} = $r->{root}->{fsos} if exists $r->{root}->{fsos};
+ $r->{product_name} = $r->{root}->{product_name}
+ if exists $r->{root}->{product_name};
$r->{distro} = $r->{root}->{osdistro} if exists $r->{root}->{osdistro};
$r->{major_version} = $r->{root}->{os_major_version}
if exists $r->{root}->{os_major_version};
if($ro) {
$g->mount_ro ($mounts->{$_}, $_)
} else {
- $g->mount ($mounts->{$_}, $_)
+ $g->mount_options ("", $mounts->{$_}, $_)
}
}
}
if ($osn eq "linux") {
my $package_format = $os->{package_format};
if (defined $package_format && $package_format eq "rpm") {
- my @lines = $g->command_lines
- (["rpm",
- "-q", "-a",
- "--qf", "%{name} %{epoch} %{version} %{release} %{arch}\n"]);
+ my @lines = ();
+ eval {
+ @lines = $g->command_lines
+ (["rpm",
+ "-q", "-a", "--qf",
+ "%{name} %{epoch} %{version} %{release} %{arch}\n"]);
+ };
+
+ warn(__x("Error running rpm -qa: {error}", error => $@)) if ($@);
+
+ @lines = sort @lines;
foreach (@lines) {
if (m/^(.*) (.*) (.*) (.*) (.*)$/) {
my $epoch = $2;
push @apps, $app
}
}
+ } elsif (defined $package_format && $package_format eq "deb") {
+ my @lines = ();
+ eval {
+ @lines = $g->command_lines
+ (["dpkg-query",
+ "-f", '${Package} ${Version} ${Architecture} ${Status}\n',
+ "-W"]);
+ };
+
+ warn(__x("Error running dpkg-query: {error}", error => $@)) if ($@);
+
+ @lines = sort @lines;
+ foreach (@lines) {
+ if (m/^(.*) (.*) (.*) (.*) (.*) (.*)$/) {
+ if ( $6 eq "installed" ) {
+ my $app = {
+ name => $1,
+ version => $2,
+ arch => $3
+ };
+ push @apps, $app
+ }
+ }
+ }
}
} elsif ($osn eq "windows") {
# XXX
# For every kernel we find, inspect it and add to $os->{kernels}
my $grub = _find_grub_prefix($g, $os);
+ my $grub_conf = "/etc/grub.conf";
+
+ # Debian and other's have no /etc/grub.conf:
+ if ( ! -f "$grub_conf" ) {
+ $grub_conf = "$grub/grub/menu.lst";
+ }
my @boot_configs;
my @configs = ();
# Get all configurations from grub
foreach my $bootable
- ($g->aug_match("/files/etc/grub.conf/title"))
+ ($g->aug_match("/files/$grub_conf/title"))
{
my %config = ();
$config{title} = $g->aug_get($bootable);
# Add the default configuration
eval {
- $boot{default} = $g->aug_get("/files/etc/grub.conf/default");
+ $boot{default} = $g->aug_get("/files/$grub_conf/default");
};
- if($@) {
- warn __"No grub default specified";
- }
$os->{boot} = \%boot;
}
# Disregard old-style compressed ext2 files and only work with real
# compressed cpio files, since cpio takes ages to (fail to) process anything
# else.
- if ($g->file ($path) =~ /cpio/) {
+ if ($g->exists($path) && $g->file($path) =~ /cpio/) {
eval {
@modules = $g->initrd_list ($path);
};