[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