summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/MAX_PROC_NR2
-rw-r--r--src/guestfs-internal.h3
-rw-r--r--src/guestfs.pod38
-rw-r--r--src/launch-libvirt.c99
-rw-r--r--src/launch.c80
5 files changed, 202 insertions, 20 deletions
diff --git a/src/MAX_PROC_NR b/src/MAX_PROC_NR
index 446dfcc5..5b0cffbc 100644
--- a/src/MAX_PROC_NR
+++ b/src/MAX_PROC_NR
@@ -1 +1 @@
-369
+370
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index ed9ada48..db9818c3 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -166,6 +166,9 @@ struct attach_ops {
int (*get_pid) (guestfs_h *g); /* get-pid API. */
int (*max_disks) (guestfs_h *g); /* max-disks API. */
+
+ /* Hotplugging drives. */
+ int (*hot_add_drive) (guestfs_h *g, struct drive *drv, size_t drv_index);
};
extern struct attach_ops attach_ops_appliance;
extern struct attach_ops attach_ops_libvirt;
diff --git a/src/guestfs.pod b/src/guestfs.pod
index 48d810b0..624e743e 100644
--- a/src/guestfs.pod
+++ b/src/guestfs.pod
@@ -143,8 +143,11 @@ one you added), etc.
Once L</guestfs_launch> has been called you cannot add any more images.
You can call L</guestfs_list_devices> to get a list of the device
-names, in the order that you added them. See also L</BLOCK DEVICE
-NAMING> below.
+names, in the order that you added them.
+See also L</BLOCK DEVICE NAMING> below.
+
+There are slightly different rules when hotplugging disks (in
+libguestfs E<ge> 1.20). See L</HOTPLUGGING> below.
=head2 MOUNTING
@@ -601,6 +604,35 @@ Libguestfs on top of FUSE performs quite poorly. For best performance
do not use it. Use ordinary libguestfs filesystem calls, upload,
download etc. instead.
+=head2 HOTPLUGGING
+
+In libguestfs E<ge> 1.20, you may add drives after calling
+L</guestfs_launch>. There are some restrictions, see below.
+This is called I<hotplugging>.
+
+Only a subset of the attach-method backends support hotplugging
+(currently only the libvirt attach-method has support). It also
+requires that you use libvirt E<ge> 0.10.3 and qemu E<ge> 1.2.
+
+To hot-add a disk, simply call L</guestfs_add_drive_opts> after
+L</guestfs_launch>. It is mandatory to specify the C<label> parameter
+so that the newly added disk has a predictable name. For example:
+
+ if (guestfs_launch (g) == -1)
+ error ("launch failed");
+
+ if (guestfs_add_drive_opts (g, filename,
+ GUESTFS_ADD_DRIVE_OPTS_LABEL, "newdisk",
+ -1) == -1)
+ error ("hot-add of disk failed");
+
+ if (guestfs_part_disk ("/dev/disk/guestfs/newdisk", "mbr") == -1)
+ error ("partitioning of hot-added disk failed");
+
+Backends that support hotplugging do not require that you add
+E<ge> 1 disk before calling launch. When hotplugging is supported
+you don't need to add any disks.
+
=head2 INSPECTION
Libguestfs has APIs for inspecting an unknown disk image to find out
@@ -2639,7 +2671,7 @@ The guest may be killed by L</guestfs_kill_subprocess>, or may die
asynchronously at any time (eg. due to some internal error), and that
causes the state to transition back to CONFIG.
-Configuration commands for qemu such as L</guestfs_add_drive> can only
+Configuration commands for qemu such as L</guestfs_set_path> can only
be issued when in the CONFIG state.
The API offers one call that goes from CONFIG through LAUNCHING to
diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c
index aa1c39f2..3eab5676 100644
--- a/src/launch-libvirt.c
+++ b/src/launch-libvirt.c
@@ -135,14 +135,6 @@ launch_libvirt (guestfs_h *g, const char *libvirt_uri)
struct drive *drv;
size_t i;
- /* At present you must add drives before starting the appliance. In
- * future when we enable hotplugging you won't need to do this.
- */
- if (!g->nr_drives) {
- error (g, _("you must call guestfs_add_drive before guestfs_launch"));
- return -1;
- }
-
/* XXX: It should be possible to make this work. */
if (g->direct) {
error (g, _("direct mode flag is not supported yet for libvirt attach method"));
@@ -1376,6 +1368,89 @@ max_disks_libvirt (guestfs_h *g)
return 255;
}
+static xmlChar *construct_libvirt_xml_hot_add_disk (guestfs_h *g, struct drive *drv, size_t drv_index);
+
+/* Hot-add a drive. Note the appliance is up when this is called. */
+static int
+hot_add_drive_libvirt (guestfs_h *g, struct drive *drv, size_t drv_index)
+{
+ virConnectPtr conn = g->virt.connv;
+ virDomainPtr dom = g->virt.domv;
+ xmlChar *xml = NULL;
+
+ if (!conn || !dom) {
+ /* This is essentially an internal error if it happens. */
+ error (g, "%s: conn == NULL or dom == NULL", __func__);
+ return -1;
+ }
+
+ /* Create overlay for read-only drive. This works around lack of
+ * support for <transient/> disks in libvirt.
+ */
+ if (make_qcow2_overlay_for_drive (g, drv) == -1)
+ return -1;
+
+ /* Create the XML for the new disk. */
+ xml = construct_libvirt_xml_hot_add_disk (g, drv, drv_index);
+ if (xml == NULL)
+ return -1;
+
+ /* Attach it. */
+ if (virDomainAttachDeviceFlags (dom, (char *) xml,
+ VIR_DOMAIN_DEVICE_MODIFY_LIVE) == -1) {
+ libvirt_error (g, _("could not attach disk to libvirt domain"));
+ goto error;
+ }
+
+ free (xml);
+ return 0;
+
+ error:
+ free (xml);
+ return -1;
+}
+
+static xmlChar *
+construct_libvirt_xml_hot_add_disk (guestfs_h *g, struct drive *drv,
+ size_t drv_index)
+{
+ xmlChar *ret = NULL;
+ xmlBufferPtr xb = NULL;
+ xmlOutputBufferPtr ob;
+ xmlTextWriterPtr xo = NULL;
+
+ XMLERROR (NULL, xb = xmlBufferCreate ());
+ XMLERROR (NULL, ob = xmlOutputBufferCreateBuffer (xb, NULL));
+ XMLERROR (NULL, xo = xmlNewTextWriter (ob));
+
+ XMLERROR (-1, xmlTextWriterSetIndent (xo, 1));
+ XMLERROR (-1, xmlTextWriterSetIndentString (xo, BAD_CAST " "));
+ XMLERROR (-1, xmlTextWriterStartDocument (xo, NULL, NULL, NULL));
+
+ if (construct_libvirt_xml_disk (g, xo, drv, drv_index) == -1)
+ goto err;
+
+ XMLERROR (-1, xmlTextWriterEndDocument (xo));
+ XMLERROR (NULL, ret = xmlBufferDetach (xb)); /* caller frees ret */
+
+ debug (g, "hot-add disk XML:\n%s", ret);
+
+ err:
+ if (xo)
+ xmlFreeTextWriter (xo); /* frees 'ob' too */
+ if (xb)
+ xmlBufferFree (xb);
+
+ return ret;
+}
+
+struct attach_ops attach_ops_libvirt = {
+ .launch = launch_libvirt,
+ .shutdown = shutdown_libvirt,
+ .max_disks = max_disks_libvirt,
+ .hot_add_drive = hot_add_drive_libvirt,
+};
+
#else /* no libvirt or libxml2 at compile time */
#define NOT_IMPL(r) \
@@ -1403,10 +1478,16 @@ max_disks_libvirt (guestfs_h *g)
NOT_IMPL (-1);
}
-#endif /* no libvirt or libxml2 at compile time */
+static int
+hot_add_drive_libvirt (guestfs_h *g, struct drive *drv, size_t drv_index)
+{
+ NOT_IMPL (-1);
+}
struct attach_ops attach_ops_libvirt = {
.launch = launch_libvirt,
.shutdown = shutdown_libvirt,
.max_disks = max_disks_libvirt,
};
+
+#endif /* no libvirt or libxml2 at compile time */
diff --git a/src/launch.c b/src/launch.c
index 1b6cf4bd..a4e10fef 100644
--- a/src/launch.c
+++ b/src/launch.c
@@ -29,6 +29,7 @@
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
+#include <assert.h>
#include "c-ctype.h"
@@ -57,14 +58,29 @@ guestfs___rollback_drives (guestfs_h *g, size_t old_i)
g->nr_drives = old_i;
}
+/* Add struct drive to the g->drives vector at the given index. */
+static void
+add_drive_to_handle_at (guestfs_h *g, struct drive *d, size_t drv_index)
+{
+ if (drv_index >= g->nr_drives) {
+ g->drives = safe_realloc (g, g->drives,
+ sizeof (struct drive *) * (drv_index + 1));
+ while (g->nr_drives <= drv_index) {
+ g->drives[g->nr_drives] = NULL;
+ g->nr_drives++;
+ }
+ }
+
+ assert (g->drives[drv_index] == NULL);
+
+ g->drives[drv_index] = d;
+}
+
/* Add struct drive to the end of the g->drives vector in the handle. */
static void
add_drive_to_handle (guestfs_h *g, struct drive *d)
{
- g->nr_drives++;
- g->drives = safe_realloc (g, g->drives,
- sizeof (struct drive *) * g->nr_drives);
- g->drives[g->nr_drives-1] = d;
+ add_drive_to_handle_at (g, d, g->nr_drives);
}
static struct drive *
@@ -207,6 +223,48 @@ valid_disk_label (const char *str)
return 1;
}
+/* The low-level function that adds a drive. */
+static int
+add_drive (guestfs_h *g, struct drive *drv)
+{
+ size_t i, drv_index;
+
+ if (g->state == CONFIG) {
+ /* Not hotplugging, so just add it to the handle. */
+ add_drive_to_handle (g, drv);
+ return 0;
+ }
+
+ /* ... else, hotplugging case. */
+ if (!g->attach_ops || !g->attach_ops->hot_add_drive) {
+ error (g, _("the current attach-method does not support hotplugging drives"));
+ return -1;
+ }
+
+ if (!drv->disk_label) {
+ error (g, _("'label' is required when hotplugging drives"));
+ return -1;
+ }
+
+ /* Get the first free index, or add it at the end. */
+ drv_index = g->nr_drives;
+ for (i = 0; i < g->nr_drives; ++i)
+ if (g->drives[i] == NULL)
+ drv_index = i;
+
+ /* Hot-add the drive. */
+ if (g->attach_ops->hot_add_drive (g, drv, drv_index) == -1)
+ return -1;
+
+ add_drive_to_handle_at (g, drv, drv_index);
+
+ /* Call into the appliance to wait for the new drive to appear. */
+ if (guestfs_internal_hot_add_drive (g, drv->disk_label) == -1)
+ return -1;
+
+ return 0;
+}
+
/* Traditionally you have been able to use /dev/null as a filename, as
* many times as you like. Ancient KVM (RHEL 5) cannot handle adding
* /dev/null readonly. qemu 1.2 + virtio-scsi segfaults when you use
@@ -220,7 +278,7 @@ add_null_drive (guestfs_h *g, int readonly, const char *format,
const char *iface, const char *name, const char *disk_label)
{
char *tmpfile = NULL;
- int fd = -1;
+ int fd = -1, r;
struct drive *drv;
if (format && STRNEQ (format, "raw")) {
@@ -252,8 +310,12 @@ add_null_drive (guestfs_h *g, int readonly, const char *format,
}
drv = create_drive_struct (g, tmpfile, readonly, format, iface, name, disk_label, 0);
- add_drive_to_handle (g, drv);
+ r = add_drive (g, drv);
free (tmpfile);
+ if (r == -1) {
+ free_drive_struct (drv);
+ return -1;
+ }
return 0;
@@ -328,7 +390,11 @@ guestfs__add_drive_opts (guestfs_h *g, const char *filename,
drv = create_drive_struct (g, filename, readonly, format, iface, name, disk_label,
use_cache_none);
- add_drive_to_handle (g, drv);
+ if (add_drive (g, drv) == -1) {
+ free_drive_struct (drv);
+ return -1;
+ }
+
return 0;
}