- libmagic (the library that corresponds to the 'file' command)
+- libvirt
+
+- libxml2
+
- squashfs-tools (mksquashfs only)
- genisoimage / mkisofs
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.
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.
-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
#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 {
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);
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)
" -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"
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' },
int inspector = 0;
int option_index;
struct sigaction sa;
- char next_drive = 'a';
int next_prepared_drive = 1;
initialize_readline ();
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':
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;
}
/* 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.
}
}
-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
{
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);
}
}
/* 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);
/* 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.
*/
guestfish -a disk.img -m dev[:mountpoint]
+ guestfish -d libvirt-domain
+
guestfish -i libvirt-domain
guestfish -i disk.img [disk.img ...]
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
--- /dev/null
+/* 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;
+}
fish/supported.c
fish/tilde.c
fish/time.c
+fish/virt.c
fuse/dircache.c
fuse/guestmount.c
inspector/virt-inspector.pl