From 1a9aa565b38eafe48621bc2fe42d35ea6a907708 Mon Sep 17 00:00:00 2001 From: Richard Jones Date: Mon, 2 Aug 2010 16:33:25 +0100 Subject: [PATCH] fish: Add -c/--connect and -d/--domain options. The -d option lets you specify libvirt domains. The disks from these domains are found and added, as if you'd named them with -a. The -c option lets you specify a libvirt URI, which is needed when we consult libvirt to implement the above. --- 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 e04357d..192e034 100644 --- a/configure.ac +++ b/configure.ac @@ -468,6 +468,16 @@ AC_CHECK_HEADER([magic.h],[],[ AC_MSG_FAILURE([magic.h header file is required]) ]) +dnl libvirt (required) +PKG_CHECK_MODULES([LIBVIRT], [libvirt]) +AC_SUBST([LIBVIRT_CFLAGS]) +AC_SUBST([LIBVIRT_LIBS]) + +dnl libxml2 (required) +PKG_CHECK_MODULES([LIBXML2], [libxml-2.0]) +AC_SUBST([LIBXML2_CFLAGS]) +AC_SUBST([LIBXML2_LIBS]) + dnl hivex library (highly recommended). dnl This used to be a part of libguestfs, but was spun off into its dnl own separate upstream project in libguestfs 1.0.85. diff --git a/fish/Makefile.am b/fish/Makefile.am index f6b3e7d..cd16733 100644 --- a/fish/Makefile.am +++ b/fish/Makefile.am @@ -52,7 +52,8 @@ guestfish_SOURCES = \ reopen.c \ supported.c \ tilde.c \ - time.c + time.c \ + virt.c # This convenience library is solely to avoid compiler warnings # in its generated sources. @@ -65,9 +66,12 @@ guestfish_CFLAGS = \ -DGUESTFS_DEFAULT_PATH='"$(libdir)/guestfs"' \ -DLOCALEBASEDIR=\""$(datadir)/locale"\" \ -I$(srcdir)/../gnulib/lib -I../gnulib/lib \ + $(LIBVIRT_CFLAGS) $(LIBXML2_CFLAGS) \ $(WARN_CFLAGS) $(WERROR_CFLAGS) -guestfish_LDADD = $(top_builddir)/src/libguestfs.la $(LIBREADLINE) +guestfish_LDADD = \ + $(LIBVIRT_LIBS) $(LIBXML2_LIBS) \ + $(top_builddir)/src/libguestfs.la $(LIBREADLINE) # Make libguestfs use the convenience library. noinst_LTLIBRARIES = librc_protocol.la diff --git a/fish/fish.c b/fish/fish.c index 68f26ed..bc7d96c 100644 --- a/fish/fish.c +++ b/fish/fish.c @@ -43,11 +43,23 @@ #include "closeout.h" #include "progname.h" +/* List of drives added via -a, -d or -N options. */ struct drv { struct drv *next; - char *filename; /* disk filename (for -a or -N options) */ - prep_data *data; /* prepared type (for -N option only) */ - char *device; /* device inside the appliance */ + enum { drv_a, drv_d, drv_N } type; + union { + struct { + char *filename; /* disk filename */ + } a; + struct { + char *guest; /* guest name */ + } d; + struct { + char *filename; /* disk filename (testX.img) */ + prep_data *data; /* prepared type */ + char *device; /* device inside the appliance */ + } N; + }; }; struct mp { @@ -56,7 +68,7 @@ struct mp { char *mountpoint; }; -static void add_drives (struct drv *drv); +static char add_drives (struct drv *drv, char next_drive); static void prepare_drives (struct drv *drv); static void mount_mps (struct mp *mp); static int launch (void); @@ -82,6 +94,7 @@ int remote_control = 0; int exit_on_error = 1; int command_num = 0; int keys_from_stdin = 0; +const char *libvirt_uri = NULL; static void __attribute__((noreturn)) usage (int status) @@ -109,6 +122,8 @@ usage (int status) " -h|--cmd-help List available commands\n" " -h|--cmd-help cmd Display detailed help on 'cmd'\n" " -a|--add image Add image\n" + " -c|--connect uri Specify libvirt URI for -d option\n" + " -d|--domain guest Add disks from libvirt guest\n" " -D|--no-dest-paths Don't tab-complete paths from guest fs\n" " -f|--file file Read commands from file\n" " -i|--inspector Run virt-inspector to get disk mountpoints\n" @@ -145,10 +160,12 @@ main (int argc, char *argv[]) enum { HELP_OPTION = CHAR_MAX + 1 }; - static const char *options = "a:Df:h::im:nN:rv?Vx"; + static const char *options = "a:c:d:Df:h::im:nN:rv?Vx"; static const struct option long_options[] = { { "add", 1, 0, 'a' }, { "cmd-help", 2, 0, 'h' }, + { "connect", 1, 0, 'c' }, + { "domain", 1, 0, 'd' }, { "file", 1, 0, 'f' }, { "help", 0, 0, HELP_OPTION }, { "inspector", 0, 0, 'i' }, @@ -174,7 +191,6 @@ main (int argc, char *argv[]) int inspector = 0; int option_index; struct sigaction sa; - char next_drive = 'a'; int next_prepared_drive = 1; initialize_readline (); @@ -262,15 +278,26 @@ main (int argc, char *argv[]) perror ("malloc"); exit (EXIT_FAILURE); } - drv->filename = optarg; - drv->data = NULL; - /* We could fill the device field in, but in fact we - * only use it for the -N option at present. - */ - drv->device = NULL; + drv->type = drv_a; + drv->a.filename = optarg; + drv->next = drvs; + drvs = drv; + break; + + case 'c': + libvirt_uri = optarg; + break; + + case 'd': + drv = malloc (sizeof (struct drv)); + if (!drv) { + perror ("malloc"); + exit (EXIT_FAILURE); + } + drv->type = drv_d; + drv->d.guest = optarg; drv->next = drvs; drvs = drv; - next_drive++; break; case 'N': @@ -283,16 +310,14 @@ main (int argc, char *argv[]) perror ("malloc"); exit (EXIT_FAILURE); } - if (asprintf (&drv->filename, "test%d.img", + drv->type = drv_N; + if (asprintf (&drv->N.filename, "test%d.img", next_prepared_drive++) == -1) { perror ("asprintf"); exit (EXIT_FAILURE); } - drv->data = create_prepared_file (optarg, drv->filename); - if (asprintf (&drv->device, "/dev/sd%c", next_drive++) == -1) { - perror ("asprintf"); - exit (EXIT_FAILURE); - } + drv->N.data = create_prepared_file (optarg, drv->N.filename); + drv->N.device = NULL; /* filled in by add_drives */ drv->next = drvs; drvs = drv; break; @@ -476,7 +501,7 @@ main (int argc, char *argv[]) } /* If we've got drives to add, add them now. */ - add_drives (drvs); + add_drives (drvs, 'a'); /* If we've got mountpoints or prepared drives, we must launch the * guest and mount them. @@ -584,21 +609,60 @@ mount_mps (struct mp *mp) } } -static void -add_drives (struct drv *drv) +static char +add_drives (struct drv *drv, char next_drive) { int r; + if (next_drive > 'z') { + fprintf (stderr, + _("guestfish: too many drives added on the command line\n")); + exit (EXIT_FAILURE); + } + if (drv) { - add_drives (drv->next); + next_drive = add_drives (drv->next, next_drive); - if (drv->data /* -N option is not affected by --ro */ || !read_only) - r = guestfs_add_drive (g, drv->filename); - else - r = guestfs_add_drive_ro (g, drv->filename); - if (r == -1) - exit (EXIT_FAILURE); + switch (drv->type) { + case drv_a: + if (!read_only) + r = guestfs_add_drive (g, drv->a.filename); + else + r = guestfs_add_drive_ro (g, drv->a.filename); + if (r == -1) + exit (EXIT_FAILURE); + + next_drive++; + break; + + case drv_d: + r = add_libvirt_drives (drv->d.guest); + if (r == -1) + exit (EXIT_FAILURE); + + next_drive += r; + break; + + case drv_N: + /* -N option is not affected by --ro */ + r = guestfs_add_drive (g, drv->N.filename); + if (r == -1) + exit (EXIT_FAILURE); + + if (asprintf (&drv->N.device, "/dev/sd%c", next_drive) == -1) { + perror ("asprintf"); + exit (EXIT_FAILURE); + } + + next_drive++; + break; + + default: /* keep GCC happy */ + abort (); + } } + + return next_drive; } static void @@ -606,8 +670,8 @@ prepare_drives (struct drv *drv) { if (drv) { prepare_drives (drv->next); - if (drv->data) - prepare_drive (drv->filename, drv->data, drv->device); + if (drv->type == drv_N) + prepare_drive (drv->N.filename, drv->N.data, drv->N.device); } } diff --git a/fish/fish.h b/fish/fish.h index da1b087..bf1f81c 100644 --- a/fish/fish.h +++ b/fish/fish.h @@ -49,9 +49,11 @@ /* in fish.c */ extern guestfs_h *g; +extern int read_only; extern int quit; extern int verbose; extern int command_num; +extern const char *libvirt_uri; extern int issue_command (const char *cmd, char *argv[], const char *pipe); extern void pod2text (const char *name, const char *shortdesc, const char *body); extern void list_builtin_commands (void); @@ -131,6 +133,9 @@ extern int do_time (const char *cmd, int argc, char *argv[]); /* in tilde.c */ extern char *try_tilde_expansion (char *path); +/* in virt.c */ +extern int add_libvirt_drives (const char *guest); + /* This should just list all the built-in commands so they can * be added to the generated auto-completion code. */ diff --git a/fish/guestfish.pod b/fish/guestfish.pod index bfcec5c..8daebc8 100644 --- a/fish/guestfish.pod +++ b/fish/guestfish.pod @@ -14,6 +14,8 @@ guestfish - the libguestfs Filesystem Interactive SHell guestfish -a disk.img -m dev[:mountpoint] + guestfish -d libvirt-domain + guestfish -i libvirt-domain guestfish -i disk.img [disk.img ...] @@ -140,6 +142,18 @@ Displays detailed help on a single command C. Add a block device or virtual machine image to the shell. +=item B<-c URI> | B<--connect URI> + +When used in conjunction with the I<-d> option, this specifies +the libvirt URI to use. The default is to use the default libvirt +connection. + +=item B<-d libvirt-domain> | B<--domain libvirt-domain> + +Add disks from the named libvirt domain. If the I<--ro> option is +also used, then any libvirt domain can be used. However in write +mode, only libvirt domains which are shut down can be named here. + =item B<-D> | B<--no-dest-paths> Don't tab-complete paths on the guest filesystem. It is useful to be diff --git a/fish/virt.c b/fish/virt.c new file mode 100644 index 0000000..9c4ce1a --- /dev/null +++ b/fish/virt.c @@ -0,0 +1,191 @@ +/* guestfish - the filesystem interactive shell + * Copyright (C) 2010 Red Hat Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include "fish.h" + +static int add_drives_from_node_set (xmlDocPtr doc, xmlNodeSetPtr nodes); + +/* Implements the guts of the '-d' option. + * + * Note that we have to observe the '--ro' flag in two respects: by + * adding the drives read-only if the flag is set, and by restricting + * guests to shut down ones unless '--ro' is set. + * + * Returns the number of drives added (> 0), or -1 for failure. + */ +int +add_libvirt_drives (const char *guest) +{ + static int initialized = 0; + if (!initialized) { + initialized = 1; + + if (virInitialize () == -1) + return -1; + + xmlInitParser (); + LIBXML_TEST_VERSION; + } + + int r = -1, nr_added = 0; + virErrorPtr err; + virConnectPtr conn = NULL; + virDomainPtr dom = NULL; + xmlDocPtr doc = NULL; + xmlXPathContextPtr xpathCtx = NULL; + xmlXPathObjectPtr xpathObj = NULL; + char *xml = NULL; + + /* Connect to libvirt, find the domain. */ + conn = virConnectOpenReadOnly (libvirt_uri); + if (!conn) { + err = virGetLastError (); + fprintf (stderr, _("guestfish: could not connect to libvirt (code %d, domain %d): %s\n"), + err->code, err->domain, err->message); + goto cleanup; + } + + dom = virDomainLookupByName (conn, guest); + if (!dom) { + err = virConnGetLastError (conn); + fprintf (stderr, _("guestfish: no libvirt domain called '%s': %s\n"), + guest, err->message); + goto cleanup; + } + if (!read_only) { + virDomainInfo info; + if (virDomainGetInfo (dom, &info) == -1) { + err = virConnGetLastError (conn); + fprintf (stderr, _("guestfish: error getting domain info about '%s': %s\n"), + guest, err->message); + goto cleanup; + } + if (info.state != VIR_DOMAIN_SHUTOFF) { + fprintf (stderr, _("guestfish: error: '%s' is a live virtual machine.\nYou must use '--ro' because write access to a running virtual machine can\ncause disk corruption.\n"), + guest); + goto cleanup; + } + } + + /* Domain XML. */ + xml = virDomainGetXMLDesc (dom, 0); + + if (!xml) { + err = virConnGetLastError (conn); + fprintf (stderr, _("guestfish: error reading libvirt XML information about '%s': %s\n"), + guest, err->message); + goto cleanup; + } + + /* Now the horrible task of parsing out the fields we need from the XML. + * http://www.xmlsoft.org/examples/xpath1.c + */ + doc = xmlParseMemory (xml, strlen (xml)); + if (doc == NULL) { + fprintf (stderr, _("guestfish: unable to parse XML information returned by libvirt\n")); + goto cleanup; + } + + xpathCtx = xmlXPathNewContext (doc); + if (xpathCtx == NULL) { + fprintf (stderr, _("guestfish: unable to create new XPath context\n")); + goto cleanup; + } + + xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk/source/@dev", + xpathCtx); + if (xpathObj == NULL) { + fprintf (stderr, _("guestfish: unable to evaluate XPath expression\n")); + goto cleanup; + } + + nr_added += add_drives_from_node_set (doc, xpathObj->nodesetval); + + xmlXPathFreeObject (xpathObj); xpathObj = NULL; + + xpathObj = xmlXPathEvalExpression (BAD_CAST "//devices/disk/source/@file", + xpathCtx); + if (xpathObj == NULL) { + fprintf (stderr, _("guestfish: unable to evaluate XPath expression\n")); + goto cleanup; + } + + nr_added += add_drives_from_node_set (doc, xpathObj->nodesetval); + + if (nr_added == 0) { + fprintf (stderr, _("guestfish: libvirt domain '%s' has no disks\n"), + guest); + goto cleanup; + } + + /* Successful. */ + r = nr_added; + +cleanup: + free (xml); + if (xpathObj) xmlXPathFreeObject (xpathObj); + if (xpathCtx) xmlXPathFreeContext (xpathCtx); + if (doc) xmlFreeDoc (doc); + if (dom) virDomainFree (dom); + if (conn) virConnectClose (conn); + + return r; +} + +static int +add_drives_from_node_set (xmlDocPtr doc, xmlNodeSetPtr nodes) +{ + if (!nodes) + return 0; + + int i; + + for (i = 0; i < nodes->nodeNr; ++i) { + assert (nodes->nodeTab[i]); + assert (nodes->nodeTab[i]->type == XML_ATTRIBUTE_NODE); + xmlAttrPtr attr = (xmlAttrPtr) nodes->nodeTab[i]; + + char *device = (char *) xmlNodeListGetString (doc, attr->children, 1); + + int r; + if (!read_only) + r = guestfs_add_drive (g, device); + else + r = guestfs_add_drive_ro (g, device); + if (r == -1) + exit (EXIT_FAILURE); + + xmlFree (device); + } + + return nodes->nodeNr; +} diff --git a/po/POTFILES.in b/po/POTFILES.in index 8ce5c97..e463bbb 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -85,6 +85,7 @@ fish/reopen.c fish/supported.c fish/tilde.c fish/time.c +fish/virt.c fuse/dircache.c fuse/guestmount.c inspector/virt-inspector.pl -- 1.8.3.1