mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
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:
4
README
4
README
@@ -52,6 +52,10 @@ Requirements
|
||||
|
||||
- libmagic (the library that corresponds to the 'file' command)
|
||||
|
||||
- libvirt
|
||||
|
||||
- libxml2
|
||||
|
||||
- squashfs-tools (mksquashfs only)
|
||||
|
||||
- genisoimage / mkisofs
|
||||
|
||||
10
configure.ac
10
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.
|
||||
|
||||
@@ -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
|
||||
|
||||
130
fish/fish.c
130
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 (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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
191
fish/virt.c
Normal 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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user