qemu has broken snapshot=on ... again.
Change the way that drives are created so that the backend no longer
has to use snapshot=on, <transient/> (which never worked), or UML's
corresponding COW-creation feature (also broken).
Instead of that, the src/drives.c code will create overlays when
required by calling into a new backend operation 'create_cow_overlay'.
This operation runs 'qemu-img create -b' or 'uml_mkcow' as determined
by the backend, and returns the name of the overlay.
The format of the overlay is still backend-specific because qemu needs
to use qcow2 and UML needs to use COW.
This patch also includes some factorization of the libvirt XML code.
This also drops the drv->priv (private per-drive data) field, since it
is no longer used by any backend.
This also moves the guestfs___drive_source_qemu_param utility
function, used & shared by the direct & libvirt backends only, into
src/launch-direct.c (from src/drives.c).
---
src/drives.c | 286 ++++++----------------
src/guestfs-internal.h | 33 ++-
src/launch-direct.c | 275 +++++++++++++++++++--
src/launch-libvirt.c | 653 ++++++++++++++++++++++---------------------------
src/launch-uml.c | 87 ++++---
src/libvirt-domain.c | 5 +-
6 files changed, 703 insertions(+), 636 deletions(-)
diff --git a/src/drives.c b/src/drives.c
index cddde4f..4911e73 100644
--- a/src/drives.c
+++ b/src/drives.c
@@ -37,8 +37,6 @@
#include <pcre.h>
-#include <libxml/uri.h>
-
#include "c-ctype.h"
#include "ignore-value.h"
@@ -81,6 +79,38 @@ free_regexps (void)
pcre_free (re_hostname_port);
}
+static void free_drive_struct (struct drive *drv);
+static void free_drive_source (struct drive_source *src);
+
+/* For readonly drives, create an overlay to protect the original
+ * drive content. Note we never need to clean up these overlays since
+ * they are created in the temporary directory and deleted when the
+ * handle is closed.
+ */
+static int
+create_overlay (guestfs_h *g, struct drive *drv)
+{
+ char *overlay;
+
+ assert (g->backend_ops != NULL);
+
+ if (g->backend_ops->create_cow_overlay == NULL) {
+ error (g, _("this backend does not support adding read-only drives"));
+ return -1;
+ }
+
+ debug (g, "creating COW overlay to protect original drive content");
+ overlay = g->backend_ops->create_cow_overlay (g, g->backend_data, drv);
+ if (overlay == NULL)
+ return -1;
+
+ if (drv->overlay)
+ free (drv->overlay);
+ drv->overlay = overlay;
+
+ return 0;
+}
+
/* Create and free the 'drive' struct. */
static struct drive *
create_drive_file (guestfs_h *g, const char *path,
@@ -92,15 +122,20 @@ create_drive_file (guestfs_h *g, const char *path,
drv->src.protocol = drive_protocol_file;
drv->src.u.path = safe_strdup (g, path);
+ drv->src.format = format ? safe_strdup (g, format) : NULL;
drv->readonly = readonly;
- drv->format = format ? safe_strdup (g, format) : NULL;
drv->iface = iface ? safe_strdup (g, iface) : NULL;
drv->name = name ? safe_strdup (g, name) : NULL;
drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL;
drv->cachemode = cachemode ? safe_strdup (g, cachemode) : NULL;
- drv->priv = drv->free_priv = NULL;
+ if (readonly) {
+ if (create_overlay (g, drv) == -1) {
+ free_drive_struct (drv);
+ return NULL;
+ }
+ }
return drv;
}
@@ -123,15 +158,20 @@ create_drive_non_file (guestfs_h *g,
drv->src.u.exportname = safe_strdup (g, exportname);
drv->src.username = username ? safe_strdup (g, username) : NULL;
drv->src.secret = secret ? safe_strdup (g, secret) : NULL;
+ drv->src.format = format ? safe_strdup (g, format) : NULL;
drv->readonly = readonly;
- drv->format = format ? safe_strdup (g, format) : NULL;
drv->iface = iface ? safe_strdup (g, iface) : NULL;
drv->name = name ? safe_strdup (g, name) : NULL;
drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL;
drv->cachemode = cachemode ? safe_strdup (g, cachemode) : NULL;
- drv->priv = drv->free_priv = NULL;
+ if (readonly) {
+ if (create_overlay (g, drv) == -1) {
+ free_drive_struct (drv);
+ return NULL;
+ }
+ }
return drv;
}
@@ -523,35 +563,49 @@ free_drive_servers (struct drive_server *servers, size_t
nr_servers)
static void
free_drive_struct (struct drive *drv)
{
- guestfs___free_drive_source (&drv->src);
- free (drv->format);
+ free_drive_source (&drv->src);
+ free (drv->overlay);
free (drv->iface);
free (drv->name);
free (drv->disk_label);
free (drv->cachemode);
- if (drv->priv && drv->free_priv)
- drv->free_priv (drv->priv);
-
free (drv);
}
+static const char *
+protocol_to_string (enum drive_protocol protocol)
+{
+ switch (protocol) {
+ case drive_protocol_file: return "file";
+ case drive_protocol_ftp: return "ftp";
+ case drive_protocol_ftps: return "ftps";
+ case drive_protocol_gluster: return "gluster";
+ case drive_protocol_http: return "http";
+ case drive_protocol_https: return "https";
+ case drive_protocol_iscsi: return "iscsi";
+ case drive_protocol_nbd: return "nbd";
+ case drive_protocol_rbd: return "rbd";
+ case drive_protocol_sheepdog: return "sheepdog";
+ case drive_protocol_ssh: return "ssh";
+ case drive_protocol_tftp: return "tftp";
+ }
+ abort ();
+}
+
/* Convert a struct drive to a string for debugging. The caller
* must free this string.
*/
static char *
drive_to_string (guestfs_h *g, const struct drive *drv)
{
- CLEANUP_FREE char *p = NULL;
-
- p = guestfs___drive_source_qemu_param (g, &drv->src);
-
return safe_asprintf
- (g, "%s%s%s%s%s%s%s%s%s%s%s%s",
- p,
+ (g, "%s%s%s%s protocol=%s%s%s%s%s%s%s%s%s",
+ drv->src.u.path,
drv->readonly ? " readonly" : "",
- drv->format ? " format=" : "",
- drv->format ? : "",
+ drv->src.format ? " format=" : "",
+ drv->src.format ? : "",
+ protocol_to_string (drv->src.protocol),
drv->iface ? " iface=" : "",
drv->iface ? : "",
drv->name ? " name=" : "",
@@ -1176,199 +1230,11 @@ guestfs__debug_drives (guestfs_h *g)
return ret.argv; /* caller frees */
}
-/* The drive_source struct is also used in the backends, so we
- * also have these utility functions.
- */
-void
-guestfs___copy_drive_source (guestfs_h *g,
- const struct drive_source *src,
- struct drive_source *dest)
-{
- size_t i;
-
- dest->protocol = src->protocol;
- dest->u.path = safe_strdup (g, src->u.path);
- dest->nr_servers = src->nr_servers;
- dest->servers = safe_calloc (g, src->nr_servers,
- sizeof (struct drive_server));
- for (i = 0; i < src->nr_servers; ++i) {
- dest->servers[i].transport = src->servers[i].transport;
- if (src->servers[i].u.hostname)
- dest->servers[i].u.hostname = safe_strdup (g, src->servers[i].u.hostname);
- dest->servers[i].port = src->servers[i].port;
- }
-}
-
-static char *
-make_uri (guestfs_h *g, const char *scheme, const char *user,
- struct drive_server *server, const char *path)
-{
- xmlURI uri = { .scheme = (char *) scheme,
- .path = (char *) path,
- .user = (char *) user };
- CLEANUP_FREE char *query = NULL;
-
- switch (server->transport) {
- case drive_transport_none:
- case drive_transport_tcp:
- uri.server = server->u.hostname;
- uri.port = server->port;
- break;
- case drive_transport_unix:
- query = safe_asprintf (g, "socket=%s", server->u.socket);
- uri.query_raw = query;
- break;
- }
-
- return (char *) xmlSaveUri (&uri);
-}
-
-char *
-guestfs___drive_source_qemu_param (guestfs_h *g, const struct drive_source *src)
-{
- /* Note that the qemu parameter is the bit after "file=". It is not
- * escaped here, but would usually be escaped if passed to qemu as
- * part of a full -drive parameter (but not for qemu-img).
- */
- switch (src->protocol) {
- case drive_protocol_file:
- /* We might need to rewrite the path if it contains a ':' character. */
- if (src->u.path[0] == '/' || strchr (src->u.path, ':') ==
NULL)
- return safe_strdup (g, src->u.path);
- else
- return safe_asprintf (g, "./%s", src->u.path);
-
- case drive_protocol_ftp:
- return make_uri (g, "ftp", src->username,
- &src->servers[0], src->u.exportname);
-
- case drive_protocol_ftps:
- return make_uri (g, "ftps", src->username,
- &src->servers[0], src->u.exportname);
-
- case drive_protocol_gluster:
- switch (src->servers[0].transport) {
- case drive_transport_none:
- return make_uri (g, "gluster", NULL, &src->servers[0],
src->u.exportname);
- case drive_transport_tcp:
- return make_uri (g, "gluster+tcp",
- NULL, &src->servers[0], src->u.exportname);
- case drive_transport_unix:
- return make_uri (g, "gluster+unix", NULL, &src->servers[0],
NULL);
- }
-
- case drive_protocol_http:
- return make_uri (g, "http", src->username,
- &src->servers[0], src->u.exportname);
-
- case drive_protocol_https:
- return make_uri (g, "https", src->username,
- &src->servers[0], src->u.exportname);
-
- case drive_protocol_iscsi:
- return make_uri (g, "iscsi", NULL, &src->servers[0],
src->u.exportname);
-
- case drive_protocol_nbd: {
- CLEANUP_FREE char *p = NULL;
- char *ret;
-
- switch (src->servers[0].transport) {
- case drive_transport_none:
- case drive_transport_tcp:
- p = safe_asprintf (g, "nbd:%s:%d",
- src->servers[0].u.hostname, src->servers[0].port);
- break;
- case drive_transport_unix:
- p = safe_asprintf (g, "nbd:unix:%s", src->servers[0].u.socket);
- break;
- }
- assert (p);
-
- if (STREQ (src->u.exportname, ""))
- ret = safe_strdup (g, p);
- else
- /* Skip the mandatory leading '/' character. */
- ret = safe_asprintf (g, "%s:exportname=%s", p,
&src->u.exportname[1]);
-
- return ret;
- }
-
- case drive_protocol_rbd: {
- /* build the list of all the mon hosts */
- CLEANUP_FREE char *mon_host = NULL, *username = NULL, *secret = NULL;
- const char *auth;
- size_t n = 0;
- size_t i, j;
-
- for (i = 0; i < src->nr_servers; i++) {
- n += strlen (src->servers[i].u.hostname);
- n += 8; /* for slashes, colons, & port numbers */
- }
- n++; /* for \0 */
- mon_host = safe_malloc (g, n);
- n = 0;
- for (i = 0; i < src->nr_servers; i++) {
- CLEANUP_FREE char *port = NULL;
-
- for (j = 0; j < strlen (src->servers[i].u.hostname); j++)
- mon_host[n++] = src->servers[i].u.hostname[j];
- mon_host[n++] = '\\';
- mon_host[n++] = ':';
- port = safe_asprintf (g, "%d", src->servers[i].port);
- for (j = 0; j < strlen (port); j++)
- mon_host[n++] = port[j];
-
- /* join each host with \; */
- if (i != src->nr_servers - 1) {
- mon_host[n++] = '\\';
- mon_host[n++] = ';';
- }
- }
- mon_host[n] = '\0';
-
- if (src->username)
- username = safe_asprintf (g, ":id=%s", src->username);
- if (src->secret)
- secret = safe_asprintf (g, ":key=%s", src->secret);
- if (username || secret)
- auth = ":auth_supported=cephx\\;none";
- else
- auth = ":auth_supported=none";
-
- /* Skip the mandatory leading '/' character on exportname. */
- return safe_asprintf (g, "rbd:%s:mon_host=%s%s%s%s",
- &src->u.exportname[1],
- mon_host,
- username ? username : "",
- auth,
- secret ? secret : "");
- }
-
- case drive_protocol_sheepdog:
- /* Skip the mandatory leading '/' character on exportname. */
- if (src->nr_servers == 0)
- return safe_asprintf (g, "sheepdog:%s", &src->u.exportname[1]);
- else /* XXX How to pass multiple hosts? */
- return safe_asprintf (g, "sheepdog:%s:%d:%s",
- src->servers[0].u.hostname, src->servers[0].port,
- &src->u.exportname[1]);
-
- case drive_protocol_ssh:
- return make_uri (g, "ssh", src->username,
- &src->servers[0], src->u.exportname);
-
- case drive_protocol_tftp:
- return make_uri (g, "tftp", src->username,
- &src->servers[0], src->u.exportname);
- }
-
- abort ();
-}
-
-void
-guestfs___free_drive_source (struct drive_source *src)
+static void
+free_drive_source (struct drive_source *src)
{
if (src) {
+ free (src->format);
free (src->u.path);
free (src->username);
free (src->secret);
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index a1f3f02..61f41a1 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -209,6 +209,9 @@ struct drive_server {
struct drive_source {
enum drive_protocol protocol;
+ /* Format (eg. raw, qcow2). NULL = autodetect. */
+ char *format;
+
/* This field is always non-NULL. It may be an empty string. */
union {
char *path; /* path to file (file) */
@@ -228,19 +231,27 @@ struct drive_source {
char *secret;
};
+/* There is one 'struct drive' per drive, including hot-plugged drives. */
struct drive {
+ /* Original source of the drive, eg. file:..., http:... */
struct drive_source src;
+ /* If the drive is readonly, then an overlay [a local file] is
+ * created before launch to protect the original drive content, and
+ * the filename is stored here. Backends should open this file if
+ * it is non-NULL, else consult the original source above.
+ *
+ * Note that the overlay is in a backend-specific format, probably
+ * different from the source format. eg. qcow2, UML COW.
+ */
+ char *overlay;
+
+ /* Various per-drive flags. */
bool readonly;
- char *format;
char *iface;
char *name;
char *disk_label;
char *cachemode;
-
- /* Data used by the backend. */
- void *priv;
- void (*free_priv) (void *);
};
/* Extra hv parameters (from guestfs_config). */
@@ -262,6 +273,12 @@ struct backend_ops {
*/
size_t data_size;
+ /* Create a COW overlay on top of a drive. This must be a local
+ * file, created in the temporary directory. This is called when
+ * the drive is added to the handle.
+ */
+ char *(*create_cow_overlay) (guestfs_h *g, void *data, struct drive *drv);
+
/* Launch and shut down. */
int (*launch) (guestfs_h *g, void *data, const char *arg);
int (*shutdown) (guestfs_h *g, void *data, int check_for_errors);
@@ -695,9 +712,6 @@ extern size_t guestfs___checkpoint_drives (guestfs_h *g);
extern void guestfs___rollback_drives (guestfs_h *g, size_t);
extern void guestfs___add_dummy_appliance_drive (guestfs_h *g);
extern void guestfs___free_drives (guestfs_h *g);
-extern void guestfs___copy_drive_source (guestfs_h *g, const struct drive_source *src,
struct drive_source *dest);
-extern char *guestfs___drive_source_qemu_param (guestfs_h *g, const struct drive_source
*src);
-extern void guestfs___free_drive_source (struct drive_source *src);
/* appliance.c */
extern int guestfs___build_appliance (guestfs_h *g, char **kernel, char **dtb, char
**initrd, char **appliance);
@@ -813,4 +827,7 @@ extern void guestfs___cmd_close (struct command *);
#endif
extern void guestfs___cleanup_cmd_close (struct command **);
+/* launch-direct.c */
+extern char *guestfs___drive_source_qemu_param (guestfs_h *g, const struct drive_source
*src);
+
#endif /* GUESTFS_INTERNAL_H_ */
diff --git a/src/launch-direct.c b/src/launch-direct.c
index 532740a..9ad5cf2 100644
--- a/src/launch-direct.c
+++ b/src/launch-direct.c
@@ -33,9 +33,12 @@
#include <sys/socket.h>
#include <sys/un.h>
#include <grp.h>
+#include <assert.h>
#include <pcre.h>
+#include <libxml/uri.h>
+
#include "cloexec.h"
#include "ignore-value.h"
@@ -101,6 +104,53 @@ static int qemu_supports_device (guestfs_h *g, struct
backend_direct_data *, con
static int qemu_supports_virtio_scsi (guestfs_h *g, struct backend_direct_data *);
static char *qemu_escape_param (guestfs_h *g, const char *param);
+static char *
+create_cow_overlay_direct (guestfs_h *g, void *datav, struct drive *drv)
+{
+ char *overlay = NULL;
+ CLEANUP_FREE char *backing_drive = NULL;
+ CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
+ int r;
+
+ backing_drive = guestfs___drive_source_qemu_param (g, &drv->src);
+ if (!backing_drive)
+ goto error;
+
+ if (guestfs___lazy_make_tmpdir (g) == -1)
+ goto error;
+
+ overlay = safe_asprintf (g, "%s/overlay%d", g->tmpdir, ++g->unique);
+
+ guestfs___cmd_add_arg (cmd, "qemu-img");
+ guestfs___cmd_add_arg (cmd, "create");
+ guestfs___cmd_add_arg (cmd, "-f");
+ guestfs___cmd_add_arg (cmd, "qcow2");
+ guestfs___cmd_add_arg (cmd, "-b");
+ guestfs___cmd_add_arg (cmd, backing_drive);
+ if (drv->src.format) {
+ guestfs___cmd_add_arg (cmd, "-o");
+ guestfs___cmd_add_arg_format (cmd, "backing_fmt=%s", drv->src.format);
+ }
+ guestfs___cmd_add_arg (cmd, overlay);
+ r = guestfs___cmd_run (cmd);
+ if (r == -1)
+ goto error;
+ if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
+ guestfs___external_command_failed (g, r, "qemu-img create",
backing_drive);
+ goto error;
+ }
+
+ /* Caller sets g->overlay in the handle to this, and then manages
+ * the memory.
+ */
+ return overlay;
+
+ error:
+ free (overlay);
+
+ return NULL;
+}
+
#ifdef QEMU_OPTIONS
/* Like 'add_cmdline' but allowing a shell-quoted string of zero or
* more options. XXX The unquoting is not very clever.
@@ -470,23 +520,35 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
ITER_DRIVES (g, i, drv) {
CLEANUP_FREE char *file = NULL, *escaped_file = NULL, *param = NULL;
- /* Make the file= parameter. */
- file = guestfs___drive_source_qemu_param (g, &drv->src);
- escaped_file = qemu_escape_param (g, file);
+ if (!drv->overlay) {
+ /* Make the file= parameter. */
+ file = guestfs___drive_source_qemu_param (g, &drv->src);
+ escaped_file = qemu_escape_param (g, file);
- /* Make the first part of the -drive parameter, everything up to
- * the if=... at the end.
- */
- param = safe_asprintf
- (g, "file=%s%s,cache=%s%s%s%s%s,id=hd%zu",
- escaped_file,
- drv->readonly ? ",snapshot=on" : "",
- drv->cachemode ? drv->cachemode : "writeback",
- drv->format ? ",format=" : "",
- drv->format ? drv->format : "",
- drv->disk_label ? ",serial=" : "",
- drv->disk_label ? drv->disk_label : "",
- i);
+ /* Make the first part of the -drive parameter, everything up to
+ * the if=... at the end.
+ */
+ param = safe_asprintf
+ (g, "file=%s%s,cache=%s%s%s%s%s,id=hd%zu",
+ escaped_file,
+ drv->readonly ? ",snapshot=on" : "",
+ drv->cachemode ? drv->cachemode : "writeback",
+ drv->src.format ? ",format=" : "",
+ drv->src.format ? drv->src.format : "",
+ drv->disk_label ? ",serial=" : "",
+ drv->disk_label ? drv->disk_label : "",
+ i);
+ }
+ else {
+ /* Writable qcow2 overlay on top of read-only drive. */
+ escaped_file = qemu_escape_param (g, drv->overlay);
+ param = safe_asprintf
+ (g, "file=%s,cache=unsafe,format=qcow2%s%s,id=hd%zu",
+ escaped_file,
+ drv->disk_label ? ",serial=" : "",
+ drv->disk_label ? drv->disk_label : "",
+ i);
+ }
/* If there's an explicit 'iface', use it. Otherwise default to
* virtio-scsi if available. Otherwise default to virtio-blk.
@@ -1130,6 +1192,186 @@ qemu_escape_param (guestfs_h *g, const char *param)
return ret;
}
+static char *
+make_uri (guestfs_h *g, const char *scheme, const char *user,
+ struct drive_server *server, const char *path)
+{
+ xmlURI uri = { .scheme = (char *) scheme,
+ .path = (char *) path,
+ .user = (char *) user };
+ CLEANUP_FREE char *query = NULL;
+
+ switch (server->transport) {
+ case drive_transport_none:
+ case drive_transport_tcp:
+ uri.server = server->u.hostname;
+ uri.port = server->port;
+ break;
+ case drive_transport_unix:
+ query = safe_asprintf (g, "socket=%s", server->u.socket);
+ uri.query_raw = query;
+ break;
+ }
+
+ return (char *) xmlSaveUri (&uri);
+}
+
+/* Useful function to format a drive + protocol for qemu. Also shared
+ * with launch-libvirt.c.
+ *
+ * Note that the qemu parameter is the bit after "file=". It is not
+ * escaped here, but would usually be escaped if passed to qemu as
+ * part of a full -drive parameter (but not for qemu-img).
+ */
+char *
+guestfs___drive_source_qemu_param (guestfs_h *g, const struct drive_source *src)
+{
+ char *path;
+
+ switch (src->protocol) {
+ case drive_protocol_file:
+ /* We have to convert the path to an absolute path, since
+ * otherwise qemu will look for the backing file relative to the
+ * overlay (which is located in g->tmpdir).
+ *
+ * As a side-effect this deals with paths that contain ':' since
+ * qemu will not process the ':' if the path begins with '/'.
+ */
+ path = realpath (src->u.path, NULL);
+ if (path == NULL) {
+ perrorf (g, _("realpath: could not convert '%s' to absolute
path"),
+ src->u.path);
+ return NULL;
+ }
+ return path;
+
+ case drive_protocol_ftp:
+ return make_uri (g, "ftp", src->username,
+ &src->servers[0], src->u.exportname);
+
+ case drive_protocol_ftps:
+ return make_uri (g, "ftps", src->username,
+ &src->servers[0], src->u.exportname);
+
+ case drive_protocol_gluster:
+ switch (src->servers[0].transport) {
+ case drive_transport_none:
+ return make_uri (g, "gluster", NULL, &src->servers[0],
src->u.exportname);
+ case drive_transport_tcp:
+ return make_uri (g, "gluster+tcp",
+ NULL, &src->servers[0], src->u.exportname);
+ case drive_transport_unix:
+ return make_uri (g, "gluster+unix", NULL, &src->servers[0],
NULL);
+ }
+
+ case drive_protocol_http:
+ return make_uri (g, "http", src->username,
+ &src->servers[0], src->u.exportname);
+
+ case drive_protocol_https:
+ return make_uri (g, "https", src->username,
+ &src->servers[0], src->u.exportname);
+
+ case drive_protocol_iscsi:
+ return make_uri (g, "iscsi", NULL, &src->servers[0],
src->u.exportname);
+
+ case drive_protocol_nbd: {
+ CLEANUP_FREE char *p = NULL;
+ char *ret;
+
+ switch (src->servers[0].transport) {
+ case drive_transport_none:
+ case drive_transport_tcp:
+ p = safe_asprintf (g, "nbd:%s:%d",
+ src->servers[0].u.hostname, src->servers[0].port);
+ break;
+ case drive_transport_unix:
+ p = safe_asprintf (g, "nbd:unix:%s", src->servers[0].u.socket);
+ break;
+ }
+ assert (p);
+
+ if (STREQ (src->u.exportname, ""))
+ ret = safe_strdup (g, p);
+ else
+ /* Skip the mandatory leading '/' character. */
+ ret = safe_asprintf (g, "%s:exportname=%s", p,
&src->u.exportname[1]);
+
+ return ret;
+ }
+
+ case drive_protocol_rbd: {
+ /* build the list of all the mon hosts */
+ CLEANUP_FREE char *mon_host = NULL, *username = NULL, *secret = NULL;
+ const char *auth;
+ size_t n = 0;
+ size_t i, j;
+
+ for (i = 0; i < src->nr_servers; i++) {
+ n += strlen (src->servers[i].u.hostname);
+ n += 8; /* for slashes, colons, & port numbers */
+ }
+ n++; /* for \0 */
+ mon_host = safe_malloc (g, n);
+ n = 0;
+ for (i = 0; i < src->nr_servers; i++) {
+ CLEANUP_FREE char *port = NULL;
+
+ for (j = 0; j < strlen (src->servers[i].u.hostname); j++)
+ mon_host[n++] = src->servers[i].u.hostname[j];
+ mon_host[n++] = '\\';
+ mon_host[n++] = ':';
+ port = safe_asprintf (g, "%d", src->servers[i].port);
+ for (j = 0; j < strlen (port); j++)
+ mon_host[n++] = port[j];
+
+ /* join each host with \; */
+ if (i != src->nr_servers - 1) {
+ mon_host[n++] = '\\';
+ mon_host[n++] = ';';
+ }
+ }
+ mon_host[n] = '\0';
+
+ if (src->username)
+ username = safe_asprintf (g, ":id=%s", src->username);
+ if (src->secret)
+ secret = safe_asprintf (g, ":key=%s", src->secret);
+ if (username || secret)
+ auth = ":auth_supported=cephx\\;none";
+ else
+ auth = ":auth_supported=none";
+
+ /* Skip the mandatory leading '/' character on exportname. */
+ return safe_asprintf (g, "rbd:%s:mon_host=%s%s%s%s",
+ &src->u.exportname[1],
+ mon_host,
+ username ? username : "",
+ auth,
+ secret ? secret : "");
+ }
+
+ case drive_protocol_sheepdog:
+ /* Skip the mandatory leading '/' character on exportname. */
+ if (src->nr_servers == 0)
+ return safe_asprintf (g, "sheepdog:%s", &src->u.exportname[1]);
+ else /* XXX How to pass multiple hosts? */
+ return safe_asprintf (g, "sheepdog:%s:%d:%s",
+ src->servers[0].u.hostname, src->servers[0].port,
+ &src->u.exportname[1]);
+
+ case drive_protocol_ssh:
+ return make_uri (g, "ssh", src->username,
+ &src->servers[0], src->u.exportname);
+
+ case drive_protocol_tftp:
+ return make_uri (g, "tftp", src->username,
+ &src->servers[0], src->u.exportname);
+ }
+
+ abort ();
+}
+
static int
shutdown_direct (guestfs_h *g, void *datav, int check_for_errors)
{
@@ -1196,6 +1438,7 @@ max_disks_direct (guestfs_h *g, void *datav)
static struct backend_ops backend_direct_ops = {
.data_size = sizeof (struct backend_direct_data),
+ .create_cow_overlay = create_cow_overlay_direct,
.launch = launch_direct,
.shutdown = shutdown_direct,
.get_pid = get_pid_direct,
diff --git a/src/launch-libvirt.c b/src/launch-libvirt.c
index d900b6d..08e2a5d 100644
--- a/src/launch-libvirt.c
+++ b/src/launch-libvirt.c
@@ -108,18 +108,6 @@ struct backend_libvirt_data {
char name[DOMAIN_NAME_LEN]; /* random name */
};
-/* Pointed to by 'struct drive *' -> priv field. */
-struct drive_libvirt {
- /* The drive that we actually add. If using an overlay, then this
- * might be different from drive->src. Call it 'real_src' so we
- * don't confuse accesses to this with accesses to 'drive->src'.
- */
- struct drive_source real_src;
-
- /* The format of the drive we add. */
- char *format;
-};
-
/* Parameters passed to construct_libvirt_xml and subfunctions. We
* keep them all in a structure for convenience!
*/
@@ -146,9 +134,6 @@ static void libvirt_error (guestfs_h *g, const char *fs, ...)
__attribute__((for
static int is_custom_hv (guestfs_h *g);
static int is_blk (const char *path);
static void ignore_errors (void *ignore, virErrorPtr ignore2);
-static char *make_qcow2_overlay (guestfs_h *g, const char *backing_device, const char
*format, const char *selinux_imagelabel);
-static int make_drive_priv (guestfs_h *g, struct drive *drv, const char
*selinux_imagelabel);
-static void drive_free_priv (void *);
static void set_socket_create_context (guestfs_h *g);
static void clear_socket_create_context (guestfs_h *g);
@@ -156,6 +141,80 @@ static void clear_socket_create_context (guestfs_h *g);
static void selinux_warning (guestfs_h *g, const char *func, const char *selinux_op,
const char *data);
#endif
+static char *
+make_qcow2_overlay (guestfs_h *g, const char *backing_drive,
+ const char *format)
+{
+ CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
+ char *overlay = NULL;
+ int r;
+
+ if (guestfs___lazy_make_tmpdir (g) == -1)
+ return NULL;
+
+ overlay = safe_asprintf (g, "%s/overlay%d", g->tmpdir, ++g->unique);
+
+ guestfs___cmd_add_arg (cmd, "qemu-img");
+ guestfs___cmd_add_arg (cmd, "create");
+ guestfs___cmd_add_arg (cmd, "-f");
+ guestfs___cmd_add_arg (cmd, "qcow2");
+ guestfs___cmd_add_arg (cmd, "-b");
+ guestfs___cmd_add_arg (cmd, backing_drive);
+ if (format) {
+ guestfs___cmd_add_arg (cmd, "-o");
+ guestfs___cmd_add_arg_format (cmd, "backing_fmt=%s", format);
+ }
+ guestfs___cmd_add_arg (cmd, overlay);
+ r = guestfs___cmd_run (cmd);
+ if (r == -1) {
+ free (overlay);
+ return NULL;
+ }
+ if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
+ guestfs___external_command_failed (g, r, "qemu-img create",
backing_drive);
+ free (overlay);
+ return NULL;
+ }
+
+ return overlay;
+}
+
+static char *
+create_cow_overlay_libvirt (guestfs_h *g, void *datav, struct drive *drv)
+{
+ struct backend_libvirt_data *data = datav;
+ CLEANUP_FREE char *backing_drive = NULL;
+ char *overlay = NULL;
+
+ backing_drive = guestfs___drive_source_qemu_param (g, &drv->src);
+ if (!backing_drive)
+ goto error;
+
+ overlay = make_qcow2_overlay (g, backing_drive, drv->src.format);
+ if (!overlay)
+ goto error;
+
+#if HAVE_LIBSELINUX
+ if (data->selinux_imagelabel) {
+ debug (g, "setting SELinux label on %s to %s",
+ overlay, data->selinux_imagelabel);
+ if (setfilecon (overlay,
+ (security_context_t) data->selinux_imagelabel) == -1)
+ selinux_warning (g, __func__, "setfilecon", overlay);
+ }
+#endif
+
+ /* Caller sets g->overlay in the handle to this, and then manages
+ * the memory.
+ */
+ return overlay;
+
+ error:
+ free (overlay);
+
+ return NULL;
+}
+
static int
launch_libvirt (guestfs_h *g, void *datav, const char *libvirt_uri)
{
@@ -178,8 +237,6 @@ launch_libvirt (guestfs_h *g, void *datav, const char *libvirt_uri)
int r;
uint32_t size;
CLEANUP_FREE void *buf = NULL;
- struct drive *drv;
- size_t i;
params.current_proc_is_root = geteuid () == 0;
@@ -274,19 +331,11 @@ launch_libvirt (guestfs_h *g, void *datav, const char *libvirt_uri)
/* Note that appliance can be NULL if using the old-style appliance. */
if (appliance) {
- params.appliance_overlay = make_qcow2_overlay (g, appliance, "raw", NULL);
+ params.appliance_overlay = make_qcow2_overlay (g, appliance, "raw");
if (!params.appliance_overlay)
goto cleanup;
}
- /* Set up the drv->priv part of the struct. A side-effect of this
- * may be that we create qcow2 overlays for drives.
- */
- ITER_DRIVES (g, i, drv) {
- if (make_drive_priv (g, drv, data->selinux_imagelabel) == -1)
- goto cleanup;
- }
-
TRACE0 (launch_build_libvirt_qcow2_overlay_end);
/* Using virtio-serial, we need to create a local Unix domain socket
@@ -746,6 +795,9 @@ static int construct_libvirt_xml_lifecycle (guestfs_h *g, const struct
libvirt_x
static int construct_libvirt_xml_devices (guestfs_h *g, const struct libvirt_xml_params
*params, xmlTextWriterPtr xo);
static int construct_libvirt_xml_qemu_cmdline (guestfs_h *g, const struct
libvirt_xml_params *params, xmlTextWriterPtr xo);
static int construct_libvirt_xml_disk (guestfs_h *g, const struct backend_libvirt_data
*data, xmlTextWriterPtr xo, struct drive *drv, size_t drv_index);
+static int construct_libvirt_xml_disk_target (guestfs_h *g, xmlTextWriterPtr xo, size_t
drv_index);
+static int construct_libvirt_xml_disk_driver_qemu (guestfs_h *g, xmlTextWriterPtr xo,
const char *format, const char *cachemode);
+static int construct_libvirt_xml_disk_address (guestfs_h *g, xmlTextWriterPtr xo, size_t
drv_index);
static int construct_libvirt_xml_disk_source_hosts (guestfs_h *g, xmlTextWriterPtr xo,
const struct drive_source *src);
static int construct_libvirt_xml_disk_source_seclabel (guestfs_h *g, const struct
backend_libvirt_data *data, xmlTextWriterPtr xo);
static int construct_libvirt_xml_appliance (guestfs_h *g, const struct libvirt_xml_params
*params, xmlTextWriterPtr xo);
@@ -756,7 +808,8 @@ static int construct_libvirt_xml_appliance (guestfs_h *g, const struct
libvirt_x
*/
#define XMLERROR_RET(code,e,ret) do { \
if ((e) == (code)) { \
- perrorf (g, _("error constructing libvirt XML at \"%s\""),
\
+ perrorf (g, _("%s:%d: error constructing libvirt XML at
\"%s\""), \
+ __FILE__, __LINE__, \
#e); \
return (ret); \
} \
@@ -1111,12 +1164,10 @@ construct_libvirt_xml_disk (guestfs_h *g,
xmlTextWriterPtr xo,
struct drive *drv, size_t drv_index)
{
- char drive_name[64] = "sd";
const char *protocol_str;
- char scsi_target[64];
- struct drive_libvirt *drv_priv = (struct drive_libvirt *) drv->priv;
- CLEANUP_FREE char *format = NULL;
+ CLEANUP_FREE char *path = NULL;
int is_host_device;
+ CLEANUP_FREE char *format = NULL;
/* XXX We probably could support this if we thought about it some more. */
if (drv->iface) {
@@ -1124,175 +1175,185 @@ construct_libvirt_xml_disk (guestfs_h *g,
return -1;
}
- guestfs___drive_name (drv_index, &drive_name[2]);
- snprintf (scsi_target, sizeof scsi_target, "%zu", drv_index);
-
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "disk"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "device",
BAD_CAST "disk"));
- switch (drv_priv->real_src.protocol) {
- case drive_protocol_file:
- /* Change the libvirt XML according to whether the host path is
- * a device or a file. For devices, use:
- * <disk type=block device=disk>
- * <source dev=[path]>
- * For files, use:
- * <disk type=file device=disk>
- * <source file=[path]>
+ if (drv->overlay) {
+ /* Overlay to protect read-only backing disk. The format of the
+ * overlay is always qcow2.
*/
- is_host_device = is_blk (drv_priv->real_src.u.path);
-
- if (!is_host_device) {
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
- BAD_CAST "file"));
-
- XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "file",
- BAD_CAST drv_priv->real_src.u.path));
- if (construct_libvirt_xml_disk_source_seclabel (g, data, xo) == -1)
- return -1;
- XMLERROR (-1, xmlTextWriterEndElement (xo));
- }
- else {
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
- BAD_CAST "block"));
-
- XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
- BAD_CAST drv_priv->real_src.u.path));
- if (construct_libvirt_xml_disk_source_seclabel (g, data, xo) == -1)
- return -1;
- XMLERROR (-1, xmlTextWriterEndElement (xo));
- }
- break;
-
- /* For network protocols:
- * <disk type=network device=disk>
- * <source protocol=[protocol] [name=exportname]>
- * and then zero or more of:
- * <host name='example.com' port='10809'/>
- * or:
- * <host transport='unix' socket='/path/to/socket'/>
- */
- case drive_protocol_gluster:
- protocol_str = "gluster"; goto network_protocols;
- case drive_protocol_iscsi:
- protocol_str = "iscsi"; goto network_protocols;
- case drive_protocol_nbd:
- protocol_str = "nbd"; goto network_protocols;
- case drive_protocol_rbd:
- protocol_str = "rbd"; goto network_protocols;
- case drive_protocol_sheepdog:
- protocol_str = "sheepdog"; goto network_protocols;
- case drive_protocol_ssh:
- protocol_str = "ssh";
- /*FALLTHROUGH*/
- network_protocols:
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
- BAD_CAST "network"));
+ BAD_CAST "file"));
+
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "protocol",
- BAD_CAST protocol_str));
- if (STRNEQ (drv_priv->real_src.u.exportname, ""))
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "name",
- BAD_CAST
drv_priv->real_src.u.exportname));
- if (construct_libvirt_xml_disk_source_hosts (g, xo,
- &drv_priv->real_src) == -1)
- return -1;
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "file",
+ BAD_CAST drv->overlay));
if (construct_libvirt_xml_disk_source_seclabel (g, data, xo) == -1)
return -1;
XMLERROR (-1, xmlTextWriterEndElement (xo));
- if (drv_priv->real_src.username != NULL) {
- XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "auth"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "username",
- BAD_CAST drv_priv->real_src.username));
- /* TODO: write the drive secret, after first storing it separately
- * in libvirt
+
+ if (construct_libvirt_xml_disk_target (g, xo, drv_index) == -1)
+ return -1;
+
+ if (construct_libvirt_xml_disk_driver_qemu (g, xo, "qcow2",
"unsafe") == -1)
+ return -1;
+ }
+ else {
+ /* Not an overlay, a writable disk. */
+
+ switch (drv->src.protocol) {
+ case drive_protocol_file:
+ /* Change the libvirt XML according to whether the host path is
+ * a device or a file. For devices, use:
+ * <disk type=block device=disk>
+ * <source dev=[path]>
+ * For files, use:
+ * <disk type=file device=disk>
+ * <source file=[path]>
+ */
+ is_host_device = is_blk (drv->src.u.path);
+
+ if (!is_host_device) {
+ path = realpath (drv->src.u.path, NULL);
+ if (path == NULL) {
+ perrorf (g, _("realpath: could not convert '%s' to absolute
path"),
+ drv->src.u.path);
+ return -1;
+ }
+
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
+ BAD_CAST "file"));
+
+ XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "file",
+ BAD_CAST path));
+ if (construct_libvirt_xml_disk_source_seclabel (g, data, xo) == -1)
+ return -1;
+ XMLERROR (-1, xmlTextWriterEndElement (xo));
+ }
+ else {
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
+ BAD_CAST "block"));
+
+ XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
+ BAD_CAST drv->src.u.path));
+ if (construct_libvirt_xml_disk_source_seclabel (g, data, xo) == -1)
+ return -1;
+ XMLERROR (-1, xmlTextWriterEndElement (xo));
+ }
+ break;
+
+ /* For network protocols:
+ * <disk type=network device=disk>
+ * <source protocol=[protocol] [name=exportname]>
+ * and then zero or more of:
+ * <host name='example.com' port='10809'/>
+ * or:
+ * <host transport='unix' socket='/path/to/socket'/>
*/
+ case drive_protocol_gluster:
+ protocol_str = "gluster"; goto network_protocols;
+ case drive_protocol_iscsi:
+ protocol_str = "iscsi"; goto network_protocols;
+ case drive_protocol_nbd:
+ protocol_str = "nbd"; goto network_protocols;
+ case drive_protocol_rbd:
+ protocol_str = "rbd"; goto network_protocols;
+ case drive_protocol_sheepdog:
+ protocol_str = "sheepdog"; goto network_protocols;
+ case drive_protocol_ssh:
+ protocol_str = "ssh";
+ /*FALLTHROUGH*/
+ network_protocols:
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
+ BAD_CAST "network"));
+ XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "protocol",
+ BAD_CAST protocol_str));
+ if (STRNEQ (drv->src.u.exportname, ""))
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "name",
+ BAD_CAST drv->src.u.exportname));
+ if (construct_libvirt_xml_disk_source_hosts (g, xo,
+ &drv->src) == -1)
+ return -1;
+ if (construct_libvirt_xml_disk_source_seclabel (g, data, xo) == -1)
+ return -1;
XMLERROR (-1, xmlTextWriterEndElement (xo));
- }
- break;
-
- /* libvirt doesn't support the qemu curl driver yet. Give a
- * reasonable error message instead of trying and failing.
- */
- case drive_protocol_ftp:
- case drive_protocol_ftps:
- case drive_protocol_http:
- case drive_protocol_https:
- case drive_protocol_tftp:
- error (g, _("libvirt does not support the qemu curl driver protocols (ftp, http,
etc.); try setting LIBGUESTFS_BACKEND=direct"));
- return -1;
- }
+ if (drv->src.username != NULL) {
+ XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "auth"));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "username",
+ BAD_CAST drv->src.username));
+ /* TODO: write the drive secret, after first storing it separately
+ * in libvirt
+ */
+ XMLERROR (-1, xmlTextWriterEndElement (xo));
+ }
+ break;
- XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "target"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
- BAD_CAST drive_name));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "bus",
- BAD_CAST "scsi"));
- XMLERROR (-1, xmlTextWriterEndElement (xo));
+ /* libvirt doesn't support the qemu curl driver yet. Give a
+ * reasonable error message instead of trying and failing.
+ */
+ case drive_protocol_ftp:
+ case drive_protocol_ftps:
+ case drive_protocol_http:
+ case drive_protocol_https:
+ case drive_protocol_tftp:
+ error (g, _("libvirt does not support the qemu curl driver protocols (ftp,
http, etc.); try setting LIBGUESTFS_BACKEND=direct"));
+ return -1;
+ }
- XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "driver"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "name",
- BAD_CAST "qemu"));
- if (drv_priv->format) {
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
- BAD_CAST drv_priv->format));
- }
- else if (drv_priv->real_src.protocol == drive_protocol_file) {
- /* libvirt has disabled the feature of detecting the disk format,
- * unless the administrator sets allow_disk_format_probing=1 in
- * qemu.conf. There is no way to detect if this option is set, so we
- * have to do format detection here using qemu-img and pass that to
- * libvirt.
- *
- * This is still a security issue, so in most cases it is recommended
- * the users pass the format to libguestfs which will faithfully pass
- * that to libvirt and this function won't be used.
- */
- format = guestfs_disk_format (g, drv_priv->real_src.u.path);
- if (!format)
+ if (construct_libvirt_xml_disk_target (g, xo, drv_index) == -1)
return -1;
- if (STREQ (format, "unknown")) {
- error (g, _("could not auto-detect the format.\n"
+ if (drv->src.format)
+ format = safe_strdup (g, drv->src.format);
+ else if (drv->src.protocol == drive_protocol_file) {
+ /* libvirt has disabled the feature of detecting the disk format,
+ * unless the administrator sets allow_disk_format_probing=1 in
+ * qemu.conf. There is no way to detect if this option is set, so we
+ * have to do format detection here using qemu-img and pass that to
+ * libvirt.
+ *
+ * This is still a security issue, so in most cases it is recommended
+ * the users pass the format to libguestfs which will faithfully pass
+ * that to libvirt and this function won't be used.
+ */
+ format = guestfs_disk_format (g, drv->src.u.path);
+ if (!format)
+ return -1;
+
+ if (STREQ (format, "unknown")) {
+ error (g, _("could not auto-detect the format.\n"
+ "If the format is known, pass the format to libguestfs, eg.
using the\n"
+ "'--format' option, or via the optional 'format'
argument to 'add-drive'."));
+ return -1;
+ }
+ }
+ else {
+ error (g, _("could not auto-detect the format when using a non-file
protocol.\n"
"If the format is known, pass the format to libguestfs, eg. using
the\n"
"'--format' option, or via the optional 'format'
argument to 'add-drive'."));
return -1;
}
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
- BAD_CAST format));
+ if (construct_libvirt_xml_disk_driver_qemu (g, xo, format,
+ drv->cachemode ? :
"writeback")
+ == -1)
+ return -1;
}
- else {
- error (g, _("could not auto-detect the format when using a non-file
protocol.\n"
- "If the format is known, pass the format to libguestfs, eg. using
the\n"
- "'--format' option, or via the optional 'format'
argument to 'add-drive'."));
- return -1;
- }
-
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "cache",
- BAD_CAST (drv->cachemode ?
- drv->cachemode :
- "writeback")));
-
- XMLERROR (-1, xmlTextWriterEndElement (xo));
if (drv->disk_label) {
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "serial"));
@@ -1300,6 +1361,62 @@ construct_libvirt_xml_disk (guestfs_h *g,
XMLERROR (-1, xmlTextWriterEndElement (xo));
}
+ if (construct_libvirt_xml_disk_address (g, xo, drv_index) == -1)
+ return -1;
+
+ XMLERROR (-1, xmlTextWriterEndElement (xo)); /* </disk> */
+
+ return 0;
+}
+
+static int
+construct_libvirt_xml_disk_target (guestfs_h *g, xmlTextWriterPtr xo,
+ size_t drv_index)
+{
+ char drive_name[64] = "sd";
+
+ guestfs___drive_name (drv_index, &drive_name[2]);
+
+ XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "target"));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "dev",
+ BAD_CAST drive_name));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "bus",
+ BAD_CAST "scsi"));
+ XMLERROR (-1, xmlTextWriterEndElement (xo));
+
+ return 0;
+}
+
+static int
+construct_libvirt_xml_disk_driver_qemu (guestfs_h *g, xmlTextWriterPtr xo,
+ const char *format,
+ const char *cachemode)
+{
+ XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "driver"));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "name",
+ BAD_CAST "qemu"));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
+ BAD_CAST format));
+ XMLERROR (-1,
+ xmlTextWriterWriteAttribute (xo, BAD_CAST "cache",
+ BAD_CAST cachemode));
+ XMLERROR (-1, xmlTextWriterEndElement (xo));
+
+ return 0;
+}
+
+static int
+construct_libvirt_xml_disk_address (guestfs_h *g, xmlTextWriterPtr xo,
+ size_t drv_index)
+{
+ char scsi_target[64];
+
+ snprintf (scsi_target, sizeof scsi_target, "%zu", drv_index);
+
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "address"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
@@ -1318,8 +1435,6 @@ construct_libvirt_xml_disk (guestfs_h *g,
BAD_CAST "0"));
XMLERROR (-1, xmlTextWriterEndElement (xo));
- XMLERROR (-1, xmlTextWriterEndElement (xo));
-
return 0;
}
@@ -1398,10 +1513,6 @@ construct_libvirt_xml_appliance (guestfs_h *g,
const struct libvirt_xml_params *params,
xmlTextWriterPtr xo)
{
- char scsi_target[64];
-
- snprintf (scsi_target, sizeof scsi_target, "%zu",
params->appliance_index);
-
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "disk"));
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
@@ -1425,46 +1536,15 @@ construct_libvirt_xml_appliance (guestfs_h *g,
BAD_CAST "scsi"));
XMLERROR (-1, xmlTextWriterEndElement (xo));
- XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "driver"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "name",
- BAD_CAST "qemu"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
- BAD_CAST "qcow2"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "cache",
- BAD_CAST "unsafe"));
- XMLERROR (-1, xmlTextWriterEndElement (xo));
+ if (construct_libvirt_xml_disk_driver_qemu (g, xo, "qcow2",
"unsafe") == -1)
+ return -1;
- XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "address"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
- BAD_CAST "drive"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "controller",
- BAD_CAST "0"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "bus",
- BAD_CAST "0"));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "target",
- BAD_CAST scsi_target));
- XMLERROR (-1,
- xmlTextWriterWriteAttribute (xo, BAD_CAST "unit",
- BAD_CAST "0"));
- XMLERROR (-1, xmlTextWriterEndElement (xo));
+ if (construct_libvirt_xml_disk_address (g, xo, params->appliance_index) == -1)
+ return -1;
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "shareable"));
XMLERROR (-1, xmlTextWriterEndElement (xo));
- /* We'd like to do this, but it's not supported by libvirt.
- * See make_drive_priv for the workaround.
- *
- * XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "transient"));
- * XMLERROR (-1, xmlTextWriterEndElement (xo));
- */
-
XMLERROR (-1, xmlTextWriterEndElement (xo));
return 0;
@@ -1562,143 +1642,6 @@ ignore_errors (void *ignore, virErrorPtr ignore2)
/* empty */
}
-/* Create a temporary qcow2 overlay on top of 'backing_device', which is
- * either an absolute path or a qemu device name.
- */
-static char *
-make_qcow2_overlay (guestfs_h *g, const char *backing_device,
- const char *format, const char *selinux_imagelabel)
-{
- char *tmpfile = NULL;
- CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
- int r;
-
- tmpfile = safe_asprintf (g, "%s/snapshot%d", g->tmpdir, ++g->unique);
-
- guestfs___cmd_add_arg (cmd, "qemu-img");
- guestfs___cmd_add_arg (cmd, "create");
- guestfs___cmd_add_arg (cmd, "-f");
- guestfs___cmd_add_arg (cmd, "qcow2");
- guestfs___cmd_add_arg (cmd, "-b");
- guestfs___cmd_add_arg (cmd, backing_device);
- if (format) {
- guestfs___cmd_add_arg (cmd, "-o");
- guestfs___cmd_add_arg_format (cmd, "backing_fmt=%s", format);
- }
- guestfs___cmd_add_arg (cmd, tmpfile);
- r = guestfs___cmd_run (cmd);
- if (r == -1)
- goto error;
- if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
- guestfs___external_command_failed (g, r, "qemu-img create",
backing_device);
- goto error;
- }
-
-#if HAVE_LIBSELINUX
- if (selinux_imagelabel) {
- debug (g, "setting SELinux label on %s to %s",
- tmpfile, selinux_imagelabel);
- if (setfilecon (tmpfile, (security_context_t) selinux_imagelabel) == -1)
- selinux_warning (g, __func__, "setfilecon", tmpfile);
- }
-#endif
-
- return tmpfile; /* caller frees */
-
- error:
- free (tmpfile);
-
- return NULL;
-}
-
-/* This sets up the drv->priv structure, which contains the drive
- * that we're actually going to add. If asked to make a drive readonly
- * then because libvirt doesn't support <transient/> we have to add
- * a qcow2 overlay here.
- */
-static int
-make_drive_priv (guestfs_h *g, struct drive *drv,
- const char *selinux_imagelabel)
-{
- char *path;
- struct drive_libvirt *drv_priv;
-
- if (drv->priv && drv->free_priv)
- drv->free_priv (drv->priv);
-
- drv->priv = drv_priv = safe_calloc (g, 1, sizeof (struct drive_libvirt));
- drv->free_priv = drive_free_priv;
-
- switch (drv->src.protocol) {
- case drive_protocol_file:
-
- /* Even for non-readonly paths, we need to make the paths absolute here. */
- path = realpath (drv->src.u.path, NULL);
- if (path == NULL) {
- perrorf (g, _("realpath: could not convert '%s' to absolute
path"),
- drv->src.u.path);
- return -1;
- }
-
- drv_priv->real_src.protocol = drive_protocol_file;
-
- if (!drv->readonly) {
- drv_priv->real_src.u.path = path;
- drv_priv->format = drv->format ? safe_strdup (g, drv->format) : NULL;
- }
- else {
- drv_priv->real_src.u.path = make_qcow2_overlay (g, path, drv->format,
- selinux_imagelabel);
- free (path);
- if (!drv_priv->real_src.u.path)
- return -1;
- drv_priv->format = safe_strdup (g, "qcow2");
- }
- break;
-
- case drive_protocol_ftp:
- case drive_protocol_ftps:
- case drive_protocol_gluster:
- case drive_protocol_http:
- case drive_protocol_https:
- case drive_protocol_iscsi:
- case drive_protocol_nbd:
- case drive_protocol_rbd:
- case drive_protocol_sheepdog:
- case drive_protocol_ssh:
- case drive_protocol_tftp:
- if (!drv->readonly) {
- guestfs___copy_drive_source (g, &drv->src, &drv_priv->real_src);
- drv_priv->format = drv->format ? safe_strdup (g, drv->format) : NULL;
- }
- else {
- CLEANUP_FREE char *qemu_device = NULL;
-
- drv_priv->real_src.protocol = drive_protocol_file;
- qemu_device = guestfs___drive_source_qemu_param (g, &drv->src);
- drv_priv->real_src.u.path = make_qcow2_overlay (g, qemu_device,
- drv->format,
- selinux_imagelabel);
- if (!drv_priv->real_src.u.path)
- return -1;
- drv_priv->format = safe_strdup (g, "qcow2");
- }
- break;
- }
-
- return 0;
-}
-
-static void
-drive_free_priv (void *priv)
-{
- struct drive_libvirt *drv_priv = priv;
-
- guestfs___free_drive_source (&drv_priv->real_src);
- free (drv_priv->format);
- free (drv_priv);
-}
-
static int
shutdown_libvirt (guestfs_h *g, void *datav, int check_for_errors)
{
@@ -1805,9 +1748,6 @@ hot_add_drive_libvirt (guestfs_h *g, void *datav,
return -1;
}
- if (make_drive_priv (g, drv, data->selinux_imagelabel) == -1)
- return -1;
-
/* Create the XML for the new disk. */
xml = construct_libvirt_xml_hot_add_disk (g, data, drv, drv_index);
if (xml == NULL)
@@ -1908,6 +1848,7 @@ set_libvirt_selinux_norelabel_disks (guestfs_h *g, void *datav, int
flag)
static struct backend_ops backend_libvirt_ops = {
.data_size = sizeof (struct backend_libvirt_data),
+ .create_cow_overlay = create_cow_overlay_libvirt,
.launch = launch_libvirt,
.shutdown = shutdown_libvirt,
.max_disks = max_disks_libvirt,
diff --git a/src/launch-uml.c b/src/launch-uml.c
index 023d033..9b2a004 100644
--- a/src/launch-uml.c
+++ b/src/launch-uml.c
@@ -50,7 +50,42 @@ struct backend_uml_data {
};
static void print_vmlinux_command_line (guestfs_h *g, char **argv);
-static char *make_cow_overlay (guestfs_h *g, const char *original);
+
+/* Run uml_mkcow to create a COW overlay. */
+static char *
+make_cow_overlay (guestfs_h *g, const char *original)
+{
+ CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
+ char *overlay;
+ int r;
+
+ if (guestfs___lazy_make_tmpdir (g) == -1)
+ return NULL;
+
+ overlay = safe_asprintf (g, "%s/overlay%d", g->tmpdir, g->unique++);
+
+ guestfs___cmd_add_arg (cmd, "uml_mkcow");
+ guestfs___cmd_add_arg (cmd, overlay);
+ guestfs___cmd_add_arg (cmd, original);
+ r = guestfs___cmd_run (cmd);
+ if (r == -1) {
+ free (overlay);
+ return NULL;
+ }
+ if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
+ guestfs___external_command_failed (g, r, "uml_mkcow", original);
+ free (overlay);
+ return NULL;
+ }
+
+ return overlay;
+}
+
+static char *
+create_cow_overlay_uml (guestfs_h *g, void *datav, struct drive *drv)
+{
+ return make_cow_overlay (g, drv->src.u.path);
+}
/* Test for features which are not supported by the UML backend.
* Possibly some of these should just be warnings, not errors.
@@ -75,7 +110,7 @@ uml_supported (guestfs_h *g)
error (g, _("uml backend does not support remote drives"));
return false;
}
- if (drv->format && STRNEQ (drv->format, "raw")) {
+ if (drv->src.format && STRNEQ (drv->src.format, "raw")) {
error (g, _("uml backend does not support non-raw-format drives"));
return false;
}
@@ -132,20 +167,10 @@ launch_uml (guestfs_h *g, void *datav, const char *arg)
return -1;
has_appliance_drive = appliance != NULL;
- /* Create COW overlays for any readonly drives, and for the root.
- * Note that the documented syntax ubd0=cow,orig does not work since
- * kernel 3.3. See:
+ /* Create COW overlays for the appliance. Note that the documented
+ * syntax ubd0=cow,orig does not work since kernel 3.3. See:
*
http://thread.gmane.org/gmane.linux.uml.devel/13556
*/
- ITER_DRIVES (g, i, drv) {
- if (drv->readonly) {
- drv->priv = make_cow_overlay (g, drv->src.u.path);
- if (!drv->priv)
- goto cleanup0;
- drv->free_priv = free;
- }
- }
-
if (has_appliance_drive) {
appliance_cow = make_cow_overlay (g, appliance);
if (!appliance_cow)
@@ -219,10 +244,10 @@ launch_uml (guestfs_h *g, void *datav, const char *arg)
/* Add the drives. */
ITER_DRIVES (g, i, drv) {
- if (!drv->readonly)
+ if (!drv->overlay)
ADD_CMDLINE_PRINTF ("ubd%zu=%s", i, drv->src.u.path);
else
- ADD_CMDLINE_PRINTF ("ubd%zu=%s", i, (char *) drv->priv);
+ ADD_CMDLINE_PRINTF ("ubd%zu=%s", i, drv->overlay);
}
/* Add the ext2 appliance drive (after all the drives). */
@@ -479,35 +504,6 @@ launch_uml (guestfs_h *g, void *datav, const char *arg)
return -1;
}
-/* Run uml_mkcow to create a COW overlay. This works around a kernel
- * bug in UML option parsing.
- */
-static char *
-make_cow_overlay (guestfs_h *g, const char *original)
-{
- CLEANUP_CMD_CLOSE struct command *cmd = guestfs___new_command (g);
- char *cow;
- int r;
-
- cow = safe_asprintf (g, "%s/cow%d", g->tmpdir, g->unique++);
-
- guestfs___cmd_add_arg (cmd, "uml_mkcow");
- guestfs___cmd_add_arg (cmd, cow);
- guestfs___cmd_add_arg (cmd, original);
- r = guestfs___cmd_run (cmd);
- if (r == -1) {
- free (cow);
- return NULL;
- }
- if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
- guestfs___external_command_failed (g, r, "uml_mkcow", original);
- free (cow);
- return NULL;
- }
-
- return cow; /* caller must free */
-}
-
/* This is called from the forked subprocess just before vmlinux runs,
* so it can just print the message straight to stderr, where it will
* be picked up and funnelled through the usual appliance event API.
@@ -605,6 +601,7 @@ max_disks_uml (guestfs_h *g, void *datav)
static struct backend_ops backend_uml_ops = {
.data_size = sizeof (struct backend_uml_data),
+ .create_cow_overlay = create_cow_overlay_uml,
.launch = launch_uml,
.shutdown = shutdown_uml,
.get_pid = get_pid_uml,
diff --git a/src/libvirt-domain.c b/src/libvirt-domain.c
index 238f64d..ace2e00 100644
--- a/src/libvirt-domain.c
+++ b/src/libvirt-domain.c
@@ -235,7 +235,10 @@ guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
if ((doc = get_domain_xml (g, dom)) == NULL)
return -1;
- /* Find and pass the SELinux security label to the libvirt back end. */
+ /* Find and pass the SELinux security label to the libvirt back end.
+ * Note this has to happen before adding the disks, since those may
+ * use the label.
+ */
if (libvirt_selinux_label (g, doc, &label, &imagelabel) == -1)
return -1;
if (label && imagelabel) {
--
1.8.4.2