diff --git a/.gitignore b/.gitignore index 496c21a..ac486e7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ libguestfs-1.4.1.tar.gz libguestfs-1.4.2.tar.gz +/libguestfs-1.4.3.tar.gz diff --git a/0001-edit-Add-e-expr-option-to-non-interactively-apply-ex.patch b/0001-edit-Add-e-expr-option-to-non-interactively-apply-ex.patch new file mode 100644 index 0000000..65dd4e2 --- /dev/null +++ b/0001-edit-Add-e-expr-option-to-non-interactively-apply-ex.patch @@ -0,0 +1,227 @@ +From 17355de7fdc473062d5e7f4ca8e50e8053381668 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Sun, 11 Jul 2010 16:46:15 +0100 +Subject: [PATCH] edit: Add -e 'expr' option to non-interactively apply expression to the file. + +(Suggested by Justin Clift). + +Cherry picked from commit 8f6d8b05152fda68e0c01848c0e5239e0093548a. +--- + tools/virt-edit | 138 +++++++++++++++++++++++++++++++++++++++++++++++++------ + 1 files changed, 124 insertions(+), 14 deletions(-) + +diff --git a/tools/virt-edit b/tools/virt-edit +index ca8e576..4f9b9ab 100755 +--- a/tools/virt-edit ++++ b/tools/virt-edit +@@ -1,6 +1,6 @@ + #!/usr/bin/perl -w + # virt-edit +-# Copyright (C) 2009 Red Hat Inc. ++# Copyright (C) 2009-2010 Red Hat Inc. + # + # This program is free software; you can redistribute it and/or modify + # it under the terms of the GNU General Public License as published by +@@ -40,6 +40,8 @@ virt-edit - Edit a file in a virtual machine + + virt-edit [--options] disk.img [disk.img ...] file + ++ virt-edit [domname|disk.img] file -e 'expr' ++ + =head1 WARNING + + You must I use C on live virtual machines. If you do +@@ -56,10 +58,18 @@ cases you should look at the L tool. + + =head1 EXAMPLES + ++Edit the named files interactively: ++ + virt-edit mydomain /boot/grub/grub.conf + + virt-edit mydomain /etc/passwd + ++You can also edit files non-interactively (see ++L below). ++To change the init default level to 5: ++ ++ virt-edit mydomain /etc/inittab -e 's/^id:.*/id:5:initdefault:/' ++ + =head1 OPTIONS + + =over 4 +@@ -92,6 +102,19 @@ connect to the default libvirt hypervisor. + If you specify guest block devices directly, then libvirt is not used + at all. + ++=cut ++ ++my $expr; ++ ++=item B<--expr EXPR> | B<-e EXPR> ++ ++Instead of launching the external editor, non-interactively ++apply the Perl expression C to each line in the file. ++See L below. ++ ++Be careful to properly quote the expression to prevent it from ++being altered by the shell. ++ + =back + + =cut +@@ -99,6 +122,7 @@ at all. + GetOptions ("help|?" => \$help, + "version" => \$version, + "connect|c=s" => \$uri, ++ "expr|e=s" => \$expr, + ) or pod2usage (2); + pod2usage (1) if $help; + if ($version) { +@@ -144,28 +168,112 @@ my ($fh_not_used, $tempname) = tempfile (UNLINK => 1); + # Allow this to fail in case eg. the file does not exist. + $g->download($filename, $tempname); + +-my $oldctime = (stat ($tempname))[10]; ++my $do_upload = $tempname; + +-my $editor = $ENV{EDITOR}; +-$editor ||= "vi"; +-system ("$editor $tempname") == 0 +- or die "edit failed: $editor: $?"; ++if (!defined $expr) { ++ # Interactively edit the file. ++ my $oldctime = (stat ($tempname))[10]; + +-my $newctime = (stat ($tempname))[10]; ++ my $editor = $ENV{EDITOR}; ++ $editor ||= "vi"; ++ system ("$editor $tempname") == 0 ++ or die "edit failed: $editor: $?"; + +-if ($oldctime != $newctime) { +- $g->upload ($tempname, $filename) ++ my $newctime = (stat ($tempname))[10]; ++ ++ if ($oldctime == $newctime) { ++ $do_upload = undef; ++ print __"File not changed.\n"; ++ } + } else { +- print __"File not changed.\n"; ++ my ($fh, $tempout) = tempfile (UNLINK => 1); ++ ++ # Apply a Perl expression to the lines of the file. ++ open IFILE, $tempname or die "$tempname: $!"; ++ my $lineno = 0; ++ while () { ++ $lineno++; ++ eval $expr; ++ die if $@; ++ print $fh $_ or die "print: $!"; ++ } ++ close $fh; ++ ++ $do_upload = $tempout; + } + +-$g->sync (); +-$g->umount_all (); ++if (defined $do_upload) { ++ $g->upload ($do_upload, $filename); ++ $g->umount_all (); ++ $g->sync (); ++} + + undef $g; + + exit 0; + ++=head1 NON-INTERACTIVE EDITING ++ ++C normally calls out to C<$EDITOR> (or vi) so ++the system administrator can interactively edit the file. ++ ++There are two ways also to use C from scripts in order to ++make automated edits to files. (Note that although you I use ++C like this, it's less error-prone to write scripts ++directly using the libguestfs API and Augeas for configuration file ++editing.) ++ ++The first method is to temporarily set C<$EDITOR> to any script or ++program you want to run. The script is invoked as C<$EDITOR tmpfile> ++and it should update C in place however it likes. ++ ++The second method is to use the C<-e> parameter of C to run ++a short Perl snippet in the style of L. For example to ++replace all instances of C with C in a file: ++ ++ virt-edit domname filename -e 's/foo/bar/' ++ ++The full power of Perl regular expressions can be used (see ++L). For example to delete root's password you could do: ++ ++ virt-edit domname /etc/passwd -e 's/^root:.*?:/root::/' ++ ++What really happens is that the snippet is evaluated as a Perl ++expression for each line of the file. The line, including the final ++C<\n>, is passed in C<$_> and the expression should update C<$_> or ++leave it unchanged. ++ ++To delete a line, set C<$_> to the empty string. For example, to ++delete the C user account from the password file you can do: ++ ++ virt-edit mydomain /etc/passwd -e '$_ = "" if /^apache:/' ++ ++To insert a line, prepend or append it to C<$_>. However appending ++lines to the end of the file is rather difficult this way since there ++is no concept of "last line of the file" - your expression just ++doesn't get called again. You might want to use the first method ++(setting C<$EDITOR>) if you want to do this. ++ ++The variable C<$lineno> contains the current line number. ++As is traditional, the first line in the file is number C<1>. ++ ++The return value from the expression is ignored, but the expression ++may call C in order to abort the whole program, leaving the ++original file untouched. ++ ++Remember when matching the end of a line that C<$_> may contain the ++final C<\n>, or (for DOS files) C<\r\n>, or if the file does not end ++with a newline then neither of these. Thus to match or substitute ++some text at the end of a line, use this regular expression: ++ ++ /some text(\r?\n)?$/ ++ ++Alternately, use the perl C function, being careful not to ++chomp C<$_> itself (since that would remove all newlines from the ++file): ++ ++ my $m = $_; chomp $m; $m =~ /some text$/ ++ + =head1 ENVIRONMENT VARIABLES + + =over 4 +@@ -187,7 +295,9 @@ L, + L, + L, + L, +-L. ++L, ++L, ++L. + + =head1 AUTHOR + +@@ -195,7 +305,7 @@ Richard W.M. Jones L + + =head1 COPYRIGHT + +-Copyright (C) 2009 Red Hat Inc. ++Copyright (C) 2009-2010 Red Hat Inc. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by +-- +1.7.1 + diff --git a/0002-edit-Add-b-backup-option-and-make-uploading-more-rob.patch b/0002-edit-Add-b-backup-option-and-make-uploading-more-rob.patch new file mode 100644 index 0000000..633b0e7 --- /dev/null +++ b/0002-edit-Add-b-backup-option-and-make-uploading-more-rob.patch @@ -0,0 +1,75 @@ +From 2610beeac2ddb9e104d3bad4edb70385e1378ab7 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Sun, 11 Jul 2010 23:09:07 +0100 +Subject: [PATCH] edit: Add -b (backup) option and make uploading more robust. + (cherry picked from commit fed8714b92b57da1a593f74b52635fd267442d5b) + +--- + tools/virt-edit | 37 ++++++++++++++++++++++++++++++++++++- + 1 files changed, 36 insertions(+), 1 deletions(-) + +diff --git a/tools/virt-edit b/tools/virt-edit +index 4f9b9ab..128872e 100755 +--- a/tools/virt-edit ++++ b/tools/virt-edit +@@ -92,6 +92,22 @@ Display version number and exit. + + =cut + ++my $backup; ++ ++=item B<--backup extension> | B<-b extension> ++ ++Create a backup of the original file I. ++The backup has the original filename with C added. ++ ++Usually the first character of C would be a dot C<.> ++so you would write: ++ ++ virt-edit -b .orig [etc] ++ ++By default, no backup file is made. ++ ++=cut ++ + my $uri; + + =item B<--connect URI> | B<-c URI> +@@ -123,6 +139,7 @@ GetOptions ("help|?" => \$help, + "version" => \$version, + "connect|c=s" => \$uri, + "expr|e=s" => \$expr, ++ "backup|b=s" => \$backup, + ) or pod2usage (2); + pod2usage (1) if $help; + if ($version) { +@@ -203,7 +220,25 @@ if (!defined $expr) { + } + + if (defined $do_upload) { +- $g->upload ($do_upload, $filename); ++ # Upload to a new file, so if it fails we don't end up with ++ # a partially written file. Give the new file a completely ++ # random name so we have only a tiny chance of overwriting ++ # some existing file. ++ my $dirname = $filename; ++ $dirname =~ s{/[^/]+$}{/}; ++ ++ my @chars = ('a'..'z', 'A'..'Z', '0'..'9'); ++ my $newname = $dirname; ++ foreach (0..7) { ++ $newname .= $chars[rand @chars]; ++ } ++ ++ $g->upload ($do_upload, $newname); ++ ++ # Backup or overwrite? ++ $g->mv ($filename, "$filename$backup") if defined $backup; ++ $g->mv ($newname, $filename); ++ + $g->umount_all (); + $g->sync (); + } +-- +1.7.1 + diff --git a/0003-New-APIs-lvm-set-filter-and-lvm-clear-filter.patch b/0003-New-APIs-lvm-set-filter-and-lvm-clear-filter.patch new file mode 100644 index 0000000..6cefc84 --- /dev/null +++ b/0003-New-APIs-lvm-set-filter-and-lvm-clear-filter.patch @@ -0,0 +1,481 @@ +From 9f6dfc8ad9799837c25e8d40dfe9d071e774ef72 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Fri, 16 Jul 2010 13:01:21 +0100 +Subject: [PATCH] New APIs: lvm-set-filter and lvm-clear-filter. + +These APIs allow you to change the device filter, the list of +block devices that LVM "sees". Either you can set it to a fixed +list of devices / partitions, or you can clear it so that LVM sees +everything. +(cherry picked from commit d2cf9a15a9f22623dbbed33fb66c5077f1275df2) +--- + .gitignore | 1 + + daemon/Makefile.am | 1 + + daemon/lvm-filter.c | 244 +++++++++++++++++++++++++++++++++++++ + po/POTFILES.in | 1 + + regressions/Makefile.am | 1 + + regressions/test-lvm-filtering.sh | 92 ++++++++++++++ + src/MAX_PROC_NR | 2 +- + src/generator.ml | 41 ++++++ + 8 files changed, 382 insertions(+), 1 deletions(-) + create mode 100644 daemon/lvm-filter.c + create mode 100755 regressions/test-lvm-filtering.sh + +diff --git a/.gitignore b/.gitignore +index 2fb003c..094acb3 100644 +--- a/.gitignore ++++ b/.gitignore +@@ -201,6 +201,7 @@ python/guestfs-py.c + python/guestfs.pyc + regressions/rhbz501893 + regressions/test1.img ++regressions/test2.img + regressions/test.err + regressions/test.out + ruby/bindtests.rb +diff --git a/daemon/Makefile.am b/daemon/Makefile.am +index 3fa2b31..cf9f7ca 100644 +--- a/daemon/Makefile.am ++++ b/daemon/Makefile.am +@@ -99,6 +99,7 @@ guestfsd_SOURCES = \ + link.c \ + ls.c \ + lvm.c \ ++ lvm-filter.c \ + mkfs.c \ + mknod.c \ + modprobe.c \ +diff --git a/daemon/lvm-filter.c b/daemon/lvm-filter.c +new file mode 100644 +index 0000000..e487c6b +--- /dev/null ++++ b/daemon/lvm-filter.c +@@ -0,0 +1,244 @@ ++/* libguestfs - the guestfsd daemon ++ * Copyright (C) 2010 Red Hat Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++ ++#include "daemon.h" ++#include "c-ctype.h" ++#include "actions.h" ++ ++/* Does the current line match the regexp /^\s*filter\s*=/ */ ++static int ++is_filter_line (const char *line) ++{ ++ while (*line && c_isspace (*line)) ++ line++; ++ if (!*line) ++ return 0; ++ ++ if (! STRPREFIX (line, "filter")) ++ return 0; ++ line += 6; ++ ++ while (*line && c_isspace (*line)) ++ line++; ++ if (!*line) ++ return 0; ++ ++ if (*line != '=') ++ return 0; ++ ++ return 1; ++} ++ ++/* Rewrite the 'filter = [ ... ]' line in /etc/lvm/lvm.conf. */ ++static int ++set_filter (const char *filter) ++{ ++ if (verbose) ++ fprintf (stderr, "LVM: setting device filter to %s\n", filter); ++ ++ FILE *ifp = fopen ("/etc/lvm/lvm.conf", "r"); ++ if (ifp == NULL) { ++ reply_with_perror ("open: /etc/lvm/lvm.conf"); ++ return -1; ++ } ++ FILE *ofp = fopen ("/etc/lvm/lvm.conf.new", "w"); ++ if (ofp == NULL) { ++ reply_with_perror ("open: /etc/lvm/lvm.conf.new"); ++ fclose (ifp); ++ return -1; ++ } ++ ++ char *line = NULL; ++ size_t len = 0; ++ while (getline (&line, &len, ifp) != -1) { ++ int r; ++ if (is_filter_line (line)) { ++ if (verbose) ++ fprintf (stderr, "LVM: replacing config line:\n%s", line); ++ r = fprintf (ofp, " filter = [ %s ]\n", filter); ++ } else { ++ r = fprintf (ofp, "%s", line); ++ } ++ if (r < 0) { ++ /* NB. fprintf doesn't set errno on error. */ ++ reply_with_error ("/etc/lvm/lvm.conf.new: write failed"); ++ fclose (ifp); ++ fclose (ofp); ++ free (line); ++ unlink ("/etc/lvm/lvm.conf.new"); ++ return -1; ++ } ++ } ++ ++ free (line); ++ ++ if (fclose (ifp) == EOF) { ++ reply_with_perror ("/etc/lvm/lvm.conf.new"); ++ unlink ("/etc/lvm/lvm.conf.new"); ++ fclose (ofp); ++ return -1; ++ } ++ if (fclose (ofp) == EOF) { ++ reply_with_perror ("/etc/lvm/lvm.conf.new"); ++ unlink ("/etc/lvm/lvm.conf.new"); ++ return -1; ++ } ++ ++ if (rename ("/etc/lvm/lvm.conf.new", "/etc/lvm/lvm.conf") == -1) { ++ reply_with_perror ("rename: /etc/lvm/lvm.conf"); ++ unlink ("/etc/lvm/lvm.conf.new"); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static int ++vgchange (const char *vgchange_flag) ++{ ++ char *err; ++ int r = command (NULL, &err, "lvm", "vgchange", vgchange_flag, NULL); ++ if (r == -1) { ++ reply_with_error ("vgscan: %s", err); ++ free (err); ++ return -1; ++ } ++ ++ free (err); ++ return 0; ++} ++ ++/* Deactivate all VGs. */ ++static int ++deactivate (void) ++{ ++ return vgchange ("-an"); ++} ++ ++/* Reactivate all VGs. */ ++static int ++reactivate (void) ++{ ++ return vgchange ("-ay"); ++} ++ ++/* Clear the cache and rescan. */ ++static int ++rescan (void) ++{ ++ unlink ("/etc/lvm/cache/.cache"); ++ ++ char *err; ++ int r = command (NULL, &err, "lvm", "vgscan", NULL); ++ if (r == -1) { ++ reply_with_error ("vgscan: %s", err); ++ free (err); ++ return -1; ++ } ++ ++ free (err); ++ return 0; ++} ++ ++/* Construct the new, specific filter string. We can assume that ++ * the 'devices' array does not contain any regexp metachars, ++ * because it's already been checked by the stub code. ++ */ ++static char * ++make_filter_string (char *const *devices) ++{ ++ size_t i; ++ size_t len = 64; ++ for (i = 0; devices[i] != NULL; ++i) ++ len += strlen (devices[i]) + 16; ++ ++ char *filter = malloc (len); ++ if (filter == NULL) { ++ reply_with_perror ("malloc"); ++ return NULL; ++ } ++ ++ char *p = filter; ++ for (i = 0; devices[i] != NULL; ++i) { ++ /* Because of the way matching works in LVM, each match clause ++ * should be either: ++ * "a|^/dev/sda|", for whole block devices, or ++ * "a|^/dev/sda1$|", for single partitions ++ * (the assumption being we have <= 26 block devices XXX). ++ */ ++ size_t slen = strlen (devices[i]); ++ char str[slen+16]; ++ ++ if (c_isdigit (devices[i][slen-1])) ++ snprintf (str, slen+16, "\"a|^%s$|\", ", devices[i]); ++ else ++ snprintf (str, slen+16, "\"a|^%s|\", ", devices[i]); ++ ++ strcpy (p, str); ++ p += strlen (str); ++ } ++ strcpy (p, "\"r|.*|\""); ++ ++ return filter; /* Caller must free. */ ++} ++ ++int ++do_lvm_set_filter (char *const *devices) ++{ ++ char *filter = make_filter_string (devices); ++ if (filter == NULL) ++ return -1; ++ ++ if (deactivate () == -1) { ++ free (filter); ++ return -1; ++ } ++ ++ int r = set_filter (filter); ++ free (filter); ++ if (r == -1) ++ return -1; ++ ++ if (rescan () == -1) ++ return -1; ++ ++ return reactivate (); ++} ++ ++int ++do_lvm_clear_filter (void) ++{ ++ if (deactivate () == -1) ++ return -1; ++ ++ if (set_filter ("\"a/.*/\"") == -1) ++ return -1; ++ ++ if (rescan () == -1) ++ return -1; ++ ++ return reactivate (); ++} +diff --git a/po/POTFILES.in b/po/POTFILES.in +index b7e9bd4..6241ffa 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -33,6 +33,7 @@ daemon/initrd.c + daemon/inotify.c + daemon/link.c + daemon/ls.c ++daemon/lvm-filter.c + daemon/lvm.c + daemon/mkfs.c + daemon/mknod.c +diff --git a/regressions/Makefile.am b/regressions/Makefile.am +index 41f5183..5736aaf 100644 +--- a/regressions/Makefile.am ++++ b/regressions/Makefile.am +@@ -35,6 +35,7 @@ TESTS = \ + test-cancellation-download-librarycancels.sh \ + test-cancellation-upload-daemoncancels.sh \ + test-find0.sh \ ++ test-lvm-filtering.sh \ + test-lvm-mapping.pl \ + test-noexec-stack.pl \ + test-qemudie-killsub.sh \ +diff --git a/regressions/test-lvm-filtering.sh b/regressions/test-lvm-filtering.sh +new file mode 100755 +index 0000000..d7c4e7c +--- /dev/null ++++ b/regressions/test-lvm-filtering.sh +@@ -0,0 +1,92 @@ ++#!/bin/bash - ++# libguestfs ++# Copyright (C) 2010 Red Hat Inc. ++# ++# This program is free software; you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation; either version 2 of the License, or ++# (at your option) any later version. ++# ++# This program is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with this program; if not, write to the Free Software ++# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ ++# Test LVM device filtering. ++ ++set -e ++ ++rm -f test1.img test2.img ++ ++actual=$(../fish/guestfish <<'EOF' ++sparse test1.img 1G ++sparse test2.img 1G ++ ++run ++ ++part-disk /dev/sda mbr ++part-disk /dev/sdb mbr ++ ++pvcreate /dev/sda1 ++pvcreate /dev/sdb1 ++ ++vgcreate VG1 /dev/sda1 ++vgcreate VG2 /dev/sdb1 ++ ++# Should see VG1 and VG2 ++vgs ++ ++# Should see just VG1 ++lvm-set-filter /dev/sda ++vgs ++lvm-set-filter /dev/sda1 ++vgs ++ ++# Should see just VG2 ++lvm-set-filter /dev/sdb ++vgs ++lvm-set-filter /dev/sdb1 ++vgs ++ ++# Should see VG1 and VG2 ++lvm-set-filter "/dev/sda /dev/sdb" ++vgs ++lvm-set-filter "/dev/sda1 /dev/sdb1" ++vgs ++lvm-set-filter "/dev/sda /dev/sdb1" ++vgs ++lvm-set-filter "/dev/sda1 /dev/sdb" ++vgs ++lvm-clear-filter ++vgs ++EOF ++) ++ ++expected="VG1 ++VG2 ++VG1 ++VG1 ++VG2 ++VG2 ++VG1 ++VG2 ++VG1 ++VG2 ++VG1 ++VG2 ++VG1 ++VG2 ++VG1 ++VG2" ++ ++rm -f test1.img test2.img ++ ++if [ "$actual" != "$expected" ]; then ++ echo "LVM filter test failed. Actual output was:" ++ echo "$actual" ++ exit 1 ++fi +diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR +index f1aaa90..9183bf0 100644 +--- a/src/MAX_PROC_NR ++++ b/src/MAX_PROC_NR +@@ -1 +1 @@ +-254 ++256 +diff --git a/src/generator.ml b/src/generator.ml +index b31b3c9..f79a1a7 100755 +--- a/src/generator.ml ++++ b/src/generator.ml +@@ -4830,6 +4830,47 @@ C. + + If the filesystem does not have a UUID, this returns the empty string."); + ++ ("lvm_set_filter", (RErr, [DeviceList "devices"]), 255, [Optional "lvm2"], ++ (* Can't be tested with the current framework because ++ * the VG is being used by the mounted filesystem, so ++ * the vgchange -an command we do first will fail. ++ *) ++ [], ++ "set LVM device filter", ++ "\ ++This sets the LVM device filter so that LVM will only be ++able to \"see\" the block devices in the list C, ++and will ignore all other attached block devices. ++ ++Where disk image(s) contain duplicate PVs or VGs, this ++command is useful to get LVM to ignore the duplicates, otherwise ++LVM can get confused. Note also there are two types ++of duplication possible: either cloned PVs/VGs which have ++identical UUIDs; or VGs that are not cloned but just happen ++to have the same name. In normal operation you cannot ++create this situation, but you can do it outside LVM, eg. ++by cloning disk images or by bit twiddling inside the LVM ++metadata. ++ ++This command also clears the LVM cache and performs a volume ++group scan. ++ ++You can filter whole block devices or individual partitions. ++ ++You cannot use this if any VG is currently in use (eg. ++contains a mounted filesystem), even if you are not ++filtering out that VG."); ++ ++ ("lvm_clear_filter", (RErr, []), 256, [], ++ [], (* see note on lvm_set_filter *) ++ "clear LVM device filter", ++ "\ ++This undoes the effect of C. LVM ++will be able to see every block device. ++ ++This command also clears the LVM cache and performs a volume ++group scan."); ++ + ] + + let all_functions = non_daemon_functions @ daemon_functions +-- +1.7.1 + diff --git a/0004-df-Minimize-the-number-of-times-we-launch-the-libgue.patch b/0004-df-Minimize-the-number-of-times-we-launch-the-libgue.patch new file mode 100644 index 0000000..a429d72 --- /dev/null +++ b/0004-df-Minimize-the-number-of-times-we-launch-the-libgue.patch @@ -0,0 +1,291 @@ +From 6be1ba8a63f53b4167262250be1c122c7c08f61d Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Fri, 16 Jul 2010 18:11:56 +0100 +Subject: [PATCH] df: Minimize the number of times we launch the libguestfs appliance. + +This commit greatly improves the performance of the 'virt-df' +command by batching as many disks as possible onto a single appliance. +In many situations this means the appliance is launched only once, +versus one launch per domain as before. + +However doing it this way is a lot more complex: + +(1) Because of limits in Linux and virtio-blk, we can only attach +26 disks maximum at a time to the appliance. + +(2) We have to use LVM filters (lvm-set-filter) to confine LVM to +the disks of a single guest. +(cherry picked from commit 45a4cd79215752234c4a5a47fb4c9c6741b1594c) +--- + tools/virt-df | 217 ++++++++++++++++++++++++++++++++++++++++++++++----------- + 1 files changed, 177 insertions(+), 40 deletions(-) + +diff --git a/tools/virt-df b/tools/virt-df +index 45b7869..790dd6a 100755 +--- a/tools/virt-df ++++ b/tools/virt-df +@@ -20,12 +20,11 @@ use warnings; + use strict; + + use Sys::Guestfs; +-use Sys::Guestfs::Lib qw(open_guest get_partitions); ++use Sys::Guestfs::Lib qw(feature_available); + + use Pod::Usage; + use Getopt::Long; +-use Data::Dumper; +-use XML::Writer; ++use File::Basename qw(basename); + use POSIX qw(ceil); + + use Locale::TextDomain 'libguestfs'; +@@ -151,9 +150,22 @@ if ($version) { + # RHBZ#600977 + die __"virt-df: cannot use -h and --csv options together\n" if $human && $csv; + +-# Open the guest handle. ++# Get the list of domains and block devices. ++# ++# We can't use Sys::Guestfs::Lib::open_guest here because we want to ++# create the libguestfs handle/appliance as few times as possible. ++# ++# If virt-df is called with no parameters, then run the libvirt ++# equivalent of "virsh list --all", get the XML for each domain, and ++# get the disk devices. ++# ++# If virt-df is called with parameters, assume it must either be a ++# single disk image filename, a list of disk image filenames, or a ++# single libvirt guest name. Construct disk devices accordingly. + +-if (@ARGV == 0) { ++my @domains = (); ++ ++if (@ARGV == 0) { # No params, use libvirt. + my $conn; + + if ($uri) { +@@ -168,61 +180,186 @@ if (@ARGV == 0) { + # https://bugzilla.redhat.com/show_bug.cgi?id=538041 + @doms = grep { $_->get_id () != 0 } @doms; + +- my @domnames = sort (map { $_->get_name () } @doms); ++ exit 0 unless @doms; ++ ++ foreach my $dom (@doms) { ++ my @disks = get_disks_from_libvirt ($dom); ++ push @domains, { dom => $dom, ++ name => $dom->get_name (), ++ disks => \@disks } ++ } ++} elsif (@ARGV == 1) { # One param, could be disk image or domname. ++ if (-e $ARGV[0]) { ++ push @domains, { name => basename ($ARGV[0]), ++ disks => [ $ARGV[0] ] } ++ } else { ++ my $conn; + +- if (@domnames) { +- print_title (); +- foreach (@domnames) { +- eval { do_df ($_); }; +- warn $@ if $@; ++ if ($uri) { ++ $conn = Sys::Virt->new (readonly => 1, address => $uri); ++ } else { ++ $conn = Sys::Virt->new (readonly => 1); + } ++ ++ my $dom = $conn->get_domain_by_name ($ARGV[0]) ++ or die __x("{name} is not the name of a libvirt domain\n", ++ name => $ARGV[0]); ++ my @disks = get_disks_from_libvirt ($dom); ++ push @domains, { dom => $dom, ++ name => $dom->get_name (), ++ disks => \@disks } + } +-} else { +- print_title (); +- do_df (@ARGV); ++} else { # >= 2 params, all disk images. ++ push @domains, { name => basename ($ARGV[0]), ++ disks => \@ARGV } + } + +-sub do_df ++sub get_disks_from_libvirt + { +- my $g; ++ my $dom = shift; ++ my $xml = $dom->get_xml_description (); + +- if ($uri) { +- $g = open_guest (\@_, address => $uri); +- } else { +- $g = open_guest (\@_); ++ my $p = XML::XPath->new (xml => $xml); ++ my @disks = $p->findnodes ('//devices/disk/source/@dev'); ++ push (@disks, $p->findnodes ('//devices/disk/source/@file')); ++ ++ # Code in Sys::Guestfs::Lib dies here if there are no disks at all. ++ ++ return map { $_->getData } @disks; ++} ++ ++# Sort the domains by name for display. ++@domains = sort { $a->{name} cmp $b->{name} } @domains; ++ ++# Since we got this far, we're somewhat sure we're going to ++# get to print the result, so display the title. ++print_title (); ++ ++# To minimize the number of times we have to launch the appliance, ++# shuffle as many domains together as we can, but not exceeding 26 ++# disks per request. (26 = # of letters in the English alphabet, and ++# we are only confident that /dev/sd[a-z] will work because of various ++# limits). ++my $n = 0; ++my @request = (); ++while (@domains) { ++ while (@domains) { ++ my $c = @{$domains[0]->{disks}}; ++ last if $n + $c > 26; ++ push @request, shift @domains; ++ } ++ multi_df (@request); ++} ++ ++sub multi_df ++{ ++ local $_; ++ my $g = Sys::Guestfs->new (); ++ ++ my ($d, $disk); ++ ++ foreach $d (@_) { ++ foreach $disk (@{$d->{disks}}) { ++ $g->add_drive_ro ($disk); ++ } + } + + $g->launch (); ++ my $has_lvm2 = feature_available ($g, "lvm2"); + +- my @partitions = get_partitions ($g); +- +- # Think of a printable name for this domain. Just choose the +- # first parameter passed to this function, which will work for +- # most cases (it'll either be the domain name or the first disk +- # image name). +- my $domname = $_[0]; +- +- # Mount each partition in turn, and if mountable, do a statvfs on it. +- foreach my $partition (@partitions) { +- my %stat; +- eval { +- $g->mount_ro ($partition, "/"); +- %stat = $g->statvfs ("/"); +- }; +- if (!$@) { +- print_stat ($domname, $partition, \%stat); ++ my @devices = $g->list_devices (); ++ my @partitions = $g->list_partitions (); ++ ++ my $n = 0; ++ foreach $d (@_) { ++ my $name = $d->{name}; ++ my $nr_disks = @{$d->{disks}}; ++ ++ # Filter LVM to only the devices applying to the original domain. ++ my @devs = @devices[$n .. $n+$nr_disks-1]; ++ $g->lvm_set_filter (\@devs) if $has_lvm2; ++ ++ # Find which whole devices (RHBZ#590167), partitions and LVs ++ # contain mountable filesystems. Stat those which are ++ # mountable, and ignore the others. ++ foreach (@devs) { ++ try_df ($name, $g, $_, canonical_dev ($_, $n)); ++ } ++ foreach (filter_partitions (\@devs, @partitions)) { ++ try_df ($name, $g, $_, canonical_dev ($_, $n)); ++ } ++ if ($has_lvm2) { ++ foreach ($g->lvs ()) { ++ try_df ($name, $g, $_); ++ } ++ } ++ ++ $n += $nr_disks; ++ } ++} ++ ++sub filter_partitions ++{ ++ my $devs = shift; ++ my @devs = @$devs; ++ my @r; ++ ++ foreach my $p (@_) { ++ foreach my $d (@devs) { ++ if ($p =~ /^$d\d/) { ++ push @r, $p; ++ last; ++ } + } +- $g->umount_all (); + } ++ ++ return @r; ++} ++ ++# Calculate the canonical name for a device. ++# eg: /dev/vdb1 when offset = 1 ++# => canonical name is /dev/sda1 ++sub canonical_dev ++{ ++ local $_; ++ my $dev = shift; ++ my $offset = shift; ++ ++ return $dev unless $dev =~ m{^/dev/.d([a-z])(\d*)$}; ++ my $disk = $1; ++ my $partnum = $2; ++ ++ $disk = chr (ord ($disk) - $offset); ++ ++ return "/dev/sd$disk$partnum" ++} ++ ++sub try_df ++{ ++ local $_; ++ my $domname = shift; ++ my $g = shift; ++ my $dev = shift; ++ my $display = shift || $dev; ++ ++ my %stat; ++ eval { ++ $g->mount_ro ($dev, "/"); ++ %stat = $g->statvfs ("/"); ++ }; ++ if (!$@) { ++ print_stat ($domname, $display, \%stat); ++ } ++ $g->umount_all (); + } + + sub print_stat + { + my $domname = shift; +- my $partition = shift; ++ my $dev = shift; + my $stat = shift; + +- my @cols = ($domname, $partition); ++ my @cols = ($domname, $dev); + + if (!$inodes) { + my $bsize = $stat->{bsize}; # block size +-- +1.7.1 + diff --git a/0005-generator-Add-Key-parameter-type.patch b/0005-generator-Add-Key-parameter-type.patch new file mode 100644 index 0000000..59d2a04 --- /dev/null +++ b/0005-generator-Add-Key-parameter-type.patch @@ -0,0 +1,719 @@ +From 01f1d87dbbad5bb0b825ebb8a0ce2b5924546e41 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Wed, 21 Jul 2010 12:52:51 +0100 +Subject: [PATCH] generator: Add 'Key' parameter type. + +Add a 'Key' parameter type, used for passing sensitive key material +into libguestfs. + +Eventually the plan is to mlock() key material into memory. However +this is very difficult to achieve because the encoded XDR strings +end up in many places. Therefore users should note that key material +passed to libguestfs might end up in swap. + +The only difference between 'Key' and 'String' currently is that +guestfish requests the key from /dev/tty with echoing turned off. +(cherry picked from commit 581a7965faa5bf242ab3f8b7c259ab17c2e967f4) +--- + fish/fish.c | 67 +++++++++++++++++ + fish/fish.h | 1 + + fish/guestfish.pod | 5 ++ + src/generator.ml | 202 ++++++++++++++++++++++++++++++++------------------- + src/guestfs.pod | 15 ++++ + 5 files changed, 215 insertions(+), 75 deletions(-) + +diff --git a/fish/fish.c b/fish/fish.c +index 4276ae1..68f26ed 100644 +--- a/fish/fish.c ++++ b/fish/fish.c +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + + #ifdef HAVE_LIBREADLINE + #include +@@ -80,6 +81,7 @@ int remote_control_listen = 0; + int remote_control = 0; + int exit_on_error = 1; + int command_num = 0; ++int keys_from_stdin = 0; + + static void __attribute__((noreturn)) + usage (int status) +@@ -110,6 +112,7 @@ usage (int status) + " -D|--no-dest-paths Don't tab-complete paths from guest fs\n" + " -f|--file file Read commands from file\n" + " -i|--inspector Run virt-inspector to get disk mountpoints\n" ++ " --keys-from-stdin Read passphrases from stdin\n" + " --listen Listen for remote commands\n" + " -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n" + " -n|--no-sync Don't autosync\n" +@@ -149,6 +152,7 @@ main (int argc, char *argv[]) + { "file", 1, 0, 'f' }, + { "help", 0, 0, HELP_OPTION }, + { "inspector", 0, 0, 'i' }, ++ { "keys-from-stdin", 0, 0, 0 }, + { "listen", 0, 0, 0 }, + { "mount", 1, 0, 'm' }, + { "new", 1, 0, 'N' }, +@@ -239,6 +243,8 @@ main (int argc, char *argv[]) + } + } else if (STREQ (long_options[option_index].name, "selinux")) { + guestfs_set_selinux (g, 1); ++ } else if (STREQ (long_options[option_index].name, "keys-from-stdin")) { ++ keys_from_stdin = 1; + } else { + fprintf (stderr, _("%s: unknown long option: %s (%d)\n"), + program_name, long_options[option_index].name, option_index); +@@ -1710,6 +1716,67 @@ file_out (const char *arg) + return ret; + } + ++/* Read a passphrase ('Key') from /dev/tty with echo off. ++ * The caller (cmds.c) will call free on the string afterwards. ++ * Based on the code in cryptsetup file lib/utils.c. ++ */ ++char * ++read_key (const char *param) ++{ ++ FILE *infp, *outfp; ++ struct termios orig, temp; ++ char *ret = NULL; ++ ++ /* Read and write to /dev/tty if available. */ ++ if (keys_from_stdin || ++ (infp = outfp = fopen ("/dev/tty", "w+")) == NULL) { ++ infp = stdin; ++ outfp = stdout; ++ } ++ ++ /* Print the prompt and set no echo. */ ++ int tty = isatty (fileno (infp)); ++ int tcset = 0; ++ if (tty) { ++ fprintf (outfp, _("Enter key or passphrase (\"%s\"): "), param); ++ ++ if (tcgetattr (fileno (infp), &orig) == -1) { ++ perror ("tcgetattr"); ++ goto error; ++ } ++ memcpy (&temp, &orig, sizeof temp); ++ temp.c_lflag &= ~ECHO; ++ ++ tcsetattr (fileno (infp), TCSAFLUSH, &temp); ++ tcset = 1; ++ } ++ ++ size_t n = 0; ++ ssize_t len; ++ len = getline (&ret, &n, infp); ++ if (len == -1) { ++ perror ("getline"); ++ ret = NULL; ++ goto error; ++ } ++ ++ /* Remove the terminating \n if there is one. */ ++ if (len > 0 && ret[len-1] == '\n') ++ ret[len-1] = '\0'; ++ ++ error: ++ /* Restore echo, close file descriptor. */ ++ if (tty && tcset) { ++ printf ("\n"); ++ tcsetattr (fileno (infp), TCSAFLUSH, &orig); ++ } ++ ++ if (infp != stdin) ++ fclose (infp); /* outfp == infp, so this is closed also */ ++ ++ return ret; ++} ++ + static void + print_shell_quote (FILE *stream, const char *str) + { +diff --git a/fish/fish.h b/fish/fish.h +index 9f64979..da1b087 100644 +--- a/fish/fish.h ++++ b/fish/fish.h +@@ -68,6 +68,7 @@ extern char *file_in (const char *arg); + extern void free_file_in (char *s); + extern char *file_out (const char *arg); + extern void extended_help_message (void); ++extern char *read_key (const char *param); + + /* in cmds.c (auto-generated) */ + extern void list_commands (void); +diff --git a/fish/guestfish.pod b/fish/guestfish.pod +index 5737c46..86dcf58 100644 +--- a/fish/guestfish.pod ++++ b/fish/guestfish.pod +@@ -179,6 +179,11 @@ I<--ro> might not behave correctly. + + See also: L. + ++=item B<--keys-from-stdin> ++ ++Read key or passphrase parameters from stdin. The default is ++to try to read passphrases from the user by opening C. ++ + =item B<--listen> + + Fork into the background and listen for remote commands. See section +diff --git a/src/generator.ml b/src/generator.ml +index f79a1a7..70cba24 100755 +--- a/src/generator.ml ++++ b/src/generator.ml +@@ -174,6 +174,14 @@ and argt = + * To return an arbitrary buffer, use RBufferOut. + *) + | BufferIn of string ++ (* Key material / passphrase. Eventually we should treat this ++ * as sensitive and mlock it into physical RAM. However this ++ * is highly complex because of all the places that XDR-encoded ++ * strings can end up. So currently the only difference from ++ * 'String' is the way that guestfish requests these parameters ++ * from the user. ++ *) ++ | Key of string + + type flags = + | ProtocolLimitWarning (* display warning about protocol size limits *) +@@ -5292,7 +5300,7 @@ let map_chars f str = + let name_of_argt = function + | Pathname n | Device n | Dev_or_Path n | String n | OptString n + | StringList n | DeviceList n | Bool n | Int n | Int64 n +- | FileIn n | FileOut n | BufferIn n -> n ++ | FileIn n | FileOut n | BufferIn n | Key n -> n + + let java_name_of_struct typ = + try List.assoc typ java_structs +@@ -5653,6 +5661,10 @@ I.\n\n" + pr "%s\n\n" protocol_limit_warning; + if List.mem DangerWillRobinson flags then + pr "%s\n\n" danger_will_robinson; ++ if List.exists (function Key _ -> true | _ -> false) (snd style) then ++ pr "This function takes a key or passphrase parameter which ++could contain sensitive material. Read the section ++L for more information.\n\n"; + match deprecation_notice flags with + | None -> () + | Some txt -> pr "%s\n\n" txt +@@ -5758,7 +5770,7 @@ and generate_xdr () = + pr "struct %s_args {\n" name; + List.iter ( + function +- | Pathname n | Device n | Dev_or_Path n | String n -> ++ | Pathname n | Device n | Dev_or_Path n | String n | Key n -> + pr " string %s<>;\n" n + | OptString n -> pr " guestfs_str *%s;\n" n + | StringList n | DeviceList n -> pr " guestfs_str %s<>;\n" n +@@ -6037,7 +6049,8 @@ check_state (guestfs_h *g, const char *caller) + | FileOut n + | BufferIn n + | StringList n +- | DeviceList n -> ++ | DeviceList n ++ | Key n -> + pr " if (%s == NULL) {\n" n; + pr " error (g, \"%%s: %%s: parameter cannot be NULL\",\n"; + pr " \"%s\", \"%s\");\n" shortname n; +@@ -6079,7 +6092,8 @@ check_state (guestfs_h *g, const char *caller) + | Dev_or_Path n + | FileIn n + | FileOut n +- | BufferIn n -> ++ | BufferIn n ++ | Key n -> + (* guestfish doesn't support string escaping, so neither do we *) + pr " printf (\" \\\"%%s\\\"\", %s);\n" n + | OptString n -> (* string option *) +@@ -6172,7 +6186,7 @@ check_state (guestfs_h *g, const char *caller) + | args -> + List.iter ( + function +- | Pathname n | Device n | Dev_or_Path n | String n -> ++ | Pathname n | Device n | Dev_or_Path n | String n | Key n -> + pr " args.%s = (char *) %s;\n" n n + | OptString n -> + pr " args.%s = %s ? (char **) &%s : NULL;\n" n n n +@@ -6452,7 +6466,8 @@ and generate_daemon_actions () = + function + | Device n | Dev_or_Path n + | Pathname n +- | String n -> () ++ | String n ++ | Key n -> () + | OptString n -> pr " char *%s;\n" n + | StringList n | DeviceList n -> pr " char **%s;\n" n + | Bool n -> pr " int %s;\n" n +@@ -6509,7 +6524,7 @@ and generate_daemon_actions () = + pr_args n; + pr " REQUIRE_ROOT_OR_RESOLVE_DEVICE (%s, %s, goto done);\n" + n (if is_filein then "cancel_receive ()" else "0"); +- | String n -> pr_args n ++ | String n | Key n -> pr_args n + | OptString n -> pr " %s = args.%s ? *args.%s : NULL;\n" n n n + | StringList n -> + pr_list_handling_code n; +@@ -7524,7 +7539,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd = + | Device n, arg + | Dev_or_Path n, arg + | String n, arg +- | OptString n, arg -> ++ | OptString n, arg ++ | Key n, arg -> + pr " const char *%s = \"%s\";\n" n (c_quote arg); + | BufferIn n, arg -> + pr " const char *%s = \"%s\";\n" n (c_quote arg); +@@ -7579,7 +7595,8 @@ and generate_test_command_call ?(expect_error = false) ?test test_name cmd = + | Pathname n, _ + | Device n, _ | Dev_or_Path n, _ + | String n, _ +- | OptString n, _ -> ++ | OptString n, _ ++ | Key n, _ -> + pr ", %s" n + | BufferIn n, _ -> + pr ", %s, %s_size" n n +@@ -7705,6 +7722,7 @@ and generate_fish_cmds () = + match snd style with + | [] -> name2 + | args -> ++ let args = List.filter (function Key _ -> false | _ -> true) args in + sprintf "%s %s" + name2 (String.concat " " (List.map name_of_argt args)) in + +@@ -7870,7 +7888,8 @@ and generate_fish_cmds () = + | Pathname n + | Dev_or_Path n + | FileIn n +- | FileOut n -> pr " char *%s;\n" n ++ | FileOut n ++ | Key n -> pr " char *%s;\n" n + | BufferIn n -> + pr " const char *%s;\n" n; + pr " size_t %s_size;\n" n +@@ -7881,7 +7900,10 @@ and generate_fish_cmds () = + ) (snd style); + + (* Check and convert parameters. *) +- let argc_expected = List.length (snd style) in ++ let argc_expected = ++ let args_no_keys = ++ List.filter (function Key _ -> false | _ -> true) (snd style) in ++ List.length args_no_keys in + pr " if (argc != %d) {\n" argc_expected; + pr " fprintf (stderr, _(\"%%s should have %%d parameter(s)\\n\"), cmd, %d);\n" + argc_expected; +@@ -7889,12 +7911,12 @@ and generate_fish_cmds () = + pr " return -1;\n"; + pr " }\n"; + +- let parse_integer fn fntyp rtyp range name i = ++ let parse_integer fn fntyp rtyp range name = + pr " {\n"; + pr " strtol_error xerr;\n"; + pr " %s r;\n" fntyp; + pr "\n"; +- pr " xerr = %s (argv[%d], NULL, 0, &r, xstrtol_suffixes);\n" fn i; ++ pr " xerr = %s (argv[i++], NULL, 0, &r, xstrtol_suffixes);\n" fn; + pr " if (xerr != LONGINT_OK) {\n"; + pr " fprintf (stderr,\n"; + pr " _(\"%%s: %%s: invalid integer parameter (%%s returned %%d)\\n\"),\n"; +@@ -7916,43 +7938,49 @@ and generate_fish_cmds () = + pr " }\n"; + in + +- iteri ( +- fun i -> +- function +- | Device name +- | String name -> +- pr " %s = argv[%d];\n" name i +- | Pathname name +- | Dev_or_Path name -> +- pr " %s = resolve_win_path (argv[%d]);\n" name i; +- pr " if (%s == NULL) return -1;\n" name +- | OptString name -> +- pr " %s = STRNEQ (argv[%d], \"\") ? argv[%d] : NULL;\n" +- name i i +- | BufferIn name -> +- pr " %s = argv[%d];\n" name i; +- pr " %s_size = strlen (argv[%d]);\n" name i +- | FileIn name -> +- pr " %s = file_in (argv[%d]);\n" name i; +- pr " if (%s == NULL) return -1;\n" name +- | FileOut name -> +- pr " %s = file_out (argv[%d]);\n" name i; +- pr " if (%s == NULL) return -1;\n" name +- | StringList name | DeviceList name -> +- pr " %s = parse_string_list (argv[%d]);\n" name i; +- pr " if (%s == NULL) return -1;\n" name; +- | Bool name -> +- pr " %s = is_true (argv[%d]) ? 1 : 0;\n" name i +- | Int name -> +- let range = +- let min = "(-(2LL<<30))" +- and max = "((2LL<<30)-1)" +- and comment = +- "The Int type in the generator is a signed 31 bit int." in +- Some (min, max, comment) in +- parse_integer "xstrtoll" "long long" "int" range name i +- | Int64 name -> +- parse_integer "xstrtoll" "long long" "int64_t" None name i ++ if snd style <> [] then ++ pr " size_t i = 0;\n"; ++ ++ List.iter ( ++ function ++ | Device name ++ | String name -> ++ pr " %s = argv[i++];\n" name ++ | Pathname name ++ | Dev_or_Path name -> ++ pr " %s = resolve_win_path (argv[i++]);\n" name; ++ pr " if (%s == NULL) return -1;\n" name ++ | OptString name -> ++ pr " %s = STRNEQ (argv[i], \"\") ? argv[i] : NULL;\n" name; ++ pr " i++;\n" ++ | BufferIn name -> ++ pr " %s = argv[i];\n" name; ++ pr " %s_size = strlen (argv[i]);\n" name; ++ pr " i++;\n" ++ | FileIn name -> ++ pr " %s = file_in (argv[i++]);\n" name; ++ pr " if (%s == NULL) return -1;\n" name ++ | FileOut name -> ++ pr " %s = file_out (argv[i++]);\n" name; ++ pr " if (%s == NULL) return -1;\n" name ++ | StringList name | DeviceList name -> ++ pr " %s = parse_string_list (argv[i++]);\n" name; ++ pr " if (%s == NULL) return -1;\n" name ++ | Key name -> ++ pr " %s = read_key (\"%s\");\n" name name; ++ pr " if (%s == NULL) return -1;\n" name ++ | Bool name -> ++ pr " %s = is_true (argv[i++]) ? 1 : 0;\n" name ++ | Int name -> ++ let range = ++ let min = "(-(2LL<<30))" ++ and max = "((2LL<<30)-1)" ++ and comment = ++ "The Int type in the generator is a signed 31 bit int." in ++ Some (min, max, comment) in ++ parse_integer "xstrtoll" "long long" "int" range name ++ | Int64 name -> ++ parse_integer "xstrtoll" "long long" "int64_t" None name + ) (snd style); + + (* Call C API function. *) +@@ -7966,7 +7994,8 @@ and generate_fish_cmds () = + | OptString _ | Bool _ + | Int _ | Int64 _ + | BufferIn _ -> () +- | Pathname name | Dev_or_Path name | FileOut name -> ++ | Pathname name | Dev_or_Path name | FileOut name ++ | Key name -> + pr " free (%s);\n" name + | FileIn name -> + pr " free_file_in (%s);\n" name +@@ -8219,7 +8248,8 @@ and generate_fish_actions_pod () = + pr " %s" name; + List.iter ( + function +- | Pathname n | Device n | Dev_or_Path n | String n -> pr " %s" n ++ | Pathname n | Device n | Dev_or_Path n | String n -> ++ pr " %s" n + | OptString n -> pr " %s" n + | StringList n | DeviceList n -> pr " '%s ...'" n + | Bool _ -> pr " true|false" +@@ -8227,6 +8257,7 @@ and generate_fish_actions_pod () = + | Int64 n -> pr " %s" n + | FileIn n | FileOut n -> pr " (%s|-)" n + | BufferIn n -> pr " %s" n ++ | Key _ -> () (* keys are entered at a prompt *) + ) (snd style); + pr "\n"; + pr "\n"; +@@ -8236,6 +8267,10 @@ and generate_fish_actions_pod () = + | _ -> false) (snd style) then + pr "Use C<-> instead of a filename to read/write from stdin/stdout.\n\n"; + ++ if List.exists (function Key _ -> true | _ -> false) (snd style) then ++ pr "This command has one or more key or passphrase parameters. ++Guestfish will prompt for these separately.\n\n"; ++ + if List.mem ProtocolLimitWarning flags then + pr "%s\n\n" protocol_limit_warning; + +@@ -8290,7 +8325,8 @@ and generate_prototype ?(extern = true) ?(static = false) ?(semicolon = true) + | Pathname n + | Device n | Dev_or_Path n + | String n +- | OptString n -> ++ | OptString n ++ | Key n -> + next (); + pr "const char *%s" n + | StringList n | DeviceList n -> +@@ -8599,7 +8635,8 @@ copy_table (char * const * argv) + | Device n | Dev_or_Path n + | String n + | FileIn n +- | FileOut n -> ++ | FileOut n ++ | Key n -> + (* Copy strings in case the GC moves them: RHBZ#604691 *) + pr " char *%s = guestfs_safe_strdup (g, String_val (%sv));\n" n n + | OptString n -> +@@ -8655,7 +8692,7 @@ copy_table (char * const * argv) + List.iter ( + function + | Pathname n | Device n | Dev_or_Path n | String n | OptString n +- | FileIn n | FileOut n | BufferIn n -> ++ | FileIn n | FileOut n | BufferIn n | Key n -> + pr " free (%s);\n" n + | StringList n | DeviceList n -> + pr " ocaml_guestfs_free_strings (%s);\n" n; +@@ -8746,7 +8783,7 @@ and generate_ocaml_prototype ?(is_external = false) name style = + List.iter ( + function + | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _ +- | BufferIn _ -> pr "string -> " ++ | BufferIn _ | Key _ -> pr "string -> " + | OptString _ -> pr "string option -> " + | StringList _ | DeviceList _ -> pr "string array -> " + | Bool _ -> pr "bool -> " +@@ -8915,7 +8952,7 @@ close (g) + fun i -> + function + | Pathname n | Device n | Dev_or_Path n | String n +- | FileIn n | FileOut n -> ++ | FileIn n | FileOut n | Key n -> + pr " char *%s;\n" n + | BufferIn n -> + pr " char *%s;\n" n; +@@ -8938,7 +8975,7 @@ close (g) + | Pathname _ | Device _ | Dev_or_Path _ | String _ | OptString _ + | Bool _ | Int _ | Int64 _ + | FileIn _ | FileOut _ +- | BufferIn _ -> () ++ | BufferIn _ | Key _ -> () + | StringList n | DeviceList n -> pr " free (%s);\n" n + ) (snd style) + in +@@ -9334,7 +9371,7 @@ and generate_perl_prototype name style = + match arg with + | Pathname n | Device n | Dev_or_Path n | String n + | OptString n | Bool n | Int n | Int64 n | FileIn n | FileOut n +- | BufferIn n -> ++ | BufferIn n | Key n -> + pr "$%s" n + | StringList n | DeviceList n -> + pr "\\@%s" n +@@ -9605,7 +9642,7 @@ py_guestfs_close (PyObject *self, PyObject *args) + + List.iter ( + function +- | Pathname n | Device n | Dev_or_Path n | String n ++ | Pathname n | Device n | Dev_or_Path n | String n | Key n + | FileIn n | FileOut n -> + pr " const char *%s;\n" n + | OptString n -> pr " const char *%s;\n" n +@@ -9626,7 +9663,8 @@ py_guestfs_close (PyObject *self, PyObject *args) + pr " if (!PyArg_ParseTuple (args, (char *) \"O"; + List.iter ( + function +- | Pathname _ | Device _ | Dev_or_Path _ | String _ | FileIn _ | FileOut _ -> pr "s" ++ | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ ++ | FileIn _ | FileOut _ -> pr "s" + | OptString _ -> pr "z" + | StringList _ | DeviceList _ -> pr "O" + | Bool _ -> pr "i" (* XXX Python has booleans? *) +@@ -9640,7 +9678,8 @@ py_guestfs_close (PyObject *self, PyObject *args) + pr " &py_g"; + List.iter ( + function +- | Pathname n | Device n | Dev_or_Path n | String n | FileIn n | FileOut n -> pr ", &%s" n ++ | Pathname n | Device n | Dev_or_Path n | String n | Key n ++ | FileIn n | FileOut n -> pr ", &%s" n + | OptString n -> pr ", &%s" n + | StringList n | DeviceList n -> pr ", &py_%s" n + | Bool n -> pr ", &%s" n +@@ -9655,7 +9694,7 @@ py_guestfs_close (PyObject *self, PyObject *args) + pr " g = get_handle (py_g);\n"; + List.iter ( + function +- | Pathname _ | Device _ | Dev_or_Path _ | String _ ++ | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ + | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ + | BufferIn _ -> () + | StringList n | DeviceList n -> +@@ -9671,7 +9710,7 @@ py_guestfs_close (PyObject *self, PyObject *args) + + List.iter ( + function +- | Pathname _ | Device _ | Dev_or_Path _ | String _ ++ | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ + | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ + | BufferIn _ -> () + | StringList n | DeviceList n -> +@@ -9978,7 +10017,8 @@ static VALUE ruby_guestfs_close (VALUE gv) + + List.iter ( + function +- | Pathname n | Device n | Dev_or_Path n | String n | FileIn n | FileOut n -> ++ | Pathname n | Device n | Dev_or_Path n | String n | Key n ++ | FileIn n | FileOut n -> + pr " Check_Type (%sv, T_STRING);\n" n; + pr " const char *%s = StringValueCStr (%sv);\n" n n; + pr " if (!%s)\n" n; +@@ -10039,7 +10079,7 @@ static VALUE ruby_guestfs_close (VALUE gv) + + List.iter ( + function +- | Pathname _ | Device _ | Dev_or_Path _ | String _ ++ | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ + | FileIn _ | FileOut _ | OptString _ | Bool _ | Int _ | Int64 _ + | BufferIn _ -> () + | StringList n | DeviceList n -> +@@ -10356,7 +10396,8 @@ and generate_java_prototype ?(public=false) ?(privat=false) ?(native=false) + | String n + | OptString n + | FileIn n +- | FileOut n -> ++ | FileOut n ++ | Key n -> + pr "String %s" n + | BufferIn n -> + pr "byte[] %s" n +@@ -10479,7 +10520,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close + | String n + | OptString n + | FileIn n +- | FileOut n -> ++ | FileOut n ++ | Key n -> + pr ", jstring j%s" n + | BufferIn n -> + pr ", jbyteArray j%s" n +@@ -10536,7 +10578,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close + | String n + | OptString n + | FileIn n +- | FileOut n -> ++ | FileOut n ++ | Key n -> + pr " const char *%s;\n" n + | BufferIn n -> + pr " jbyte *%s;\n" n; +@@ -10573,7 +10616,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close + | Device n | Dev_or_Path n + | String n + | FileIn n +- | FileOut n -> ++ | FileOut n ++ | Key n -> + pr " %s = (*env)->GetStringUTFChars (env, j%s, NULL);\n" n n + | OptString n -> + (* This is completely undocumented, but Java null becomes +@@ -10610,7 +10654,8 @@ Java_com_redhat_et_libguestfs_GuestFS__1close + | Device n | Dev_or_Path n + | String n + | FileIn n +- | FileOut n -> ++ | FileOut n ++ | Key n -> + pr " (*env)->ReleaseStringUTFChars (env, j%s, %s);\n" n n + | OptString n -> + pr " if (j%s)\n" n; +@@ -10891,7 +10936,7 @@ last_error h = do + function + | FileIn n + | FileOut n +- | Pathname n | Device n | Dev_or_Path n | String n -> ++ | Pathname n | Device n | Dev_or_Path n | String n | Key n -> + pr "withCString %s $ \\%s -> " n n + | BufferIn n -> + pr "withCStringLen %s $ \\(%s, %s_size) -> " n n n +@@ -10907,7 +10952,10 @@ last_error h = do + | Int n -> sprintf "(fromIntegral %s)" n + | Int64 n -> sprintf "(fromIntegral %s)" n + | FileIn n | FileOut n +- | Pathname n | Device n | Dev_or_Path n | String n | OptString n | StringList n | DeviceList n -> n ++ | Pathname n | Device n | Dev_or_Path n ++ | String n | OptString n ++ | StringList n | DeviceList n ++ | Key n -> n + | BufferIn n -> sprintf "%s (fromIntegral %s_size)" n n + ) (snd style) in + pr "withForeignPtr h (\\p -> c_%s %s)\n" name +@@ -10958,7 +11006,8 @@ and generate_haskell_prototype ~handle ?(hs = false) style = + List.iter ( + fun arg -> + (match arg with +- | Pathname _ | Device _ | Dev_or_Path _ | String _ -> pr "%s" string ++ | Pathname _ | Device _ | Dev_or_Path _ | String _ | Key _ -> ++ pr "%s" string + | BufferIn _ -> + if hs then pr "String" + else pr "CString -> CInt" +@@ -11152,6 +11201,7 @@ namespace Guestfs + function + | Pathname n | Device n | Dev_or_Path n | String n | OptString n + | FileIn n | FileOut n ++ | Key n + | BufferIn n -> + pr ", [In] string %s" n + | StringList n | DeviceList n -> +@@ -11176,6 +11226,7 @@ namespace Guestfs + function + | Pathname n | Device n | Dev_or_Path n | String n | OptString n + | FileIn n | FileOut n ++ | Key n + | BufferIn n -> + next (); pr "string %s" n + | StringList n | DeviceList n -> +@@ -11280,7 +11331,8 @@ print_strings (char *const *argv) + | Device n | Dev_or_Path n + | String n + | FileIn n +- | FileOut n -> pr " printf (\"%%s\\n\", %s);\n" n ++ | FileOut n ++ | Key n -> pr " printf (\"%%s\\n\", %s);\n" n + | BufferIn n -> + pr " {\n"; + pr " size_t i;\n"; +diff --git a/src/guestfs.pod b/src/guestfs.pod +index e876016..8e3d07c 100644 +--- a/src/guestfs.pod ++++ b/src/guestfs.pod +@@ -675,6 +675,21 @@ L and L document how to do this. + You might also consider mounting the disk image using our FUSE + filesystem support (L). + ++=head2 KEYS AND PASSPHRASES ++ ++Certain libguestfs calls take a parameter that contains sensitive key ++material, passed in as a C string. ++ ++In the future we would hope to change the libguestfs implementation so ++that keys are L-ed into physical RAM, and thus can never end ++up in swap. However this is I done at the moment, because of the ++complexity of such an implementation. ++ ++Therefore you should be aware that any key parameter you pass to ++libguestfs might end up being written out to the swap partition. If ++this is a concern, scrub the swap partition or don't use libguestfs on ++encrypted devices. ++ + =head1 CONNECTION MANAGEMENT + + =head2 guestfs_h * +-- +1.7.1 + diff --git a/0006-New-APIs-Support-for-opening-LUKS-encrypted-disks.patch b/0006-New-APIs-Support-for-opening-LUKS-encrypted-disks.patch new file mode 100644 index 0000000..1d2391e --- /dev/null +++ b/0006-New-APIs-Support-for-opening-LUKS-encrypted-disks.patch @@ -0,0 +1,443 @@ +From 04e2408a8fb2251befe45a77c6eea87615402c6b Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Wed, 21 Jul 2010 19:50:06 +0100 +Subject: [PATCH] New APIs: Support for opening LUKS-encrypted disks. + +This adds support for opening LUKS-encrypted disks, via +three new APIs: + + luks_open: Create a mapping for an encrypted disk. + luks_open_ro: Same, but read-only mapping. + luks_close: Close a mapping. + +A typical guestfish session using this functionality looks +like this: + + $ guestfish --ro -a encrypted.img + > run + > list-devices + /dev/vda + > list-partitions + /dev/vda1 + /dev/vda2 + > vfs-type /dev/vda2 + crypto_LUKS + > luks-open /dev/vda2 luksdev + Enter key or passphrase ("key"): + > vgscan + > vg-activate-all true + > pvs + /dev/dm-0 + > vgs + vg_f13x64encrypted + > lvs + /dev/vg_f13x64encrypted/lv_root + /dev/vg_f13x64encrypted/lv_swap + > mount /dev/vg_f13x64encrypted/lv_root / + > ll / + total 132 + dr-xr-xr-x. 24 root root 4096 Jul 21 12:01 . + dr-xr-xr-x 20 root root 0 Jul 21 20:06 .. + drwx------. 3 root root 4096 Jul 21 11:59 .dbus + drwx------. 2 root root 4096 Jul 21 12:00 .pulse + -rw-------. 1 root root 256 Jul 21 12:00 .pulse-cookie + dr-xr-xr-x. 2 root root 4096 May 13 03:03 bin + +NOT included in this patch: + + - An easier way to use this from guestfish. + - Ability to create LUKS devices. + - Ability to change LUKS keys on existing devices. + - Direct access to the /dev/mapper device (eg. if it contains + anything apart from VGs). +(cherry picked from commit 637f8df83726ab9b50e8a6d2181bd1e0e93ec13e) +--- + TODO | 13 ++++ + appliance/kmod.whitelist.in | 15 +++++ + appliance/packagelist.in | 2 + + daemon/Makefile.am | 1 + + daemon/luks.c | 138 +++++++++++++++++++++++++++++++++++++++++++ + fish/guestfish.pod | 33 ++++++++++ + po/POTFILES.in | 1 + + src/MAX_PROC_NR | 2 +- + src/generator.ml | 37 ++++++++++++ + src/guestfs.pod | 31 ++++++++++ + 10 files changed, 272 insertions(+), 1 deletions(-) + create mode 100644 daemon/luks.c + +diff --git a/TODO b/TODO +index fc6b3fd..d0196c8 100644 +--- a/TODO ++++ b/TODO +@@ -356,3 +356,16 @@ Progress of long-running operations + For example, copying in virt-resize. How can we display the progress + of these operations? This is a basic usability requirement, and + frequently requested. ++ ++Better support for encrypted devices ++------------------------------------ ++ ++Currently LUKS support only works if the device contains volume ++groups. If it contains, eg., partitions, you cannot access them. ++We would like to add: ++ ++ - An easier way to use this from guestfish. ++ - Ability to create LUKS devices. ++ - Ability to change LUKS keys on existing devices. ++ - Direct access to the /dev/mapper device (eg. if it contains ++ anything apart from VGs). +diff --git a/appliance/kmod.whitelist.in b/appliance/kmod.whitelist.in +index 0a92122..850b7b8 100644 +--- a/appliance/kmod.whitelist.in ++++ b/appliance/kmod.whitelist.in +@@ -69,3 +69,18 @@ loop.ko + gfs2.ko + dlm.ko + configfs.ko ++ ++# Used by dm-crypt. Probably many more crypto modules ++# should be added here. ++aes*.ko ++blkcipher.ko ++cbc.ko ++cryptd.ko ++crypto_blkcipher.ko ++gf128mul.ko ++padlock-aes.ko ++sha256*.ko ++sha512*.ko ++xor.ko ++xts.ko ++zlib.ko +diff --git a/appliance/packagelist.in b/appliance/packagelist.in +index 16dc88d..4d45963 100644 +--- a/appliance/packagelist.in ++++ b/appliance/packagelist.in +@@ -11,6 +11,7 @@ + #if REDHAT == 1 + augeas-libs + btrfs-progs ++ cryptsetup-luks + diffutils + e2fsprogs + /* e4fsprogs only exists on RHEL 5, will be ignored everywhere else. */ +@@ -34,6 +35,7 @@ + #elif DEBIAN == 1 + bsdmainutils + btrfs-tools ++ cryptsetup + /* Dependency problem prevents installation of these two: + gfs-tools + gfs2-tools +diff --git a/daemon/Makefile.am b/daemon/Makefile.am +index cf9f7ca..27fca2a 100644 +--- a/daemon/Makefile.am ++++ b/daemon/Makefile.am +@@ -98,6 +98,7 @@ guestfsd_SOURCES = \ + inotify.c \ + link.c \ + ls.c \ ++ luks.c \ + lvm.c \ + lvm-filter.c \ + mkfs.c \ +diff --git a/daemon/luks.c b/daemon/luks.c +new file mode 100644 +index 0000000..f5a0b9d +--- /dev/null ++++ b/daemon/luks.c +@@ -0,0 +1,138 @@ ++/* libguestfs - the guestfsd daemon ++ * Copyright (C) 2010 Red Hat Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++ ++#include "daemon.h" ++#include "c-ctype.h" ++#include "actions.h" ++#include "optgroups.h" ++ ++int ++optgroup_luks_available (void) ++{ ++ return prog_exists ("cryptsetup"); ++} ++ ++static int ++luks_open (const char *device, const char *key, const char *mapname, ++ int readonly) ++{ ++ /* Sanity check: /dev/mapper/mapname must not exist already. Note ++ * that the device-mapper control device (/dev/mapper/control) is ++ * always there, so you can't ever have mapname == "control". ++ */ ++ size_t len = strlen (mapname); ++ char devmapper[len+32]; ++ snprintf (devmapper, len+32, "/dev/mapper/%s", mapname); ++ if (access (devmapper, F_OK) == 0) { ++ reply_with_error ("%s: device already exists", devmapper); ++ return -1; ++ } ++ ++ char tempfile[] = "/tmp/luksXXXXXX"; ++ int fd = mkstemp (tempfile); ++ if (fd == -1) { ++ reply_with_perror ("mkstemp"); ++ return -1; ++ } ++ ++ len = strlen (key); ++ if (xwrite (fd, key, len) == -1) { ++ reply_with_perror ("write"); ++ close (fd); ++ unlink (tempfile); ++ return -1; ++ } ++ ++ if (close (fd) == -1) { ++ reply_with_perror ("close"); ++ unlink (tempfile); ++ return -1; ++ } ++ ++ const char *argv[16]; ++ size_t i = 0; ++ ++ argv[i++] = "cryptsetup"; ++ argv[i++] = "-d"; ++ argv[i++] = tempfile; ++ if (readonly) argv[i++] = "--readonly"; ++ argv[i++] = "luksOpen"; ++ argv[i++] = device; ++ argv[i++] = mapname; ++ argv[i++] = NULL; ++ ++ char *err; ++ int r = commandv (NULL, &err, (const char * const *) argv); ++ unlink (tempfile); ++ ++ if (r == -1) { ++ reply_with_error ("%s", err); ++ free (err); ++ return -1; ++ } ++ ++ free (err); ++ ++ udev_settle (); ++ ++ return 0; ++} ++ ++int ++do_luks_open (const char *device, const char *key, const char *mapname) ++{ ++ return luks_open (device, key, mapname, 0); ++} ++ ++int ++do_luks_open_ro (const char *device, const char *key, const char *mapname) ++{ ++ return luks_open (device, key, mapname, 1); ++} ++ ++int ++do_luks_close (const char *device) ++{ ++ /* Must be /dev/mapper/... */ ++ if (! STRPREFIX (device, "/dev/mapper/")) { ++ reply_with_error ("luks_close: you must call this on the /dev/mapper device created by luks_open"); ++ return -1; ++ } ++ ++ const char *mapname = &device[12]; ++ ++ char *err; ++ int r = command (NULL, &err, "cryptsetup", "luksClose", mapname, NULL); ++ if (r == -1) { ++ reply_with_error ("%s", err); ++ free (err); ++ return -1; ++ } ++ ++ free (err); ++ ++ udev_settle (); ++ ++ return 0; ++} +diff --git a/fish/guestfish.pod b/fish/guestfish.pod +index 86dcf58..bfcec5c 100644 +--- a/fish/guestfish.pod ++++ b/fish/guestfish.pod +@@ -530,6 +530,39 @@ it, eg: + + echo "~" + ++=head1 ENCRYPTED DISKS ++ ++Libguestfs has some support for Linux guests encrypted according to ++the Linux Unified Key Setup (LUKS) standard, which includes nearly all ++whole disk encryption systems used by modern Linux guests. Currently ++only LVM-on-LUKS is supported. ++ ++Identify encrypted block devices and partitions using L: ++ ++ > vfs-type /dev/sda2 ++ crypto_LUKS ++ ++Then open those devices using L. This creates a ++device-mapper device called C. ++ ++ > luks-open /dev/sda2 luksdev ++ Enter key or passphrase ("key"): ++ ++Finally you have to tell LVM to scan for volume groups on ++the newly created mapper device: ++ ++ > vgscan ++ > vg-activate-all true ++ ++The logical volume(s) can now be mounted in the usual way. ++ ++Before closing a LUKS device you must unmount any logical volumes on ++it and deactivate the volume groups by calling C ++on each one. Then you can close the mapper device: ++ ++ > vg-activate false /dev/VG ++ > luks-close /dev/mapper/luksdev ++ + =head1 WINDOWS PATHS + + If a path is prefixed with C then you can use Windows-style +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 6241ffa..fdc2b70 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -33,6 +33,7 @@ daemon/initrd.c + daemon/inotify.c + daemon/link.c + daemon/ls.c ++daemon/luks.c + daemon/lvm-filter.c + daemon/lvm.c + daemon/mkfs.c +diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR +index 9183bf0..98ecf58 100644 +--- a/src/MAX_PROC_NR ++++ b/src/MAX_PROC_NR +@@ -1 +1 @@ +-256 ++259 +diff --git a/src/generator.ml b/src/generator.ml +index 70cba24..43ca70a 100755 +--- a/src/generator.ml ++++ b/src/generator.ml +@@ -4879,6 +4879,43 @@ will be able to see every block device. + This command also clears the LVM cache and performs a volume + group scan."); + ++ ("luks_open", (RErr, [Device "device"; Key "key"; String "mapname"]), 257, [Optional "luks"], ++ [], ++ "open a LUKS-encrypted block device", ++ "\ ++This command opens a block device which has been encrypted ++according to the Linux Unified Key Setup (LUKS) standard. ++ ++C is the encrypted block device or partition. ++ ++The caller must supply one of the keys associated with the ++LUKS block device, in the C parameter. ++ ++This creates a new block device called C. ++Reads and writes to this block device are decrypted from and ++encrypted to the underlying C respectively. ++ ++If this block device contains LVM volume groups, then ++calling C followed by C ++will make them visible."); ++ ++ ("luks_open_ro", (RErr, [Device "device"; Key "key"; String "mapname"]), 258, [Optional "luks"], ++ [], ++ "open a LUKS-encrypted block device read-only", ++ "\ ++This is the same as C except that a read-only ++mapping is created."); ++ ++ ("luks_close", (RErr, [Device "device"]), 259, [Optional "luks"], ++ [], ++ "close a LUKS device", ++ "\ ++This closes a LUKS device that was created earlier by ++C or C. The ++C parameter must be the name of the LUKS mapping ++device (ie. C) and I the name ++of the underlying block device."); ++ + ] + + let all_functions = non_daemon_functions @ daemon_functions +diff --git a/src/guestfs.pod b/src/guestfs.pod +index 8e3d07c..5a2e7a5 100644 +--- a/src/guestfs.pod ++++ b/src/guestfs.pod +@@ -450,6 +450,37 @@ L after creating each file or directory. + + For more information about umask, see L. + ++=head2 ENCRYPTED DISKS ++ ++Libguestfs allows you to access Linux guests which have been ++encrypted using whole disk encryption that conforms to the ++Linux Unified Key Setup (LUKS) standard. This includes ++nearly all whole disk encryption systems used by modern ++Linux guests. ++ ++Use L to identify LUKS-encrypted block ++devices (it returns the string C). ++ ++Then open these devices by calling L. ++Obviously you will require the passphrase! ++ ++Opening a LUKS device creates a new device mapper device ++called C (where C is the ++string you supply to L). ++Reads and writes to this mapper device are decrypted from and ++encrypted to the underlying block device respectively. ++ ++LVM volume groups on the device can be made visible by calling ++L followed by L. ++The logical volume(s) can now be mounted in the usual way. ++ ++Use the reverse process to close a LUKS device. Unmount ++any logical volumes on it, deactivate the volume groups ++by caling C. ++Then close the mapper device by calling ++L on the C ++device (I the underlying encrypted block device). ++ + =head2 SPECIAL CONSIDERATIONS FOR WINDOWS GUESTS + + Libguestfs can mount NTFS partitions. It does this using the +-- +1.7.1 + diff --git a/0007-New-APIs-Support-for-creating-LUKS-and-managing-keys.patch b/0007-New-APIs-Support-for-creating-LUKS-and-managing-keys.patch new file mode 100644 index 0000000..139f9fd --- /dev/null +++ b/0007-New-APIs-Support-for-creating-LUKS-and-managing-keys.patch @@ -0,0 +1,452 @@ +From 088f1c169ed1f685647ab0a71cad54068bebb757 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Thu, 22 Jul 2010 11:00:59 +0100 +Subject: [PATCH] New APIs: Support for creating LUKS and managing keys. + +This commit adds four APIs for creating new LUKS devices +and key management. These are: + + luks_format Format a LUKS device with the default cipher. + luks_format_cipher Format with a chosen cipher. + luks_add_key Add another key to an existing device. + luks_kill_slot Delete a key from an existing device. + +This enables all the significant functionality of the +cryptsetup luks* commands. + +Note that you can obtain the UUID of a LUKS device already +by using vfs-uuid. + +This also includes a regression test covering all the LUKS +functions. +(cherry picked from commit 945e569db64ab2608b21feba0aa94044c9835ac3) +--- + TODO | 2 - + daemon/luks.c | 204 +++++++++++++++++++++++++++++++++++++++++----- + regressions/Makefile.am | 1 + + regressions/test-luks.sh | 88 ++++++++++++++++++++ + src/MAX_PROC_NR | 2 +- + src/generator.ml | 37 ++++++++ + 6 files changed, 311 insertions(+), 23 deletions(-) + create mode 100755 regressions/test-luks.sh + +diff --git a/TODO b/TODO +index d0196c8..5bce5d9 100644 +--- a/TODO ++++ b/TODO +@@ -365,7 +365,5 @@ groups. If it contains, eg., partitions, you cannot access them. + We would like to add: + + - An easier way to use this from guestfish. +- - Ability to create LUKS devices. +- - Ability to change LUKS keys on existing devices. + - Direct access to the /dev/mapper device (eg. if it contains + anything apart from VGs). +diff --git a/daemon/luks.c b/daemon/luks.c +index f5a0b9d..07aebdd 100644 +--- a/daemon/luks.c ++++ b/daemon/luks.c +@@ -33,43 +33,69 @@ optgroup_luks_available (void) + return prog_exists ("cryptsetup"); + } + +-static int +-luks_open (const char *device, const char *key, const char *mapname, +- int readonly) ++/* Callers must also call remove_temp (tempfile). */ ++static char * ++write_key_to_temp (const char *key) + { +- /* Sanity check: /dev/mapper/mapname must not exist already. Note +- * that the device-mapper control device (/dev/mapper/control) is +- * always there, so you can't ever have mapname == "control". +- */ +- size_t len = strlen (mapname); +- char devmapper[len+32]; +- snprintf (devmapper, len+32, "/dev/mapper/%s", mapname); +- if (access (devmapper, F_OK) == 0) { +- reply_with_error ("%s: device already exists", devmapper); +- return -1; ++ char *tempfile = strdup ("/tmp/luksXXXXXX"); ++ if (!tempfile) { ++ reply_with_perror ("strdup"); ++ return NULL; + } + +- char tempfile[] = "/tmp/luksXXXXXX"; + int fd = mkstemp (tempfile); + if (fd == -1) { + reply_with_perror ("mkstemp"); +- return -1; ++ goto error; + } + +- len = strlen (key); ++ size_t len = strlen (key); + if (xwrite (fd, key, len) == -1) { + reply_with_perror ("write"); + close (fd); +- unlink (tempfile); +- return -1; ++ goto error; + } + + if (close (fd) == -1) { + reply_with_perror ("close"); +- unlink (tempfile); ++ goto error; ++ } ++ ++ return tempfile; ++ ++ error: ++ unlink (tempfile); ++ free (tempfile); ++ return NULL; ++} ++ ++static void ++remove_temp (char *tempfile) ++{ ++ unlink (tempfile); ++ free (tempfile); ++} ++ ++static int ++luks_open (const char *device, const char *key, const char *mapname, ++ int readonly) ++{ ++ /* Sanity check: /dev/mapper/mapname must not exist already. Note ++ * that the device-mapper control device (/dev/mapper/control) is ++ * always there, so you can't ever have mapname == "control". ++ */ ++ size_t len = strlen (mapname); ++ char devmapper[len+32]; ++ snprintf (devmapper, len+32, "/dev/mapper/%s", mapname); ++ if (access (devmapper, F_OK) == 0) { ++ reply_with_error ("%s: device already exists", devmapper); + return -1; + } + ++ char *tempfile = write_key_to_temp (key); ++ if (!tempfile) ++ return -1; ++ + const char *argv[16]; + size_t i = 0; + +@@ -84,7 +110,7 @@ luks_open (const char *device, const char *key, const char *mapname, + + char *err; + int r = commandv (NULL, &err, (const char * const *) argv); +- unlink (tempfile); ++ remove_temp (tempfile); + + if (r == -1) { + reply_with_error ("%s", err); +@@ -136,3 +162,141 @@ do_luks_close (const char *device) + + return 0; + } ++ ++static int ++luks_format (const char *device, const char *key, int keyslot, ++ const char *cipher) ++{ ++ char *tempfile = write_key_to_temp (key); ++ if (!tempfile) ++ return -1; ++ ++ const char *argv[16]; ++ char keyslot_s[16]; ++ size_t i = 0; ++ ++ argv[i++] = "cryptsetup"; ++ argv[i++] = "-q"; ++ if (cipher) { ++ argv[i++] = "--cipher"; ++ argv[i++] = cipher; ++ } ++ argv[i++] = "--key-slot"; ++ snprintf (keyslot_s, sizeof keyslot_s, "%d", keyslot); ++ argv[i++] = keyslot_s; ++ argv[i++] = "luksFormat"; ++ argv[i++] = device; ++ argv[i++] = tempfile; ++ argv[i++] = NULL; ++ ++ char *err; ++ int r = commandv (NULL, &err, (const char * const *) argv); ++ remove_temp (tempfile); ++ ++ if (r == -1) { ++ reply_with_error ("%s", err); ++ free (err); ++ return -1; ++ } ++ ++ free (err); ++ ++ udev_settle (); ++ ++ return 0; ++} ++ ++int ++do_luks_format (const char *device, const char *key, int keyslot) ++{ ++ return luks_format (device, key, keyslot, NULL); ++} ++ ++int ++do_luks_format_cipher (const char *device, const char *key, int keyslot, ++ const char *cipher) ++{ ++ return luks_format (device, key, keyslot, cipher); ++} ++ ++int ++do_luks_add_key (const char *device, const char *key, const char *newkey, ++ int keyslot) ++{ ++ char *keyfile = write_key_to_temp (key); ++ if (!keyfile) ++ return -1; ++ ++ char *newkeyfile = write_key_to_temp (newkey); ++ if (!newkeyfile) { ++ remove_temp (keyfile); ++ return -1; ++ } ++ ++ const char *argv[16]; ++ char keyslot_s[16]; ++ size_t i = 0; ++ ++ argv[i++] = "cryptsetup"; ++ argv[i++] = "-q"; ++ argv[i++] = "-d"; ++ argv[i++] = keyfile; ++ argv[i++] = "--key-slot"; ++ snprintf (keyslot_s, sizeof keyslot_s, "%d", keyslot); ++ argv[i++] = keyslot_s; ++ argv[i++] = "luksAddKey"; ++ argv[i++] = device; ++ argv[i++] = newkeyfile; ++ argv[i++] = NULL; ++ ++ char *err; ++ int r = commandv (NULL, &err, (const char * const *) argv); ++ remove_temp (keyfile); ++ remove_temp (newkeyfile); ++ ++ if (r == -1) { ++ reply_with_error ("%s", err); ++ free (err); ++ return -1; ++ } ++ ++ free (err); ++ ++ return 0; ++} ++ ++int ++do_luks_kill_slot (const char *device, const char *key, int keyslot) ++{ ++ char *tempfile = write_key_to_temp (key); ++ if (!tempfile) ++ return -1; ++ ++ const char *argv[16]; ++ char keyslot_s[16]; ++ size_t i = 0; ++ ++ argv[i++] = "cryptsetup"; ++ argv[i++] = "-q"; ++ argv[i++] = "-d"; ++ argv[i++] = tempfile; ++ argv[i++] = "luksKillSlot"; ++ argv[i++] = device; ++ snprintf (keyslot_s, sizeof keyslot_s, "%d", keyslot); ++ argv[i++] = keyslot_s; ++ argv[i++] = NULL; ++ ++ char *err; ++ int r = commandv (NULL, &err, (const char * const *) argv); ++ remove_temp (tempfile); ++ ++ if (r == -1) { ++ reply_with_error ("%s", err); ++ free (err); ++ return -1; ++ } ++ ++ free (err); ++ ++ return 0; ++} +diff --git a/regressions/Makefile.am b/regressions/Makefile.am +index 5736aaf..ca4292a 100644 +--- a/regressions/Makefile.am ++++ b/regressions/Makefile.am +@@ -35,6 +35,7 @@ TESTS = \ + test-cancellation-download-librarycancels.sh \ + test-cancellation-upload-daemoncancels.sh \ + test-find0.sh \ ++ test-luks.sh \ + test-lvm-filtering.sh \ + test-lvm-mapping.pl \ + test-noexec-stack.pl \ +diff --git a/regressions/test-luks.sh b/regressions/test-luks.sh +new file mode 100755 +index 0000000..fe42d87 +--- /dev/null ++++ b/regressions/test-luks.sh +@@ -0,0 +1,88 @@ ++#!/bin/bash - ++# libguestfs ++# Copyright (C) 2010 Red Hat Inc. ++# ++# This program is free software; you can redistribute it and/or modify ++# it under the terms of the GNU General Public License as published by ++# the Free Software Foundation; either version 2 of the License, or ++# (at your option) any later version. ++# ++# This program is distributed in the hope that it will be useful, ++# but WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++# GNU General Public License for more details. ++# ++# You should have received a copy of the GNU General Public License ++# along with this program; if not, write to the Free Software ++# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ ++# Test LUKS device creation, opening, key slots. ++ ++set -e ++ ++rm -f test1.img ++ ++../fish/guestfish --keys-from-stdin < parameter must be the name of the LUKS mapping + device (ie. C) and I the name + of the underlying block device."); + ++ ("luks_format", (RErr, [Device "device"; Key "key"; Int "keyslot"]), 260, [Optional "luks"; DangerWillRobinson], ++ [], ++ "format a block device as a LUKS encrypted device", ++ "\ ++This command erases existing data on C and formats ++the device as a LUKS encrypted device. C is the ++initial key, which is added to key slot C. (LUKS ++supports 8 key slots, numbered 0-7)."); ++ ++ ("luks_format_cipher", (RErr, [Device "device"; Key "key"; Int "keyslot"; String "cipher"]), 261, [Optional "luks"; DangerWillRobinson], ++ [], ++ "format a block device as a LUKS encrypted device", ++ "\ ++This command is the same as C but ++it also allows you to set the C used."); ++ ++ ("luks_add_key", (RErr, [Device "device"; Key "key"; Key "newkey"; Int "keyslot"]), 262, [Optional "luks"], ++ [], ++ "add a key on a LUKS encrypted device", ++ "\ ++This command adds a new key on LUKS device C. ++C is any existing key, and is used to access the device. ++C is the new key to add. C is the key slot ++that will be replaced. ++ ++Note that if C already contains a key, then this ++command will fail. You have to use C ++first to remove that key."); ++ ++ ("luks_kill_slot", (RErr, [Device "device"; Key "key"; Int "keyslot"]), 263, [Optional "luks"], ++ [], ++ "remove a key from a LUKS encrypted device", ++ "\ ++This command deletes the key in key slot C from the ++encrypted LUKS device C. C must be one of the ++I keys."); ++ + ] + + let all_functions = non_daemon_functions @ daemon_functions +-- +1.7.1 + diff --git a/0008-New-API-is-lv-check-if-a-block-device-is-a-logical-v.patch b/0008-New-API-is-lv-check-if-a-block-device-is-a-logical-v.patch new file mode 100644 index 0000000..ab0436e --- /dev/null +++ b/0008-New-API-is-lv-check-if-a-block-device-is-a-logical-v.patch @@ -0,0 +1,128 @@ +From 5a6f9558a980c6c7d3d08379edcde85dd85b5190 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Fri, 30 Jul 2010 16:32:35 +0100 +Subject: [PATCH] New API: is-lv: check if a block device is a logical volume (RHBZ#619793) + +This adds a new API, guestfs_is_lv (g, device), which returns true iff +the named device is an LVM2 logical volume. + +A sample guestfish session: + +> lvs +/dev/vg_f13x64/lv_root +/dev/vg_f13x64/lv_swap +> list-devices +/dev/vda +> list-partitions +/dev/vda1 +/dev/vda2 +> is-lv /dev/vg_f13x64/lv_root +true +> is-lv /dev/vg_f13x64/lv_swap +true +> is-lv /dev/vda +false +> is-lv /dev/vda1 +false +> is-lv /dev/vda2 +false +(cherry picked from commit 6280ac9b987c14f89749b4b4fdfec5a647567432) +--- + daemon/lvm.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++ + src/MAX_PROC_NR | 2 +- + src/generator.ml | 10 ++++++++++ + 3 files changed, 58 insertions(+), 1 deletions(-) + +diff --git a/daemon/lvm.c b/daemon/lvm.c +index 70c3c90..0df27e2 100644 +--- a/daemon/lvm.c ++++ b/daemon/lvm.c +@@ -23,6 +23,7 @@ + #include + #include + #include ++#include + + #include "daemon.h" + #include "c-ctype.h" +@@ -662,3 +663,49 @@ do_vgscan (void) + free (err); + return 0; + } ++ ++/* Test if a device is a logical volume (RHBZ#619793). ++ * ++ * This is harder than it should be. A LV device like /dev/VG/LV is ++ * really a symlink to a device-mapper device like /dev/dm-0. However ++ * at the device-mapper (kernel) level, nothing is really known about ++ * LVM (a userspace concept). Therefore we use a convoluted method to ++ * determine this, by listing out known LVs and checking whether the ++ * rdev (major/minor) of the device we are passed matches any of them. ++ * ++ * Note use of 'stat' instead of 'lstat' so that symlinks are fully ++ * resolved. ++ */ ++int ++do_is_lv (const char *device) ++{ ++ struct stat stat1, stat2; ++ ++ int r = stat (device, &stat1); ++ if (r == -1) { ++ reply_with_perror ("stat: %s", device); ++ return -1; ++ } ++ ++ char **lvs = do_lvs (); ++ if (lvs == NULL) ++ return -1; ++ ++ size_t i; ++ for (i = 0; lvs[i] != NULL; ++i) { ++ r = stat (lvs[i], &stat2); ++ if (r == -1) { ++ reply_with_perror ("stat: %s", lvs[i]); ++ free_strings (lvs); ++ return -1; ++ } ++ if (stat1.st_rdev == stat2.st_rdev) { /* found it */ ++ free_strings (lvs); ++ return 1; ++ } ++ } ++ ++ /* not found */ ++ free_strings (lvs); ++ return 0; ++} +diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR +index 175b6c5..10b0c0d 100644 +--- a/src/MAX_PROC_NR ++++ b/src/MAX_PROC_NR +@@ -1 +1 @@ +-263 ++264 +diff --git a/src/generator.ml b/src/generator.ml +index 8675828..b5da6cf 100755 +--- a/src/generator.ml ++++ b/src/generator.ml +@@ -4953,6 +4953,16 @@ This command deletes the key in key slot C from the + encrypted LUKS device C. C must be one of the + I keys."); + ++ ("is_lv", (RBool "lvflag", [Device "device"]), 264, [Optional "lvm2"], ++ [InitBasicFSonLVM, IfAvailable "lvm2", TestOutputTrue ( ++ [["is_lv"; "/dev/VG/LV"]]); ++ InitBasicFSonLVM, IfAvailable "lvm2", TestOutputFalse ( ++ [["is_lv"; "/dev/sda1"]])], ++ "test if device is a logical volume", ++ "\ ++This command tests whether C is a logical volume, and ++returns true iff this is the case."); ++ + ] + + let all_functions = non_daemon_functions @ daemon_functions +-- +1.7.1 + diff --git a/0009-New-API-file-architecture.patch b/0009-New-API-file-architecture.patch new file mode 100644 index 0000000..d3fb0e5 --- /dev/null +++ b/0009-New-API-file-architecture.patch @@ -0,0 +1,775 @@ +From 3bce04c39a55bfa02ee1d9eec4e09f6c0ec6a70f Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Wed, 28 Jul 2010 15:38:57 +0100 +Subject: [PATCH] New API: file-architecture + +This change simply converts the existing Perl-only function +file_architecture into a core API call. The core API call is +written in C and available in all languages and from guestfish. +(cherry picked from commit ad4cff2625651bda9de25de9aba96bdf213d0a0a) +--- + README | 4 + + configure.ac | 19 +++ + perl/lib/Sys/Guestfs/Lib.pm | 147 +---------------------- + perl/t/510-lib-file-arch.t | 70 ----------- + po/POTFILES.in | 1 + + src/Makefile.am | 3 +- + src/generator.ml | 128 ++++++++++++++++++++ + src/inspect.c | 280 +++++++++++++++++++++++++++++++++++++++++++ + 8 files changed, 437 insertions(+), 215 deletions(-) + delete mode 100644 perl/t/510-lib-file-arch.t + create mode 100644 src/inspect.c + +diff --git a/README b/README +index ea1da1f..867bc56 100644 +--- a/README ++++ b/README +@@ -48,6 +48,10 @@ Requirements + + - XDR, rpcgen (on Linux these are provided by glibc) + ++- pcre (Perl Compatible Regular Expressions C library) ++ ++- libmagic (the library that corresponds to the 'file' command) ++ + - squashfs-tools (mksquashfs only) + + - genisoimage / mkisofs +diff --git a/configure.ac b/configure.ac +index 7daa2ac..5e884ea 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -185,6 +185,15 @@ AC_ARG_ENABLE([appliance], + AM_CONDITIONAL([ENABLE_APPLIANCE],[test "x$enable_appliance" = "xyes"]) + AC_MSG_RESULT([$enable_appliance]) + ++dnl Check for PCRE. ++AC_CHECK_LIB([pcre],[pcre_compile], ++ [AC_SUBST([LIBPCRE], ["-lpcre"])], ++ [AC_MSG_FAILURE( ++ [Perl Compatible Regular Expressions library (PCRE) is required])]) ++AC_CHECK_HEADER([pcre.h],[], ++ [AC_MSG_FAILURE( ++ [Perl Compatible Regular Expressions library (PCRE) header file pcre.h is required])]) ++ + dnl Check for rpcgen and XDR library. rpcgen is optional. + AC_CHECK_PROG([RPCGEN],[rpcgen],[rpcgen],[no]) + AM_CONDITIONAL([HAVE_RPCGEN],[test "x$RPCGEN" != "xno"]) +@@ -449,6 +458,16 @@ dnl For i18n. + AM_GNU_GETTEXT([external]) + AM_GNU_GETTEXT_VERSION([0.17]) + ++dnl libmagic (required) ++AC_CHECK_LIB([magic],[magic_file],[ ++ AC_SUBST([LIBMAGIC], ["-lmagic"]) ++ ],[ ++ AC_MSG_FAILURE([libmagic is required]) ++ ]) ++AC_CHECK_HEADER([magic.h],[],[ ++ AC_MSG_FAILURE([magic.h header file is required]) ++ ]) ++ + dnl hivex library (highly recommended). + dnl This used to be a part of libguestfs, but was spun off into its + dnl own separate upstream project in libguestfs 1.0.85. +diff --git a/perl/lib/Sys/Guestfs/Lib.pm b/perl/lib/Sys/Guestfs/Lib.pm +index bdc788e..bb97506 100644 +--- a/perl/lib/Sys/Guestfs/Lib.pm ++++ b/perl/lib/Sys/Guestfs/Lib.pm +@@ -347,159 +347,18 @@ sub resolve_windows_path + + =head2 file_architecture + +- $arch = file_architecture ($g, $path) ++Deprecated function. Replace any calls to this function with: + +-The C function lets you get the architecture for a +-particular binary or library in the guest. By "architecture" we mean +-what processor it is compiled for (eg. C or C). +- +-The function works on at least the following types of files: +- +-=over 4 +- +-=item * +- +-many types of Un*x binary +- +-=item * +- +-many types of Un*x shared library +- +-=item * +- +-Windows Win32 and Win64 binaries +- +-=item * +- +-Windows Win32 and Win64 DLLs +- +-Win32 binaries and DLLs return C. +- +-Win64 binaries and DLLs return C. +- +-=item * +- +-Linux kernel modules +- +-=item * +- +-Linux new-style initrd images +- +-=item * +- +-some non-x86 Linux vmlinuz kernels +- +-=back +- +-What it can't do currently: +- +-=over 4 +- +-=item * +- +-static libraries (libfoo.a) +- +-=item * +- +-Linux old-style initrd as compressed ext2 filesystem (RHEL 3) +- +-=item * +- +-x86 Linux vmlinuz kernels +- +-x86 vmlinuz images (bzImage format) consist of a mix of 16-, 32- and +-compressed code, and are horribly hard to unpack. If you want to find +-the architecture of a kernel, use the architecture of the associated +-initrd or kernel module(s) instead. +- +-=back ++ $g->file_architecture ($path); + + =cut + +-sub _elf_arch_to_canonical +-{ +- local $_ = shift; +- +- if ($_ eq "Intel 80386") { +- return "i386"; +- } elsif ($_ eq "Intel 80486") { +- return "i486"; # probably not in the wild +- } elsif ($_ eq "x86-64") { +- return "x86_64"; +- } elsif ($_ eq "AMD x86-64") { +- return "x86_64"; +- } elsif (/SPARC32/) { +- return "sparc"; +- } elsif (/SPARC V9/) { +- return "sparc64"; +- } elsif ($_ eq "IA-64") { +- return "ia64"; +- } elsif (/64.*PowerPC/) { +- return "ppc64"; +- } elsif (/PowerPC/) { +- return "ppc"; +- } else { +- warn __x("returning non-canonical architecture type '{arch}'", +- arch => $_); +- return $_; +- } +-} +- +-my @_initrd_binaries = ("nash", "modprobe", "sh", "bash"); +- + sub file_architecture + { +- local $_; + my $g = shift; + my $path = shift; + +- # Our basic tool is 'file' ... +- my $file = $g->file ($path); +- +- if ($file =~ /ELF.*(?:executable|shared object|relocatable), (.+?),/) { +- # ELF executable or shared object. We need to convert +- # what file(1) prints into the canonical form. +- return _elf_arch_to_canonical ($1); +- } elsif ($file =~ /PE32 executable/) { +- return "i386"; # Win32 executable or DLL +- } elsif ($file =~ /PE32\+ executable/) { +- return "x86_64"; # Win64 executable or DLL +- } +- +- elsif ($file =~ /cpio archive/) { +- # Probably an initrd. +- my $zcat = "cat"; +- if ($file =~ /gzip/) { +- $zcat = "zcat"; +- } elsif ($file =~ /bzip2/) { +- $zcat = "bzcat"; +- } +- +- # Download and unpack it to find a binary file. +- my $dir = tempdir (CLEANUP => 1); +- $g->download ($path, "$dir/initrd"); +- +- my $bins = join " ", map { "bin/$_" } @_initrd_binaries; +- my $cmd = "cd $dir && $zcat initrd | cpio --quiet -id $bins"; +- my $r = system ($cmd); +- die __x("cpio command failed: {error}", error => $?) +- unless $r == 0; +- +- foreach my $bin (@_initrd_binaries) { +- if (-f "$dir/bin/$bin") { +- $_ = `file $dir/bin/$bin`; +- if (/ELF.*executable, (.+?),/) { +- return _elf_arch_to_canonical ($1); +- } +- } +- } +- +- die __x("file_architecture: no known binaries found in initrd image: {path}", +- path => $path); +- } +- +- die __x("file_architecture: unknown architecture: {path}", +- path => $path); ++ return $g->file_architecture ($path); + } + + =head1 OPERATING SYSTEM INSPECTION FUNCTIONS +diff --git a/perl/t/510-lib-file-arch.t b/perl/t/510-lib-file-arch.t +deleted file mode 100644 +index dfe32bc..0000000 +--- a/perl/t/510-lib-file-arch.t ++++ /dev/null +@@ -1,70 +0,0 @@ +-# libguestfs Perl bindings -*- perl -*- +-# Copyright (C) 2009 Red Hat Inc. +-# +-# This program is free software; you can redistribute it and/or modify +-# it under the terms of the GNU General Public License as published by +-# the Free Software Foundation; either version 2 of the License, or +-# (at your option) any later version. +-# +-# This program is distributed in the hope that it will be useful, +-# but WITHOUT ANY WARRANTY; without even the implied warranty of +-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-# GNU General Public License for more details. +-# +-# You should have received a copy of the GNU General Public License +-# along with this program; if not, write to the Free Software +-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +- +-use strict; +-use warnings; +- +-BEGIN { +- use Test::More; +- eval "use Locale::TextDomain";; +- if (exists $INC{"Locale/TextDomain.pm"}) { +- plan tests => 16; +- } else { +- plan skip_all => "no perl-libintl module"; +- exit 0; +- } +-} +- +-use Sys::Guestfs; +-use Sys::Guestfs::Lib; +- +-my $h = Sys::Guestfs->new (); +-ok ($h); +- +-$h->add_drive_ro ("../images/test.iso"); +-ok (1); +- +-$h->launch (); +-ok (1); +- +-$h->mount_ro ("/dev/sda", "/"); +-ok (1); +- +-is (Sys::Guestfs::Lib::file_architecture ($h, "/bin-i586-dynamic"), +- "i386"); +-is (Sys::Guestfs::Lib::file_architecture ($h, "/bin-sparc-dynamic"), +- "sparc"); +-is (Sys::Guestfs::Lib::file_architecture ($h, "/bin-win32.exe"), +- "i386"); +-is (Sys::Guestfs::Lib::file_architecture ($h, "/bin-win64.exe"), +- "x86_64"); +-is (Sys::Guestfs::Lib::file_architecture ($h, "/bin-x86_64-dynamic"), +- "x86_64"); +-is (Sys::Guestfs::Lib::file_architecture ($h, "/lib-i586.so"), +- "i386"); +-is (Sys::Guestfs::Lib::file_architecture ($h, "/lib-sparc.so"), +- "sparc"); +-is (Sys::Guestfs::Lib::file_architecture ($h, "/lib-win32.dll"), +- "i386"); +-is (Sys::Guestfs::Lib::file_architecture ($h, "/lib-win64.dll"), +- "x86_64"); +-is (Sys::Guestfs::Lib::file_architecture ($h, "/lib-x86_64.so"), +- "x86_64"); +-is (Sys::Guestfs::Lib::file_architecture ($h, "/initrd-x86_64.img"), +- "x86_64"); +-is (Sys::Guestfs::Lib::file_architecture ($h, "/initrd-x86_64.img.gz"), +- "x86_64"); +diff --git a/po/POTFILES.in b/po/POTFILES.in +index fdc2b70..bf066ea 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -102,6 +102,7 @@ ruby/ext/guestfs/_guestfs.c + src/actions.c + src/bindtests.c + src/guestfs.c ++src/inspect.c + src/launch.c + src/proto.c + test-tool/helper.c +diff --git a/src/Makefile.am b/src/Makefile.am +index 4135c8c..61cec04 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -126,11 +126,12 @@ libguestfs_la_SOURCES = \ + gettext.h \ + actions.c \ + bindtests.c \ ++ inspect.c \ + launch.c \ + proto.c \ + libguestfs.syms + +-libguestfs_la_LIBADD = $(LTLIBTHREAD) ../gnulib/lib/libgnu.la ++libguestfs_la_LIBADD = $(LIBPCRE) $(LIBMAGIC) $(LTLIBTHREAD) ../gnulib/lib/libgnu.la + + # Make libguestfs include the convenience library. + noinst_LTLIBRARIES = libprotocol.la +diff --git a/src/generator.ml b/src/generator.ml +index b5da6cf..b0cdb6e 100755 +--- a/src/generator.ml ++++ b/src/generator.ml +@@ -940,6 +940,134 @@ to specify the QEMU interface emulation to use at run time."); + This is the same as C but it allows you + to specify the QEMU interface emulation to use at run time."); + ++ ("file_architecture", (RString "arch", [Pathname "filename"]), -1, [], ++ [InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/bin-i586-dynamic"]], "i386"); ++ InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/bin-sparc-dynamic"]], "sparc"); ++ InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/bin-win32.exe"]], "i386"); ++ InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/bin-win64.exe"]], "x86_64"); ++ InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/bin-x86_64-dynamic"]], "x86_64"); ++ InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/lib-i586.so"]], "i386"); ++ InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/lib-sparc.so"]], "sparc"); ++ InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/lib-win32.dll"]], "i386"); ++ InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/lib-win64.dll"]], "x86_64"); ++ InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/lib-x86_64.so"]], "x86_64"); ++ InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/initrd-x86_64.img"]], "x86_64"); ++ InitISOFS, Always, TestOutput ( ++ [["file_architecture"; "/initrd-x86_64.img.gz"]], "x86_64");], ++ "detect the architecture of a binary file", ++ "\ ++This detects the architecture of the binary C, ++and returns it if known. ++ ++Currently defined architectures are: ++ ++=over 4 ++ ++=item \"i386\" ++ ++This string is returned for all 32 bit i386, i486, i586, i686 binaries ++irrespective of the precise processor requirements of the binary. ++ ++=item \"x86_64\" ++ ++64 bit x86-64. ++ ++=item \"sparc\" ++ ++32 bit SPARC. ++ ++=item \"sparc64\" ++ ++64 bit SPARC V9 and above. ++ ++=item \"ia64\" ++ ++Intel Itanium. ++ ++=item \"ppc\" ++ ++32 bit Power PC. ++ ++=item \"ppc64\" ++ ++64 bit Power PC. ++ ++=back ++ ++Libguestfs may return other architecture strings in future. ++ ++The function works on at least the following types of files: ++ ++=over 4 ++ ++=item * ++ ++many types of Un*x and Linux binary ++ ++=item * ++ ++many types of Un*x and Linux shared library ++ ++=item * ++ ++Windows Win32 and Win64 binaries ++ ++=item * ++ ++Windows Win32 and Win64 DLLs ++ ++Win32 binaries and DLLs return C. ++ ++Win64 binaries and DLLs return C. ++ ++=item * ++ ++Linux kernel modules ++ ++=item * ++ ++Linux new-style initrd images ++ ++=item * ++ ++some non-x86 Linux vmlinuz kernels ++ ++=back ++ ++What it can't do currently: ++ ++=over 4 ++ ++=item * ++ ++static libraries (libfoo.a) ++ ++=item * ++ ++Linux old-style initrd as compressed ext2 filesystem (RHEL 3) ++ ++=item * ++ ++x86 Linux vmlinuz kernels ++ ++x86 vmlinuz images (bzImage format) consist of a mix of 16-, 32- and ++compressed code, and are horribly hard to unpack. If you want to find ++the architecture of a kernel, use the architecture of the associated ++initrd or kernel module(s) instead. ++ ++=back"); ++ + ] + + (* daemon_functions are any functions which cause some action +diff --git a/src/inspect.c b/src/inspect.c +new file mode 100644 +index 0000000..d19e23b +--- /dev/null ++++ b/src/inspect.c +@@ -0,0 +1,280 @@ ++/* libguestfs ++ * Copyright (C) 2010 Red Hat Inc. ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include "ignore-value.h" ++ ++#include "guestfs.h" ++#include "guestfs-internal.h" ++#include "guestfs-internal-actions.h" ++#include "guestfs_protocol.h" ++ ++/* Compile all the regular expressions once when the shared library is ++ * loaded. PCRE is thread safe so we're supposedly OK here if ++ * multiple threads call into the libguestfs API functions below ++ * simultaneously. ++ */ ++static pcre *re_file_elf; ++static pcre *re_file_win64; ++static pcre *re_elf_ppc64; ++ ++static void compile_regexps (void) __attribute__((constructor)); ++static void ++compile_regexps (void) ++{ ++ const char *err; ++ int offset; ++ ++#define COMPILE(re,pattern,options) \ ++ do { \ ++ re = pcre_compile ((pattern), (options), &err, &offset, NULL); \ ++ if (re == NULL) { \ ++ ignore_value (write (2, err, strlen (err))); \ ++ abort (); \ ++ } \ ++ } while (0) ++ ++ COMPILE (re_file_elf, ++ "ELF.*(?:executable|shared object|relocatable), (.+?),", 0); ++ COMPILE (re_elf_ppc64, "64.*PowerPC", 0); ++} ++ ++/* Match a regular expression which contains no captures. Returns ++ * true if it matches or false if it doesn't. ++ */ ++static int ++match (guestfs_h *g, const char *str, const pcre *re) ++{ ++ size_t len = strlen (str); ++ int vec[30], r; ++ ++ r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof vec / sizeof vec[0]); ++ if (r == PCRE_ERROR_NOMATCH) ++ return 0; ++ if (r != 1) { ++ /* Internal error -- should not happen. */ ++ fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n", ++ __FILE__, __func__, r, str); ++ return 0; ++ } ++ ++ return 1; ++} ++ ++/* Match a regular expression which contains exactly one capture. If ++ * the string matches, return the capture, otherwise return NULL. The ++ * caller must free the result. ++ */ ++static char * ++match1 (guestfs_h *g, const char *str, const pcre *re) ++{ ++ size_t len = strlen (str); ++ int vec[30], r; ++ ++ r = pcre_exec (re, NULL, str, len, 0, 0, vec, sizeof vec / sizeof vec[0]); ++ if (r == PCRE_ERROR_NOMATCH) ++ return NULL; ++ if (r != 2) { ++ /* Internal error -- should not happen. */ ++ fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n", ++ __FILE__, __func__, r, str); ++ return NULL; ++ } ++ ++ return safe_strndup (g, &str[vec[2]], vec[3]-vec[2]); ++} ++ ++/* Convert output from 'file' command on ELF files to the canonical ++ * architecture string. Caller must free the result. ++ */ ++static char * ++canonical_elf_arch (guestfs_h *g, const char *elf_arch) ++{ ++ const char *r; ++ ++ if (strstr (elf_arch, "Intel 80386")) ++ r = "i386"; ++ else if (strstr (elf_arch, "Intel 80486")) ++ r = "i486"; ++ else if (strstr (elf_arch, "x86-64")) ++ r = "x86_64"; ++ else if (strstr (elf_arch, "AMD x86-64")) ++ r = "x86_64"; ++ else if (strstr (elf_arch, "SPARC32")) ++ r = "sparc"; ++ else if (strstr (elf_arch, "SPARC V9")) ++ r = "sparc64"; ++ else if (strstr (elf_arch, "IA-64")) ++ r = "ia64"; ++ else if (match (g, elf_arch, re_elf_ppc64)) ++ r = "ppc64"; ++ else if (strstr (elf_arch, "PowerPC")) ++ r = "ppc"; ++ else ++ r = elf_arch; ++ ++ char *ret = safe_strdup (g, r); ++ return ret; ++} ++ ++static int ++is_regular_file (const char *filename) ++{ ++ struct stat statbuf; ++ ++ return lstat (filename, &statbuf) == 0 && S_ISREG (statbuf.st_mode); ++} ++ ++/* Download and uncompress the cpio file to find binaries within. ++ * Notes: ++ * (1) Two lists must be identical. ++ * (2) Implicit limit of 31 bytes for length of each element (see code ++ * below). ++ */ ++#define INITRD_BINARIES1 "bin/ls bin/rm bin/modprobe sbin/modprobe bin/sh bin/bash bin/dash bin/nash" ++#define INITRD_BINARIES2 {"bin/ls", "bin/rm", "bin/modprobe", "sbin/modprobe", "bin/sh", "bin/bash", "bin/dash", "bin/nash"} ++ ++static char * ++cpio_arch (guestfs_h *g, const char *file, const char *path) ++{ ++ char *ret = NULL; ++ ++ const char *method; ++ if (strstr (file, "gzip")) ++ method = "zcat"; ++ else if (strstr (file, "bzip2")) ++ method = "bzcat"; ++ else ++ method = "cat"; ++ ++ char dir[] = "/tmp/initrd.XXXXXX"; ++#define dir_len (sizeof dir) ++ if (mkdtemp (dir) == NULL) { ++ perrorf (g, "mkdtemp"); ++ goto out; ++ } ++ ++ char dir_initrd[dir_len + 16]; ++ snprintf (dir_initrd, dir_len + 16, "%s/initrd", dir); ++ if (guestfs_download (g, path, dir_initrd) == -1) ++ goto out; ++ ++ char cmd[dir_len + 256]; ++ snprintf (cmd, dir_len + 256, ++ "cd %s && %s initrd | cpio --quiet -id " INITRD_BINARIES1, ++ dir, method); ++ int r = system (cmd); ++ if (r == -1 || WEXITSTATUS (r) != 0) { ++ perrorf (g, "cpio command failed"); ++ goto out; ++ } ++ ++ char bin[dir_len + 32]; ++ const char *bins[] = INITRD_BINARIES2; ++ size_t i; ++ for (i = 0; i < sizeof bins / sizeof bins[0]; ++i) { ++ snprintf (bin, dir_len + 32, "%s/%s", dir, bins[i]); ++ ++ if (is_regular_file (bin)) { ++ int flags = g->verbose ? MAGIC_DEBUG : 0; ++ flags |= MAGIC_ERROR | MAGIC_RAW; ++ ++ magic_t m = magic_open (flags); ++ if (m == NULL) { ++ perrorf (g, "magic_open"); ++ goto out; ++ } ++ ++ if (magic_load (m, NULL) == -1) { ++ perrorf (g, "magic_load: default magic database file"); ++ magic_close (m); ++ goto out; ++ } ++ ++ const char *line = magic_file (m, bin); ++ if (line == NULL) { ++ perrorf (g, "magic_file: %s", bin); ++ magic_close (m); ++ goto out; ++ } ++ ++ char *elf_arch; ++ if ((elf_arch = match1 (g, line, re_file_elf)) != NULL) { ++ ret = canonical_elf_arch (g, elf_arch); ++ free (elf_arch); ++ magic_close (m); ++ goto out; ++ } ++ magic_close (m); ++ } ++ } ++ error (g, "file_architecture: could not determine architecture of cpio archive"); ++ ++ out: ++ /* Free up the temporary directory. Note the directory name cannot ++ * contain shell meta-characters because of the way it was ++ * constructed above. ++ */ ++ snprintf (cmd, dir_len + 256, "rm -rf %s", dir); ++ ignore_value (system (cmd)); ++ ++ return ret; ++#undef dir_len ++} ++ ++char * ++guestfs__file_architecture (guestfs_h *g, const char *path) ++{ ++ char *file = NULL; ++ char *elf_arch = NULL; ++ char *ret = NULL; ++ ++ /* Get the output of the "file" command. Note that because this ++ * runs in the daemon, LANG=C so it's in English. ++ */ ++ file = guestfs_file (g, path); ++ if (file == NULL) ++ return NULL; ++ ++ if ((elf_arch = match1 (g, file, re_file_elf)) != NULL) ++ ret = canonical_elf_arch (g, elf_arch); ++ else if (strstr (file, "PE32 executable")) ++ ret = safe_strdup (g, "i386"); ++ else if (strstr (file, "PE32+ executable")) ++ ret = safe_strdup (g, "x86_64"); ++ else if (strstr (file, "cpio archive")) ++ ret = cpio_arch (g, file, path); ++ else ++ error (g, "file_architecture: unknown architecture: %s", path); ++ ++ free (file); ++ free (elf_arch); ++ return ret; /* caller frees */ ++} +-- +1.7.1 + diff --git a/0010-New-APIs-findfs-label-and-findfs-uuid.patch b/0010-New-APIs-findfs-label-and-findfs-uuid.patch new file mode 100644 index 0000000..d55a79e --- /dev/null +++ b/0010-New-APIs-findfs-label-and-findfs-uuid.patch @@ -0,0 +1,202 @@ +From 1bc37cbac535010063b07fce95089e7739c787ff Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Wed, 28 Jul 2010 23:11:38 +0100 +Subject: [PATCH] New APIs: findfs-label and findfs-uuid + +These two calls wrap up the /sbin/findfs command, allowing you +to find a filesystem by only knowing its label or UUID. + +This is especially useful when resolving LABEL=... or UUID=... +entries in /etc/fstab. + +Sample guestfish session: + +> vfs-uuid /dev/vda1 +277dd61c-bf34-4253-a8dc-df500a05e7df +> findfs-uuid 277dd61c-bf34-4253-a8dc-df500a05e7df +/dev/vda1 +> vfs-label /dev/vda1 +/boot +> findfs-label /boot +/dev/vda1 +> vfs-uuid /dev/VolGroup00/LogVol00 +40ce7c36-82ce-4a12-a99d-48f5e054162c +> findfs-uuid 40ce7c36-82ce-4a12-a99d-48f5e054162c +/dev/mapper/VolGroup00-LogVol00 +> findfs-uuid 12345678 +libguestfs: error: findfs_uuid: findfs: unable to resolve 'UUID=12345678' +(cherry picked from commit 65e9ac4595fbace8f301030469932be518456246) +--- + daemon/Makefile.am | 1 + + daemon/findfs.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + po/POTFILES.in | 1 + + src/MAX_PROC_NR | 2 +- + src/generator.ml | 28 ++++++++++++++++++- + 5 files changed, 101 insertions(+), 3 deletions(-) + create mode 100644 daemon/findfs.c + +diff --git a/daemon/Makefile.am b/daemon/Makefile.am +index 27fca2a..0c8be08 100644 +--- a/daemon/Makefile.am ++++ b/daemon/Makefile.am +@@ -84,6 +84,7 @@ guestfsd_SOURCES = \ + ext2.c \ + fallocate.c \ + file.c \ ++ findfs.c \ + fill.c \ + find.c \ + fsck.c \ +diff --git a/daemon/findfs.c b/daemon/findfs.c +new file mode 100644 +index 0000000..0520f18 +--- /dev/null ++++ b/daemon/findfs.c +@@ -0,0 +1,72 @@ ++/* libguestfs - the guestfsd daemon ++ * Copyright (C) 2010 Red Hat Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include "daemon.h" ++#include "actions.h" ++ ++static char * ++findfs (const char *tag, const char *label_or_uuid) ++{ ++ /* Kill the cache file, forcing blkid to reread values from the ++ * original filesystems. In blkid there is a '-p' option which is ++ * supposed to do this, but (a) it doesn't work and (b) that option ++ * is not supported in RHEL 5. ++ */ ++ unlink ("/etc/blkid/blkid.tab"); ++ ++ size_t len = strlen (tag) + strlen (label_or_uuid) + 2; ++ char arg[len]; ++ snprintf (arg, len, "%s=%s", tag, label_or_uuid); ++ ++ char *out, *err; ++ int r = command (&out, &err, "findfs", arg, NULL); ++ if (r == -1) { ++ reply_with_error ("%s", err); ++ free (out); ++ free (err); ++ return NULL; ++ } ++ ++ free (err); ++ ++ /* Trim trailing \n if present. */ ++ len = strlen (out); ++ if (len > 0 && out[len-1] == '\n') ++ out[len-1] = '\0'; ++ ++ return out; /* caller frees */ ++} ++ ++char * ++do_findfs_uuid (const char *uuid) ++{ ++ return findfs ("UUID", uuid); ++} ++ ++char * ++do_findfs_label (const char *label) ++{ ++ return findfs ("LABEL", label); ++} +diff --git a/po/POTFILES.in b/po/POTFILES.in +index bf066ea..8ce5c97 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -21,6 +21,7 @@ daemon/fallocate.c + daemon/file.c + daemon/fill.c + daemon/find.c ++daemon/findfs.c + daemon/fsck.c + daemon/glob.c + daemon/grep.c +diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR +index 10b0c0d..c1d1ffb 100644 +--- a/src/MAX_PROC_NR ++++ b/src/MAX_PROC_NR +@@ -1 +1 @@ +-264 ++266 +diff --git a/src/generator.ml b/src/generator.ml +index b0cdb6e..2153e23 100755 +--- a/src/generator.ml ++++ b/src/generator.ml +@@ -4952,7 +4952,9 @@ a file in the host and attach it as a device."); + This returns the filesystem label of the filesystem on + C. + +-If the filesystem is unlabeled, this returns the empty string."); ++If the filesystem is unlabeled, this returns the empty string. ++ ++To find a filesystem from the label, use C."); + + ("vfs_uuid", (RString "uuid", [Device "device"]), 254, [], + (let uuid = uuidgen () in +@@ -4964,7 +4966,9 @@ If the filesystem is unlabeled, this returns the empty string."); + This returns the filesystem UUID of the filesystem on + C. + +-If the filesystem does not have a UUID, this returns the empty string."); ++If the filesystem does not have a UUID, this returns the empty string. ++ ++To find a filesystem from the UUID, use C."); + + ("lvm_set_filter", (RErr, [DeviceList "devices"]), 255, [Optional "lvm2"], + (* Can't be tested with the current framework because +@@ -5091,6 +5095,26 @@ I keys."); + This command tests whether C is a logical volume, and + returns true iff this is the case."); + ++ ("findfs_uuid", (RString "device", [String "uuid"]), 265, [], ++ [], ++ "find a filesystem by UUID", ++ "\ ++This command searches the filesystems and returns the one ++which has the given UUID. An error is returned if no such ++filesystem can be found. ++ ++To find the UUID of a filesystem, use C."); ++ ++ ("findfs_label", (RString "device", [String "label"]), 266, [], ++ [], ++ "find a filesystem by label", ++ "\ ++This command searches the filesystems and returns the one ++which has the given label. An error is returned if no such ++filesystem can be found. ++ ++To find the label of a filesystem, use C."); ++ + ] + + let all_functions = non_daemon_functions @ daemon_functions +-- +1.7.1 + diff --git a/0011-New-APIs-for-guest-inspection.patch b/0011-New-APIs-for-guest-inspection.patch new file mode 100644 index 0000000..941b571 --- /dev/null +++ b/0011-New-APIs-for-guest-inspection.patch @@ -0,0 +1,1523 @@ +From ca29ab0891239812cd3799d48d5f97860c570c3c Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Wed, 28 Jul 2010 15:40:42 +0100 +Subject: [PATCH] New APIs for guest inspection. + +This commit converts (some of) the Perl inspection code to C and +makes it available through core APIs. The new APIs are: + +inspect-os - Does the inspection, returns list of OSes +inspect-get-* - Get results of the inspection + +where '*' is one of: + + type - 'windows' or 'linux' + distro - Linux distro + arch - architecture + product-name - long product name string + major-version + minor-version - major.minor version of OS + mountpoints - get a list of the mountpoints + filesystems - get all filesystems associated with the OS + +This works for all existing supported Linux and Windows OSes. + +Cherry picked from commit 8289aa1ad68ec94c87fc4d538f638d8816052d92. +--- + src/Makefile.am | 3 +- + src/generator.ml | 221 +++++++++++ + src/guestfs-internal.h | 54 +++ + src/guestfs.c | 2 + + src/guestfs.pod | 68 +++- + src/inspect.c | 1017 ++++++++++++++++++++++++++++++++++++++++++++++++ + 6 files changed, 1360 insertions(+), 5 deletions(-) + +diff --git a/src/Makefile.am b/src/Makefile.am +index 61cec04..cc01459 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -131,7 +131,7 @@ libguestfs_la_SOURCES = \ + proto.c \ + libguestfs.syms + +-libguestfs_la_LIBADD = $(LIBPCRE) $(LIBMAGIC) $(LTLIBTHREAD) ../gnulib/lib/libgnu.la ++libguestfs_la_LIBADD = $(HIVEX_LIBS) $(LIBPCRE) $(LIBMAGIC) $(LTLIBTHREAD) ../gnulib/lib/libgnu.la + + # Make libguestfs include the convenience library. + noinst_LTLIBRARIES = libprotocol.la +@@ -139,6 +139,7 @@ libguestfs_la_LIBADD += libprotocol.la + + libguestfs_la_CFLAGS = \ + -DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' \ ++ $(HIVEX_CFLAGS) \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) + + libguestfs_la_CPPFLAGS = -I$(top_srcdir)/gnulib/lib +diff --git a/src/generator.ml b/src/generator.ml +index 2153e23..7ac7f6a 100755 +--- a/src/generator.ml ++++ b/src/generator.ml +@@ -1068,6 +1068,227 @@ initrd or kernel module(s) instead. + + =back"); + ++ ("inspect_os", (RStringList "roots", []), -1, [], ++ [], ++ "inspect disk and return list of operating systems found", ++ "\ ++This function uses other libguestfs functions and certain ++heuristics to inspect the disk(s) (usually disks belonging to ++a virtual machine), looking for operating systems. ++ ++The list returned is empty if no operating systems were found. ++ ++If one operating system was found, then this returns a list with ++a single element, which is the name of the root filesystem of ++this operating system. It is also possible for this function ++to return a list containing more than one element, indicating ++a dual-boot or multi-boot virtual machine, with each element being ++the root filesystem of one of the operating systems. ++ ++You can pass the root string(s) returned to other ++C functions in order to query further ++information about each operating system, such as the name ++and version. ++ ++This function uses other libguestfs features such as ++C and C in order to mount ++and unmount filesystems and look at the contents. This should ++be called with no disks currently mounted. The function may also ++use Augeas, so any existing Augeas handle will be closed. ++ ++This function cannot decrypt encrypted disks. The caller ++must do that first (supplying the necessary keys) if the ++disk is encrypted. ++ ++Please read L for more details."); ++ ++ ("inspect_get_type", (RString "name", [Device "root"]), -1, [], ++ [], ++ "get type of inspected operating system", ++ "\ ++This function should only be called with a root device string ++as returned by C. ++ ++This returns the type of the inspected operating system. ++Currently defined types are: ++ ++=over 4 ++ ++=item \"linux\" ++ ++Any Linux-based operating system. ++ ++=item \"windows\" ++ ++Any Microsoft Windows operating system. ++ ++=item \"unknown\" ++ ++The operating system type could not be determined. ++ ++=back ++ ++Future versions of libguestfs may return other strings here. ++The caller should be prepared to handle any string. ++ ++Please read L for more details."); ++ ++ ("inspect_get_arch", (RString "arch", [Device "root"]), -1, [], ++ [], ++ "get architecture of inspected operating system", ++ "\ ++This function should only be called with a root device string ++as returned by C. ++ ++This returns the architecture of the inspected operating system. ++The possible return values are listed under ++C. ++ ++If the architecture could not be determined, then the ++string C is returned. ++ ++Please read L for more details."); ++ ++ ("inspect_get_distro", (RString "distro", [Device "root"]), -1, [], ++ [], ++ "get distro of inspected operating system", ++ "\ ++This function should only be called with a root device string ++as returned by C. ++ ++This returns the distro (distribution) of the inspected operating ++system. ++ ++Currently defined distros are: ++ ++=over 4 ++ ++=item \"debian\" ++ ++Debian or a Debian-derived distro such as Ubuntu. ++ ++=item \"fedora\" ++ ++Fedora. ++ ++=item \"redhat-based\" ++ ++Some Red Hat-derived distro. ++ ++=item \"rhel\" ++ ++Red Hat Enterprise Linux and some derivatives. ++ ++=item \"windows\" ++ ++Windows does not have distributions. This string is ++returned if the OS type is Windows. ++ ++=item \"unknown\" ++ ++The distro could not be determined. ++ ++=back ++ ++Future versions of libguestfs may return other strings here. ++The caller should be prepared to handle any string. ++ ++Please read L for more details."); ++ ++ ("inspect_get_major_version", (RInt "major", [Device "root"]), -1, [], ++ [], ++ "get major version of inspected operating system", ++ "\ ++This function should only be called with a root device string ++as returned by C. ++ ++This returns the major version number of the inspected operating ++system. ++ ++Windows uses a consistent versioning scheme which is I ++reflected in the popular public names used by the operating system. ++Notably the operating system known as \"Windows 7\" is really ++version 6.1 (ie. major = 6, minor = 1). You can find out the ++real versions corresponding to releases of Windows by consulting ++Wikipedia or MSDN. ++ ++If the version could not be determined, then C<0> is returned. ++ ++Please read L for more details."); ++ ++ ("inspect_get_minor_version", (RInt "minor", [Device "root"]), -1, [], ++ [], ++ "get minor version of inspected operating system", ++ "\ ++This function should only be called with a root device string ++as returned by C. ++ ++This returns the minor version number of the inspected operating ++system. ++ ++If the version could not be determined, then C<0> is returned. ++ ++Please read L for more details. ++See also C."); ++ ++ ("inspect_get_product_name", (RString "product", [Device "root"]), -1, [], ++ [], ++ "get product name of inspected operating system", ++ "\ ++This function should only be called with a root device string ++as returned by C. ++ ++This returns the product name of the inspected operating ++system. The product name is generally some freeform string ++which can be displayed to the user, but should not be ++parsed by programs. ++ ++If the product name could not be determined, then the ++string C is returned. ++ ++Please read L for more details."); ++ ++ ("inspect_get_mountpoints", (RHashtable "mountpoints", [Device "root"]), -1, [], ++ [], ++ "get mountpoints of inspected operating system", ++ "\ ++This function should only be called with a root device string ++as returned by C. ++ ++This returns a hash of where we think the filesystems ++associated with this operating system should be mounted. ++Callers should note that this is at best an educated guess ++made by reading configuration files such as C. ++ ++Each element in the returned hashtable has a key which ++is the path of the mountpoint (eg. C) and a value ++which is the filesystem that would be mounted there ++(eg. C). ++ ++Non-mounted devices such as swap devices are I ++returned in this list. ++ ++Please read L for more details. ++See also C."); ++ ++ ("inspect_get_filesystems", (RStringList "filesystems", [Device "root"]), -1, [], ++ [], ++ "get filesystems associated with inspected operating system", ++ "\ ++This function should only be called with a root device string ++as returned by C. ++ ++This returns a list of all the filesystems that we think ++are associated with this operating system. This includes ++the root filesystem, other ordinary filesystems, and ++non-mounted devices like swap partitions. ++ ++In the case of a multi-boot virtual machine, it is possible ++for a filesystem to be shared between operating systems. ++ ++Please read L for more details. ++See also C."); ++ + ] + + (* daemon_functions are any functions which cause some action +diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h +index a593458..73a14ab 100644 +--- a/src/guestfs-internal.h ++++ b/src/guestfs-internal.h +@@ -131,6 +131,59 @@ struct guestfs_h + void * close_cb_data; + + int msg_next_serial; ++ ++ /* Information gathered by inspect_os. Must be freed by calling ++ * guestfs___free_inspect_info. ++ */ ++ struct inspect_fs *fses; ++ size_t nr_fses; ++}; ++ ++/* Per-filesystem data stored for inspect_os. */ ++enum inspect_fs_content { ++ FS_CONTENT_UNKNOWN = 0, ++ FS_CONTENT_LINUX_ROOT, ++ FS_CONTENT_WINDOWS_ROOT, ++ FS_CONTENT_LINUX_BOOT, ++ FS_CONTENT_LINUX_USR, ++ FS_CONTENT_LINUX_USR_LOCAL, ++ FS_CONTENT_LINUX_VAR, ++}; ++ ++enum inspect_os_type { ++ OS_TYPE_UNKNOWN = 0, ++ OS_TYPE_LINUX, ++ OS_TYPE_WINDOWS, ++}; ++ ++enum inspect_os_distro { ++ OS_DISTRO_UNKNOWN = 0, ++ OS_DISTRO_DEBIAN, ++ OS_DISTRO_FEDORA, ++ OS_DISTRO_REDHAT_BASED, ++ OS_DISTRO_RHEL, ++ OS_DISTRO_WINDOWS, ++}; ++ ++struct inspect_fs { ++ int is_root; ++ char *device; ++ int is_mountable; ++ int is_swap; ++ enum inspect_fs_content content; ++ enum inspect_os_type type; ++ enum inspect_os_distro distro; ++ char *product_name; ++ int major_version; ++ int minor_version; ++ char *arch; ++ struct inspect_fstab_entry *fstab; ++ size_t nr_fstab; ++}; ++ ++struct inspect_fstab_entry { ++ char *device; ++ char *mountpoint; + }; + + struct guestfs_message_header; +@@ -146,6 +199,7 @@ extern char *guestfs_safe_strndup (guestfs_h *g, const char *str, size_t n); + extern void *guestfs_safe_memdup (guestfs_h *g, void *ptr, size_t size); + extern void guestfs___print_timestamped_message (guestfs_h *g, const char *fs, ...); + extern const char *guestfs___tmpdir (void); ++extern void guestfs___free_inspect_info (guestfs_h *g); + extern int guestfs___set_busy (guestfs_h *g); + extern int guestfs___end_busy (guestfs_h *g); + extern int guestfs___send (guestfs_h *g, int proc_nr, xdrproc_t xdrp, char *args); +diff --git a/src/guestfs.c b/src/guestfs.c +index 871d713..cef80db 100644 +--- a/src/guestfs.c ++++ b/src/guestfs.c +@@ -184,6 +184,8 @@ guestfs_close (guestfs_h *g) + if (g->close_cb) + g->close_cb (g, g->close_cb_data); + ++ guestfs___free_inspect_info (g); ++ + /* Try to sync if autosync flag is set. */ + if (g->autosync && g->state == READY) { + guestfs_umount_all (g); +diff --git a/src/guestfs.pod b/src/guestfs.pod +index 5a2e7a5..5deccb5 100644 +--- a/src/guestfs.pod ++++ b/src/guestfs.pod +@@ -160,9 +160,10 @@ you have to find out. Libguestfs can do that too: use + L and L to list possible + partitions and LVs, and either try mounting each to see what is + mountable, or else examine them with L or +-L. But you might find it easier to look at higher level +-programs built on top of libguestfs, in particular +-L. ++L. Libguestfs also has a set of APIs for inspection of ++disk images (see L below). But you might find it easier ++to look at higher level programs built on top of libguestfs, in ++particular L. + + To mount a disk image read-only, use L. There are + several other variations of the C call. +@@ -481,6 +482,65 @@ Then close the mapper device by calling + L on the C + device (I the underlying encrypted block device). + ++=head2 INSPECTION ++ ++Libguestfs has APIs for inspecting an unknown disk image to find out ++if it contains operating systems. (These APIs used to be in a ++separate Perl-only library called L but since ++version 1.5.3 the most frequently used part of this library has been ++rewritten in C and moved into the core code). ++ ++Add all disks belonging to the unknown virtual machine and call ++L in the usual way. ++ ++Then call L. This function uses other libguestfs ++calls and certain heuristics, and returns a list of operating systems ++that were found. An empty list means none were found. A single ++element is the root filesystem of the operating system. For dual- or ++multi-boot guests, multiple roots can be returned, each one ++corresponding to a separate operating system. (Multi-boot virtual ++machines are extremely rare in the world of virtualization, but since ++this scenario can happen, we have built libguestfs to deal with it.) ++ ++For each root, you can then call various C ++functions to get additional details about that operating system. For ++example, call L to return the string ++C or C for Windows and Linux-based operating systems ++respectively. ++ ++Un*x-like and Linux-based operating systems usually consist of several ++filesystems which are mounted at boot time (for example, a separate ++boot partition mounted on C). The inspection rules are able to ++detect how filesystems correspond to mount points. Call ++C to get this mapping. It might ++return a hash table like this example: ++ ++ /boot => /dev/sda1 ++ / => /dev/vg_guest/lv_root ++ /usr => /dev/vg_guest/lv_usr ++ ++The caller can then make calls to L to ++mount the filesystems as suggested. ++ ++Be careful to mount filesystems in the right order (eg. C before ++C). Sorting the keys of the hash by length, shortest first, ++should work. ++ ++Inspection currently only works for some common operating systems. ++Contributors are welcome to send patches for other operating systems ++that we currently cannot detect. ++ ++Encrypted disks must be opened before inspection. See ++L for more details. The L ++function just ignores any encrypted devices. ++ ++A note on the implementation: The call L performs ++inspection and caches the results in the guest handle. Subsequent ++calls to C return this cached information, but ++I re-read the disks. If you change the content of the guest ++disks, you can redo inspection by calling L ++again. ++ + =head2 SPECIAL CONSIDERATIONS FOR WINDOWS GUESTS + + Libguestfs can mount NTFS partitions. It does this using the +@@ -495,7 +555,7 @@ that directory might be referred to as C. + Drive letter mappings are outside the scope of libguestfs. You have + to use libguestfs to read the appropriate Windows Registry and + configuration files, to determine yourself how drives are mapped (see +-also L). ++also L and L). + + Replacing backslash characters with forward slash characters is also + outside the scope of libguestfs, but something that you can easily do. +diff --git a/src/inspect.c b/src/inspect.c +index d19e23b..d1bb7bb 100644 +--- a/src/inspect.c ++++ b/src/inspect.c +@@ -28,8 +28,11 @@ + + #include + #include ++#include ++#include + + #include "ignore-value.h" ++#include "xstrtol.h" + + #include "guestfs.h" + #include "guestfs-internal.h" +@@ -44,6 +47,14 @@ + static pcre *re_file_elf; + static pcre *re_file_win64; + static pcre *re_elf_ppc64; ++static pcre *re_fedora; ++static pcre *re_rhel_old; ++static pcre *re_rhel; ++static pcre *re_rhel_no_minor; ++static pcre *re_debian; ++static pcre *re_aug_seq; ++static pcre *re_xdev; ++static pcre *re_windows_version; + + static void compile_regexps (void) __attribute__((constructor)); + static void +@@ -64,6 +75,17 @@ compile_regexps (void) + COMPILE (re_file_elf, + "ELF.*(?:executable|shared object|relocatable), (.+?),", 0); + COMPILE (re_elf_ppc64, "64.*PowerPC", 0); ++ COMPILE (re_fedora, "Fedora release (\\d+)", 0); ++ COMPILE (re_rhel_old, ++ "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+).*Update (\\d+)", 0); ++ COMPILE (re_rhel, ++ "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)\\.(\\d+)", 0); ++ COMPILE (re_rhel_no_minor, ++ "(?:Red Hat Enterprise Linux|CentOS|Scientific Linux).*release (\\d+)", 0); ++ COMPILE (re_debian, "(\\d+)\\.(\\d+)", 0); ++ COMPILE (re_aug_seq, "/\\d+$", 0); ++ COMPILE (re_xdev, "^/dev/(?:h|s|v|xv)d([a-z]\\d*)$", 0); ++ COMPILE (re_windows_version, "^(\\d+)\\.(\\d+)", 0); + } + + /* Match a regular expression which contains no captures. Returns +@@ -111,6 +133,29 @@ match1 (guestfs_h *g, const char *str, const pcre *re) + return safe_strndup (g, &str[vec[2]], vec[3]-vec[2]); + } + ++/* Match a regular expression which contains exactly two captures. */ ++static int ++match2 (guestfs_h *g, const char *str, const pcre *re, char **ret1, char **ret2) ++{ ++ size_t len = strlen (str); ++ int vec[30], r; ++ ++ r = pcre_exec (re, NULL, str, len, 0, 0, vec, 30); ++ if (r == PCRE_ERROR_NOMATCH) ++ return 0; ++ if (r != 3) { ++ /* Internal error -- should not happen. */ ++ fprintf (stderr, "libguestfs: %s: %s: internal error: pcre_exec returned unexpected error code %d when matching against the string \"%s\"\n", ++ __FILE__, __func__, r, str); ++ return 0; ++ } ++ ++ *ret1 = safe_strndup (g, &str[vec[2]], vec[3]-vec[2]); ++ *ret2 = safe_strndup (g, &str[vec[4]], vec[5]-vec[4]); ++ ++ return 1; ++} ++ + /* Convert output from 'file' command on ELF files to the canonical + * architecture string. Caller must free the result. + */ +@@ -278,3 +323,975 @@ guestfs__file_architecture (guestfs_h *g, const char *path) + free (elf_arch); + return ret; /* caller frees */ + } ++ ++/* The main inspection code. */ ++static int feature_available (guestfs_h *g, const char *feature); ++static void free_string_list (char **); ++static int check_for_filesystem_on (guestfs_h *g, const char *device); ++ ++char ** ++guestfs__inspect_os (guestfs_h *g) ++{ ++ /* Remove any information previously stored in the handle. */ ++ guestfs___free_inspect_info (g); ++ ++ if (guestfs_umount_all (g) == -1) ++ return NULL; ++ ++ /* Iterate over all possible devices. Try to mount each ++ * (read-only). Examine ones which contain filesystems and add that ++ * information to the handle. ++ */ ++ /* Look to see if any devices directly contain filesystems (RHBZ#590167). */ ++ char **devices; ++ devices = guestfs_list_devices (g); ++ if (devices == NULL) ++ return NULL; ++ ++ size_t i; ++ for (i = 0; devices[i] != NULL; ++i) { ++ if (check_for_filesystem_on (g, devices[i]) == -1) { ++ free_string_list (devices); ++ guestfs___free_inspect_info (g); ++ return NULL; ++ } ++ } ++ free_string_list (devices); ++ ++ /* Look at all partitions. */ ++ char **partitions; ++ partitions = guestfs_list_partitions (g); ++ if (partitions == NULL) { ++ guestfs___free_inspect_info (g); ++ return NULL; ++ } ++ ++ for (i = 0; partitions[i] != NULL; ++i) { ++ if (check_for_filesystem_on (g, partitions[i]) == -1) { ++ free_string_list (partitions); ++ guestfs___free_inspect_info (g); ++ return NULL; ++ } ++ } ++ free_string_list (partitions); ++ ++ /* Look at all LVs. */ ++ if (feature_available (g, "lvm2")) { ++ char **lvs; ++ lvs = guestfs_lvs (g); ++ if (lvs == NULL) { ++ guestfs___free_inspect_info (g); ++ return NULL; ++ } ++ ++ for (i = 0; lvs[i] != NULL; ++i) { ++ if (check_for_filesystem_on (g, lvs[i]) == -1) { ++ free_string_list (lvs); ++ guestfs___free_inspect_info (g); ++ return NULL; ++ } ++ } ++ free_string_list (lvs); ++ } ++ ++ /* At this point we have, in the handle, a list of all filesystems ++ * found and data about each one. Now we assemble the list of ++ * filesystems which are root devices and return that to the user. ++ */ ++ size_t count = 0; ++ for (i = 0; i < g->nr_fses; ++i) ++ if (g->fses[i].is_root) ++ count++; ++ ++ char **ret = calloc (count+1, sizeof (char *)); ++ if (ret == NULL) { ++ perrorf (g, "calloc"); ++ guestfs___free_inspect_info (g); ++ return NULL; ++ } ++ ++ count = 0; ++ for (i = 0; i < g->nr_fses; ++i) { ++ if (g->fses[i].is_root) { ++ ret[count] = safe_strdup (g, g->fses[i].device); ++ count++; ++ } ++ } ++ ret[count] = NULL; ++ ++ return ret; ++} ++ ++void ++guestfs___free_inspect_info (guestfs_h *g) ++{ ++ size_t i; ++ for (i = 0; i < g->nr_fses; ++i) { ++ free (g->fses[i].device); ++ free (g->fses[i].product_name); ++ free (g->fses[i].arch); ++ size_t j; ++ for (j = 0; j < g->fses[i].nr_fstab; ++j) { ++ free (g->fses[i].fstab[j].device); ++ free (g->fses[i].fstab[j].mountpoint); ++ } ++ free (g->fses[i].fstab); ++ } ++ free (g->fses); ++ g->nr_fses = 0; ++ g->fses = NULL; ++} ++ ++static void ++free_string_list (char **argv) ++{ ++ size_t i; ++ for (i = 0; argv[i] != NULL; ++i) ++ free (argv[i]); ++ free (argv); ++} ++ ++/* In the Perl code this is a public function. */ ++static int ++feature_available (guestfs_h *g, const char *feature) ++{ ++ /* If there's an error we should ignore it, so to do that we have to ++ * temporarily replace the error handler with a null one. ++ */ ++ guestfs_error_handler_cb old_error_cb = g->error_cb; ++ g->error_cb = NULL; ++ ++ const char *groups[] = { feature, NULL }; ++ int r = guestfs_available (g, (char * const *) groups); ++ ++ g->error_cb = old_error_cb; ++ ++ return r == 0 ? 1 : 0; ++} ++ ++/* Find out if 'device' contains a filesystem. If it does, add ++ * another entry in g->fses. ++ */ ++static int check_filesystem (guestfs_h *g, const char *device); ++static int check_linux_root (guestfs_h *g, struct inspect_fs *fs); ++static int check_fstab (guestfs_h *g, struct inspect_fs *fs); ++static int check_windows_root (guestfs_h *g, struct inspect_fs *fs); ++static int check_windows_arch (guestfs_h *g, struct inspect_fs *fs, ++ const char *systemroot); ++static int check_windows_registry (guestfs_h *g, struct inspect_fs *fs, ++ const char *systemroot); ++static char *resolve_windows_path_silently (guestfs_h *g, const char *); ++static int extend_fses (guestfs_h *g); ++static int parse_unsigned_int (guestfs_h *g, const char *str); ++static int add_fstab_entry (guestfs_h *g, struct inspect_fs *fs, ++ const char *spec, const char *mp); ++static char *resolve_fstab_device (guestfs_h *g, const char *spec); ++ ++static int ++check_for_filesystem_on (guestfs_h *g, const char *device) ++{ ++ /* Get vfs-type in order to check if it's a Linux(?) swap device. ++ * If there's an error we should ignore it, so to do that we have to ++ * temporarily replace the error handler with a null one. ++ */ ++ guestfs_error_handler_cb old_error_cb = g->error_cb; ++ g->error_cb = NULL; ++ char *vfs_type = guestfs_vfs_type (g, device); ++ g->error_cb = old_error_cb; ++ ++ int is_swap = vfs_type && STREQ (vfs_type, "swap"); ++ ++ if (g->verbose) ++ fprintf (stderr, "check_for_filesystem_on: %s (%s)\n", ++ device, vfs_type ? vfs_type : "failed to get vfs type"); ++ ++ if (is_swap) { ++ free (vfs_type); ++ if (extend_fses (g) == -1) ++ return -1; ++ g->fses[g->nr_fses-1].is_swap = 1; ++ return 0; ++ } ++ ++ /* Try mounting the device. As above, ignore errors. */ ++ g->error_cb = NULL; ++ int r = guestfs_mount_ro (g, device, "/"); ++ if (r == -1 && vfs_type && STREQ (vfs_type, "ufs")) /* Hack for the *BSDs. */ ++ r = guestfs_mount_vfs (g, "ro,ufstype=ufs2", "ufs", device, "/"); ++ free (vfs_type); ++ g->error_cb = old_error_cb; ++ if (r == -1) ++ return 0; ++ ++ /* Do the rest of the checks. */ ++ r = check_filesystem (g, device); ++ ++ /* Unmount the filesystem. */ ++ if (guestfs_umount_all (g) == -1) ++ return -1; ++ ++ return r; ++} ++ ++static int ++check_filesystem (guestfs_h *g, const char *device) ++{ ++ if (extend_fses (g) == -1) ++ return -1; ++ ++ struct inspect_fs *fs = &g->fses[g->nr_fses-1]; ++ ++ fs->device = safe_strdup (g, device); ++ fs->is_mountable = 1; ++ ++ /* Grub /boot? */ ++ if (guestfs_is_file (g, "/grub/menu.lst") > 0 || ++ guestfs_is_file (g, "/grub/grub.conf") > 0) ++ fs->content = FS_CONTENT_LINUX_BOOT; ++ /* Linux root? */ ++ else if (guestfs_is_dir (g, "/etc") > 0 && ++ guestfs_is_dir (g, "/bin") > 0 && ++ guestfs_is_file (g, "/etc/fstab") > 0) { ++ fs->is_root = 1; ++ fs->content = FS_CONTENT_LINUX_ROOT; ++ if (check_linux_root (g, fs) == -1) ++ return -1; ++ } ++ /* Linux /usr/local? */ ++ else if (guestfs_is_dir (g, "/etc") > 0 && ++ guestfs_is_dir (g, "/bin") > 0 && ++ guestfs_is_dir (g, "/share") > 0 && ++ guestfs_exists (g, "/local") == 0 && ++ guestfs_is_file (g, "/etc/fstab") == 0) ++ fs->content = FS_CONTENT_LINUX_USR_LOCAL; ++ /* Linux /usr? */ ++ else if (guestfs_is_dir (g, "/etc") > 0 && ++ guestfs_is_dir (g, "/bin") > 0 && ++ guestfs_is_dir (g, "/share") > 0 && ++ guestfs_exists (g, "/local") > 0 && ++ guestfs_is_file (g, "/etc/fstab") == 0) ++ fs->content = FS_CONTENT_LINUX_USR; ++ /* Linux /var? */ ++ else if (guestfs_is_dir (g, "/log") > 0 && ++ guestfs_is_dir (g, "/run") > 0 && ++ guestfs_is_dir (g, "/spool") > 0) ++ fs->content = FS_CONTENT_LINUX_VAR; ++ /* Windows root? */ ++ else if (guestfs_is_file (g, "/AUTOEXEC.BAT") > 0 || ++ guestfs_is_file (g, "/autoexec.bat") > 0 || ++ guestfs_is_dir (g, "/Program Files") > 0 || ++ guestfs_is_dir (g, "/WINDOWS") > 0 || ++ guestfs_is_dir (g, "/Windows") > 0 || ++ guestfs_is_dir (g, "/windows") > 0 || ++ guestfs_is_dir (g, "/WIN32") > 0 || ++ guestfs_is_dir (g, "/Win32") > 0 || ++ guestfs_is_dir (g, "/WINNT") > 0 || ++ guestfs_is_file (g, "/boot.ini") > 0 || ++ guestfs_is_file (g, "/ntldr") > 0) { ++ fs->is_root = 1; ++ fs->content = FS_CONTENT_WINDOWS_ROOT; ++ if (check_windows_root (g, fs) == -1) ++ return -1; ++ } ++ ++ return 0; ++} ++ ++/* The currently mounted device is known to be a Linux root. Try to ++ * determine from this the distro, version, etc. Also parse ++ * /etc/fstab to determine the arrangement of mountpoints and ++ * associated devices. ++ */ ++static int ++check_linux_root (guestfs_h *g, struct inspect_fs *fs) ++{ ++ fs->type = OS_TYPE_LINUX; ++ ++ if (guestfs_exists (g, "/etc/redhat-release") > 0) { ++ fs->distro = OS_DISTRO_REDHAT_BASED; /* Something generic Red Hat-like. */ ++ ++ char **product_name = guestfs_head_n (g, 1, "/etc/redhat-release"); ++ if (product_name == NULL) ++ return -1; ++ if (product_name[0] == NULL) { ++ error (g, "/etc/redhat-release file is empty"); ++ free_string_list (product_name); ++ return -1; ++ } ++ ++ /* Note that this string becomes owned by the handle and will ++ * be freed by guestfs___free_inspect_info. ++ */ ++ fs->product_name = product_name[0]; ++ free (product_name); ++ ++ char *major, *minor; ++ if ((major = match1 (g, fs->product_name, re_fedora)) != NULL) { ++ fs->distro = OS_DISTRO_FEDORA; ++ fs->major_version = parse_unsigned_int (g, major); ++ free (major); ++ if (fs->major_version == -1) ++ return -1; ++ } ++ else if (match2 (g, fs->product_name, re_rhel_old, &major, &minor) || ++ match2 (g, fs->product_name, re_rhel, &major, &minor)) { ++ fs->distro = OS_DISTRO_RHEL; ++ fs->major_version = parse_unsigned_int (g, major); ++ free (major); ++ if (fs->major_version == -1) { ++ free (minor); ++ return -1; ++ } ++ fs->minor_version = parse_unsigned_int (g, minor); ++ free (minor); ++ if (fs->minor_version == -1) ++ return -1; ++ } ++ else if ((major = match1 (g, fs->product_name, re_rhel_no_minor)) != NULL) { ++ fs->distro = OS_DISTRO_RHEL; ++ fs->major_version = parse_unsigned_int (g, major); ++ free (major); ++ if (fs->major_version == -1) ++ return -1; ++ fs->minor_version = 0; ++ } ++ } ++ else if (guestfs_exists (g, "/etc/debian_version") > 0) { ++ fs->distro = OS_DISTRO_DEBIAN; ++ ++ char **product_name = guestfs_head_n (g, 1, "/etc/debian_version"); ++ if (product_name == NULL) ++ return -1; ++ if (product_name[0] == NULL) { ++ error (g, "/etc/debian_version file is empty"); ++ free_string_list (product_name); ++ return -1; ++ } ++ ++ /* Note that this string becomes owned by the handle and will ++ * be freed by guestfs___free_inspect_info. ++ */ ++ fs->product_name = product_name[0]; ++ free (product_name); ++ ++ char *major, *minor; ++ if (match2 (g, fs->product_name, re_debian, &major, &minor)) { ++ fs->major_version = parse_unsigned_int (g, major); ++ free (major); ++ if (fs->major_version == -1) { ++ free (minor); ++ return -1; ++ } ++ fs->minor_version = parse_unsigned_int (g, minor); ++ free (minor); ++ if (fs->minor_version == -1) ++ return -1; ++ } ++ } ++ ++ /* Determine the architecture. */ ++ const char *binaries[] = ++ { "/bin/bash", "/bin/ls", "/bin/echo", "/bin/rm", "/bin/sh" }; ++ size_t i; ++ for (i = 0; i < sizeof binaries / sizeof binaries[0]; ++i) { ++ if (guestfs_is_file (g, binaries[i]) > 0) { ++ /* Ignore errors from file_architecture call. */ ++ guestfs_error_handler_cb old_error_cb = g->error_cb; ++ g->error_cb = NULL; ++ char *arch = guestfs_file_architecture (g, binaries[i]); ++ g->error_cb = old_error_cb; ++ ++ if (arch) { ++ /* String will be owned by handle, freed by ++ * guestfs___free_inspect_info. ++ */ ++ fs->arch = arch; ++ break; ++ } ++ } ++ } ++ ++ /* We already know /etc/fstab exists because it's part of the test ++ * for Linux root above. We must now parse this file to determine ++ * which filesystems are used by the operating system and how they ++ * are mounted. ++ * XXX What if !feature_available (g, "augeas")? ++ */ ++ if (guestfs_aug_init (g, "/", AUG_NO_LOAD|AUG_SAVE_NOOP) == -1) ++ return -1; ++ ++ /* Tell Augeas to only load /etc/fstab (thanks Raphaël Pinson). */ ++ guestfs_aug_rm (g, "/augeas/load//incl[. != \"/etc/fstab\"]"); ++ guestfs_aug_load (g); ++ ++ int r = check_fstab (g, fs); ++ guestfs_aug_close (g); ++ if (r == -1) ++ return -1; ++ ++ return 0; ++} ++ ++static int ++check_fstab (guestfs_h *g, struct inspect_fs *fs) ++{ ++ char **lines = guestfs_aug_ls (g, "/files/etc/fstab"); ++ if (lines == NULL) ++ return -1; ++ ++ if (lines[0] == NULL) { ++ error (g, "could not parse /etc/fstab or empty file"); ++ free_string_list (lines); ++ return -1; ++ } ++ ++ size_t i; ++ char augpath[256]; ++ for (i = 0; lines[i] != NULL; ++i) { ++ /* Ignore comments. Only care about sequence lines which ++ * match m{/\d+$}. ++ */ ++ if (match (g, lines[i], re_aug_seq)) { ++ snprintf (augpath, sizeof augpath, "%s/spec", lines[i]); ++ char *spec = guestfs_aug_get (g, augpath); ++ if (spec == NULL) { ++ free_string_list (lines); ++ return -1; ++ } ++ ++ snprintf (augpath, sizeof augpath, "%s/file", lines[i]); ++ char *mp = guestfs_aug_get (g, augpath); ++ if (mp == NULL) { ++ free_string_list (lines); ++ free (spec); ++ return -1; ++ } ++ ++ int r = add_fstab_entry (g, fs, spec, mp); ++ free (spec); ++ free (mp); ++ ++ if (r == -1) { ++ free_string_list (lines); ++ return -1; ++ } ++ } ++ } ++ ++ free_string_list (lines); ++ return 0; ++} ++ ++/* Add a filesystem and possibly a mountpoint entry for ++ * the root filesystem 'fs'. ++ * ++ * 'spec' is the fstab spec field, which might be a device name or a ++ * pseudodevice or 'UUID=...' or 'LABEL=...'. ++ * ++ * 'mp' is the mount point, which could also be 'swap' or 'none'. ++ */ ++static int ++add_fstab_entry (guestfs_h *g, struct inspect_fs *fs, ++ const char *spec, const char *mp) ++{ ++ /* Ignore certain mountpoints. */ ++ if (STRPREFIX (mp, "/dev/") || ++ STREQ (mp, "/dev") || ++ STRPREFIX (mp, "/media/") || ++ STRPREFIX (mp, "/proc/") || ++ STREQ (mp, "/proc") || ++ STRPREFIX (mp, "/selinux/") || ++ STREQ (mp, "/selinux") || ++ STRPREFIX (mp, "/sys/") || ++ STREQ (mp, "/sys")) ++ return 0; ++ ++ /* Resolve UUID= and LABEL= to the actual device. */ ++ char *device = NULL; ++ if (STRPREFIX (spec, "UUID=")) ++ device = guestfs_findfs_uuid (g, &spec[5]); ++ else if (STRPREFIX (spec, "LABEL=")) ++ device = guestfs_findfs_label (g, &spec[6]); ++ /* Resolve guest block device names. */ ++ else if (spec[0] == '/') ++ device = resolve_fstab_device (g, spec); ++ /* Also ignore pseudo-devices completely, like spec == "tmpfs". ++ * If we haven't resolved the device successfully by this point, ++ * we don't care, just ignore it. ++ */ ++ if (device == NULL) ++ return 0; ++ ++ char *mountpoint = safe_strdup (g, mp); ++ ++ /* Add this to the fstab entry in 'fs'. ++ * Note these are further filtered by guestfs_inspect_get_mountpoints ++ * and guestfs_inspect_get_filesystems. ++ */ ++ size_t n = fs->nr_fstab + 1; ++ struct inspect_fstab_entry *p; ++ ++ p = realloc (fs->fstab, n * sizeof (struct inspect_fstab_entry)); ++ if (p == NULL) { ++ perrorf (g, "realloc"); ++ free (device); ++ free (mountpoint); ++ return -1; ++ } ++ ++ fs->fstab = p; ++ fs->nr_fstab = n; ++ ++ /* These are owned by the handle and freed by guestfs___free_inspect_info. */ ++ fs->fstab[n-1].device = device; ++ fs->fstab[n-1].mountpoint = mountpoint; ++ ++ if (g->verbose) ++ fprintf (stderr, "fstab: device=%s mountpoint=%s\n", device, mountpoint); ++ ++ return 0; ++} ++ ++/* Resolve block device name to the libguestfs device name, eg. ++ * /dev/xvdb1 => /dev/vdb1. This assumes that disks were added in the ++ * same order as they appear to the real VM, which is a reasonable ++ * assumption to make. Return things like LV names unchanged (or ++ * anything we don't recognize). ++ */ ++static char * ++resolve_fstab_device (guestfs_h *g, const char *spec) ++{ ++ char **devices = guestfs_list_devices (g); ++ if (devices == NULL) ++ return NULL; ++ ++ size_t count; ++ for (count = 0; devices[count] != NULL; count++) ++ ; ++ ++ char *device = NULL; ++ char *a1 = match1 (g, spec, re_xdev); ++ if (a1) { ++ size_t i = a1[0] - 'a'; /* a1[0] is always [a-z] because of regex. */ ++ if (i < count) { ++ size_t len = strlen (devices[i]) + strlen (a1) + 16; ++ device = safe_malloc (g, len); ++ snprintf (device, len, "%s%s", devices[i], &a1[1]); ++ } ++ } else { ++ /* Didn't match device pattern, return original spec unchanged. */ ++ device = safe_strdup (g, spec); ++ } ++ ++ free (a1); ++ free_string_list (devices); ++ ++ return device; ++} ++ ++/* XXX Handling of boot.ini in the Perl version was pretty broken. It ++ * essentially didn't do anything for modern Windows guests. ++ * Therefore I've omitted all that code. ++ */ ++static int ++check_windows_root (guestfs_h *g, struct inspect_fs *fs) ++{ ++ fs->type = OS_TYPE_WINDOWS; ++ fs->distro = OS_DISTRO_WINDOWS; ++ ++ /* Try to find Windows systemroot using some common locations. */ ++ const char *systemroots[] = ++ { "/windows", "/winnt", "/win32", "/win" }; ++ size_t i; ++ char *systemroot = NULL; ++ for (i = 0; ++ systemroot == NULL && i < sizeof systemroots / sizeof systemroots[0]; ++ ++i) { ++ systemroot = resolve_windows_path_silently (g, systemroots[i]); ++ } ++ ++ if (!systemroot) { ++ error (g, _("cannot resolve Windows %%SYSTEMROOT%%")); ++ return -1; ++ } ++ ++ /* XXX There is a case for exposing systemroot and many variables ++ * from the registry through the libguestfs API. ++ */ ++ ++ if (g->verbose) ++ fprintf (stderr, "windows %%SYSTEMROOT%% = %s", systemroot); ++ ++ if (check_windows_arch (g, fs, systemroot) == -1) { ++ free (systemroot); ++ return -1; ++ } ++ ++ if (check_windows_registry (g, fs, systemroot) == -1) { ++ free (systemroot); ++ return -1; ++ } ++ ++ free (systemroot); ++ return 0; ++} ++ ++static int ++check_windows_arch (guestfs_h *g, struct inspect_fs *fs, ++ const char *systemroot) ++{ ++ size_t len = strlen (systemroot) + 32; ++ char cmd_exe[len]; ++ snprintf (cmd_exe, len, "%s/system32/cmd.exe", systemroot); ++ ++ char *cmd_exe_path = resolve_windows_path_silently (g, cmd_exe); ++ if (!cmd_exe_path) ++ return 0; ++ ++ char *arch = guestfs_file_architecture (g, cmd_exe_path); ++ free (cmd_exe_path); ++ ++ if (arch) ++ fs->arch = arch; /* freed by guestfs___free_inspect_info */ ++ ++ return 0; ++} ++ ++/* At the moment, pull just the ProductName and version numbers from ++ * the registry. In future there is a case for making many more ++ * registry fields available to callers. ++ */ ++static int ++check_windows_registry (guestfs_h *g, struct inspect_fs *fs, ++ const char *systemroot) ++{ ++ size_t len = strlen (systemroot) + 64; ++ char software[len]; ++ snprintf (software, len, "%s/system32/config/software", systemroot); ++ ++ char *software_path = resolve_windows_path_silently (g, software); ++ if (!software_path) ++ /* If the software hive doesn't exist, just accept that we cannot ++ * find product_name etc. ++ */ ++ return 0; ++ ++ int ret = -1; ++ hive_h *h = NULL; ++ hive_value_h *values = NULL; ++ ++ char dir[] = "/tmp/winreg.XXXXXX"; ++#define dir_len 18 ++ if (mkdtemp (dir) == NULL) { ++ perrorf (g, "mkdtemp"); ++ goto out; ++ } ++ ++ char software_hive[dir_len + 16]; ++ snprintf (software_hive, dir_len + 16, "%s/software", dir); ++ ++ if (guestfs_download (g, software_path, software_hive) == -1) ++ goto out; ++ ++ h = hivex_open (software_hive, g->verbose ? HIVEX_OPEN_VERBOSE : 0); ++ if (h == NULL) { ++ perrorf (g, "hivex_open"); ++ goto out; ++ } ++ ++ hive_node_h node = hivex_root (h); ++ const char *hivepath[] = ++ { "Microsoft", "Windows NT", "CurrentVersion" }; ++ size_t i; ++ for (i = 0; ++ node != 0 && i < sizeof hivepath / sizeof hivepath[0]; ++ ++i) { ++ node = hivex_node_get_child (h, node, hivepath[i]); ++ } ++ ++ if (node == 0) { ++ perrorf (g, "hivex: cannot locate HKLM\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion"); ++ goto out; ++ } ++ ++ values = hivex_node_values (h, node); ++ ++ for (i = 0; values[i] != 0; ++i) { ++ char *key = hivex_value_key (h, values[i]); ++ if (key == NULL) { ++ perrorf (g, "hivex_value_key"); ++ goto out; ++ } ++ ++ if (STRCASEEQ (key, "ProductName")) { ++ fs->product_name = hivex_value_string (h, values[i]); ++ if (!fs->product_name) { ++ perrorf (g, "hivex_value_string"); ++ free (key); ++ goto out; ++ } ++ } ++ else if (STRCASEEQ (key, "CurrentVersion")) { ++ char *version = hivex_value_string (h, values[i]); ++ if (!version) { ++ perrorf (g, "hivex_value_string"); ++ free (key); ++ goto out; ++ } ++ char *major, *minor; ++ if (match2 (g, version, re_windows_version, &major, &minor)) { ++ fs->major_version = parse_unsigned_int (g, major); ++ free (major); ++ if (fs->major_version == -1) { ++ free (minor); ++ free (key); ++ free (version); ++ goto out; ++ } ++ fs->minor_version = parse_unsigned_int (g, minor); ++ free (minor); ++ if (fs->minor_version == -1) { ++ free (key); ++ free (version); ++ return -1; ++ } ++ } ++ ++ free (version); ++ } ++ ++ free (key); ++ } ++ ++ ret = 0; ++ ++ out: ++ if (h) hivex_close (h); ++ free (values); ++ free (software_path); ++ ++ /* Free up the temporary directory. Note the directory name cannot ++ * contain shell meta-characters because of the way it was ++ * constructed above. ++ */ ++ char cmd[dir_len + 16]; ++ snprintf (cmd, dir_len + 16, "rm -rf %s", dir); ++ ignore_value (system (cmd)); ++#undef dir_len ++ ++ return ret; ++} ++ ++static char * ++resolve_windows_path_silently (guestfs_h *g, const char *path) ++{ ++ guestfs_error_handler_cb old_error_cb = g->error_cb; ++ g->error_cb = NULL; ++ char *ret = guestfs_case_sensitive_path (g, path); ++ g->error_cb = old_error_cb; ++ return ret; ++} ++ ++static int ++extend_fses (guestfs_h *g) ++{ ++ size_t n = g->nr_fses + 1; ++ struct inspect_fs *p; ++ ++ p = realloc (g->fses, n * sizeof (struct inspect_fs)); ++ if (p == NULL) { ++ perrorf (g, "realloc"); ++ return -1; ++ } ++ ++ g->fses = p; ++ g->nr_fses = n; ++ ++ memset (&g->fses[n-1], 0, sizeof (struct inspect_fs)); ++ ++ return 0; ++} ++ ++/* Parse small, unsigned ints, as used in version numbers. */ ++static int ++parse_unsigned_int (guestfs_h *g, const char *str) ++{ ++ long ret; ++ int r = xstrtol (str, NULL, 10, &ret, ""); ++ if (r != LONGINT_OK) { ++ error (g, "could not parse integer in version number: %s", str); ++ return -1; ++ } ++ return ret; ++} ++ ++static struct inspect_fs * ++search_for_root (guestfs_h *g, const char *root) ++{ ++ if (g->nr_fses == 0) { ++ error (g, _("no inspection data: call guestfs_inspect_os first")); ++ return NULL; ++ } ++ ++ size_t i; ++ struct inspect_fs *fs; ++ for (i = 0; i < g->nr_fses; ++i) { ++ fs = &g->fses[i]; ++ if (fs->is_root && STREQ (root, fs->device)) ++ return fs; ++ } ++ ++ error (g, _("%s: root device not found: only call this function with a root device previously returned by guestfs_inspect_os"), ++ root); ++ return NULL; ++} ++ ++char * ++guestfs__inspect_get_type (guestfs_h *g, const char *root) ++{ ++ struct inspect_fs *fs = search_for_root (g, root); ++ if (!fs) ++ return NULL; ++ ++ char *ret; ++ switch (fs->type) { ++ case OS_TYPE_LINUX: ret = safe_strdup (g, "linux"); break; ++ case OS_TYPE_WINDOWS: ret = safe_strdup (g, "windows"); break; ++ case OS_TYPE_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break; ++ } ++ ++ return ret; ++} ++ ++char * ++guestfs__inspect_get_arch (guestfs_h *g, const char *root) ++{ ++ struct inspect_fs *fs = search_for_root (g, root); ++ if (!fs) ++ return NULL; ++ ++ return safe_strdup (g, fs->arch ? : "unknown"); ++} ++ ++char * ++guestfs__inspect_get_distro (guestfs_h *g, const char *root) ++{ ++ struct inspect_fs *fs = search_for_root (g, root); ++ if (!fs) ++ return NULL; ++ ++ char *ret; ++ switch (fs->distro) { ++ case OS_DISTRO_DEBIAN: ret = safe_strdup (g, "debian"); break; ++ case OS_DISTRO_FEDORA: ret = safe_strdup (g, "fedora"); break; ++ case OS_DISTRO_REDHAT_BASED: ret = safe_strdup (g, "redhat-based"); break; ++ case OS_DISTRO_RHEL: ret = safe_strdup (g, "rhel"); break; ++ case OS_DISTRO_WINDOWS: ret = safe_strdup (g, "windows"); break; ++ case OS_DISTRO_UNKNOWN: default: ret = safe_strdup (g, "unknown"); break; ++ } ++ ++ return ret; ++} ++ ++int ++guestfs__inspect_get_major_version (guestfs_h *g, const char *root) ++{ ++ struct inspect_fs *fs = search_for_root (g, root); ++ if (!fs) ++ return -1; ++ ++ return fs->major_version; ++} ++ ++int ++guestfs__inspect_get_minor_version (guestfs_h *g, const char *root) ++{ ++ struct inspect_fs *fs = search_for_root (g, root); ++ if (!fs) ++ return -1; ++ ++ return fs->minor_version; ++} ++ ++char * ++guestfs__inspect_get_product_name (guestfs_h *g, const char *root) ++{ ++ struct inspect_fs *fs = search_for_root (g, root); ++ if (!fs) ++ return NULL; ++ ++ return safe_strdup (g, fs->product_name ? : "unknown"); ++} ++ ++char ** ++guestfs__inspect_get_mountpoints (guestfs_h *g, const char *root) ++{ ++ struct inspect_fs *fs = search_for_root (g, root); ++ if (!fs) ++ return NULL; ++ ++ char **ret; ++ ++ /* If no fstab information (Windows) return just the root. */ ++ if (fs->nr_fstab == 0) { ++ ret = calloc (3, sizeof (char *)); ++ ret[0] = safe_strdup (g, "/"); ++ ret[1] = safe_strdup (g, root); ++ ret[2] = NULL; ++ return ret; ++ } ++ ++#define CRITERION fs->fstab[i].mountpoint[0] == '/' ++ size_t i, count = 0; ++ for (i = 0; i < fs->nr_fstab; ++i) ++ if (CRITERION) ++ count++; ++ ++ /* Hashtables have 2N+1 entries. */ ++ ret = calloc (2*count+1, sizeof (char *)); ++ if (ret == NULL) { ++ perrorf (g, "calloc"); ++ return NULL; ++ } ++ ++ count = 0; ++ for (i = 0; i < fs->nr_fstab; ++i) ++ if (CRITERION) { ++ ret[2*count] = safe_strdup (g, fs->fstab[i].mountpoint); ++ ret[2*count+1] = safe_strdup (g, fs->fstab[i].device); ++ count++; ++ } ++#undef CRITERION ++ ++ return ret; ++} ++ ++char ** ++guestfs__inspect_get_filesystems (guestfs_h *g, const char *root) ++{ ++ struct inspect_fs *fs = search_for_root (g, root); ++ if (!fs) ++ return NULL; ++ ++ char **ret; ++ ++ /* If no fstab information (Windows) return just the root. */ ++ if (fs->nr_fstab == 0) { ++ ret = calloc (2, sizeof (char *)); ++ ret[0] = safe_strdup (g, root); ++ ret[1] = NULL; ++ return ret; ++ } ++ ++ ret = calloc (fs->nr_fstab + 1, sizeof (char *)); ++ if (ret == NULL) { ++ perrorf (g, "calloc"); ++ return NULL; ++ } ++ ++ size_t i; ++ for (i = 0; i < fs->nr_fstab; ++i) ++ ret[i] = safe_strdup (g, fs->fstab[i].device); ++ ++ return ret; ++} +-- +1.7.1 + diff --git a/0012-fish-Add-c-connect-and-d-domain-options.patch b/0012-fish-Add-c-connect-and-d-domain-options.patch new file mode 100644 index 0000000..fb8c858 --- /dev/null +++ b/0012-fish-Add-c-connect-and-d-domain-options.patch @@ -0,0 +1,581 @@ +From b754d57fb439c5a2a1d930f97ebd156f54c95ff5 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Mon, 2 Aug 2010 16:33:25 +0100 +Subject: [PATCH] fish: Add -c/--connect and -d/--domain options. + +The -d option lets you specify libvirt domains. The disks from +these domains are found and added, as if you'd named them with -a. + +The -c option lets you specify a libvirt URI, which is needed +when we consult libvirt to implement the above. +(cherry picked from commit 1a9aa565b38eafe48621bc2fe42d35ea6a907708) +--- + README | 4 + + configure.ac | 10 +++ + fish/Makefile.am | 8 ++- + fish/fish.c | 126 ++++++++++++++++++++++++++--------- + fish/fish.h | 5 ++ + fish/guestfish.pod | 14 ++++ + fish/virt.c | 191 ++++++++++++++++++++++++++++++++++++++++++++++++++++ + po/POTFILES.in | 1 + + 8 files changed, 326 insertions(+), 33 deletions(-) + create mode 100644 fish/virt.c + +diff --git a/README b/README +index 867bc56..15e6581 100644 +--- a/README ++++ b/README +@@ -52,6 +52,10 @@ Requirements + + - libmagic (the library that corresponds to the 'file' command) + ++- libvirt ++ ++- libxml2 ++ + - squashfs-tools (mksquashfs only) + + - genisoimage / mkisofs +diff --git a/configure.ac b/configure.ac +index 5e884ea..a1c8fe0 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -468,6 +468,16 @@ AC_CHECK_HEADER([magic.h],[],[ + AC_MSG_FAILURE([magic.h header file is required]) + ]) + ++dnl libvirt (required) ++PKG_CHECK_MODULES([LIBVIRT], [libvirt]) ++AC_SUBST([LIBVIRT_CFLAGS]) ++AC_SUBST([LIBVIRT_LIBS]) ++ ++dnl libxml2 (required) ++PKG_CHECK_MODULES([LIBXML2], [libxml-2.0]) ++AC_SUBST([LIBXML2_CFLAGS]) ++AC_SUBST([LIBXML2_LIBS]) ++ + dnl hivex library (highly recommended). + dnl This used to be a part of libguestfs, but was spun off into its + dnl own separate upstream project in libguestfs 1.0.85. +diff --git a/fish/Makefile.am b/fish/Makefile.am +index f6b3e7d..cd16733 100644 +--- a/fish/Makefile.am ++++ b/fish/Makefile.am +@@ -52,7 +52,8 @@ guestfish_SOURCES = \ + reopen.c \ + supported.c \ + tilde.c \ +- time.c ++ time.c \ ++ virt.c + + # This convenience library is solely to avoid compiler warnings + # in its generated sources. +@@ -65,9 +66,12 @@ guestfish_CFLAGS = \ + -DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' \ + -DLOCALEBASEDIR=\""$(datadir)/locale"\" \ + -I$(srcdir)/../gnulib/lib -I../gnulib/lib \ ++ $(LIBVIRT_CFLAGS) $(LIBXML2_CFLAGS) \ + $(WARN_CFLAGS) $(WERROR_CFLAGS) + +-guestfish_LDADD = $(top_builddir)/src/libguestfs.la $(LIBREADLINE) ++guestfish_LDADD = \ ++ $(LIBVIRT_LIBS) $(LIBXML2_LIBS) \ ++ $(top_builddir)/src/libguestfs.la $(LIBREADLINE) + + # Make libguestfs use the convenience library. + noinst_LTLIBRARIES = librc_protocol.la +diff --git a/fish/fish.c b/fish/fish.c +index 68f26ed..bc7d96c 100644 +--- a/fish/fish.c ++++ b/fish/fish.c +@@ -43,11 +43,23 @@ + #include "closeout.h" + #include "progname.h" + ++/* List of drives added via -a, -d or -N options. */ + struct drv { + struct drv *next; +- char *filename; /* disk filename (for -a or -N options) */ +- prep_data *data; /* prepared type (for -N option only) */ +- char *device; /* device inside the appliance */ ++ enum { drv_a, drv_d, drv_N } type; ++ union { ++ struct { ++ char *filename; /* disk filename */ ++ } a; ++ struct { ++ char *guest; /* guest name */ ++ } d; ++ struct { ++ char *filename; /* disk filename (testX.img) */ ++ prep_data *data; /* prepared type */ ++ char *device; /* device inside the appliance */ ++ } N; ++ }; + }; + + struct mp { +@@ -56,7 +68,7 @@ struct mp { + char *mountpoint; + }; + +-static void add_drives (struct drv *drv); ++static char add_drives (struct drv *drv, char next_drive); + static void prepare_drives (struct drv *drv); + static void mount_mps (struct mp *mp); + static int launch (void); +@@ -82,6 +94,7 @@ int remote_control = 0; + int exit_on_error = 1; + int command_num = 0; + int keys_from_stdin = 0; ++const char *libvirt_uri = NULL; + + static void __attribute__((noreturn)) + usage (int status) +@@ -109,6 +122,8 @@ usage (int status) + " -h|--cmd-help List available commands\n" + " -h|--cmd-help cmd Display detailed help on 'cmd'\n" + " -a|--add image Add image\n" ++ " -c|--connect uri Specify libvirt URI for -d option\n" ++ " -d|--domain guest Add disks from libvirt guest\n" + " -D|--no-dest-paths Don't tab-complete paths from guest fs\n" + " -f|--file file Read commands from file\n" + " -i|--inspector Run virt-inspector to get disk mountpoints\n" +@@ -145,10 +160,12 @@ main (int argc, char *argv[]) + + enum { HELP_OPTION = CHAR_MAX + 1 }; + +- static const char *options = "a:Df:h::im:nN:rv?Vx"; ++ static const char *options = "a:c:d:Df:h::im:nN:rv?Vx"; + static const struct option long_options[] = { + { "add", 1, 0, 'a' }, + { "cmd-help", 2, 0, 'h' }, ++ { "connect", 1, 0, 'c' }, ++ { "domain", 1, 0, 'd' }, + { "file", 1, 0, 'f' }, + { "help", 0, 0, HELP_OPTION }, + { "inspector", 0, 0, 'i' }, +@@ -174,7 +191,6 @@ main (int argc, char *argv[]) + int inspector = 0; + int option_index; + struct sigaction sa; +- char next_drive = 'a'; + int next_prepared_drive = 1; + + initialize_readline (); +@@ -262,15 +278,26 @@ main (int argc, char *argv[]) + perror ("malloc"); + exit (EXIT_FAILURE); + } +- drv->filename = optarg; +- drv->data = NULL; +- /* We could fill the device field in, but in fact we +- * only use it for the -N option at present. +- */ +- drv->device = NULL; ++ drv->type = drv_a; ++ drv->a.filename = optarg; ++ drv->next = drvs; ++ drvs = drv; ++ break; ++ ++ case 'c': ++ libvirt_uri = optarg; ++ break; ++ ++ case 'd': ++ drv = malloc (sizeof (struct drv)); ++ if (!drv) { ++ perror ("malloc"); ++ exit (EXIT_FAILURE); ++ } ++ drv->type = drv_d; ++ drv->d.guest = optarg; + drv->next = drvs; + drvs = drv; +- next_drive++; + break; + + case 'N': +@@ -283,16 +310,14 @@ main (int argc, char *argv[]) + perror ("malloc"); + exit (EXIT_FAILURE); + } +- if (asprintf (&drv->filename, "test%d.img", ++ drv->type = drv_N; ++ if (asprintf (&drv->N.filename, "test%d.img", + next_prepared_drive++) == -1) { + perror ("asprintf"); + exit (EXIT_FAILURE); + } +- drv->data = create_prepared_file (optarg, drv->filename); +- if (asprintf (&drv->device, "/dev/sd%c", next_drive++) == -1) { +- perror ("asprintf"); +- exit (EXIT_FAILURE); +- } ++ drv->N.data = create_prepared_file (optarg, drv->N.filename); ++ drv->N.device = NULL; /* filled in by add_drives */ + drv->next = drvs; + drvs = drv; + break; +@@ -476,7 +501,7 @@ main (int argc, char *argv[]) + } + + /* If we've got drives to add, add them now. */ +- add_drives (drvs); ++ add_drives (drvs, 'a'); + + /* If we've got mountpoints or prepared drives, we must launch the + * guest and mount them. +@@ -584,21 +609,60 @@ mount_mps (struct mp *mp) + } + } + +-static void +-add_drives (struct drv *drv) ++static char ++add_drives (struct drv *drv, char next_drive) + { + int r; + ++ if (next_drive > 'z') { ++ fprintf (stderr, ++ _("guestfish: too many drives added on the command line\n")); ++ exit (EXIT_FAILURE); ++ } ++ + if (drv) { +- add_drives (drv->next); ++ next_drive = add_drives (drv->next, next_drive); + +- if (drv->data /* -N option is not affected by --ro */ || !read_only) +- r = guestfs_add_drive (g, drv->filename); +- else +- r = guestfs_add_drive_ro (g, drv->filename); +- if (r == -1) +- exit (EXIT_FAILURE); ++ switch (drv->type) { ++ case drv_a: ++ if (!read_only) ++ r = guestfs_add_drive (g, drv->a.filename); ++ else ++ r = guestfs_add_drive_ro (g, drv->a.filename); ++ if (r == -1) ++ exit (EXIT_FAILURE); ++ ++ next_drive++; ++ break; ++ ++ case drv_d: ++ r = add_libvirt_drives (drv->d.guest); ++ if (r == -1) ++ exit (EXIT_FAILURE); ++ ++ next_drive += r; ++ break; ++ ++ case drv_N: ++ /* -N option is not affected by --ro */ ++ r = guestfs_add_drive (g, drv->N.filename); ++ if (r == -1) ++ exit (EXIT_FAILURE); ++ ++ if (asprintf (&drv->N.device, "/dev/sd%c", next_drive) == -1) { ++ perror ("asprintf"); ++ exit (EXIT_FAILURE); ++ } ++ ++ next_drive++; ++ break; ++ ++ default: /* keep GCC happy */ ++ abort (); ++ } + } ++ ++ return next_drive; + } + + static void +@@ -606,8 +670,8 @@ prepare_drives (struct drv *drv) + { + if (drv) { + prepare_drives (drv->next); +- if (drv->data) +- prepare_drive (drv->filename, drv->data, drv->device); ++ if (drv->type == drv_N) ++ prepare_drive (drv->N.filename, drv->N.data, drv->N.device); + } + } + +diff --git a/fish/fish.h b/fish/fish.h +index da1b087..bf1f81c 100644 +--- a/fish/fish.h ++++ b/fish/fish.h +@@ -49,9 +49,11 @@ + + /* in fish.c */ + extern guestfs_h *g; ++extern int read_only; + extern int quit; + extern int verbose; + extern int command_num; ++extern const char *libvirt_uri; + extern int issue_command (const char *cmd, char *argv[], const char *pipe); + extern void pod2text (const char *name, const char *shortdesc, const char *body); + extern void list_builtin_commands (void); +@@ -131,6 +133,9 @@ extern int do_time (const char *cmd, int argc, char *argv[]); + /* in tilde.c */ + extern char *try_tilde_expansion (char *path); + ++/* in virt.c */ ++extern int add_libvirt_drives (const char *guest); ++ + /* This should just list all the built-in commands so they can + * be added to the generated auto-completion code. + */ +diff --git a/fish/guestfish.pod b/fish/guestfish.pod +index bfcec5c..8daebc8 100644 +--- a/fish/guestfish.pod ++++ b/fish/guestfish.pod +@@ -14,6 +14,8 @@ guestfish - the libguestfs Filesystem Interactive SHell + + guestfish -a disk.img -m dev[:mountpoint] + ++ guestfish -d libvirt-domain ++ + guestfish -i libvirt-domain + + guestfish -i disk.img [disk.img ...] +@@ -140,6 +142,18 @@ Displays detailed help on a single command C. + + Add a block device or virtual machine image to the shell. + ++=item B<-c URI> | B<--connect URI> ++ ++When used in conjunction with the I<-d> option, this specifies ++the libvirt URI to use. The default is to use the default libvirt ++connection. ++ ++=item B<-d libvirt-domain> | B<--domain libvirt-domain> ++ ++Add disks from the named libvirt domain. If the I<--ro> option is ++also used, then any libvirt domain can be used. However in write ++mode, only libvirt domains which are shut down can be named here. ++ + =item B<-D> | B<--no-dest-paths> + + Don't tab-complete paths on the guest filesystem. It is useful to be +diff --git a/fish/virt.c b/fish/virt.c +new file mode 100644 +index 0000000..9c4ce1a +--- /dev/null ++++ b/fish/virt.c +@@ -0,0 +1,191 @@ ++/* guestfish - the filesystem interactive shell ++ * Copyright (C) 2010 Red Hat Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++ ++#include ++#include ++ ++#include ++#include ++#include ++ ++#include "fish.h" ++ ++static int add_drives_from_node_set (xmlDocPtr doc, xmlNodeSetPtr nodes); ++ ++/* Implements the guts of the '-d' option. ++ * ++ * Note that we have to observe the '--ro' flag in two respects: by ++ * adding the drives read-only if the flag is set, and by restricting ++ * guests to shut down ones unless '--ro' is set. ++ * ++ * Returns the number of drives added (> 0), or -1 for failure. ++ */ ++int ++add_libvirt_drives (const char *guest) ++{ ++ static int initialized = 0; ++ if (!initialized) { ++ initialized = 1; ++ ++ if (virInitialize () == -1) ++ return -1; ++ ++ xmlInitParser (); ++ LIBXML_TEST_VERSION; ++ } ++ ++ int r = -1, nr_added = 0; ++ virErrorPtr err; ++ virConnectPtr conn = NULL; ++ virDomainPtr dom = NULL; ++ xmlDocPtr doc = NULL; ++ xmlXPathContextPtr xpathCtx = NULL; ++ xmlXPathObjectPtr xpathObj = NULL; ++ char *xml = NULL; ++ ++ /* Connect to libvirt, find the domain. */ ++ conn = virConnectOpenReadOnly (libvirt_uri); ++ if (!conn) { ++ err = virGetLastError (); ++ fprintf (stderr, _("guestfish: could not connect to libvirt (code %d, domain %d): %s\n"), ++ err->code, err->domain, err->message); ++ goto cleanup; ++ } ++ ++ dom = virDomainLookupByName (conn, guest); ++ if (!dom) { ++ err = virConnGetLastError (conn); ++ fprintf (stderr, _("guestfish: no libvirt domain called '%s': %s\n"), ++ guest, err->message); ++ goto cleanup; ++ } ++ if (!read_only) { ++ virDomainInfo info; ++ if (virDomainGetInfo (dom, &info) == -1) { ++ err = virConnGetLastError (conn); ++ fprintf (stderr, _("guestfish: error getting domain info about '%s': %s\n"), ++ guest, err->message); ++ goto cleanup; ++ } ++ if (info.state != VIR_DOMAIN_SHUTOFF) { ++ fprintf (stderr, _("guestfish: error: '%s' is a live virtual machine.\nYou must use '--ro' because write access to a running virtual machine can\ncause disk corruption.\n"), ++ guest); ++ goto cleanup; ++ } ++ } ++ ++ /* Domain XML. */ ++ xml = virDomainGetXMLDesc (dom, 0); ++ ++ if (!xml) { ++ err = virConnGetLastError (conn); ++ fprintf (stderr, _("guestfish: error reading libvirt XML information about '%s': %s\n"), ++ guest, err->message); ++ goto cleanup; ++ } ++ ++ /* Now the horrible task of parsing out the fields we need from the XML. ++ * http://www.xmlsoft.org/examples/xpath1.c ++ */ ++ doc = xmlParseMemory (xml, strlen (xml)); ++ if (doc == NULL) { ++ fprintf (stderr, _("guestfish: unable to parse XML information returned by libvirt\n")); ++ goto cleanup; ++ } ++ ++ xpathCtx = xmlXPathNewContext (doc); ++ if (xpathCtx == NULL) { ++ fprintf (stderr, _("guestfish: unable to create new XPath context\n")); ++ goto cleanup; ++ } ++ ++ xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk/source/@dev", ++ xpathCtx); ++ if (xpathObj == NULL) { ++ fprintf (stderr, _("guestfish: unable to evaluate XPath expression\n")); ++ goto cleanup; ++ } ++ ++ nr_added += add_drives_from_node_set (doc, xpathObj->nodesetval); ++ ++ xmlXPathFreeObject (xpathObj); xpathObj = NULL; ++ ++ xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk/source/@file", ++ xpathCtx); ++ if (xpathObj == NULL) { ++ fprintf (stderr, _("guestfish: unable to evaluate XPath expression\n")); ++ goto cleanup; ++ } ++ ++ nr_added += add_drives_from_node_set (doc, xpathObj->nodesetval); ++ ++ if (nr_added == 0) { ++ fprintf (stderr, _("guestfish: libvirt domain '%s' has no disks\n"), ++ guest); ++ goto cleanup; ++ } ++ ++ /* Successful. */ ++ r = nr_added; ++ ++cleanup: ++ free (xml); ++ if (xpathObj) xmlXPathFreeObject (xpathObj); ++ if (xpathCtx) xmlXPathFreeContext (xpathCtx); ++ if (doc) xmlFreeDoc (doc); ++ if (dom) virDomainFree (dom); ++ if (conn) virConnectClose (conn); ++ ++ return r; ++} ++ ++static int ++add_drives_from_node_set (xmlDocPtr doc, xmlNodeSetPtr nodes) ++{ ++ if (!nodes) ++ return 0; ++ ++ int i; ++ ++ for (i = 0; i < nodes->nodeNr; ++i) { ++ assert (nodes->nodeTab[i]); ++ assert (nodes->nodeTab[i]->type == XML_ATTRIBUTE_NODE); ++ xmlAttrPtr attr = (xmlAttrPtr) nodes->nodeTab[i]; ++ ++ char *device = (char *) xmlNodeListGetString (doc, attr->children, 1); ++ ++ int r; ++ if (!read_only) ++ r = guestfs_add_drive (g, device); ++ else ++ r = guestfs_add_drive_ro (g, device); ++ if (r == -1) ++ exit (EXIT_FAILURE); ++ ++ xmlFree (device); ++ } ++ ++ return nodes->nodeNr; ++} +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 8ce5c97..e463bbb 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -85,6 +85,7 @@ fish/reopen.c + fish/supported.c + fish/tilde.c + fish/time.c ++fish/virt.c + fuse/dircache.c + fuse/guestmount.c + inspector/virt-inspector.pl +-- +1.7.1 + diff --git a/0013-fish-Reimplement-i-option-using-new-C-based-inspecti.patch b/0013-fish-Reimplement-i-option-using-new-C-based-inspecti.patch new file mode 100644 index 0000000..063ea63 --- /dev/null +++ b/0013-fish-Reimplement-i-option-using-new-C-based-inspecti.patch @@ -0,0 +1,512 @@ +From a65397cc74f6b0867c4c5cb31f4f2af47cd3e295 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Mon, 2 Aug 2010 17:43:23 +0100 +Subject: [PATCH] fish: Reimplement -i option using new C-based inspection. + +Don't shell out to virt-inspector. Instead, use the new C-based +inspection APIs. + +This is much faster. + +The new syntax is slightly different: + + guestfish -a disk.img -i + guestfish -d guest -i + +However, the old syntax still works. +(cherry picked from commit 4440e22f4f7ebffe0728a8c019319d1a2b260cf5) +--- + fish/Makefile.am | 1 + + fish/fish.c | 164 ++++++++++++++------------------------------------- + fish/fish.h | 4 + + fish/guestfish.pod | 37 ++++++------ + fish/inspect.c | 117 +++++++++++++++++++++++++++++++++++++ + po/POTFILES.in | 1 + + 6 files changed, 187 insertions(+), 137 deletions(-) + create mode 100644 fish/inspect.c + +diff --git a/fish/Makefile.am b/fish/Makefile.am +index cd16733..9bc5b73 100644 +--- a/fish/Makefile.am ++++ b/fish/Makefile.am +@@ -44,6 +44,7 @@ guestfish_SOURCES = \ + fish.c \ + fish.h \ + glob.c \ ++ inspect.c \ + lcd.c \ + man.c \ + more.c \ +diff --git a/fish/fish.c b/fish/fish.c +index bc7d96c..a896a92 100644 +--- a/fish/fish.c ++++ b/fish/fish.c +@@ -81,7 +81,6 @@ static void cleanup_readline (void); + #ifdef HAVE_LIBREADLINE + static void add_history_line (const char *); + #endif +-static void print_shell_quote (FILE *stream, const char *str); + + /* Currently open libguestfs handle. */ + guestfs_h *g; +@@ -95,6 +94,7 @@ int exit_on_error = 1; + int command_num = 0; + int keys_from_stdin = 0; + const char *libvirt_uri = NULL; ++int inspector = 0; + + static void __attribute__((noreturn)) + usage (int status) +@@ -126,7 +126,7 @@ usage (int status) + " -d|--domain guest Add disks from libvirt guest\n" + " -D|--no-dest-paths Don't tab-complete paths from guest fs\n" + " -f|--file file Read commands from file\n" +- " -i|--inspector Run virt-inspector to get disk mountpoints\n" ++ " -i|--inspector Automatically mount filesystems\n" + " --keys-from-stdin Read passphrases from stdin\n" + " --listen Listen for remote commands\n" + " -m|--mount dev[:mnt] Mount dev on mnt (if omitted, /)\n" +@@ -188,7 +188,6 @@ main (int argc, char *argv[]) + struct mp *mp; + char *p, *file = NULL; + int c; +- int inspector = 0; + int option_index; + struct sigaction sa; + int next_prepared_drive = 1; +@@ -228,7 +227,7 @@ main (int argc, char *argv[]) + * using it just above. + * + * getopt_long uses argv[0], so give it the sanitized name. Save a copy +- * of the original, in case it's needed in virt-inspector mode, below. ++ * of the original, in case it's needed below. + */ + char *real_argv0 = argv[0]; + argv[0] = bad_cast (program_name); +@@ -398,115 +397,46 @@ main (int argc, char *argv[]) + } + } + +- /* Inspector mode invalidates most of the other arguments. */ +- if (inspector) { +- if (drvs || mps || remote_control_listen || remote_control || +- guestfs_get_selinux (g)) { +- fprintf (stderr, _("%s: cannot use -i option with -a, -m, -N, " +- "--listen, --remote or --selinux\n"), +- program_name); +- exit (EXIT_FAILURE); +- } +- if (optind >= argc) { +- fprintf (stderr, +- _("%s: -i requires a libvirt domain or path(s) to disk image(s)\n"), +- program_name); +- exit (EXIT_FAILURE); +- } +- +- char *cmd; +- size_t cmdlen; +- FILE *fp = open_memstream (&cmd, &cmdlen); +- if (fp == NULL) { +- perror ("open_memstream"); +- exit (EXIT_FAILURE); +- } +- +- fprintf (fp, "virt-inspector"); ++ /* Old-style -i syntax? Since -a/-d/-N and -i was disallowed ++ * previously, if we have -i without any drives but with something ++ * on the command line, it must be old-style syntax. ++ */ ++ if (inspector && drvs == NULL && optind < argc) { + while (optind < argc) { +- fputc (' ', fp); +- print_shell_quote (fp, argv[optind]); +- optind++; +- } +- +- if (read_only) +- fprintf (fp, " --ro-fish"); +- else +- fprintf (fp, " --fish"); +- +- if (fclose (fp) == -1) { +- perror ("fclose"); +- exit (EXIT_FAILURE); +- } +- +- if (verbose) +- fprintf (stderr, +- "%s -i: running: %s\n", program_name, cmd); +- +- FILE *pp = popen (cmd, "r"); +- if (pp == NULL) { +- perror (cmd); +- exit (EXIT_FAILURE); +- } +- +- char *cmd2; +- fp = open_memstream (&cmd2, &cmdlen); +- if (fp == NULL) { +- perror ("open_memstream"); +- exit (EXIT_FAILURE); +- } +- +- fprintf (fp, "%s", real_argv0); +- +- if (guestfs_get_verbose (g)) +- fprintf (fp, " -v"); +- if (!guestfs_get_autosync (g)) +- fprintf (fp, " -n"); +- if (guestfs_get_trace (g)) +- fprintf (fp, " -x"); +- +- char *insp = NULL; +- size_t insplen; +- if (getline (&insp, &insplen, pp) == -1) { +- perror (cmd); +- exit (EXIT_FAILURE); +- } +- fprintf (fp, " %s", insp); +- +- if (pclose (pp) == -1) { +- perror (cmd); +- exit (EXIT_FAILURE); +- } +- +- if (fclose (fp) == -1) { +- perror ("fclose"); +- exit (EXIT_FAILURE); +- } +- +- if (verbose) +- fprintf (stderr, +- "%s -i: running: %s\n", program_name, cmd2); ++ if (strchr (argv[optind], '/') || ++ access (argv[optind], F_OK) == 0) { /* simulate -a option */ ++ drv = malloc (sizeof (struct drv)); ++ if (!drv) { ++ perror ("malloc"); ++ exit (EXIT_FAILURE); ++ } ++ drv->type = drv_a; ++ drv->a.filename = argv[optind]; ++ drv->next = drvs; ++ drvs = drv; ++ } else { /* simulate -d option */ ++ drv = malloc (sizeof (struct drv)); ++ if (!drv) { ++ perror ("malloc"); ++ exit (EXIT_FAILURE); ++ } ++ drv->type = drv_d; ++ drv->d.guest = argv[optind]; ++ drv->next = drvs; ++ drvs = drv; ++ } + +- int r = system (cmd2); +- if (r == -1) { +- perror (cmd2); +- exit (EXIT_FAILURE); ++ optind++; + } +- +- free (cmd); +- free (cmd2); +- free (insp); +- +- exit (WEXITSTATUS (r)); + } + + /* If we've got drives to add, add them now. */ + add_drives (drvs, 'a'); + +- /* If we've got mountpoints or prepared drives, we must launch the +- * guest and mount them. ++ /* If we've got mountpoints or prepared drives or -i option, we must ++ * launch the guest and mount them. + */ +- if (next_prepared_drive > 1 || mps != NULL) { ++ if (next_prepared_drive > 1 || mps != NULL || inspector) { + /* RHBZ#612178: If --listen flag is given, then we will fork into + * the background in rc_listen(). However you can't do this while + * holding a libguestfs handle open because the recovery process +@@ -519,6 +449,10 @@ main (int argc, char *argv[]) + guestfs_set_recovery_proc (g, 0); + + if (launch () == -1) exit (EXIT_FAILURE); ++ ++ if (inspector) ++ inspect_mount (); ++ + prepare_drives (drvs); + mount_mps (mps); + } +@@ -747,7 +681,7 @@ script (int prompt) + int global_exit_on_error = !prompt; + int tilde_candidate; + +- if (prompt) ++ if (prompt) { + printf (_("\n" + "Welcome to guestfish, the libguestfs filesystem interactive shell for\n" + "editing virtual machine filesystems.\n" +@@ -757,6 +691,12 @@ script (int prompt) + " 'quit' to quit the shell\n" + "\n")); + ++ if (inspector) { ++ print_inspect_prompt (); ++ printf ("\n"); ++ } ++ } ++ + while (!quit) { + char *pipe = NULL; + +@@ -1840,17 +1780,3 @@ read_key (const char *param) + + return ret; + } +- +-static void +-print_shell_quote (FILE *stream, const char *str) +-{ +-#define SAFE(c) (c_isalnum((c)) || \ +- (c) == '/' || (c) == '-' || (c) == '_' || (c) == '.') +- int i; +- +- for (i = 0; str[i]; ++i) { +- if (!SAFE(str[i])) +- putc ('\\', stream); +- putc (str[i], stream); +- } +-} +diff --git a/fish/fish.h b/fish/fish.h +index bf1f81c..660b8ee 100644 +--- a/fish/fish.h ++++ b/fish/fish.h +@@ -96,6 +96,10 @@ extern int do_echo (const char *cmd, int argc, char *argv[]); + /* in edit.c */ + extern int do_edit (const char *cmd, int argc, char *argv[]); + ++/* in inspect.c */ ++extern void inspect_mount (void); ++extern void print_inspect_prompt (void); ++ + /* in lcd.c */ + extern int do_lcd (const char *cmd, int argc, char *argv[]); + +diff --git a/fish/guestfish.pod b/fish/guestfish.pod +index 8daebc8..cf1140a 100644 +--- a/fish/guestfish.pod ++++ b/fish/guestfish.pod +@@ -16,9 +16,9 @@ guestfish - the libguestfs Filesystem Interactive SHell + + guestfish -d libvirt-domain + +- guestfish -i libvirt-domain ++ guestfish -a disk.img -i + +- guestfish -i disk.img [disk.img ...] ++ guestfish -d libvirt-domain -i + + =head1 WARNING + +@@ -75,13 +75,14 @@ Edit C interactively: + --mount /dev/sda1:/boot \ + edit /boot/grub/grub.conf + +-=head2 Using virt-inspector ++=head2 Mount disks automatically + +-Use the I<-i> option to get virt-inspector to mount +-the filesystems automatically as they would be mounted +-in the virtual machine: ++Use the I<-i> option to automatically mount the ++disks from a virtual machine: + +- guestfish --ro -i disk.img cat /etc/group ++ guestfish --ro -a disk.img -i cat /etc/group ++ ++ guestfish --ro -d libvirt-domain -i cat /etc/group + + =head2 As a script interpreter + +@@ -170,28 +171,28 @@ scripts, use: + + =item B<-i> | B<--inspector> + +-Run virt-inspector on the named libvirt domain or list of disk +-images. If virt-inspector is available and if it can identify +-the domain or disk images, then partitions will be mounted +-correctly at start-up. ++Using L code, inspect the disks looking for ++an operating system and mount filesystems as they would be ++mounted on the real virtual machine. + + Typical usage is either: + +- guestfish -i myguest ++ guestfish -d myguest -i + + (for an inactive libvirt domain called I), or: + +- guestfish --ro -i myguest ++ guestfish --ro -d myguest -i + + (for active domains, readonly), or specify the block device directly: + +- guestfish -i /dev/Guests/MyGuest ++ guestfish -a /dev/Guests/MyGuest -i ++ ++Note that the command line syntax changed slightly over older ++versions of guestfish. You can still use the old syntax: + +-You cannot use I<-a>, I<-m>, I<-N>, I<--listen>, I<--remote> or +-I<--selinux> in conjunction with this option, and options other than +-I<--ro> might not behave correctly. ++ guestfish [--ro] -i disk.img + +-See also: L. ++ guestfish [--ro] -i libvirt-domain + + =item B<--keys-from-stdin> + +diff --git a/fish/inspect.c b/fish/inspect.c +new file mode 100644 +index 0000000..d17496f +--- /dev/null ++++ b/fish/inspect.c +@@ -0,0 +1,117 @@ ++/* guestfish - the filesystem interactive shell ++ * Copyright (C) 2010 Red Hat Inc. ++ * ++ * This program is free software; you can redistribute it and/or modify ++ * it under the terms of the GNU General Public License as published by ++ * the Free Software Foundation; either version 2 of the License, or ++ * (at your option) any later version. ++ * ++ * This program is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ++ * GNU General Public License for more details. ++ * ++ * You should have received a copy of the GNU General Public License ++ * along with this program; if not, write to the Free Software ++ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ++ */ ++ ++#include ++ ++#include ++#include ++#include ++ ++#include "fish.h" ++ ++/* Global that saves the root device between inspect_mount and ++ * print_inspect_prompt. ++ */ ++static char *root = NULL; ++ ++static int ++compare_keys_len (const void *p1, const void *p2) ++{ ++ const char *key1 = * (char * const *) p1; ++ const char *key2 = * (char * const *) p2; ++ return strlen (key1) - strlen (key2); ++} ++ ++static int ++compare_keys (const void *p1, const void *p2) ++{ ++ const char *key1 = * (char * const *) p1; ++ const char *key2 = * (char * const *) p2; ++ return strcasecmp (key1, key2); ++} ++ ++/* This function implements the -i option. */ ++void ++inspect_mount (void) ++{ ++ char **roots = guestfs_inspect_os (g); ++ if (roots == NULL) ++ exit (EXIT_FAILURE); ++ ++ if (roots[0] == NULL) { ++ fprintf (stderr, _("guestfish: no operating system was found on this disk\n")); ++ exit (EXIT_FAILURE); ++ } ++ ++ if (roots[1] != NULL) { ++ fprintf (stderr, _("guestfish: multi-boot operating systems are not supported by the -i option\n")); ++ exit (EXIT_FAILURE); ++ } ++ ++ root = roots[0]; ++ free (roots); ++ ++ char **mountpoints = guestfs_inspect_get_mountpoints (g, root); ++ if (mountpoints == NULL) ++ exit (EXIT_FAILURE); ++ ++ /* Sort by key length, shortest key first, so that we end up ++ * mounting the filesystems in the correct order. ++ */ ++ qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *), ++ compare_keys_len); ++ ++ size_t i; ++ for (i = 0; mountpoints[i] != NULL; i += 2) { ++ int r; ++ if (!read_only) ++ r = guestfs_mount_options (g, "", mountpoints[i+1], mountpoints[i]); ++ else ++ r = guestfs_mount_ro (g, mountpoints[i+1], mountpoints[i]); ++ if (r == -1) ++ exit (EXIT_FAILURE); ++ } ++ ++ free_strings (mountpoints); ++} ++ ++/* This function is called only if the above function was called, ++ * and only after we've printed the prompt in interactive mode. ++ */ ++void ++print_inspect_prompt (void) ++{ ++ char *name = guestfs_inspect_get_product_name (g, root); ++ if (STRNEQ (name, "unknown")) ++ printf (_("Operating system: %s\n"), name); ++ free (name); ++ ++ char **mountpoints = guestfs_inspect_get_mountpoints (g, root); ++ if (mountpoints == NULL) ++ return; ++ ++ /* Sort by key. */ ++ qsort (mountpoints, count_strings (mountpoints) / 2, 2 * sizeof (char *), ++ compare_keys); ++ ++ size_t i; ++ for (i = 0; mountpoints[i] != NULL; i += 2) ++ printf (_("%s mounted on %s\n"), mountpoints[i+1], mountpoints[i]); ++ ++ free_strings (mountpoints); ++} +diff --git a/po/POTFILES.in b/po/POTFILES.in +index e463bbb..843e8e0 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -76,6 +76,7 @@ fish/echo.c + fish/edit.c + fish/fish.c + fish/glob.c ++fish/inspect.c + fish/lcd.c + fish/man.c + fish/more.c +-- +1.7.1 + diff --git a/0014-Remove-old-ocaml-inspector-code.patch b/0014-Remove-old-ocaml-inspector-code.patch new file mode 100644 index 0000000..de30a6d --- /dev/null +++ b/0014-Remove-old-ocaml-inspector-code.patch @@ -0,0 +1,627 @@ +From 8a47a238e62c7cb10e9e9ad2c94b0579adc835a3 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Mon, 2 Aug 2010 23:29:43 +0100 +Subject: [PATCH] Remove old ocaml-inspector code. + +Not used by anyone, didn't work well, and replaced now by the +C inspection APIs. +(cherry picked from commit ad373a4d6c2367b78b0dd337c1797f889a94b713) +--- + ocaml/Makefile.am | 13 +- + ocaml/t/guestfs_500_inspect.ml | 42 ---- + src/generator.ml | 490 ---------------------------------------- + 3 files changed, 3 insertions(+), 542 deletions(-) + delete mode 100644 ocaml/t/guestfs_500_inspect.ml + +diff --git a/ocaml/Makefile.am b/ocaml/Makefile.am +index 99bb390..5c7b929 100644 +--- a/ocaml/Makefile.am ++++ b/ocaml/Makefile.am +@@ -20,15 +20,12 @@ include $(top_srcdir)/subdir-rules.mk + generator_built = \ + guestfs.mli \ + guestfs.ml \ +- guestfs_inspector.mli \ +- guestfs_inspector.ml \ + guestfs_c_actions.c \ + bindtests.ml + + EXTRA_DIST = \ + $(generator_built) \ + guestfs_c.c guestfs_c.h \ +- guestfs_inspector.mli guestfs_inspector.ml \ + .depend META.in \ + run-bindtests \ + t/*.ml +@@ -45,7 +42,7 @@ if HAVE_XML_LIGHT + + noinst_DATA = mlguestfs.cma mlguestfs.cmxa META + +-OBJS = guestfs_c.o guestfs_c_actions.o guestfs.cmo guestfs_inspector.cmo ++OBJS = guestfs_c.o guestfs_c_actions.o guestfs.cmo + XOBJS = $(OBJS:.cmo=.cmx) + + mlguestfs.cma: $(OBJS) +@@ -67,10 +64,10 @@ TESTS_ENVIRONMENT = \ + + TESTS = run-bindtests \ + t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate \ +- t/guestfs_060_readdir t/guestfs_070_threads t/guestfs_500_inspect ++ t/guestfs_060_readdir t/guestfs_070_threads + noinst_DATA += bindtests \ + t/guestfs_005_load t/guestfs_010_launch t/guestfs_050_lvcreate \ +- t/guestfs_060_readdir t/guestfs_070_threads t/guestfs_500_inspect ++ t/guestfs_060_readdir t/guestfs_070_threads + + bindtests: bindtests.cmx mlguestfs.cmxa + mkdir -p t +@@ -96,10 +93,6 @@ t/guestfs_070_threads: t/guestfs_070_threads.cmx mlguestfs.cmxa + mkdir -p t + $(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . -package unix,threads -thread -linkpkg mlguestfs.cmxa $< -o $@ + +-t/guestfs_500_inspect: t/guestfs_500_inspect.cmx mlguestfs.cmxa +- mkdir -p t +- $(OCAMLFIND) ocamlopt -cclib -L$(top_builddir)/src/.libs -I . -package xml-light,unix -linkpkg mlguestfs.cmxa $< -o $@ +- + # Need to rebuild the tests from source if the main library has + # changed at all, otherwise we get inconsistent assumptions. + t/guestfs_070_threads.cmx: t/guestfs_070_threads.ml mlguestfs.cmxa +diff --git a/ocaml/t/guestfs_500_inspect.ml b/ocaml/t/guestfs_500_inspect.ml +deleted file mode 100644 +index ec1071a..0000000 +--- a/ocaml/t/guestfs_500_inspect.ml ++++ /dev/null +@@ -1,42 +0,0 @@ +-(* libguestfs OCaml bindings +- * Copyright (C) 2009 Red Hat Inc. +- * +- * This program is free software; you can redistribute it and/or modify +- * it under the terms of the GNU General Public License as published by +- * the Free Software Foundation; either version 2 of the License, or +- * (at your option) any later version. +- * +- * This program is distributed in the hope that it will be useful, +- * but WITHOUT ANY WARRANTY; without even the implied warranty of +- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +- * GNU General Public License for more details. +- * +- * You should have received a copy of the GNU General Public License +- * along with this program; if not, write to the Free Software +- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. +- *) +- +-open Unix +- +-let (//) = Filename.concat +-let dotdot = Filename.parent_dir_name +- +-let read_file name = +- let chan = open_in name in +- let lines = ref [] in +- let lines = +- try while true do lines := input_line chan :: !lines done; [] +- with End_of_file -> List.rev !lines in +- close_in chan; +- String.concat "" lines +- +-let parse name = +- let xml = read_file name in +- let os = Guestfs_inspector.inspect ~xml [] in +- os +- +-let () = +- ignore (parse (dotdot // "inspector" // "example1.xml")); +- ignore (parse (dotdot // "inspector" // "example2.xml")); +- ignore (parse (dotdot // "inspector" // "example3.xml")); +- ignore (parse (dotdot // "inspector" // "example4.xml")) +diff --git a/src/generator.ml b/src/generator.ml +index 7ac7f6a..8fb1477 100755 +--- a/src/generator.ml ++++ b/src/generator.ml +@@ -12175,494 +12175,6 @@ and generate_lang_bindtests call = + + (* XXX Add here tests of the return and error functions. *) + +-(* Code to generator bindings for virt-inspector. Currently only +- * implemented for OCaml code (for virt-p2v 2.0). +- *) +-let rng_input = "inspector/virt-inspector.rng" +- +-(* Read the input file and parse it into internal structures. This is +- * by no means a complete RELAX NG parser, but is just enough to be +- * able to parse the specific input file. +- *) +-type rng = +- | Element of string * rng list (* *) +- | Attribute of string * rng list (* *) +- | Interleave of rng list (* *) +- | ZeroOrMore of rng (* *) +- | OneOrMore of rng (* *) +- | Optional of rng (* *) +- | Choice of string list (* * *) +- | Value of string (* str *) +- | Text (* *) +- +-let rec string_of_rng = function +- | Element (name, xs) -> +- "Element (\"" ^ name ^ "\", (" ^ string_of_rng_list xs ^ "))" +- | Attribute (name, xs) -> +- "Attribute (\"" ^ name ^ "\", (" ^ string_of_rng_list xs ^ "))" +- | Interleave xs -> "Interleave (" ^ string_of_rng_list xs ^ ")" +- | ZeroOrMore rng -> "ZeroOrMore (" ^ string_of_rng rng ^ ")" +- | OneOrMore rng -> "OneOrMore (" ^ string_of_rng rng ^ ")" +- | Optional rng -> "Optional (" ^ string_of_rng rng ^ ")" +- | Choice values -> "Choice [" ^ String.concat ", " values ^ "]" +- | Value value -> "Value \"" ^ value ^ "\"" +- | Text -> "Text" +- +-and string_of_rng_list xs = +- String.concat ", " (List.map string_of_rng xs) +- +-let rec parse_rng ?defines context = function +- | [] -> [] +- | Xml.Element ("element", ["name", name], children) :: rest -> +- Element (name, parse_rng ?defines context children) +- :: parse_rng ?defines context rest +- | Xml.Element ("attribute", ["name", name], children) :: rest -> +- Attribute (name, parse_rng ?defines context children) +- :: parse_rng ?defines context rest +- | Xml.Element ("interleave", [], children) :: rest -> +- Interleave (parse_rng ?defines context children) +- :: parse_rng ?defines context rest +- | Xml.Element ("zeroOrMore", [], [child]) :: rest -> +- let rng = parse_rng ?defines context [child] in +- (match rng with +- | [child] -> ZeroOrMore child :: parse_rng ?defines context rest +- | _ -> +- failwithf "%s: contains more than one child element" +- context +- ) +- | Xml.Element ("oneOrMore", [], [child]) :: rest -> +- let rng = parse_rng ?defines context [child] in +- (match rng with +- | [child] -> OneOrMore child :: parse_rng ?defines context rest +- | _ -> +- failwithf "%s: contains more than one child element" +- context +- ) +- | Xml.Element ("optional", [], [child]) :: rest -> +- let rng = parse_rng ?defines context [child] in +- (match rng with +- | [child] -> Optional child :: parse_rng ?defines context rest +- | _ -> +- failwithf "%s: contains more than one child element" +- context +- ) +- | Xml.Element ("choice", [], children) :: rest -> +- let values = List.map ( +- function Xml.Element ("value", [], [Xml.PCData value]) -> value +- | _ -> +- failwithf "%s: can't handle anything except in " +- context +- ) children in +- Choice values +- :: parse_rng ?defines context rest +- | Xml.Element ("value", [], [Xml.PCData value]) :: rest -> +- Value value :: parse_rng ?defines context rest +- | Xml.Element ("text", [], []) :: rest -> +- Text :: parse_rng ?defines context rest +- | Xml.Element ("ref", ["name", name], []) :: rest -> +- (* Look up the reference. Because of limitations in this parser, +- * we can't handle arbitrarily nested yet. You can only +- * use from inside . +- *) +- (match defines with +- | None -> +- failwithf "%s: contains , but no refs are defined yet" context +- | Some map -> +- let rng = StringMap.find name map in +- rng @ parse_rng ?defines context rest +- ) +- | x :: _ -> +- failwithf "%s: can't handle '%s' in schema" context (Xml.to_string x) +- +-let grammar = +- let xml = Xml.parse_file rng_input in +- match xml with +- | Xml.Element ("grammar", _, +- Xml.Element ("start", _, gram) :: defines) -> +- (* The elements are referenced in the section, +- * so build a map of those first. +- *) +- let defines = List.fold_left ( +- fun map -> +- function Xml.Element ("define", ["name", name], defn) -> +- StringMap.add name defn map +- | _ -> +- failwithf "%s: expected " rng_input +- ) StringMap.empty defines in +- let defines = StringMap.mapi parse_rng defines in +- +- (* Parse the clause, passing the defines. *) +- parse_rng ~defines "" gram +- | _ -> +- failwithf "%s: input is not *" +- rng_input +- +-let name_of_field = function +- | Element (name, _) | Attribute (name, _) +- | ZeroOrMore (Element (name, _)) +- | OneOrMore (Element (name, _)) +- | Optional (Element (name, _)) -> name +- | Optional (Attribute (name, _)) -> name +- | Text -> (* an unnamed field in an element *) +- "data" +- | rng -> +- failwithf "name_of_field failed at: %s" (string_of_rng rng) +- +-(* At the moment this function only generates OCaml types. However we +- * should parameterize it later so it can generate types/structs in a +- * variety of languages. +- *) +-let generate_types xs = +- (* A simple type is one that can be printed out directly, eg. +- * "string option". A complex type is one which has a name and has +- * to be defined via another toplevel definition, eg. a struct. +- * +- * generate_type generates code for either simple or complex types. +- * In the simple case, it returns the string ("string option"). In +- * the complex case, it returns the name ("mountpoint"). In the +- * complex case it has to print out the definition before returning, +- * so it should only be called when we are at the beginning of a +- * new line (BOL context). +- *) +- let rec generate_type = function +- | Text -> (* string *) +- "string", true +- | Choice values -> (* [`val1|`val2|...] *) +- "[" ^ String.concat "|" (List.map ((^)"`") values) ^ "]", true +- | ZeroOrMore rng -> (* list *) +- let t, is_simple = generate_type rng in +- t ^ " list (* 0 or more *)", is_simple +- | OneOrMore rng -> (* list *) +- let t, is_simple = generate_type rng in +- t ^ " list (* 1 or more *)", is_simple +- (* virt-inspector hack: bool *) +- | Optional (Attribute (name, [Value "1"])) -> +- "bool", true +- | Optional rng -> (* list *) +- let t, is_simple = generate_type rng in +- t ^ " option", is_simple +- (* type name = { fields ... } *) +- | Element (name, fields) when is_attrs_interleave fields -> +- generate_type_struct name (get_attrs_interleave fields) +- | Element (name, [field]) (* type name = field *) +- | Attribute (name, [field]) -> +- let t, is_simple = generate_type field in +- if is_simple then (t, true) +- else ( +- pr "type %s = %s\n" name t; +- name, false +- ) +- | Element (name, fields) -> (* type name = { fields ... } *) +- generate_type_struct name fields +- | rng -> +- failwithf "generate_type failed at: %s" (string_of_rng rng) +- +- and is_attrs_interleave = function +- | [Interleave _] -> true +- | Attribute _ :: fields -> is_attrs_interleave fields +- | Optional (Attribute _) :: fields -> is_attrs_interleave fields +- | _ -> false +- +- and get_attrs_interleave = function +- | [Interleave fields] -> fields +- | ((Attribute _) as field) :: fields +- | ((Optional (Attribute _)) as field) :: fields -> +- field :: get_attrs_interleave fields +- | _ -> assert false +- +- and generate_types xs = +- List.iter (fun x -> ignore (generate_type x)) xs +- +- and generate_type_struct name fields = +- (* Calculate the types of the fields first. We have to do this +- * before printing anything so we are still in BOL context. +- *) +- let types = List.map fst (List.map generate_type fields) in +- +- (* Special case of a struct containing just a string and another +- * field. Turn it into an assoc list. +- *) +- match types with +- | ["string"; other] -> +- let fname1, fname2 = +- match fields with +- | [f1; f2] -> name_of_field f1, name_of_field f2 +- | _ -> assert false in +- pr "type %s = string * %s (* %s -> %s *)\n" name other fname1 fname2; +- name, false +- +- | types -> +- pr "type %s = {\n" name; +- List.iter ( +- fun (field, ftype) -> +- let fname = name_of_field field in +- pr " %s_%s : %s;\n" name fname ftype +- ) (List.combine fields types); +- pr "}\n"; +- (* Return the name of this type, and +- * false because it's not a simple type. +- *) +- name, false +- in +- +- generate_types xs +- +-let generate_parsers xs = +- (* As for generate_type above, generate_parser makes a parser for +- * some type, and returns the name of the parser it has generated. +- * Because it (may) need to print something, it should always be +- * called in BOL context. +- *) +- let rec generate_parser = function +- | Text -> (* string *) +- "string_child_or_empty" +- | Choice values -> (* [`val1|`val2|...] *) +- sprintf "(fun x -> match Xml.pcdata (first_child x) with %s | str -> failwith (\"unexpected field value: \" ^ str))" +- (String.concat "|" +- (List.map (fun v -> sprintf "%S -> `%s" v v) values)) +- | ZeroOrMore rng -> (* list *) +- let pa = generate_parser rng in +- sprintf "(fun x -> List.map %s (Xml.children x))" pa +- | OneOrMore rng -> (* list *) +- let pa = generate_parser rng in +- sprintf "(fun x -> List.map %s (Xml.children x))" pa +- (* virt-inspector hack: bool *) +- | Optional (Attribute (name, [Value "1"])) -> +- sprintf "(fun x -> try ignore (Xml.attrib x %S); true with Xml.No_attribute _ -> false)" name +- | Optional rng -> (* list *) +- let pa = generate_parser rng in +- sprintf "(function None -> None | Some x -> Some (%s x))" pa +- (* type name = { fields ... } *) +- | Element (name, fields) when is_attrs_interleave fields -> +- generate_parser_struct name (get_attrs_interleave fields) +- | Element (name, [field]) -> (* type name = field *) +- let pa = generate_parser field in +- let parser_name = sprintf "parse_%s_%d" name (unique ()) in +- pr "let %s =\n" parser_name; +- pr " %s\n" pa; +- pr "let parse_%s = %s\n" name parser_name; +- parser_name +- | Attribute (name, [field]) -> +- let pa = generate_parser field in +- let parser_name = sprintf "parse_%s_%d" name (unique ()) in +- pr "let %s =\n" parser_name; +- pr " %s\n" pa; +- pr "let parse_%s = %s\n" name parser_name; +- parser_name +- | Element (name, fields) -> (* type name = { fields ... } *) +- generate_parser_struct name ([], fields) +- | rng -> +- failwithf "generate_parser failed at: %s" (string_of_rng rng) +- +- and is_attrs_interleave = function +- | [Interleave _] -> true +- | Attribute _ :: fields -> is_attrs_interleave fields +- | Optional (Attribute _) :: fields -> is_attrs_interleave fields +- | _ -> false +- +- and get_attrs_interleave = function +- | [Interleave fields] -> [], fields +- | ((Attribute _) as field) :: fields +- | ((Optional (Attribute _)) as field) :: fields -> +- let attrs, interleaves = get_attrs_interleave fields in +- (field :: attrs), interleaves +- | _ -> assert false +- +- and generate_parsers xs = +- List.iter (fun x -> ignore (generate_parser x)) xs +- +- and generate_parser_struct name (attrs, interleaves) = +- (* Generate parsers for the fields first. We have to do this +- * before printing anything so we are still in BOL context. +- *) +- let fields = attrs @ interleaves in +- let pas = List.map generate_parser fields in +- +- (* Generate an intermediate tuple from all the fields first. +- * If the type is just a string + another field, then we will +- * return this directly, otherwise it is turned into a record. +- * +- * RELAX NG note: This code treats and plain lists of +- * fields the same. In other words, it doesn't bother enforcing +- * any ordering of fields in the XML. +- *) +- pr "let parse_%s x =\n" name; +- pr " let t = (\n "; +- let comma = ref false in +- List.iter ( +- fun x -> +- if !comma then pr ",\n "; +- comma := true; +- match x with +- | Optional (Attribute (fname, [field])), pa -> +- pr "%s x" pa +- | Optional (Element (fname, [field])), pa -> +- pr "%s (optional_child %S x)" pa fname +- | Attribute (fname, [Text]), _ -> +- pr "attribute %S x" fname +- | (ZeroOrMore _ | OneOrMore _), pa -> +- pr "%s x" pa +- | Text, pa -> +- pr "%s x" pa +- | (field, pa) -> +- let fname = name_of_field field in +- pr "%s (child %S x)" pa fname +- ) (List.combine fields pas); +- pr "\n ) in\n"; +- +- (match fields with +- | [Element (_, [Text]) | Attribute (_, [Text]); _] -> +- pr " t\n" +- +- | _ -> +- pr " (Obj.magic t : %s)\n" name +-(* +- List.iter ( +- function +- | (Optional (Attribute (fname, [field])), pa) -> +- pr " %s_%s =\n" name fname; +- pr " %s x;\n" pa +- | (Optional (Element (fname, [field])), pa) -> +- pr " %s_%s =\n" name fname; +- pr " (let x = optional_child %S x in\n" fname; +- pr " %s x);\n" pa +- | (field, pa) -> +- let fname = name_of_field field in +- pr " %s_%s =\n" name fname; +- pr " (let x = child %S x in\n" fname; +- pr " %s x);\n" pa +- ) (List.combine fields pas); +- pr "}\n" +-*) +- ); +- sprintf "parse_%s" name +- in +- +- generate_parsers xs +- +-(* Generate ocaml/guestfs_inspector.mli. *) +-let generate_ocaml_inspector_mli () = +- generate_header ~extra_inputs:[rng_input] OCamlStyle LGPLv2plus; +- +- pr "\ +-(** This is an OCaml language binding to the external [virt-inspector] +- program. +- +- For more information, please read the man page [virt-inspector(1)]. +-*) +- +-"; +- +- generate_types grammar; +- pr "(** The nested information returned from the {!inspect} function. *)\n"; +- pr "\n"; +- +- pr "\ +-val inspect : ?connect:string -> ?xml:string -> string list -> operatingsystems +-(** To inspect a libvirt domain called [name], pass a singleton +- list: [inspect [name]]. When using libvirt only, you may +- optionally pass a libvirt URI using [inspect ~connect:uri ...]. +- +- To inspect a disk image or images, pass a list of the filenames +- of the disk images: [inspect filenames] +- +- This function inspects the given guest or disk images and +- returns a list of operating system(s) found and a large amount +- of information about them. In the vast majority of cases, +- a virtual machine only contains a single operating system. +- +- If the optional [~xml] parameter is given, then this function +- skips running the external virt-inspector program and just +- parses the given XML directly (which is expected to be XML +- produced from a previous run of virt-inspector). The list of +- names and connect URI are ignored in this case. +- +- This function can throw a wide variety of exceptions, for example +- if the external virt-inspector program cannot be found, or if +- it doesn't generate valid XML. +-*) +-" +- +-(* Generate ocaml/guestfs_inspector.ml. *) +-let generate_ocaml_inspector_ml () = +- generate_header ~extra_inputs:[rng_input] OCamlStyle LGPLv2plus; +- +- pr "open Unix\n"; +- pr "\n"; +- +- generate_types grammar; +- pr "\n"; +- +- pr "\ +-(* Misc functions which are used by the parser code below. *) +-let first_child = function +- | Xml.Element (_, _, c::_) -> c +- | Xml.Element (name, _, []) -> +- failwith (\"expected <\" ^ name ^ \"/> to have a child node\") +- | Xml.PCData str -> +- failwith (\"expected XML tag, but read PCDATA '\" ^ str ^ \"' instead\") +- +-let string_child_or_empty = function +- | Xml.Element (_, _, [Xml.PCData s]) -> s +- | Xml.Element (_, _, []) -> \"\" +- | Xml.Element (x, _, _) -> +- failwith (\"expected XML tag with a single PCDATA child, but got \" ^ +- x ^ \" instead\") +- | Xml.PCData str -> +- failwith (\"expected XML tag, but read PCDATA '\" ^ str ^ \"' instead\") +- +-let optional_child name xml = +- let children = Xml.children xml in +- try +- Some (List.find (function +- | Xml.Element (n, _, _) when n = name -> true +- | _ -> false) children) +- with +- Not_found -> None +- +-let child name xml = +- match optional_child name xml with +- | Some c -> c +- | None -> +- failwith (\"mandatory field <\" ^ name ^ \"/> missing in XML output\") +- +-let attribute name xml = +- try Xml.attrib xml name +- with Xml.No_attribute _ -> +- failwith (\"mandatory attribute \" ^ name ^ \" missing in XML output\") +- +-"; +- +- generate_parsers grammar; +- pr "\n"; +- +- pr "\ +-(* Run external virt-inspector, then use parser to parse the XML. *) +-let inspect ?connect ?xml names = +- let xml = +- match xml with +- | None -> +- if names = [] then invalid_arg \"inspect: no names given\"; +- let cmd = [ \"virt-inspector\"; \"--xml\" ] @ +- (match connect with None -> [] | Some uri -> [ \"--connect\"; uri ]) @ +- names in +- let cmd = List.map Filename.quote cmd in +- let cmd = String.concat \" \" cmd in +- let chan = open_process_in cmd in +- let xml = Xml.parse_in chan in +- (match close_process_in chan with +- | WEXITED 0 -> () +- | WEXITED _ -> failwith \"external virt-inspector command failed\" +- | WSIGNALED i | WSTOPPED i -> +- failwith (\"external virt-inspector command died or stopped on sig \" ^ +- string_of_int i) +- ); +- xml +- | Some doc -> +- Xml.parse_string doc in +- parse_operatingsystems xml +-" +- + and generate_max_proc_nr () = + pr "%d\n" max_proc_nr + +@@ -12742,8 +12254,6 @@ Run it from the top source directory using the command + output_to "ocaml/guestfs.ml" generate_ocaml_ml; + output_to "ocaml/guestfs_c_actions.c" generate_ocaml_c; + output_to "ocaml/bindtests.ml" generate_ocaml_bindtests; +- output_to "ocaml/guestfs_inspector.mli" generate_ocaml_inspector_mli; +- output_to "ocaml/guestfs_inspector.ml" generate_ocaml_inspector_ml; + output_to "perl/Guestfs.xs" generate_perl_xs; + output_to "perl/lib/Sys/Guestfs.pm" generate_perl_pm; + output_to "perl/bindtests.pl" generate_perl_bindtests; +-- +1.7.1 + diff --git a/0015-Change-to-using-ext2-based-cached-supermin-appliance.patch b/0015-Change-to-using-ext2-based-cached-supermin-appliance.patch new file mode 100644 index 0000000..c1c7763 --- /dev/null +++ b/0015-Change-to-using-ext2-based-cached-supermin-appliance.patch @@ -0,0 +1,925 @@ +From 081d057d4d9924e5c66db96beea90763665397d8 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Mon, 23 Aug 2010 11:12:29 +0100 +Subject: [PATCH] Change to using ext2-based, cached supermin appliance. + +This changes the method used to build the supermin appliance +to use the new ext2-based appliance supported by latest febootstrap. +The appliance can also be cached, so we avoid rebuilding it +each time it is used. + +Mailing list discussion goes into the rationale and details: +https://www.redhat.com/archives/libguestfs/2010-August/msg00028.html + +Requires febootstrap >= 2.8. + +Cherry picked from commit 5c31f6126ba4ea3e9056c34c300f6f5e332ab997. +--- + README | 2 +- + daemon/daemon.h | 4 + + daemon/devsparts.c | 4 + + daemon/guestfsd.c | 26 +++ + po/POTFILES.in | 1 + + src/Makefile.am | 1 + + src/appliance.c | 465 ++++++++++++++++++++++++++++++++++++++++++++++++ + src/guestfs-internal.h | 1 + + src/guestfs.c | 6 - + src/launch.c | 210 +++------------------- + 10 files changed, 529 insertions(+), 191 deletions(-) + create mode 100644 src/appliance.c + +diff --git a/README b/README +index 15e6581..b31f9a1 100644 +--- a/README ++++ b/README +@@ -40,7 +40,7 @@ Requirements + - recent QEMU >= 0.10 with vmchannel support + http://lists.gnu.org/archive/html/qemu-devel/2009-02/msg01042.html + +-- febootstrap >= 2.7 ++- febootstrap >= 2.8 + + - fakeroot + +diff --git a/daemon/daemon.h b/daemon/daemon.h +index 55f7b08..4c1b9b0 100644 +--- a/daemon/daemon.h ++++ b/daemon/daemon.h +@@ -37,6 +37,8 @@ extern int sysroot_len; + + extern char *sysroot_path (const char *path); + ++extern int is_root_device (const char *device); ++ + extern int xwrite (int sock, const void *buf, size_t len) + __attribute__((__warn_unused_result__)); + extern int xread (int sock, void *buf, size_t len) +@@ -198,6 +200,8 @@ extern void reply (xdrproc_t xdrp, char *ret); + reply_with_error ("%s: %s: expecting a device name", __func__, (path)); \ + fail_stmt; \ + } \ ++ if (is_root_device (path)) \ ++ reply_with_error ("%s: %s: device not found", __func__, path); \ + if (device_name_translation ((path)) == -1) { \ + int err = errno; \ + int r = cancel_stmt; \ +diff --git a/daemon/devsparts.c b/daemon/devsparts.c +index 60e7aa8..95e4a68 100644 +--- a/daemon/devsparts.c ++++ b/daemon/devsparts.c +@@ -60,6 +60,10 @@ foreach_block_device (block_dev_func_t func) + char dev_path[256]; + snprintf (dev_path, sizeof dev_path, "/dev/%s", d->d_name); + ++ /* Ignore the root device. */ ++ if (is_root_device (dev_path)) ++ continue; ++ + /* RHBZ#514505: Some versions of qemu <= 0.10 add a + * CD-ROM device even though we didn't request it. Try to + * detect this by seeing if the device contains media. +diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c +index 49aca08..4e29338 100644 +--- a/daemon/guestfsd.c ++++ b/daemon/guestfsd.c +@@ -74,6 +74,12 @@ static char *read_cmdline (void); + # define MAX(a,b) ((a)>(b)?(a):(b)) + #endif + ++/* If root device is an ext2 filesystem, this is the major and minor. ++ * This is so we can ignore this device from the point of view of the ++ * user, eg. in guestfs_list_devices and many other places. ++ */ ++static dev_t root_device = 0; ++ + int verbose = 0; + + static int print_shell_quote (FILE *stream, const struct printf_info *info, const void *const *args); +@@ -163,6 +169,10 @@ main (int argc, char *argv[]) + #endif + #endif + ++ struct stat statbuf; ++ if (stat ("/", &statbuf) == 0) ++ root_device = statbuf.st_dev; ++ + for (;;) { + c = getopt_long (argc, argv, options, long_options, NULL); + if (c == -1) break; +@@ -449,6 +459,22 @@ read_cmdline (void) + return r; + } + ++/* Return true iff device is the root device (and therefore should be ++ * ignored from the point of view of user calls). ++ */ ++int ++is_root_device (const char *device) ++{ ++ struct stat statbuf; ++ if (stat (device, &statbuf) == -1) { ++ perror (device); ++ return 0; ++ } ++ if (statbuf.st_rdev == root_device) ++ return 1; ++ return 0; ++} ++ + /* Turn "/path" into "/sysroot/path". + * + * Caller must check for NULL and call reply_with_perror ("malloc") +diff --git a/po/POTFILES.in b/po/POTFILES.in +index 843e8e0..e5fb857 100644 +--- a/po/POTFILES.in ++++ b/po/POTFILES.in +@@ -103,6 +103,7 @@ regressions/test-lvm-mapping.pl + regressions/test-noexec-stack.pl + ruby/ext/guestfs/_guestfs.c + src/actions.c ++src/appliance.c + src/bindtests.c + src/guestfs.c + src/inspect.c +diff --git a/src/Makefile.am b/src/Makefile.am +index cc01459..39fa230 100644 +--- a/src/Makefile.am ++++ b/src/Makefile.am +@@ -125,6 +125,7 @@ libguestfs_la_SOURCES = \ + guestfs_protocol.h \ + gettext.h \ + actions.c \ ++ appliance.c \ + bindtests.c \ + inspect.c \ + launch.c \ +diff --git a/src/appliance.c b/src/appliance.c +new file mode 100644 +index 0000000..3c3279b +--- /dev/null ++++ b/src/appliance.c +@@ -0,0 +1,465 @@ ++/* libguestfs ++ * Copyright (C) 2010 Red Hat Inc. ++ * ++ * This library is free software; you can redistribute it and/or ++ * modify it under the terms of the GNU Lesser General Public ++ * License as published by the Free Software Foundation; either ++ * version 2 of the License, or (at your option) any later version. ++ * ++ * This library is distributed in the hope that it will be useful, ++ * but WITHOUT ANY WARRANTY; without even the implied warranty of ++ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++ * Lesser General Public License for more details. ++ * ++ * You should have received a copy of the GNU Lesser General Public ++ * License along with this library; if not, write to the Free Software ++ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA ++ */ ++ ++#include ++ ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++#ifdef HAVE_SYS_TYPES_H ++#include ++#endif ++ ++#include "guestfs.h" ++#include "guestfs-internal.h" ++#include "guestfs-internal-actions.h" ++#include "guestfs_protocol.h" ++ ++static const char *kernel_name = "vmlinuz." REPO "." host_cpu; ++static const char *initrd_name = "initramfs." REPO "." host_cpu ".img"; ++ ++static int find_path (guestfs_h *g, int (*pred) (guestfs_h *g, const char *pelem, void *data), void *data, char **pelem); ++static int dir_contains_file (const char *dir, const char *file); ++static int dir_contains_files (const char *dir, ...); ++static int contains_supermin_appliance (guestfs_h *g, const char *path, void *data); ++static int contains_ordinary_appliance (guestfs_h *g, const char *path, void *data); ++static char *calculate_supermin_checksum (guestfs_h *g, const char *supermin_path); ++static int check_for_cached_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance); ++static int build_supermin_appliance (guestfs_h *g, const char *supermin_path, const char *checksum, char **kernel, char **initrd, char **appliance); ++ ++/* Locate or build the appliance. ++ * ++ * This function locates or builds the appliance as necessary, ++ * handling the supermin appliance, caching of supermin-built ++ * appliances, or using an ordinary appliance. ++ * ++ * The return value is 0 = good, -1 = error. Returned in '*kernel' ++ * will be the name of the kernel to use, '*initrd' the name of the ++ * initrd, '*appliance' the name of the ext2 root filesystem. ++ * '*appliance' can be NULL, meaning that we are using an ordinary ++ * (non-ext2) appliance. All three strings must be freed by the ++ * caller. However the referenced files themselves must not be ++ * deleted. ++ * ++ * The process is as follows: ++ * ++ * (1) Look for the first element of g->path which contains a ++ * supermin appliance skeleton. If no element has this, skip ++ * straight to step (5). ++ * (2) Calculate the checksum of this supermin appliance. ++ * (3) Check whether $TMPDIR/$checksum/ directory exists, contains ++ * a cached appliance, and passes basic security checks. If so, ++ * return this appliance. ++ * (4) Try to build the supermin appliance into $TMPDIR/$checksum/. ++ * If this is successful, return it. ++ * (5) Check each element of g->path, looking for an ordinary appliance. ++ * If one is found, return it. ++ */ ++int ++guestfs___build_appliance (guestfs_h *g, ++ char **kernel, char **initrd, char **appliance) ++{ ++ int r; ++ ++ /* Step (1). */ ++ char *supermin_path; ++ r = find_path (g, contains_supermin_appliance, NULL, &supermin_path); ++ if (r == -1) ++ return -1; ++ ++ if (r == 1) { ++ /* Step (2): calculate checksum. */ ++ char *checksum = calculate_supermin_checksum (g, supermin_path); ++ if (checksum) { ++ /* Step (3): cached appliance exists? */ ++ r = check_for_cached_appliance (g, supermin_path, checksum, ++ kernel, initrd, appliance); ++ if (r != 0) { ++ free (supermin_path); ++ free (checksum); ++ return r == 1 ? 0 : -1; ++ } ++ ++ /* Step (4): build supermin appliance. */ ++ r = build_supermin_appliance (g, supermin_path, checksum, ++ kernel, initrd, appliance); ++ free (supermin_path); ++ free (checksum); ++ return r; ++ } ++ free (supermin_path); ++ } ++ ++ /* Step (5). */ ++ char *path; ++ r = find_path (g, contains_ordinary_appliance, NULL, &path); ++ if (r == -1) ++ return -1; ++ ++ if (r == 1) { ++ size_t len = strlen (path); ++ *kernel = safe_malloc (g, len + strlen (kernel_name) + 2); ++ *initrd = safe_malloc (g, len + strlen (initrd_name) + 2); ++ sprintf (*kernel, "%s/%s", path, kernel_name); ++ sprintf (*initrd, "%s/%s", path, initrd_name); ++ *appliance = NULL; ++ ++ free (path); ++ return 0; ++ } ++ ++ error (g, _("cannot find any suitable libguestfs supermin or ordinary appliance on LIBGUESTFS_PATH (search path: %s)"), ++ g->path); ++ return -1; ++} ++ ++static int ++contains_supermin_appliance (guestfs_h *g, const char *path, void *data) ++{ ++ return dir_contains_files (path, "supermin.d", "kmod.whitelist", NULL); ++} ++ ++static int ++contains_ordinary_appliance (guestfs_h *g, const char *path, void *data) ++{ ++ return dir_contains_files (path, kernel_name, initrd_name, NULL); ++} ++ ++/* supermin_path is a path which is known to contain a supermin ++ * appliance. Using febootstrap-supermin-helper -f checksum calculate ++ * the checksum so we can see if it is cached. ++ */ ++static char * ++calculate_supermin_checksum (guestfs_h *g, const char *supermin_path) ++{ ++ size_t len = 2 * strlen (supermin_path) + 256; ++ char cmd[len]; ++ snprintf (cmd, len, ++ "febootstrap-supermin-helper%s " ++ "-f checksum " ++ "-k '%s/kmod.whitelist' " ++ "'%s/supermin.d' " ++ host_cpu, ++ g->verbose ? " --verbose" : "", ++ supermin_path, ++ supermin_path); ++ ++ if (g->verbose) ++ guestfs___print_timestamped_message (g, "%s", cmd); ++ ++ /* Errors here are non-fatal, so we don't need to call error(). */ ++ FILE *pp = popen (cmd, "r"); ++ if (pp == NULL) ++ return NULL; ++ ++ char checksum[256]; ++ if (fgets (checksum, sizeof checksum, pp) == NULL) { ++ pclose (pp); ++ return NULL; ++ } ++ ++ if (pclose (pp) == -1) { ++ perror ("pclose"); ++ return NULL; ++ } ++ ++ len = strlen (checksum); ++ ++ if (len < 16) { /* sanity check */ ++ fprintf (stderr, "libguestfs: internal error: febootstrap-supermin-helper -f checksum returned a short string\n"); ++ return NULL; ++ } ++ ++ if (len > 0 && checksum[len-1] == '\n') ++ checksum[--len] = '\0'; ++ ++ return safe_strndup (g, checksum, len); ++} ++ ++/* Check for cached appliance in $TMPDIR/$checksum. Check it exists ++ * and passes some basic security checks. ++ * ++ * Returns: ++ * 1 = exists, and passes ++ * 0 = does not exist ++ * -1 = error which should abort the whole launch process ++ */ ++static int ++security_check_cache_file (guestfs_h *g, const char *filename, ++ const struct stat *statbuf) ++{ ++ uid_t uid = geteuid (); ++ ++ if (statbuf->st_uid != uid) { ++ error (g, ("libguestfs cached appliance %s is not owned by UID %d\n"), ++ filename, uid); ++ return -1; ++ } ++ ++ if ((statbuf->st_mode & 0022) != 0) { ++ error (g, ("libguestfs cached appliance %s is writable by group or other (mode %o)\n"), ++ filename, statbuf->st_mode); ++ return -1; ++ } ++ ++ return 0; ++} ++ ++static int ++check_for_cached_appliance (guestfs_h *g, ++ const char *supermin_path, const char *checksum, ++ char **kernel, char **initrd, char **appliance) ++{ ++ const char *tmpdir = guestfs___tmpdir (); ++ ++ size_t len = strlen (tmpdir) + strlen (checksum) + 2; ++ char cachedir[len]; ++ snprintf (cachedir, len, "%s/%s", tmpdir, checksum); ++ ++ /* Touch the directory to prevent it being deleting in a rare race ++ * between us doing the checks and a tmp cleaner running. Note this ++ * doesn't create the directory, and we ignore any error. ++ */ ++ (void) utime (cachedir, NULL); ++ ++ /* See if the cache directory exists and passes some simple checks ++ * to make sure it has not been tampered with. Note that geteuid() ++ * forms a part of the checksum. ++ */ ++ struct stat statbuf; ++ if (lstat (cachedir, &statbuf) == -1) ++ return 0; ++ ++ if (security_check_cache_file (g, cachedir, &statbuf) == -1) ++ return -1; ++ ++ int ret; ++ ++ *kernel = safe_malloc (g, len + 8 /* / + "kernel" + \0 */); ++ *initrd = safe_malloc (g, len + 8 /* / + "initrd" + \0 */); ++ *appliance = safe_malloc (g, len + 6 /* / + "root" + \0 */); ++ sprintf (*kernel, "%s/kernel", cachedir); ++ sprintf (*initrd, "%s/initrd", cachedir); ++ sprintf (*appliance, "%s/root", cachedir); ++ ++ /* Touch the files to prevent them being deleted, and to bring the ++ * cache up to date. Note this doesn't create the files. ++ */ ++ (void) utime (*kernel, NULL); ++ ++ /* NB. *kernel is a symlink, so we want to check the kernel, not the ++ * link (stat, not lstat). We don't do a security check on the ++ * kernel since it's always under /boot. ++ */ ++ if (stat (*kernel, &statbuf) == -1) { ++ ret = 0; ++ goto error; ++ } ++ ++ (void) utime (*initrd, NULL); ++ ++ if (lstat (*initrd, &statbuf) == -1) { ++ ret = 0; ++ goto error; ++ } ++ ++ if (security_check_cache_file (g, *initrd, &statbuf) == -1) { ++ ret = -1; ++ goto error; ++ } ++ ++ (void) utime (*appliance, NULL); ++ ++ if (lstat (*appliance, &statbuf) == -1) { ++ ret = 0; ++ goto error; ++ } ++ ++ if (security_check_cache_file (g, *appliance, &statbuf) == -1) { ++ ret = -1; ++ goto error; ++ } ++ ++ /* Exists! */ ++ return 1; ++ ++ error: ++ free (*kernel); ++ free (*initrd); ++ free (*appliance); ++ return ret; ++} ++ ++/* Build supermin appliance from supermin_path to $TMPDIR/$checksum. ++ * ++ * Returns: ++ * 0 = built ++ * -1 = error (aborts launch) ++ */ ++static int ++build_supermin_appliance (guestfs_h *g, ++ const char *supermin_path, const char *checksum, ++ char **kernel, char **initrd, char **appliance) ++{ ++ if (g->verbose) ++ guestfs___print_timestamped_message (g, "begin building supermin appliance"); ++ ++ const char *tmpdir = guestfs___tmpdir (); ++ size_t cdlen = strlen (tmpdir) + strlen (checksum) + 2; ++ char cachedir[cdlen]; ++ snprintf (cachedir, cdlen, "%s/%s", tmpdir, checksum); ++ ++ /* Don't worry about this failing, because the command below will ++ * fail if the directory doesn't exist. Note the directory might ++ * already exist, eg. if a tmp cleaner has removed the existing ++ * appliance but not the directory itself. ++ */ ++ (void) mkdir (cachedir, 0755); ++ ++ /* Set a sensible umask in the subprocess, so kernel and initrd ++ * output files are world-readable (RHBZ#610880). ++ */ ++ size_t cmdlen = 2 * strlen (supermin_path) + 3 * (cdlen + 16) + 256; ++ char cmd[cmdlen]; ++ snprintf (cmd, cmdlen, ++ "umask 0022; " ++ "febootstrap-supermin-helper%s " ++ "-f ext2 " ++ "-k '%s/kmod.whitelist' " ++ "'%s/supermin.d' " ++ host_cpu " " ++ "%s/kernel %s/initrd %s/root", ++ g->verbose ? " --verbose" : "", ++ supermin_path, supermin_path, ++ cachedir, cachedir, cachedir); ++ if (g->verbose) ++ guestfs___print_timestamped_message (g, "%s", cmd); ++ int r = system (cmd); ++ if (r == -1 || WEXITSTATUS (r) != 0) { ++ error (g, _("external command failed: %s"), cmd); ++ return -1; ++ } ++ ++ if (g->verbose) ++ guestfs___print_timestamped_message (g, "finished building supermin appliance"); ++ ++ *kernel = safe_malloc (g, cdlen + 8 /* / + "kernel" + \0 */); ++ *initrd = safe_malloc (g, cdlen + 8 /* / + "initrd" + \0 */); ++ *appliance = safe_malloc (g, cdlen + 6 /* / + "root" + \0 */); ++ sprintf (*kernel, "%s/kernel", cachedir); ++ sprintf (*initrd, "%s/initrd", cachedir); ++ sprintf (*appliance, "%s/root", cachedir); ++ ++ return 0; ++} ++ ++/* Search elements of g->path, returning the first path element which ++ * matches the predicate function 'pred'. ++ * ++ * Function 'pred' must return a true or false value. If it returns ++ * -1 then the entire search is aborted. ++ * ++ * Return values: ++ * 1 = a path element matched, it is returned in *pelem_ret and must be ++ * freed by the caller, ++ * 0 = no path element matched, *pelem_ret is set to NULL, or ++ * -1 = error which aborts the launch process ++ */ ++static int ++find_path (guestfs_h *g, ++ int (*pred) (guestfs_h *g, const char *pelem, void *data), ++ void *data, ++ char **pelem_ret) ++{ ++ size_t len; ++ int r; ++ const char *pelem = g->path; ++ ++ /* Note that if g->path is an empty string, we want to check the ++ * current directory (for backwards compatibility with ++ * libguestfs < 1.5.4). ++ */ ++ do { ++ len = strcspn (pelem, ":"); ++ ++ /* Empty element or "." means current directory. */ ++ if (len == 0) ++ *pelem_ret = safe_strdup (g, "."); ++ else ++ *pelem_ret = safe_strndup (g, pelem, len); ++ ++ r = pred (g, *pelem_ret, data); ++ if (r == -1) { ++ free (*pelem_ret); ++ return -1; ++ } ++ ++ if (r != 0) /* predicate matched */ ++ return 1; ++ ++ free (*pelem_ret); ++ ++ if (pelem[len] == ':') ++ pelem += len + 1; ++ else ++ pelem += len; ++ } while (*pelem); ++ ++ /* Predicate didn't match on any path element. */ ++ *pelem_ret = NULL; ++ return 0; ++} ++ ++/* Returns true iff file is contained in dir. */ ++static int ++dir_contains_file (const char *dir, const char *file) ++{ ++ size_t dirlen = strlen (dir); ++ size_t filelen = strlen (file); ++ size_t len = dirlen + filelen + 2; ++ char path[len]; ++ ++ snprintf (path, len, "%s/%s", dir, file); ++ return access (path, F_OK) == 0; ++} ++ ++/* Returns true iff every listed file is contained in 'dir'. */ ++static int ++dir_contains_files (const char *dir, ...) ++{ ++ va_list args; ++ const char *file; ++ ++ va_start (args, dir); ++ while ((file = va_arg (args, const char *)) != NULL) { ++ if (!dir_contains_file (dir, file)) { ++ va_end (args); ++ return 0; ++ } ++ } ++ va_end (args); ++ return 1; ++} +diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h +index 73a14ab..f2abeba 100644 +--- a/src/guestfs-internal.h ++++ b/src/guestfs-internal.h +@@ -209,6 +209,7 @@ extern int guestfs___recv_file (guestfs_h *g, const char *filename); + extern int guestfs___send_to_daemon (guestfs_h *g, const void *v_buf, size_t n); + extern int guestfs___recv_from_daemon (guestfs_h *g, uint32_t *size_rtn, void **buf_rtn); + extern int guestfs___accept_from_daemon (guestfs_h *g); ++extern int guestfs___build_appliance (guestfs_h *g, char **kernel, char **initrd, char **appliance); + + #define error guestfs_error + #define perrorf guestfs_perrorf +diff --git a/src/guestfs.c b/src/guestfs.c +index cef80db..74de38c 100644 +--- a/src/guestfs.c ++++ b/src/guestfs.c +@@ -220,12 +220,6 @@ guestfs_close (guestfs_h *g) + snprintf (filename, sizeof filename, "%s/sock", g->tmpdir); + unlink (filename); + +- snprintf (filename, sizeof filename, "%s/initrd", g->tmpdir); +- unlink (filename); +- +- snprintf (filename, sizeof filename, "%s/kernel", g->tmpdir); +- unlink (filename); +- + rmdir (g->tmpdir); + + free (g->tmpdir); +diff --git a/src/launch.c b/src/launch.c +index 1f68346..195c9d0 100644 +--- a/src/launch.c ++++ b/src/launch.c +@@ -224,53 +224,15 @@ guestfs__add_cdrom (guestfs_h *g, const char *filename) + return guestfs__config (g, "-cdrom", filename); + } + +-/* Returns true iff file is contained in dir. */ +-static int +-dir_contains_file (const char *dir, const char *file) +-{ +- int dirlen = strlen (dir); +- int filelen = strlen (file); +- int len = dirlen+filelen+2; +- char path[len]; +- +- snprintf (path, len, "%s/%s", dir, file); +- return access (path, F_OK) == 0; +-} +- +-/* Returns true iff every listed file is contained in 'dir'. */ +-static int +-dir_contains_files (const char *dir, ...) +-{ +- va_list args; +- const char *file; +- +- va_start (args, dir); +- while ((file = va_arg (args, const char *)) != NULL) { +- if (!dir_contains_file (dir, file)) { +- va_end (args); +- return 0; +- } +- } +- va_end (args); +- return 1; +-} +- +-static int build_supermin_appliance (guestfs_h *g, const char *path, char **kernel, char **initrd); + static int is_openable (guestfs_h *g, const char *path, int flags); + static void print_cmdline (guestfs_h *g); + +-static const char *kernel_name = "vmlinuz." REPO "." host_cpu; +-static const char *initrd_name = "initramfs." REPO "." host_cpu ".img"; +- + int + guestfs__launch (guestfs_h *g) + { +- int r, pmore; +- size_t len; ++ int r; + int wfd[2], rfd[2]; + int tries; +- char *path, *pelem, *pend; +- char *kernel = NULL, *initrd = NULL; + int null_vmchannel_sock; + char unixsock[256]; + struct sockaddr_un addr; +@@ -302,101 +264,17 @@ guestfs__launch (guestfs_h *g) + } + } + +- /* Allow anyone to read the temporary directory. There are no +- * secrets in the kernel or initrd files. The socket in this ++ /* Allow anyone to read the temporary directory. The socket in this + * directory won't be readable but anyone can see it exists if they + * want. (RHBZ#610880). + */ + if (chmod (g->tmpdir, 0755) == -1) + fprintf (stderr, "chmod: %s: %m (ignored)\n", g->tmpdir); + +- /* First search g->path for the supermin appliance, and try to +- * synthesize a kernel and initrd from that. If it fails, we +- * try the path search again looking for a backup ordinary +- * appliance. +- */ +- pelem = path = safe_strdup (g, g->path); +- do { +- pend = strchrnul (pelem, ':'); +- pmore = *pend == ':'; +- *pend = '\0'; +- len = pend - pelem; +- +- /* Empty element of "." means cwd. */ +- if (len == 0 || (len == 1 && *pelem == '.')) { +- if (g->verbose) +- fprintf (stderr, +- "looking for supermin appliance in current directory\n"); +- if (dir_contains_files (".", +- "supermin.d", "kmod.whitelist", NULL)) { +- if (build_supermin_appliance (g, ".", &kernel, &initrd) == -1) +- return -1; +- break; +- } +- } +- /* Look at /supermin* etc. */ +- else { +- if (g->verbose) +- fprintf (stderr, "looking for supermin appliance in %s\n", pelem); +- +- if (dir_contains_files (pelem, +- "supermin.d", "kmod.whitelist", NULL)) { +- if (build_supermin_appliance (g, pelem, &kernel, &initrd) == -1) +- return -1; +- break; +- } +- } +- +- pelem = pend + 1; +- } while (pmore); +- +- free (path); +- +- if (kernel == NULL || initrd == NULL) { +- /* Search g->path for the kernel and initrd. */ +- pelem = path = safe_strdup (g, g->path); +- do { +- pend = strchrnul (pelem, ':'); +- pmore = *pend == ':'; +- *pend = '\0'; +- len = pend - pelem; +- +- /* Empty element or "." means cwd. */ +- if (len == 0 || (len == 1 && *pelem == '.')) { +- if (g->verbose) +- fprintf (stderr, +- "looking for appliance in current directory\n"); +- if (dir_contains_files (".", kernel_name, initrd_name, NULL)) { +- kernel = safe_strdup (g, kernel_name); +- initrd = safe_strdup (g, initrd_name); +- break; +- } +- } +- /* Look at /kernel etc. */ +- else { +- if (g->verbose) +- fprintf (stderr, "looking for appliance in %s\n", pelem); +- +- if (dir_contains_files (pelem, kernel_name, initrd_name, NULL)) { +- kernel = safe_malloc (g, len + strlen (kernel_name) + 2); +- initrd = safe_malloc (g, len + strlen (initrd_name) + 2); +- sprintf (kernel, "%s/%s", pelem, kernel_name); +- sprintf (initrd, "%s/%s", pelem, initrd_name); +- break; +- } +- } +- +- pelem = pend + 1; +- } while (pmore); +- +- free (path); +- } +- +- if (kernel == NULL || initrd == NULL) { +- error (g, _("cannot find %s or %s on LIBGUESTFS_PATH (current path = %s)"), +- kernel_name, initrd_name, g->path); +- goto cleanup0; +- } ++ /* Locate and/or build the appliance. */ ++ char *kernel = NULL, *initrd = NULL, *appliance = NULL; ++ if (guestfs___build_appliance (g, &kernel, &initrd, &appliance) == -1) ++ return -1; + + if (g->verbose) + guestfs___print_timestamped_message (g, "begin testing qemu features"); +@@ -617,12 +495,29 @@ guestfs__launch (guestfs_h *g) + g->append ? g->append : ""); + + add_cmdline (g, "-kernel"); +- add_cmdline (g, (char *) kernel); ++ add_cmdline (g, kernel); + add_cmdline (g, "-initrd"); +- add_cmdline (g, (char *) initrd); ++ add_cmdline (g, initrd); + add_cmdline (g, "-append"); + add_cmdline (g, buf); + ++ /* Add the ext2 appliance drive (last of all). */ ++ if (appliance) { ++ const char *cachemode = ""; ++ if (qemu_supports (g, "cache=")) { ++ if (qemu_supports (g, "unsafe")) ++ cachemode = ",cache=unsafe"; ++ else if (qemu_supports (g, "writeback")) ++ cachemode = ",cache=writeback"; ++ } ++ ++ char buf2[PATH_MAX + 64]; ++ add_cmdline (g, "-drive"); ++ snprintf (buf2, sizeof buf2, "file=%s,snapshot=on,if=" DRIVE_IF "%s", ++ appliance, cachemode); ++ add_cmdline (g, buf2); ++ } ++ + /* Finish off the command line. */ + incr_cmdline_size (g); + g->cmdline[g->cmdline_size-1] = NULL; +@@ -869,6 +764,7 @@ guestfs__launch (guestfs_h *g) + g->state = CONFIG; + free (kernel); + free (initrd); ++ free (appliance); + return -1; + } + +@@ -916,60 +812,6 @@ print_cmdline (guestfs_h *g) + fputc ('\n', stderr); + } + +-/* This function does the hard work of building the supermin appliance +- * on the fly. 'path' is the directory containing the control files. +- * 'kernel' and 'initrd' are where we will return the names of the +- * kernel and initrd (only initrd is built). The work is done by +- * an external script. We just tell it where to put the result. +- */ +-static int +-build_supermin_appliance (guestfs_h *g, const char *path, +- char **kernel, char **initrd) +-{ +- char cmd[4096]; +- int r, len; +- +- if (g->verbose) +- guestfs___print_timestamped_message (g, "begin building supermin appliance"); +- +- len = strlen (g->tmpdir); +- *kernel = safe_malloc (g, len + 8); +- snprintf (*kernel, len+8, "%s/kernel", g->tmpdir); +- *initrd = safe_malloc (g, len + 8); +- snprintf (*initrd, len+8, "%s/initrd", g->tmpdir); +- +- /* Set a sensible umask in the subprocess, so kernel and initrd +- * output files are world-readable (RHBZ#610880). +- */ +- snprintf (cmd, sizeof cmd, +- "umask 0002; " +- "febootstrap-supermin-helper%s " +- "-k '%s/kmod.whitelist' " +- "'%s/supermin.d' " +- host_cpu " " +- "%s %s", +- g->verbose ? " --verbose" : "", +- path, +- path, +- *kernel, *initrd); +- if (g->verbose) +- guestfs___print_timestamped_message (g, "%s", cmd); +- +- r = system (cmd); +- if (r == -1 || WEXITSTATUS(r) != 0) { +- error (g, _("external command failed: %s"), cmd); +- free (*kernel); +- free (*initrd); +- *kernel = *initrd = NULL; +- return -1; +- } +- +- if (g->verbose) +- guestfs___print_timestamped_message (g, "finished building supermin appliance"); +- +- return 0; +-} +- + /* Compute Y - X and return the result in milliseconds. + * Approximately the same as this code: + * http://www.mpp.mpg.de/~huber/util/timevaldiff.c +-- +1.7.1 + diff --git a/0016-Use-virtio-serial-remove-other-vmchannel-methods.patch b/0016-Use-virtio-serial-remove-other-vmchannel-methods.patch new file mode 100644 index 0000000..3df75ad --- /dev/null +++ b/0016-Use-virtio-serial-remove-other-vmchannel-methods.patch @@ -0,0 +1,668 @@ +From 8bb41b33aac9b0e61192336b9ae1852a01edde75 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Mon, 23 Aug 2010 21:53:32 +0100 +Subject: [PATCH] Use virtio-serial, remove other vmchannel methods. + +This adds support for virtio-serial, and removes all other +vmchannel methods. + +Virtio-serial is faster than other methods, and is now widely +available. + +I tested this by using the guestfs_upload API on an 83 MB file: + before: 6.12 seconds (14.1 MB/sec) + after: 4.20 seconds (20.6 MB/sec) +(note this is with the current 8K chunk size) +(cherry picked from commit 866ec00d1f8bc40042795b66ceec12608bb1f9e8) +--- + README | 3 +- + daemon/guestfsd.c | 139 +-------------------- + src/guestfs-internal.h | 8 -- + src/launch.c | 323 +++++++++--------------------------------------- + 4 files changed, 63 insertions(+), 410 deletions(-) + +diff --git a/README b/README +index b31f9a1..f128eb5 100644 +--- a/README ++++ b/README +@@ -37,8 +37,7 @@ Home page + Requirements + ---------------------------------------------------------------------- + +-- recent QEMU >= 0.10 with vmchannel support +- http://lists.gnu.org/archive/html/qemu-devel/2009-02/msg01042.html ++- recent QEMU >= 0.12 with virtio-serial support + + - febootstrap >= 2.8 + +diff --git a/daemon/guestfsd.c b/daemon/guestfsd.c +index 4e29338..8130524 100644 +--- a/daemon/guestfsd.c ++++ b/daemon/guestfsd.c +@@ -56,20 +56,6 @@ + + static char *read_cmdline (void); + +-/* This is the default address we connect to for very old libraries +- * which didn't specify the address and port number explicitly on the +- * kernel command line. It's now recommended to always specify the +- * address and port number on the command line, so this will not be +- * used any more. +- */ +-#define OLD_GUESTFWD_ADDR "10.0.2.4" +-#define OLD_GUESTFWD_PORT "6666" +- +-/* This is only a hint. If not defined, ignore it. */ +-#ifndef AI_ADDRCONFIG +-# define AI_ADDRCONFIG 0 +-#endif +- + #ifndef MAX + # define MAX(a,b) ((a)>(b)?(a):(b)) + #endif +@@ -134,15 +120,14 @@ static void + usage (void) + { + fprintf (stderr, +- "guestfsd [-f|--foreground] [-c|--channel vmchannel] [-v|--verbose]\n"); ++ "guestfsd [-f|--foreground] [-v|--verbose]\n"); + } + + int + main (int argc, char *argv[]) + { +- static const char *options = "fc:v?"; ++ static const char *options = "fv?"; + static const struct option long_options[] = { +- { "channel", required_argument, 0, 'c' }, + { "foreground", 0, 0, 'f' }, + { "help", 0, 0, '?' }, + { "verbose", 0, 0, 'v' }, +@@ -151,7 +136,6 @@ main (int argc, char *argv[]) + int c; + int dont_fork = 0; + char *cmdline; +- char *vmchannel = NULL; + + if (winsock_init () == -1) + error (EXIT_FAILURE, 0, "winsock initialization failed"); +@@ -178,10 +162,6 @@ main (int argc, char *argv[]) + if (c == -1) break; + + switch (c) { +- case 'c': +- vmchannel = optarg; +- break; +- + case 'f': + dont_fork = 1; + break; +@@ -256,118 +236,12 @@ main (int argc, char *argv[]) + _umask (0); + #endif + +- /* Get the vmchannel string. +- * +- * Sources: +- * --channel/-c option on the command line +- * guestfs_vmchannel=... from the kernel command line +- * guestfs=... from the kernel command line +- * built-in default +- * +- * At the moment we expect this to contain "tcp:ip:port" but in +- * future it might contain a device name, eg. "/dev/vcon4" for +- * virtio-console vmchannel. +- */ +- if (vmchannel == NULL && cmdline) { +- char *p; +- size_t len; +- +- p = strstr (cmdline, "guestfs_vmchannel="); +- if (p) { +- len = strcspn (p + 18, " \t\n"); +- vmchannel = strndup (p + 18, len); +- if (!vmchannel) { +- perror ("strndup"); +- exit (EXIT_FAILURE); +- } +- } +- +- /* Old libraries passed guestfs=host:port. Rewrite it as tcp:host:port. */ +- if (vmchannel == NULL) { +- /* We will rewrite it part of the "guestfs=" string with +- * "tcp:" hence p + 4 below. */ +- p = strstr (cmdline, "guestfs="); +- if (p) { +- len = strcspn (p + 4, " \t\n"); +- vmchannel = strndup (p + 4, len); +- if (!vmchannel) { +- perror ("strndup"); +- exit (EXIT_FAILURE); +- } +- memcpy (vmchannel, "tcp:", 4); +- } +- } +- } +- +- /* Default vmchannel. */ +- if (vmchannel == NULL) { +- vmchannel = strdup ("tcp:" OLD_GUESTFWD_ADDR ":" OLD_GUESTFWD_PORT); +- if (!vmchannel) { +- perror ("strdup"); +- exit (EXIT_FAILURE); +- } +- } +- +- if (verbose) +- printf ("vmchannel: %s\n", vmchannel); +- +- /* Connect to vmchannel. */ +- int sock = -1; +- +- if (STREQLEN (vmchannel, "tcp:", 4)) { +- /* Resolve the hostname. */ +- struct addrinfo *res, *rr; +- struct addrinfo hints; +- int r; +- char *host, *port; +- +- host = vmchannel+4; +- port = strchr (host, ':'); +- if (port) { +- port[0] = '\0'; +- port++; +- } else { +- fprintf (stderr, "vmchannel: expecting \"tcp::\": %s\n", +- vmchannel); +- exit (EXIT_FAILURE); +- } +- +- memset (&hints, 0, sizeof hints); +- hints.ai_socktype = SOCK_STREAM; +- hints.ai_flags = AI_ADDRCONFIG; +- r = getaddrinfo (host, port, &hints, &res); +- if (r != 0) { +- fprintf (stderr, "%s:%s: %s\n", +- host, port, gai_strerror (r)); +- exit (EXIT_FAILURE); +- } +- +- /* Connect to the given TCP socket. */ +- for (rr = res; rr != NULL; rr = rr->ai_next) { +- sock = socket (rr->ai_family, rr->ai_socktype, rr->ai_protocol); +- if (sock != -1) { +- if (connect (sock, rr->ai_addr, rr->ai_addrlen) == 0) +- break; +- perror ("connect"); +- +- close (sock); +- sock = -1; +- } +- } +- freeaddrinfo (res); +- } else { +- fprintf (stderr, +- "unknown vmchannel connection type: %s\n" +- "expecting \"tcp::\"\n", +- vmchannel); +- exit (EXIT_FAILURE); +- } +- ++ /* Connect to virtio-serial channel. */ ++ int sock = open ("/dev/virtio-ports/org.libguestfs.channel.0", O_RDWR); + if (sock == -1) { + fprintf (stderr, + "\n" +- "Failed to connect to any vmchannel implementation.\n" +- "vmchannel: %s\n" ++ "Failed to connect to virtio-serial channel.\n" + "\n" + "This is a fatal error and the appliance will now exit.\n" + "\n" +@@ -377,8 +251,7 @@ main (int argc, char *argv[]) + "'libguestfs-test-tool' and provide the complete, unedited\n" + "output to the libguestfs developers, either in a bug report\n" + "or on the libguestfs redhat com mailing list.\n" +- "\n", +- vmchannel); ++ "\n"); + exit (EXIT_FAILURE); + } + +diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h +index f2abeba..b534b6a 100644 +--- a/src/guestfs-internal.h ++++ b/src/guestfs-internal.h +@@ -38,12 +38,6 @@ + #define N_(str) str + #endif + +-#ifdef __linux__ +-#define CAN_CHECK_PEER_EUID 1 +-#else +-#define CAN_CHECK_PEER_EUID 0 +-#endif +- + #define UNIX_PATH_MAX 108 + + #ifndef MAX +@@ -74,8 +68,6 @@ + */ + #define NETWORK "169.254.0.0/16" + #define ROUTER "169.254.2.2" +-#define GUESTFWD_ADDR "169.254.2.4" +-#define GUESTFWD_PORT "6666" + + /* GuestFS handle and connection. */ + enum state { CONFIG, LAUNCHING, READY, BUSY, NO_HANDLE }; +diff --git a/src/launch.c b/src/launch.c +index 195c9d0..95ed25f 100644 +--- a/src/launch.c ++++ b/src/launch.c +@@ -70,7 +70,6 @@ + #include "guestfs-internal-actions.h" + #include "guestfs_protocol.h" + +-static int check_peer_euid (guestfs_h *g, int sock, uid_t *rtn); + static int qemu_supports (guestfs_h *g, const char *option); + + /* Add a string to the current command line. */ +@@ -233,7 +232,6 @@ guestfs__launch (guestfs_h *g) + int r; + int wfd[2], rfd[2]; + int tries; +- int null_vmchannel_sock; + char unixsock[256]; + struct sockaddr_un addr; + +@@ -283,53 +281,35 @@ guestfs__launch (guestfs_h *g) + if (qemu_supports (g, NULL) == -1) + goto cleanup0; + +- /* Choose which vmchannel implementation to use. */ +- if (CAN_CHECK_PEER_EUID && qemu_supports (g, "-net user")) { +- /* The "null vmchannel" implementation. Requires SLIRP (user mode +- * networking in qemu) but no other vmchannel support. The daemon +- * will connect back to a random port number on localhost. +- */ +- struct sockaddr_in addr; +- socklen_t addrlen = sizeof addr; ++ /* Using virtio-serial, we need to create a local Unix domain socket ++ * for qemu to connect to. ++ */ ++ snprintf (unixsock, sizeof unixsock, "%s/sock", g->tmpdir); ++ unlink (unixsock); + +- g->sock = socket (AF_INET, SOCK_STREAM, IPPROTO_TCP); +- if (g->sock == -1) { +- perrorf (g, "socket"); +- goto cleanup0; +- } +- addr.sin_family = AF_INET; +- addr.sin_port = htons (0); +- addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); +- if (bind (g->sock, (struct sockaddr *) &addr, addrlen) == -1) { +- perrorf (g, "bind"); +- goto cleanup0; +- } ++ g->sock = socket (AF_UNIX, SOCK_STREAM, 0); ++ if (g->sock == -1) { ++ perrorf (g, "socket"); ++ goto cleanup0; ++ } + +- if (listen (g->sock, 256) == -1) { +- perrorf (g, "listen"); +- goto cleanup0; +- } ++ if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) { ++ perrorf (g, "fcntl"); ++ goto cleanup0; ++ } + +- if (getsockname (g->sock, (struct sockaddr *) &addr, &addrlen) == -1) { +- perrorf (g, "getsockname"); +- goto cleanup0; +- } ++ addr.sun_family = AF_UNIX; ++ strncpy (addr.sun_path, unixsock, UNIX_PATH_MAX); ++ addr.sun_path[UNIX_PATH_MAX-1] = '\0'; + +- if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) { +- perrorf (g, "fcntl"); +- goto cleanup0; +- } ++ if (bind (g->sock, &addr, sizeof addr) == -1) { ++ perrorf (g, "bind"); ++ goto cleanup0; ++ } + +- null_vmchannel_sock = ntohs (addr.sin_port); +- if (g->verbose) +- fprintf (stderr, "null_vmchannel_sock = %d\n", null_vmchannel_sock); +- } else { +- /* Using some vmchannel impl. We need to create a local Unix +- * domain socket for qemu to use. +- */ +- snprintf (unixsock, sizeof unixsock, "%s/sock", g->tmpdir); +- unlink (unixsock); +- null_vmchannel_sock = 0; ++ if (listen (g->sock, 1) == -1) { ++ perrorf (g, "listen"); ++ goto cleanup0; + } + + if (!g->direct) { +@@ -356,7 +336,6 @@ guestfs__launch (guestfs_h *g) + + if (r == 0) { /* Child (qemu). */ + char buf[256]; +- const char *vmchannel = NULL; + + /* Set up the full command line. Do this in the subprocess so we + * don't need to worry about cleaning up. +@@ -394,8 +373,6 @@ guestfs__launch (guestfs_h *g) + add_cmdline (g, "-nodefaults"); + + add_cmdline (g, "-nographic"); +- add_cmdline (g, "-serial"); +- add_cmdline (g, "stdio"); + + snprintf (buf, sizeof buf, "%d", g->memsize); + add_cmdline (g, "-m"); +@@ -411,65 +388,30 @@ guestfs__launch (guestfs_h *g) + if (qemu_supports (g, "-rtc-td-hack")) + add_cmdline (g, "-rtc-td-hack"); + +- /* If qemu has SLIRP (user mode network) enabled then we can get +- * away with "no vmchannel", where we just connect back to a random +- * host port. +- */ +- if (null_vmchannel_sock) { +- add_cmdline (g, "-net"); +- add_cmdline (g, "user,vlan=0,net=" NETWORK); +- +- snprintf (buf, sizeof buf, +- "guestfs_vmchannel=tcp:" ROUTER ":%d", +- null_vmchannel_sock); +- vmchannel = strdup (buf); +- } +- +- /* New-style -net user,guestfwd=... syntax for guestfwd. See: +- * +- * http://git.savannah.gnu.org/cgit/qemu.git/commit/?id=c92ef6a22d3c71538fcc48fb61ad353f7ba03b62 +- * +- * The original suggested format doesn't work, see: +- * +- * http://lists.gnu.org/archive/html/qemu-devel/2009-07/msg01654.html +- * +- * However Gerd Hoffman privately suggested to me using -chardev +- * instead, which does work. +- */ +- else if (qemu_supports (g, "-chardev") && qemu_supports (g, "guestfwd")) { +- snprintf (buf, sizeof buf, +- "socket,id=guestfsvmc,path=%s,server,nowait", unixsock); +- +- add_cmdline (g, "-chardev"); +- add_cmdline (g, buf); ++ /* Create the virtio serial bus. */ ++ add_cmdline (g, "-device"); ++ add_cmdline (g, "virtio-serial"); + +- snprintf (buf, sizeof buf, +- "user,vlan=0,net=" NETWORK "," +- "guestfwd=tcp:" GUESTFWD_ADDR ":" GUESTFWD_PORT +- "-chardev:guestfsvmc"); +- +- add_cmdline (g, "-net"); +- add_cmdline (g, buf); +- +- vmchannel = "guestfs_vmchannel=tcp:" GUESTFWD_ADDR ":" GUESTFWD_PORT; +- } +- +- /* Not guestfwd. HOPEFULLY this qemu uses the older -net channel +- * syntax, or if not then we'll get a quick failure. ++#if 0 ++ /* Use virtio-console (a variant form of virtio-serial) for the ++ * guest's serial console. + */ +- else { +- snprintf (buf, sizeof buf, +- "channel," GUESTFWD_PORT ":unix:%s,server,nowait", unixsock); +- +- add_cmdline (g, "-net"); +- add_cmdline (g, buf); +- add_cmdline (g, "-net"); +- add_cmdline (g, "user,vlan=0,net=" NETWORK); ++ add_cmdline (g, "-chardev"); ++ add_cmdline (g, "stdio,id=console"); ++ add_cmdline (g, "-device"); ++ add_cmdline (g, "virtconsole,chardev=console,name=org.libguestfs.console.0"); ++#else ++ /* When the above works ... until then: */ ++ add_cmdline (g, "-serial"); ++ add_cmdline (g, "stdio"); ++#endif + +- vmchannel = "guestfs_vmchannel=tcp:" GUESTFWD_ADDR ":" GUESTFWD_PORT; +- } +- add_cmdline (g, "-net"); +- add_cmdline (g, "nic,model=" NET_IF ",vlan=0"); ++ /* Set up virtio-serial for the communications channel. */ ++ add_cmdline (g, "-chardev"); ++ snprintf (buf, sizeof buf, "socket,path=%s,id=channel0", unixsock); ++ add_cmdline (g, buf); ++ add_cmdline (g, "-device"); ++ add_cmdline (g, "virtserialport,chardev=channel0,name=org.libguestfs.channel.0"); + + #define LINUX_CMDLINE \ + "panic=1 " /* force kernel to panic if daemon exits */ \ +@@ -484,12 +426,10 @@ guestfs__launch (guestfs_h *g) + snprintf (buf, sizeof buf, + LINUX_CMDLINE + "%s " /* (selinux) */ +- "%s " /* (vmchannel) */ + "%s " /* (verbose) */ + "TERM=%s " /* (TERM environment variable) */ + "%s", /* (append) */ + g->selinux ? "selinux=1 enforcing=0" : "selinux=0", +- vmchannel ? vmchannel : "", + g->verbose ? "guestfs_verbose=1" : "", + getenv ("TERM") ? : "linux", + g->append ? g->append : ""); +@@ -630,90 +570,23 @@ guestfs__launch (guestfs_h *g) + } + } + +- if (null_vmchannel_sock) { +- int sock = -1; +- uid_t uid; +- +- /* Null vmchannel implementation: We listen on g->sock for a +- * connection. The connection could come from any local process +- * so we must check it comes from the appliance (or at least +- * from our UID) for security reasons. +- */ +- while (sock == -1) { +- sock = guestfs___accept_from_daemon (g); +- if (sock == -1) +- goto cleanup1; +- +- if (check_peer_euid (g, sock, &uid) == -1) +- goto cleanup1; +- if (uid != geteuid ()) { +- fprintf (stderr, +- "libguestfs: warning: unexpected connection from UID %d to port %d\n", +- uid, null_vmchannel_sock); +- close (sock); +- sock = -1; +- continue; +- } +- } +- +- if (fcntl (sock, F_SETFL, O_NONBLOCK) == -1) { +- perrorf (g, "fcntl"); +- goto cleanup1; +- } +- +- close (g->sock); +- g->sock = sock; +- } else { +- /* Other vmchannel. Open the Unix socket. +- * +- * The vmchannel implementation that got merged with qemu sucks in +- * a number of ways. Both ends do connect(2), which means that no +- * one knows what, if anything, is connected to the other end, or +- * if it becomes disconnected. Even worse, we have to wait some +- * indeterminate time for qemu to create the socket and connect to +- * it (which happens very early in qemu's start-up), so any code +- * that uses vmchannel is inherently racy. Hence this silly loop. +- */ +- g->sock = socket (AF_UNIX, SOCK_STREAM, 0); +- if (g->sock == -1) { +- perrorf (g, "socket"); +- goto cleanup1; +- } ++ g->state = LAUNCHING; + +- if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) { +- perrorf (g, "fcntl"); +- goto cleanup1; +- } ++ /* Wait for qemu to start and to connect back to us via ++ * virtio-serial and send the GUESTFS_LAUNCH_FLAG message. ++ */ ++ r = guestfs___accept_from_daemon (g); ++ if (r == -1) ++ goto cleanup1; + +- addr.sun_family = AF_UNIX; +- strncpy (addr.sun_path, unixsock, UNIX_PATH_MAX); +- addr.sun_path[UNIX_PATH_MAX-1] = '\0'; +- +- tries = 100; +- /* Always sleep at least once to give qemu a small chance to start up. */ +- usleep (10000); +- while (tries > 0) { +- r = connect (g->sock, (struct sockaddr *) &addr, sizeof addr); +- if ((r == -1 && errno == EINPROGRESS) || r == 0) +- goto connected; +- +- if (errno != ENOENT) +- perrorf (g, "connect"); +- tries--; +- usleep (100000); +- } ++ close (g->sock); /* Close the listening socket. */ ++ g->sock = r; /* This is the accepted data socket. */ + +- error (g, _("failed to connect to vmchannel socket")); ++ if (fcntl (g->sock, F_SETFL, O_NONBLOCK) == -1) { ++ perrorf (g, "fcntl"); + goto cleanup1; +- +- connected: ; + } + +- g->state = LAUNCHING; +- +- /* Wait for qemu to start and to connect back to us via vmchannel and +- * send the GUESTFS_LAUNCH_FLAG message. +- */ + uint32_t size; + void *buf = NULL; + r = guestfs___recv_from_daemon (g, &size, &buf); +@@ -957,90 +830,6 @@ is_openable (guestfs_h *g, const char *path, int flags) + return 1; + } + +-/* Check the peer effective UID for a TCP socket. Ideally we'd like +- * SO_PEERCRED for a loopback TCP socket. This isn't possible on +- * Linux (but it is on Solaris!) so we read /proc/net/tcp instead. +- */ +-static int +-check_peer_euid (guestfs_h *g, int sock, uid_t *rtn) +-{ +-#if CAN_CHECK_PEER_EUID +- struct sockaddr_in peer; +- socklen_t addrlen = sizeof peer; +- +- if (getpeername (sock, (struct sockaddr *) &peer, &addrlen) == -1) { +- perrorf (g, "getpeername"); +- return -1; +- } +- +- if (peer.sin_family != AF_INET || +- ntohl (peer.sin_addr.s_addr) != INADDR_LOOPBACK) { +- error (g, "check_peer_euid: unexpected connection from non-IPv4, non-loopback peer (family = %d, addr = %s)", +- peer.sin_family, inet_ntoa (peer.sin_addr)); +- return -1; +- } +- +- struct sockaddr_in our; +- addrlen = sizeof our; +- if (getsockname (sock, (struct sockaddr *) &our, &addrlen) == -1) { +- perrorf (g, "getsockname"); +- return -1; +- } +- +- FILE *fp = fopen ("/proc/net/tcp", "r"); +- if (fp == NULL) { +- perrorf (g, "/proc/net/tcp"); +- return -1; +- } +- +- char line[256]; +- if (fgets (line, sizeof line, fp) == NULL) { /* Drop first line. */ +- error (g, "unexpected end of file in /proc/net/tcp"); +- fclose (fp); +- return -1; +- } +- +- while (fgets (line, sizeof line, fp) != NULL) { +- unsigned line_our_addr, line_our_port, line_peer_addr, line_peer_port; +- int dummy0, dummy1, dummy2, dummy3, dummy4, dummy5, dummy6; +- int line_uid; +- +- if (sscanf (line, "%d:%08X:%04X %08X:%04X %02X %08X:%08X %02X:%08X %08X %d", +- &dummy0, +- &line_our_addr, &line_our_port, +- &line_peer_addr, &line_peer_port, +- &dummy1, &dummy2, &dummy3, &dummy4, &dummy5, &dummy6, +- &line_uid) == 12) { +- /* Note about /proc/net/tcp: local_address and rem_address are +- * always in network byte order. However the port part is +- * always in host byte order. +- * +- * The sockname and peername that we got above are in network +- * byte order. So we have to byte swap the port but not the +- * address part. +- */ +- if (line_our_addr == our.sin_addr.s_addr && +- line_our_port == ntohs (our.sin_port) && +- line_peer_addr == peer.sin_addr.s_addr && +- line_peer_port == ntohs (peer.sin_port)) { +- *rtn = line_uid; +- fclose (fp); +- return 0; +- } +- } +- } +- +- error (g, "check_peer_euid: no matching TCP connection found in /proc/net/tcp"); +- fclose (fp); +- return -1; +-#else /* !CAN_CHECK_PEER_EUID */ +- /* This function exists but should never be called in this +- * configuration. +- */ +- abort (); +-#endif /* !CAN_CHECK_PEER_EUID */ +-} +- + /* You had to call this function after launch in versions <= 1.0.70, + * but it is now a no-op. + */ +-- +1.7.1 + diff --git a/0017-New-APIs-set-network-and-get-network-to-enable-netwo.patch b/0017-New-APIs-set-network-and-get-network-to-enable-netwo.patch new file mode 100644 index 0000000..d8f1fe7 --- /dev/null +++ b/0017-New-APIs-set-network-and-get-network-to-enable-netwo.patch @@ -0,0 +1,142 @@ +From 4804fe0472a333f9876be1862ec768375ee5dda1 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Tue, 24 Aug 2010 11:53:40 +0100 +Subject: [PATCH] New APIs: set-network and get-network to enable network support. + +guestfs_set_network (g, true) enables network support in the appliance. +(cherry picked from commit 4963be850090933e5769f9d3412d9eb86f522b1b) +--- + configure.ac | 6 +++--- + src/generator.ml | 19 +++++++++++++++++++ + src/guestfs-internal.h | 1 + + src/guestfs.c | 13 +++++++++++++ + src/guestfs.pod | 5 +++++ + src/launch.c | 8 ++++++++ + 6 files changed, 49 insertions(+), 3 deletions(-) + +diff --git a/configure.ac b/configure.ac +index a1c8fe0..8d4d63e 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -300,14 +300,14 @@ AC_ARG_WITH([drive-if], + AC_DEFINE_UNQUOTED([DRIVE_IF],["$with_drive_if"],[Default drive interface.]) + + dnl Set interface used by the network. Normally you should +-dnl leave this at the default (virtio) but you can use the ++dnl leave this at the default (virtio-net-pci) but you can use the + dnl alternative (ne2k_pci) because of bugs in virtio networking + dnl eg. https://bugzilla.redhat.com/show_bug.cgi?id=516022 + AC_ARG_WITH([net-if], + [AS_HELP_STRING([--with-net-if], +- [set default net driver (virtio|ne2k_pci) @<:@default=virtio@:>@])], ++ [set default net driver (virtio-net-pci|ne2k_pci) @<:@default=virtio-net-pci@:>@])], + [], +- [with_net_if=virtio]) ++ [with_net_if=virtio-net-pci]) + AC_DEFINE_UNQUOTED([NET_IF],["$with_net_if"],[Default network interface.]) + + dnl Check for febootstrap etc. +diff --git a/src/generator.ml b/src/generator.ml +index 8fb1477..1d41539 100755 +--- a/src/generator.ml ++++ b/src/generator.ml +@@ -1289,6 +1289,25 @@ for a filesystem to be shared between operating systems. + Please read L for more details. + See also C."); + ++ ("set_network", (RErr, [Bool "network"]), -1, [FishAlias "network"], ++ [], ++ "set enable network flag", ++ "\ ++If C is true, then the network is enabled in the ++libguestfs appliance. The default is false. ++ ++This affects whether commands are able to access the network ++(see L). ++ ++You must call this before calling C, otherwise ++it has no effect."); ++ ++ ("get_network", (RBool "network", []), -1, [], ++ [], ++ "get enable network flag", ++ "\ ++This returns the enable network flag."); ++ + ] + + (* daemon_functions are any functions which cause some action +diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h +index b534b6a..e37c9c2 100644 +--- a/src/guestfs-internal.h ++++ b/src/guestfs-internal.h +@@ -98,6 +98,7 @@ struct guestfs_h + int autosync; + int direct; + int recovery_proc; ++ int enable_network; + + char *path; /* Path to kernel, initrd. */ + char *qemu; /* Qemu binary. */ +diff --git a/src/guestfs.c b/src/guestfs.c +index 74de38c..eaacd39 100644 +--- a/src/guestfs.c ++++ b/src/guestfs.c +@@ -601,6 +601,19 @@ guestfs__get_recovery_proc (guestfs_h *g) + return g->recovery_proc; + } + ++int ++guestfs__set_network (guestfs_h *g, int v) ++{ ++ g->enable_network = !!v; ++ return 0; ++} ++ ++int ++guestfs__get_network (guestfs_h *g) ++{ ++ return g->enable_network; ++} ++ + void + guestfs_set_log_message_callback (guestfs_h *g, + guestfs_log_message_cb cb, void *opaque) +diff --git a/src/guestfs.pod b/src/guestfs.pod +index 5deccb5..e986d77 100644 +--- a/src/guestfs.pod ++++ b/src/guestfs.pod +@@ -358,6 +358,11 @@ The command will be running in limited memory. + + =item * + ++The network may not be available unless you enable it ++(see L). ++ ++=item * ++ + Only supports Linux guests (not Windows, BSD, etc). + + =item * +diff --git a/src/launch.c b/src/launch.c +index 95ed25f..287cc40 100644 +--- a/src/launch.c ++++ b/src/launch.c +@@ -413,6 +413,14 @@ guestfs__launch (guestfs_h *g) + add_cmdline (g, "-device"); + add_cmdline (g, "virtserialport,chardev=channel0,name=org.libguestfs.channel.0"); + ++ /* Enable user networking. */ ++ if (g->enable_network) { ++ add_cmdline (g, "-netdev"); ++ add_cmdline (g, "user,id=usernet"); ++ add_cmdline (g, "-device"); ++ add_cmdline (g, NET_IF ",netdev=usernet"); ++ } ++ + #define LINUX_CMDLINE \ + "panic=1 " /* force kernel to panic if daemon exits */ \ + "console=ttyS0 " /* serial console */ \ +-- +1.7.1 + diff --git a/0018-Add-a-core_pattern-debug-command.patch b/0018-Add-a-core_pattern-debug-command.patch new file mode 100644 index 0000000..8ba48fb --- /dev/null +++ b/0018-Add-a-core_pattern-debug-command.patch @@ -0,0 +1,97 @@ +From 6665cbca8d2a91b9e26645ca6e5dd653a3d95ead Mon Sep 17 00:00:00 2001 +From: Matthew Booth +Date: Thu, 26 Aug 2010 13:36:10 +0100 +Subject: [PATCH] Add a core_pattern debug command + +This adds a new debug command, core_pattern, which writes a new pattern for +coredump files to the appliance kernel, and sets the daemon's hard and soft core +limits to infinity. +(cherry picked from commit a45302cb8a0ee3b4ffd0656b24a06ebdf7b50f38) +--- + daemon/debug.c | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ + 1 files changed, 53 insertions(+), 0 deletions(-) + +diff --git a/daemon/debug.c b/daemon/debug.c +index 0867ccd..c0d87da 100644 +--- a/daemon/debug.c ++++ b/daemon/debug.c +@@ -26,6 +26,7 @@ + #include + #include + #include ++#include + + #include "../src/guestfs_protocol.h" + #include "daemon.h" +@@ -52,9 +53,11 @@ static char *debug_ls (const char *subcmd, int argc, char *const *const argv); + static char *debug_ll (const char *subcmd, int argc, char *const *const argv); + static char *debug_segv (const char *subcmd, int argc, char *const *const argv); + static char *debug_sh (const char *subcmd, int argc, char *const *const argv); ++static char *debug_core_pattern (const char *subcmd, int argc, char *const *const argv); + + static struct cmd cmds[] = { + { "help", debug_help }, ++ { "core_pattern", debug_core_pattern }, + { "env", debug_env }, + { "fds", debug_fds }, + { "ls", debug_ls }, +@@ -338,6 +341,56 @@ debug_ll (const char *subcmd, int argc, char *const *const argv) + return out; + } + ++/* Enable core dumping to the given core pattern. ++ * Note that this pattern is relative to any chroot of the process which ++ * crashes. This means that if you want to write the core file to the guest's ++ * storage the pattern must start with /sysroot only if the command which ++ * crashes doesn't chroot. ++ */ ++static char * ++debug_core_pattern (const char *subcmd, int argc, char *const *const argv) ++{ ++ if (argc < 1) { ++ reply_with_error ("core_pattern: expecting a core pattern"); ++ return NULL; ++ } ++ ++ const char *pattern = argv[0]; ++ const size_t pattern_len = strlen(pattern); ++ ++#define CORE_PATTERN "/proc/sys/kernel/core_pattern" ++ int fd = open (CORE_PATTERN, O_WRONLY); ++ if (fd == -1) { ++ reply_with_perror ("open: " CORE_PATTERN); ++ return NULL; ++ } ++ if (write (fd, pattern, pattern_len) < (ssize_t) pattern_len) { ++ reply_with_perror ("write: " CORE_PATTERN); ++ return NULL; ++ } ++ if (close (fd) == -1) { ++ reply_with_perror ("close: " CORE_PATTERN); ++ return NULL; ++ } ++ ++ struct rlimit limit = { ++ .rlim_cur = RLIM_INFINITY, ++ .rlim_max = RLIM_INFINITY ++ }; ++ if (setrlimit (RLIMIT_CORE, &limit) == -1) { ++ reply_with_perror ("setrlimit (RLIMIT_CORE)"); ++ return NULL; ++ } ++ ++ char *ret = strdup ("ok"); ++ if (NULL == ret) { ++ reply_with_perror ("strdup"); ++ return NULL; ++ } ++ ++ return ret; ++} ++ + #endif /* ENABLE_DEBUG_COMMAND */ + + #if ENABLE_DEBUG_COMMAND +-- +1.7.1 + diff --git a/0019-Call-sync-after-guestfsd-exits.patch b/0019-Call-sync-after-guestfsd-exits.patch new file mode 100644 index 0000000..6509c98 --- /dev/null +++ b/0019-Call-sync-after-guestfsd-exits.patch @@ -0,0 +1,74 @@ +From c0153c943a08ff827f231fdcb3d7507f6f67136e Mon Sep 17 00:00:00 2001 +From: Matthew Booth +Date: Thu, 26 Aug 2010 12:11:59 +0100 +Subject: [PATCH] Call sync after guestfsd exits + +Core files are not reliably written to disk if guestfsd dumps core. This patch +makes libguestfs do the same appliance cleanup for guestfsd and virt-rescue, +which seems to fix the matter. + +It also removes a redundant sleep and additional sync when exiting virt-rescue. +(cherry picked from commit c0b38fbb27c8771916386f47361833722d54518f) +--- + appliance/init | 45 ++++++++++++++++++++++++--------------------- + 1 files changed, 24 insertions(+), 21 deletions(-) + +diff --git a/appliance/init b/appliance/init +index 6aeea0c..90da1cb 100755 +--- a/appliance/init ++++ b/appliance/init +@@ -88,27 +88,30 @@ if grep -sq guestfs_verbose=1 /proc/cmdline; then + fi + + if ! grep -sq guestfs_rescue=1 /proc/cmdline; then +- exec guestfsd -f ++ # The host will kill qemu abruptly if guestfsd shuts down normally ++ guestfsd -f ++ ++ # Otherwise we try to clean up gracefully. For example, this ensures that a ++ # core dump generated by the guest daemon will be written to disk. ++else ++ # Use appliance in rescue mode, also used by the virt-rescue command. ++ eval $(grep -Eo 'TERM=[^[:space:]]+' /proc/cmdline) ++ PS1='> ' ++ export TERM PS1 ++ echo ++ echo "------------------------------------------------------------" ++ echo ++ echo "Welcome to virt-rescue, the libguestfs rescue shell." ++ echo ++ echo "Note: The contents of / are the rescue appliance." ++ echo "You have to mount the guest's partitions under /sysroot" ++ echo "before you can examine them." ++ echo ++ bash -i ++ echo ++ echo "virt-rescue: Syncing the disk now before exiting ..." ++ echo "(Don't worry if you see a 'Kernel panic' message below)" ++ echo + fi + +-# Use appliance in rescue mode, also used by the virt-rescue command. +-eval $(grep -Eo 'TERM=[^[:space:]]+' /proc/cmdline) +-PS1='> ' +-export TERM PS1 +-echo +-echo "------------------------------------------------------------" +-echo +-echo "Welcome to virt-rescue, the libguestfs rescue shell." +-echo +-echo "Note: The contents of / are the rescue appliance." +-echo "You have to mount the guest's partitions under /sysroot" +-echo "before you can examine them." +-echo +-bash -i +-echo +-echo "virt-rescue: Syncing the disk now before exiting ..." +-echo "(Don't worry if you see a 'Kernel panic' message below)" +-echo +-sync +-sleep 1 + sync +-- +1.7.1 + diff --git a/0020-Shut-down-the-appliance-cleanly.patch b/0020-Shut-down-the-appliance-cleanly.patch new file mode 100644 index 0000000..19ab4e4 --- /dev/null +++ b/0020-Shut-down-the-appliance-cleanly.patch @@ -0,0 +1,36 @@ +From 02547e818821efdcf5aadb18f5764740fd6d4a17 Mon Sep 17 00:00:00 2001 +From: Matthew Booth +Date: Thu, 26 Aug 2010 14:34:44 +0100 +Subject: [PATCH] Shut down the appliance cleanly + +When guestfsd exits, or the user exits the virt-rescue shell, the init script +exits which causes the kernel to panic. This isn't really a functional issue, as +all useful work is done by this point. However, it does cause virt-rescue to +display an unsightly error message. + +This patch causes the appliance to power off cleanly before the init script +exits. Note it actually does a reboot rather than a poweroff. This is because +ACPI is disabled in the appliance, meaning poweroff doesn't work, but qemu is +configured not to restart on reboot. +(cherry picked from commit d3fc7e1e4d592dbdc6b8b9edf92dddc0a67eac28) +--- + appliance/init | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/appliance/init b/appliance/init +index 90da1cb..1c01195 100755 +--- a/appliance/init ++++ b/appliance/init +@@ -110,8 +110,8 @@ else + bash -i + echo + echo "virt-rescue: Syncing the disk now before exiting ..." +- echo "(Don't worry if you see a 'Kernel panic' message below)" + echo + fi + + sync ++/sbin/reboot -f +-- +1.7.1 + diff --git a/0021-Ignore-launch-error-in-virt-rescue.-RHBZ-618556.patch b/0021-Ignore-launch-error-in-virt-rescue.-RHBZ-618556.patch new file mode 100644 index 0000000..6e78a2e --- /dev/null +++ b/0021-Ignore-launch-error-in-virt-rescue.-RHBZ-618556.patch @@ -0,0 +1,40 @@ +From f83e639a46bf374a483efb547c9da1f91cf385a6 Mon Sep 17 00:00:00 2001 +From: Matthew Booth +Date: Thu, 26 Aug 2010 15:08:20 +0100 +Subject: [PATCH] Ignore launch() error in virt-rescue. (RHBZ#618556) + +launch() expects guestfsd to start, which it never does in virt-rescue, so it +always returns an error about the appliance shutting down unexpectedly. +(cherry picked from commit c3194e4d370d917db9900a31ea18f10492554da4) +--- + tools/virt-rescue | 7 ++++++- + 1 files changed, 6 insertions(+), 1 deletions(-) + +diff --git a/tools/virt-rescue b/tools/virt-rescue +index ec0bb5e..7a87fbc 100755 +--- a/tools/virt-rescue ++++ b/tools/virt-rescue +@@ -19,6 +19,7 @@ + use warnings; + use strict; + ++use Errno; + use Sys::Guestfs; + use Sys::Guestfs::Lib qw(open_guest); + use Pod::Usage; +@@ -214,7 +215,11 @@ $g->set_append ($str); + + # Run the appliance. This won't return until the user quits the + # appliance. +-$g->launch (); ++eval { $g->launch (); }; ++ ++# launch() expects guestfsd to start. However, virt-rescue doesn't run guestfsd, ++# so this will always fail with ECHILD when the appliance exits unexpectedly. ++die $@ unless $!{ECHILD}; + + exit 0; + +-- +1.7.1 + diff --git a/0022-build-Don-t-add-version-extra-string-to-the-version-.patch b/0022-build-Don-t-add-version-extra-string-to-the-version-.patch new file mode 100644 index 0000000..1f98ac4 --- /dev/null +++ b/0022-build-Don-t-add-version-extra-string-to-the-version-.patch @@ -0,0 +1,91 @@ +From 28b3847e96157ace009a906ac70cddbed3a07b4f Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Fri, 27 Aug 2010 13:38:49 +0100 +Subject: [PATCH] build: Don't add version extra string to the version number. + +If this string was non-empty, then it broke a lot of things because +autoconf and other parts of the build system were expecting this +string to contain a simple MAJOR.MINOR.RELEASE version number. + +This requires changes to guestfish and guestmount so they use the +guestfs_version API to fetch the version from the library. (The +Perl tools were already doing it this way). In a way this is more +accurate, because it's no longer hard-coded in the binary, but +fetched from the dynamically linked libguestfs.so. +(cherry picked from commit 4932fdca3ca1e9002164a1c0b73876f32739d34d) +--- + configure.ac | 2 +- + fish/fish.c | 8 ++++++-- + fuse/guestmount.c | 8 ++++++-- + 3 files changed, 13 insertions(+), 5 deletions(-) + +diff --git a/configure.ac b/configure.ac +index 8d4d63e..97c797c 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -22,7 +22,7 @@ m4_define([libguestfs_release], [3]) + # extra can be any string + m4_define([libguestfs_extra], []) + +-AC_INIT([libguestfs],libguestfs_major.libguestfs_minor.libguestfs_release[]libguestfs_extra) ++AC_INIT([libguestfs],libguestfs_major.libguestfs_minor.libguestfs_release) + AC_CONFIG_AUX_DIR([build-aux]) + AM_INIT_AUTOMAKE([foreign]) + +diff --git a/fish/fish.c b/fish/fish.c +index a896a92..c535e06 100644 +--- a/fish/fish.c ++++ b/fish/fish.c +@@ -20,6 +20,7 @@ + + #include + #include ++#include + #include + #include + #include +@@ -381,9 +382,12 @@ main (int argc, char *argv[]) + guestfs_set_verbose (g, verbose); + break; + +- case 'V': +- printf ("%s %s\n", program_name, PACKAGE_VERSION); ++ case 'V': { ++ struct guestfs_version *v = guestfs_version (g); ++ printf ("%s %"PRIi64".%"PRIi64".%"PRIi64"%s\n", program_name, ++ v->major, v->minor, v->release, v->extra); + exit (EXIT_SUCCESS); ++ } + + case 'x': + guestfs_set_trace (g, 1); +diff --git a/fuse/guestmount.c b/fuse/guestmount.c +index e1cb2d8..9b7e520 100644 +--- a/fuse/guestmount.c ++++ b/fuse/guestmount.c +@@ -29,6 +29,7 @@ + #include + #include + #include ++#include + #include + #include + #include +@@ -1074,9 +1075,12 @@ main (int argc, char *argv[]) + guestfs_set_verbose (g, verbose); + break; + +- case 'V': +- printf ("%s %s\n", program_name, PACKAGE_VERSION); ++ case 'V': { ++ struct guestfs_version *v = guestfs_version (g); ++ printf ("%s %"PRIi64".%"PRIi64".%"PRIi64"%s\n", program_name, ++ v->major, v->minor, v->release, v->extra); + exit (EXIT_SUCCESS); ++ } + + case HELP_OPTION: + usage (EXIT_SUCCESS); +-- +1.7.1 + diff --git a/libguestfs-1.4.3-configure-extra.patch b/libguestfs-1.4.3-configure-extra.patch new file mode 100644 index 0000000..c0526e5 --- /dev/null +++ b/libguestfs-1.4.3-configure-extra.patch @@ -0,0 +1,25 @@ +From 5df8c2d991639d08736a9a2f47e6ae0c768df390 Mon Sep 17 00:00:00 2001 +From: Richard Jones +Date: Fri, 27 Aug 2010 10:29:46 +0100 +Subject: [PATCH] configure: Add backported features to version extra field. + +--- + configure.ac | 2 +- + 1 files changed, 1 insertions(+), 1 deletions(-) + +diff --git a/configure.ac b/configure.ac +index 97c797c..ce25d04 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -20,7 +20,7 @@ m4_define([libguestfs_major], [1]) + m4_define([libguestfs_minor], [4]) + m4_define([libguestfs_release], [3]) + # extra can be any string +-m4_define([libguestfs_extra], []) ++m4_define([libguestfs_extra], [+fastdf+luks+islv+inspection+ext2+serial+core]) + + AC_INIT([libguestfs],libguestfs_major.libguestfs_minor.libguestfs_release) + AC_CONFIG_AUX_DIR([build-aux]) +-- +1.7.1 + diff --git a/libguestfs.spec b/libguestfs.spec index 8c029fd..b367932 100644 --- a/libguestfs.spec +++ b/libguestfs.spec @@ -41,21 +41,47 @@ Summary: Access and modify virtual machine disk images Name: libguestfs Epoch: 1 -Version: 1.4.2 -Release: 1.1%{?dist} +Version: 1.4.3 +Release: 1%{?dist} License: LGPLv2+ Group: Development/Libraries URL: http://libguestfs.org/ -Source0: http://libguestfs.org/download/%{name}-%{version}.tar.gz +Source0: http://libguestfs.org/download/1.4-stable/%{name}-%{version}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root +Patch1: 0001-edit-Add-e-expr-option-to-non-interactively-apply-ex.patch +Patch2: 0002-edit-Add-b-backup-option-and-make-uploading-more-rob.patch +Patch3: 0003-New-APIs-lvm-set-filter-and-lvm-clear-filter.patch +Patch4: 0004-df-Minimize-the-number-of-times-we-launch-the-libgue.patch +Patch5: 0005-generator-Add-Key-parameter-type.patch +Patch6: 0006-New-APIs-Support-for-opening-LUKS-encrypted-disks.patch +Patch7: 0007-New-APIs-Support-for-creating-LUKS-and-managing-keys.patch +Patch8: 0008-New-API-is-lv-check-if-a-block-device-is-a-logical-v.patch +Patch9: 0009-New-API-file-architecture.patch +Patch10: 0010-New-APIs-findfs-label-and-findfs-uuid.patch +Patch11: 0011-New-APIs-for-guest-inspection.patch +Patch12: 0012-fish-Add-c-connect-and-d-domain-options.patch +Patch13: 0013-fish-Reimplement-i-option-using-new-C-based-inspecti.patch +Patch14: 0014-Remove-old-ocaml-inspector-code.patch +Patch15: 0015-Change-to-using-ext2-based-cached-supermin-appliance.patch +Patch16: 0016-Use-virtio-serial-remove-other-vmchannel-methods.patch +Patch17: 0017-New-APIs-set-network-and-get-network-to-enable-netwo.patch +Patch18: 0018-Add-a-core_pattern-debug-command.patch +Patch19: 0019-Call-sync-after-guestfsd-exits.patch +Patch20: 0020-Shut-down-the-appliance-cleanly.patch +Patch21: 0021-Ignore-launch-error-in-virt-rescue.-RHBZ-618556.patch +Patch22: 0022-build-Don-t-add-version-extra-string-to-the-version-.patch + # Disable FUSE tests, not supported in Koji at the moment. -Patch0: libguestfs-1.0.79-no-fuse-test.patch +Patch9998: libguestfs-1.0.79-no-fuse-test.patch + +# Summarise backports in the version extra field. +Patch9999: libguestfs-1.4.3-configure-extra.patch # Basic build requirements: BuildRequires: /usr/bin/pod2man BuildRequires: /usr/bin/pod2text -BuildRequires: febootstrap >= 2.7 +BuildRequires: febootstrap >= 2.8 BuildRequires: hivex-devel >= 1.2.2 BuildRequires: augeas-devel >= 0.5.0 BuildRequires: readline-devel @@ -66,9 +92,12 @@ BuildRequires: createrepo BuildRequires: glibc-static BuildRequires: libselinux-devel BuildRequires: fuse-devel +BuildRequires: pcre-devel +BuildRequires: file-devel +BuildRequires: libvirt-devel -# Temporary BR because openssl libcrypto moved location again. -BuildRequires: openssl >= 1.0.0a-1 +# This is needed because we rerun autoreconf. +BuildRequires: autoconf, automake, libtool, gettext-devel # This is only needed for RHEL 5 because readline-devel doesn't # properly depend on it, but doesn't do any harm on other platforms: @@ -84,6 +113,7 @@ BuildRequires: hfsplus-tools, nilfs-utils, reiserfs-utils BuildRequires: jfsutils, xfsprogs BuildRequires: vim-minimal BuildRequires: binutils +BuildRequires: cryptsetup-luks %ifarch %{ix86} x86_64 BuildRequires: grub, ntfsprogs %endif @@ -98,6 +128,7 @@ Requires: hfsplus-tools, nilfs-utils, reiserfs-utils Requires: jfsutils, xfsprogs Requires: vim-minimal Requires: binutils +Requires: cryptsetup-luks %ifarch %{ix86} x86_64 Requires: grub, ntfsprogs %endif @@ -127,8 +158,8 @@ BuildRequires: perl-Sys-Virt BuildRequires: qemu-img # Runtime requires: -Requires: qemu-kvm >= 0.10-7 -Requires: febootstrap >= 2.7 +Requires: qemu-kvm >= 0.12 +Requires: febootstrap >= 2.8 # For libguestfs-test-tool. Requires: genisoimage @@ -393,7 +424,34 @@ Requires: jpackage-utils %prep %setup -q -%patch0 -p1 +%patch1 -p1 +%patch2 -p1 +%patch3 -p1 +%patch4 -p1 +%patch5 -p1 +%patch6 -p1 +%patch7 -p1 +%patch8 -p1 +%patch9 -p1 +%patch10 -p1 +%patch11 -p1 +%patch12 -p1 +%patch13 -p1 +%patch14 -p1 +%patch15 -p1 +%patch16 -p1 +%patch17 -p1 +%patch18 -p1 +%patch19 -p1 +%patch20 -p1 +%patch21 -p1 +%patch22 -p1 + +%patch9998 -p1 +%patch9999 -p1 + +# Rerun autoreconf because patches don't contain these changes. +autoreconf -i -f mkdir -p daemon/m4 @@ -424,6 +482,17 @@ createrepo repo %endif %{extra} +# The patches don't include generated files. We need to run the +# generator here before starting the build. There are still some +# missing dependencies in the build which mean that (eg.) +# guestfs_protocol.h isn't updated before it can be used in another +# make. Therefore make sure other generated files are built too. +make -C images test.iso +mkdir -p csharp +ocaml -warn-error A src/generator.ml +make -C src guestfs_protocol.c guestfs_protocol.h +make -C daemon guestfs_protocol.c guestfs_protocol.h + # This ensures that /usr/sbin/chroot is on the path. Not needed # except for RHEL 5, it shouldn't do any harm on other platforms. export PATH=/usr/sbin:$PATH @@ -471,6 +540,8 @@ export LIBGUESTFS_DEBUG=1 # 567567 32-bit all guestfish xstrtol test failure on 32-bit (FIXED) # 575734 all F-14 microsecond resolution for blkid cache # (FIXED upstream but still broken in F-14) +# 575734 all F-14 microsecond resolution for blkid cache (FIXED) +# 624854 all F-15 kernel hangs during boot # Workaround #563103 cat > rhbz563103.c <<'EOF' @@ -710,6 +781,13 @@ rm -rf $RPM_BUILD_ROOT %changelog +* Fri Aug 27 2010 Richard W.M. Jones - 1:1.4.3-1 +- New stable branch version 1.4.3. +- Backport major features from development branch, see: + https://www.redhat.com/archives/libguestfs/2010-August/msg00143.html +- Run autoreconf by hand after prepping. +- Run the generator by hand before building. + * Tue Aug 17 2010 Richard W.M. Jones - 1:1.4.2-1.1 - New stable branch version 1.4.2. - Workaround bug that still exists in Gnulib test getlogin_r. diff --git a/sources b/sources index 826db9a..09f463e 100644 --- a/sources +++ b/sources @@ -1 +1 @@ -70eb72f0b4a63b588e5d83c5680b2a51 libguestfs-1.4.2.tar.gz +ddd348024357c0d48a923db5551b6fbb libguestfs-1.4.3.tar.gz