fish: Add -c/--connect and -d/--domain options.
authorRichard Jones <rjones@redhat.com>
Mon, 2 Aug 2010 15:33:25 +0000 (16:33 +0100)
committerRichard Jones <rjones@redhat.com>
Tue, 17 Aug 2010 13:09:25 +0000 (14:09 +0100)
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.

README
configure.ac
fish/Makefile.am
fish/fish.c
fish/fish.h
fish/guestfish.pod
fish/virt.c [new file with mode: 0644]
po/POTFILES.in

diff --git a/README b/README
index 867bc56..15e6581 100644 (file)
--- 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
index e04357d..192e034 100644 (file)
@@ -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.
index f6b3e7d..cd16733 100644 (file)
@@ -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
index 68f26ed..bc7d96c 100644 (file)
 #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);
   }
 }
 
index da1b087..bf1f81c 100644 (file)
 
 /* 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.
  */
index bfcec5c..8daebc8 100644 (file)
@@ -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 (file)
index 0000000..9c4ce1a
--- /dev/null
@@ -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;
+}
index 8ce5c97..e463bbb 100644 (file)
@@ -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