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.
This commit is contained in:
Richard Jones
2010-08-02 16:33:25 +01:00
parent 8289aa1ad6
commit 1a9aa565b3
8 changed files with 328 additions and 35 deletions

4
README
View File

@@ -52,6 +52,10 @@ Requirements
- libmagic (the library that corresponds to the 'file' command)
- libvirt
- libxml2
- squashfs-tools (mksquashfs only)
- genisoimage / mkisofs

View 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.

View 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

View File

@@ -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 (drv) {
add_drives (drv->next);
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);
if (next_drive > 'z') {
fprintf (stderr,
_("guestfish: too many drives added on the command line\n"));
exit (EXIT_FAILURE);
}
if (drv) {
next_drive = add_drives (drv->next, next_drive);
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);
}
}

View File

@@ -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.
*/

View 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

191
fish/virt.c Normal file
View File

@@ -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;
}

View 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