[libguestfs/f13/master] - New stable branch version 1.4.3. - Backport major features from development branch, see: https:/
Richard W.M. Jones
rjones at fedoraproject.org
Fri Aug 27 13:28:35 UTC 2010
commit 0882396b810d74e68771e98ea22bb110bbbcefca
Author: Richard Jones <rjones at redhat.com>
Date: Fri Aug 27 11:05:44 2010 +0100
- 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 after prep.
.gitignore | 1 +
...expr-option-to-non-interactively-apply-ex.patch | 227 +++
...backup-option-and-make-uploading-more-rob.patch | 75 +
...-APIs-lvm-set-filter-and-lvm-clear-filter.patch | 481 ++++++
...-the-number-of-times-we-launch-the-libgue.patch | 291 ++++
0005-generator-Add-Key-parameter-type.patch | 719 +++++++++
...-Support-for-opening-LUKS-encrypted-disks.patch | 443 ++++++
...pport-for-creating-LUKS-and-managing-keys.patch | 452 ++++++
...lv-check-if-a-block-device-is-a-logical-v.patch | 128 ++
0009-New-API-file-architecture.patch | 775 ++++++++++
0010-New-APIs-findfs-label-and-findfs-uuid.patch | 202 +++
0011-New-APIs-for-guest-inspection.patch | 1523 ++++++++++++++++++++
0012-fish-Add-c-connect-and-d-domain-options.patch | 581 ++++++++
...ement-i-option-using-new-C-based-inspecti.patch | 512 +++++++
0014-Remove-old-ocaml-inspector-code.patch | 627 ++++++++
...sing-ext2-based-cached-supermin-appliance.patch | 925 ++++++++++++
...tio-serial-remove-other-vmchannel-methods.patch | 668 +++++++++
...t-network-and-get-network-to-enable-netwo.patch | 142 ++
0018-Add-a-core_pattern-debug-command.patch | 97 ++
0019-Call-sync-after-guestfsd-exits.patch | 74 +
0020-Shut-down-the-appliance-cleanly.patch | 36 +
...-launch-error-in-virt-rescue.-RHBZ-618556.patch | 40 +
...-add-version-extra-string-to-the-version-.patch | 91 ++
libguestfs-1.4.3-configure-extra.patch | 25 +
libguestfs.spec | 98 ++-
sources | 2 +-
26 files changed, 9224 insertions(+), 11 deletions(-)
---
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 <rjones at redhat.com>
+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<not> use C<virt-edit> on live virtual machines. If you do
+@@ -56,10 +58,18 @@ cases you should look at the L<guestfish(1)> 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</NON-INTERACTIVE EDITING> 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<EXPR> to each line in the file.
++See L</NON-INTERACTIVE EDITING> 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 (<IFILE>) {
++ $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<virt-edit> 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<virt-edit> from scripts in order to
++make automated edits to files. (Note that although you I<can> use
++C<virt-edit> 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<tmpfile> in place however it likes.
++
++The second method is to use the C<-e> parameter of C<virt-edit> to run
++a short Perl snippet in the style of L<sed(1)>. For example to
++replace all instances of C<foo> with C<bar> in a file:
++
++ virt-edit domname filename -e 's/foo/bar/'
++
++The full power of Perl regular expressions can be used (see
++L<perlre(1)>). 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<apache> 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<die> 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<chomp> 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<virt-cat(1)>,
+ L<Sys::Guestfs(3)>,
+ L<Sys::Guestfs::Lib(3)>,
+ L<Sys::Virt(3)>,
+-L<http://libguestfs.org/>.
++L<http://libguestfs.org/>,
++L<perl(1)>,
++L<perlre(1)>.
+
+ =head1 AUTHOR
+
+@@ -195,7 +305,7 @@ Richard W.M. Jones L<http://people.redhat.com/~rjones/>
+
+ =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 <rjones at redhat.com>
+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<in the guest disk image>.
++The backup has the original filename with C<extension> added.
++
++Usually the first character of C<extension> 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 <rjones at redhat.com>
+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 <config.h>
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <inttypes.h>
++#include <string.h>
++#include <unistd.h>
++
++#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<device>.
+
+ 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<devices>,
++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<guestfs_lvm_set_filter>. 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 <rjones at redhat.com>
+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.
++ at 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 <rjones at redhat.com>
+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 <sys/types.h>
+ #include <sys/wait.h>
+ #include <locale.h>
++#include <termios.h>
+
+ #ifdef HAVE_LIBREADLINE
+ #include <readline/readline.h>
+@@ -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<virt-inspector(1)>.
+
++=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</dev/tty>.
++
+ =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<The caller must free the returned buffer after use>.\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</KEYS AND PASSPHRASES> 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</UPLOADING> and L</DOWNLOADING> document how to do this.
+ You might also consider mounting the disk image using our FUSE
+ filesystem support (L<guestmount(1)>).
+
++=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<mlock(2)>-ed into physical RAM, and thus can never end
++up in swap. However this is I<not> 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 <rjones at redhat.com>
+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
+ ><fs> run
+ ><fs> list-devices
+ /dev/vda
+ ><fs> list-partitions
+ /dev/vda1
+ /dev/vda2
+ ><fs> vfs-type /dev/vda2
+ crypto_LUKS
+ ><fs> luks-open /dev/vda2 luksdev
+ Enter key or passphrase ("key"):
+ ><fs> vgscan
+ ><fs> vg-activate-all true
+ ><fs> pvs
+ /dev/dm-0
+ ><fs> vgs
+ vg_f13x64encrypted
+ ><fs> lvs
+ /dev/vg_f13x64encrypted/lv_root
+ /dev/vg_f13x64encrypted/lv_swap
+ ><fs> mount /dev/vg_f13x64encrypted/lv_root /
+ ><fs> 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 <config.h>
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++
++#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>:
++
++ ><fs> vfs-type /dev/sda2
++ crypto_LUKS
++
++Then open those devices using L</luks-open>. This creates a
++device-mapper device called C</dev/mapper/luksdev>.
++
++ ><fs> luks-open /dev/sda2 luksdev
++ Enter key or passphrase ("key"): <enter the passphrase>
++
++Finally you have to tell LVM to scan for volume groups on
++the newly created mapper device:
++
++ ><fs> vgscan
++ ><fs> 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<vg-activate false VG>
++on each one. Then you can close the mapper device:
++
++ ><fs> vg-activate false /dev/VG
++ ><fs> luks-close /dev/mapper/luksdev
++
+ =head1 WINDOWS PATHS
+
+ If a path is prefixed with C<win:> 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<device> is the encrypted block device or partition.
++
++The caller must supply one of the keys associated with the
++LUKS block device, in the C<key> parameter.
++
++This creates a new block device called C</dev/mapper/mapname>.
++Reads and writes to this block device are decrypted from and
++encrypted to the underlying C<device> respectively.
++
++If this block device contains LVM volume groups, then
++calling C<guestfs_vgscan> followed by C<guestfs_vg_activate_all>
++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<guestfs_luks_open> 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<guestfs_luks_open> or C<guestfs_luks_open_ro>. The
++C<device> parameter must be the name of the LUKS mapping
++device (ie. C</dev/mapper/mapname>) and I<not> 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</guestfs_chmod> after creating each file or directory.
+
+ For more information about umask, see L<umask(2)>.
+
++=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</guestfs_vfs_type> to identify LUKS-encrypted block
++devices (it returns the string C<crypto_LUKS>).
++
++Then open these devices by calling L</guestfs_luks_open>.
++Obviously you will require the passphrase!
++
++Opening a LUKS device creates a new device mapper device
++called C</dev/mapper/mapname> (where C<mapname> is the
++string you supply to L</guestfs_luks_open>).
++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</guestfs_vgscan> followed by L</guestfs_vg_activate_all>.
++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<guestfs_vg_activate (g, 0, ["/dev/VG"])>.
++Then close the mapper device by calling
++L</guestfs_luks_close> on the C</dev/mapper/mapname>
++device (I<not> 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 <rjones at redhat.com>
+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 <<EOF
++sparse test1.img 1G
++run
++part-disk /dev/sda mbr
++
++# Create LUKS device with key "key0" in slot 0.
++luks-format /dev/sda1 0
++key0
++
++# Open the device.
++luks-open /dev/sda1 lukstest
++key0
++
++# Put some LVM structures on the encrypted device.
++pvcreate /dev/mapper/lukstest
++vgcreate VG /dev/mapper/lukstest
++lvcreate LV1 VG 64
++lvcreate LV2 VG 64
++vg-activate-all false
++
++# Close the device.
++luks-close /dev/mapper/lukstest
++
++# Add keys in other slots.
++luks-add-key /dev/sda1 1
++key0
++key1
++luks-add-key /dev/sda1 2
++key1
++key2
++luks-add-key /dev/sda1 3
++key2
++key3
++
++# Check we can open the device with one of the new keys.
++luks-open /dev/sda1 lukstest
++key1
++luks-close /dev/mapper/lukstest
++luks-open /dev/sda1 lukstest
++key3
++luks-close /dev/mapper/lukstest
++
++# Remove a key.
++luks-kill-slot /dev/sda1 1
++key0
++
++# This is expected to fail.
++-luks-open /dev/sda1 lukstest
++key1
++
++# Replace a key slot.
++luks-kill-slot /dev/sda1 3
++key2
++luks-add-key /dev/sda1 3
++key2
++newkey3
++
++luks-open /dev/sda1 lukstest
++newkey3
++luks-close /dev/mapper/lukstest
++
++EOF
++
++rm -f test1.img
+diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR
+index 98ecf58..175b6c5 100644
+--- a/src/MAX_PROC_NR
++++ b/src/MAX_PROC_NR
+@@ -1 +1 @@
+-259
++263
+diff --git a/src/generator.ml b/src/generator.ml
+index 43ca70a..8675828 100755
+--- a/src/generator.ml
++++ b/src/generator.ml
+@@ -4916,6 +4916,43 @@ C<device> parameter must be the name of the LUKS mapping
+ device (ie. C</dev/mapper/mapname>) and I<not> 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<device> and formats
++the device as a LUKS encrypted device. C<key> is the
++initial key, which is added to key slot C<slot>. (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<guestfs_luks_format> but
++it also allows you to set the C<cipher> 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<device>.
++C<key> is any existing key, and is used to access the device.
++C<newkey> is the new key to add. C<keyslot> is the key slot
++that will be replaced.
++
++Note that if C<keyslot> already contains a key, then this
++command will fail. You have to use C<guestfs_luks_kill_slot>
++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<keyslot> from the
++encrypted LUKS device C<device>. C<key> must be one of the
++I<other> 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 <rjones at redhat.com>
+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:
+
+><fs> lvs
+/dev/vg_f13x64/lv_root
+/dev/vg_f13x64/lv_swap
+><fs> list-devices
+/dev/vda
+><fs> list-partitions
+/dev/vda1
+/dev/vda2
+><fs> is-lv /dev/vg_f13x64/lv_root
+true
+><fs> is-lv /dev/vg_f13x64/lv_swap
+true
+><fs> is-lv /dev/vda
+false
+><fs> is-lv /dev/vda1
+false
+><fs> 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 <inttypes.h>
+ #include <string.h>
+ #include <unistd.h>
++#include <sys/stat.h>
+
+ #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<keyslot> from the
+ encrypted LUKS device C<device>. C<key> must be one of the
+ I<other> 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<device> 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 <rjones at redhat.com>
+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<file_architecture> 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<i586> or C<x86_64>).
+-
+-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<i386>.
+-
+-Win64 binaries and DLLs return C<x86_64>.
+-
+-=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<guestfs_add_drive_ro> 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<filename>,
++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<i386>.
++
++Win64 binaries and DLLs return C<x86_64>.
++
++=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 <config.h>
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <stdint.h>
++#include <inttypes.h>
++#include <unistd.h>
++#include <string.h>
++#include <sys/stat.h>
++
++#include <pcre.h>
++#include <magic.h>
++
++#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 <rjones at redhat.com>
+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:
+
+><fs> vfs-uuid /dev/vda1
+277dd61c-bf34-4253-a8dc-df500a05e7df
+><fs> findfs-uuid 277dd61c-bf34-4253-a8dc-df500a05e7df
+/dev/vda1
+><fs> vfs-label /dev/vda1
+/boot
+><fs> findfs-label /boot
+/dev/vda1
+><fs> vfs-uuid /dev/VolGroup00/LogVol00
+40ce7c36-82ce-4a12-a99d-48f5e054162c
+><fs> findfs-uuid 40ce7c36-82ce-4a12-a99d-48f5e054162c
+/dev/mapper/VolGroup00-LogVol00
+><fs> 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 <config.h>
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <unistd.h>
++
++#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<device>.
+
+-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<guestfs_findfs_label>.");
+
+ ("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<device>.
+
+-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<guestfs_findfs_uuid>.");
+
+ ("lvm_set_filter", (RErr, [DeviceList "devices"]), 255, [Optional "lvm2"],
+ (* Can't be tested with the current framework because
+@@ -5091,6 +5095,26 @@ I<other> keys.");
+ This command tests whether C<device> 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<guestfs_vfs_uuid>.");
++
++ ("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<guestfs_vfs_label>.");
++
+ ]
+
+ 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 <rjones at redhat.com>
+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<guestfs_inspect_get_*> 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<guestfs_mount_ro> and C<guestfs_umount_all> 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<guestfs(3)/INSPECTION> 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<guestfs_inspect_os>.
++
++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<guestfs(3)/INSPECTION> 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<guestfs_inspect_os>.
++
++This returns the architecture of the inspected operating system.
++The possible return values are listed under
++C<guestfs_file_architecture>.
++
++If the architecture could not be determined, then the
++string C<unknown> is returned.
++
++Please read L<guestfs(3)/INSPECTION> 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<guestfs_inspect_os>.
++
++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<guestfs(3)/INSPECTION> 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<guestfs_inspect_os>.
++
++This returns the major version number of the inspected operating
++system.
++
++Windows uses a consistent versioning scheme which is I<not>
++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<guestfs(3)/INSPECTION> 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<guestfs_inspect_os>.
++
++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<guestfs(3)/INSPECTION> for more details.
++See also C<guestfs_inspect_get_major_version>.");
++
++ ("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<guestfs_inspect_os>.
++
++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<unknown> is returned.
++
++Please read L<guestfs(3)/INSPECTION> 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<guestfs_inspect_os>.
++
++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</etc/fstab>.
++
++Each element in the returned hashtable has a key which
++is the path of the mountpoint (eg. C</boot>) and a value
++which is the filesystem that would be mounted there
++(eg. C</dev/sda1>).
++
++Non-mounted devices such as swap devices are I<not>
++returned in this list.
++
++Please read L<guestfs(3)/INSPECTION> for more details.
++See also C<guestfs_inspect_get_filesystems>.");
++
++ ("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<guestfs_inspect_os>.
++
++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<guestfs(3)/INSPECTION> for more details.
++See also C<guestfs_inspect_get_mountpoints>.");
++
+ ]
+
+ (* 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</guestfs_list_partitions> and L</guestfs_lvs> to list possible
+ partitions and LVs, and either try mounting each to see what is
+ mountable, or else examine them with L</guestfs_vfs_type> or
+-L</guestfs_file>. But you might find it easier to look at higher level
+-programs built on top of libguestfs, in particular
+-L<virt-inspector(1)>.
++L</guestfs_file>. Libguestfs also has a set of APIs for inspection of
++disk images (see L</INSPECTION> below). But you might find it easier
++to look at higher level programs built on top of libguestfs, in
++particular L<virt-inspector(1)>.
+
+ To mount a disk image read-only, use L</guestfs_mount_ro>. There are
+ several other variations of the C<guestfs_mount_*> call.
+@@ -481,6 +482,65 @@ Then close the mapper device by calling
+ L</guestfs_luks_close> on the C</dev/mapper/mapname>
+ device (I<not> 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<Sys::Guestfs::Lib(3)> 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</guestfs_launch> in the usual way.
++
++Then call L</guestfs_inspect_os>. 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<guestfs_inspect_get_*>
++functions to get additional details about that operating system. For
++example, call L</guestfs_inspect_get_type> to return the string
++C<windows> or C<linux> 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</boot>). The inspection rules are able to
++detect how filesystems correspond to mount points. Call
++C<guestfs_inspect_get_mountpoints> 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</guestfs_mount_options> to
++mount the filesystems as suggested.
++
++Be careful to mount filesystems in the right order (eg. C</> before
++C</usr>). 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</ENCRYPTED DISKS> for more details. The L</guestfs_inspect_os>
++function just ignores any encrypted devices.
++
++A note on the implementation: The call L</guestfs_inspect_os> performs
++inspection and caches the results in the guest handle. Subsequent
++calls to C<guestfs_inspect_get_*> return this cached information, but
++I<do not> re-read the disks. If you change the content of the guest
++disks, you can redo inspection by calling L</guestfs_inspect_os>
++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</WINDOWS/System32>.
+ 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<virt-inspector(1)>).
++also L<hivex(3)> and L<virt-inspector(1)>).
+
+ 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 <pcre.h>
+ #include <magic.h>
++#include <hivex.h>
++#include <augeas.h>
+
+ #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 <rjones at redhat.com>
+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<cmd>.
+
+ 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 <config.h>
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <assert.h>
++
++#include <libvirt/libvirt.h>
++#include <libvirt/virterror.h>
++
++#include <libxml/xpath.h>
++#include <libxml/parser.h>
++#include <libxml/tree.h>
++
++#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 <rjones at redhat.com>
+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</boot/grub/grub.conf> 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<virt-inspector(1)> 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<myguest>), 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<virt-inspector(1)>.
++ 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 <config.h>
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++
++#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 <rjones at redhat.com>
+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 (* <element name=name/> *)
+- | Attribute of string * rng list (* <attribute name=name/> *)
+- | Interleave of rng list (* <interleave/> *)
+- | ZeroOrMore of rng (* <zeroOrMore/> *)
+- | OneOrMore of rng (* <oneOrMore/> *)
+- | Optional of rng (* <optional/> *)
+- | Choice of string list (* <choice><value/>*</choice> *)
+- | Value of string (* <value>str</value> *)
+- | Text (* <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: <zeroOrMore> 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: <oneOrMore> 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: <optional> 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 <value> in <choice>"
+- 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 <ref> yet. You can only
+- * use <ref> from inside <start>.
+- *)
+- (match defines with
+- | None ->
+- failwithf "%s: contains <ref>, 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 <define/> elements are referenced in the <start> 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 <define name=name/>" rng_input
+- ) StringMap.empty defines in
+- let defines = StringMap.mapi parse_rng defines in
+-
+- (* Parse the <start> clause, passing the defines. *)
+- parse_rng ~defines "<start>" gram
+- | _ ->
+- failwithf "%s: input is not <grammar><start/><define>*</grammar>"
+- 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 -> (* <rng> list *)
+- let t, is_simple = generate_type rng in
+- t ^ " list (* 0 or more *)", is_simple
+- | OneOrMore rng -> (* <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 -> (* <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 -> (* <rng> list *)
+- let pa = generate_parser rng in
+- sprintf "(fun x -> List.map %s (Xml.children x))" pa
+- | OneOrMore rng -> (* <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 -> (* <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 <interleave> 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 <rjones at redhat.com>
+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 <config.h>
++
++#include <stdio.h>
++#include <stdlib.h>
++#include <stdarg.h>
++#include <unistd.h>
++#include <string.h>
++#include <fcntl.h>
++#include <time.h>
++#include <sys/stat.h>
++#include <sys/select.h>
++#include <utime.h>
++
++#ifdef HAVE_SYS_TYPES_H
++#include <sys/types.h>
++#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 <path>/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 <path>/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 <rjones at redhat.com>
+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:<ip>:<port>\": %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:<ip>:<port>\"\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 <rjones at redhat.com>
+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<guestfs(3)/INSPECTION> for more details.
+ See also C<guestfs_inspect_get_mountpoints>.");
+
++ ("set_network", (RErr, [Bool "network"]), -1, [FishAlias "network"],
++ [],
++ "set enable network flag",
++ "\
++If C<network> 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<guestfs(3)/RUNNING COMMANDS>).
++
++You must call this before calling C<guestfs_launch>, 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</guestfs_set_network>).
++
++=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 <mbooth at redhat.com>
+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 <unistd.h>
+ #include <fcntl.h>
+ #include <dirent.h>
++#include <sys/resource.h>
+
+ #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 <mbooth at redhat.com>
+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='><rescue> '
++ 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='><rescue> '
+-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 <mbooth at redhat.com>
+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 <mbooth at redhat.com>
+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 <rjones at redhat.com>
+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 <stdio.h>
+ #include <stdlib.h>
++#include <inttypes.h>
+ #include <string.h>
+ #include <unistd.h>
+ #include <fcntl.h>
+@@ -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 <stdio.h>
+ #include <stdlib.h>
+ #include <stdint.h>
++#include <inttypes.h>
+ #include <string.h>
+ #include <unistd.h>
+ #include <getopt.h>
+@@ -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 <rjones at redhat.com>
+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 <rjones at redhat.com> - 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 <rjones at redhat.com> - 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
More information about the scm-commits
mailing list