drives: Centrally create overlays for read-only drives.

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).
This commit is contained in:
Richard W.M. Jones
2014-01-16 13:40:02 +00:00
parent 07e47513df
commit 4a0f5ed382
6 changed files with 701 additions and 634 deletions

View File

@@ -37,8 +37,6 @@
#include <pcre.h> #include <pcre.h>
#include <libxml/uri.h>
#include "c-ctype.h" #include "c-ctype.h"
#include "ignore-value.h" #include "ignore-value.h"
@@ -81,6 +79,38 @@ free_regexps (void)
pcre_free (re_hostname_port); 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. */ /* Create and free the 'drive' struct. */
static struct drive * static struct drive *
create_drive_file (guestfs_h *g, const char *path, 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.protocol = drive_protocol_file;
drv->src.u.path = safe_strdup (g, path); drv->src.u.path = safe_strdup (g, path);
drv->src.format = format ? safe_strdup (g, format) : NULL;
drv->readonly = readonly; drv->readonly = readonly;
drv->format = format ? safe_strdup (g, format) : NULL;
drv->iface = iface ? safe_strdup (g, iface) : NULL; drv->iface = iface ? safe_strdup (g, iface) : NULL;
drv->name = name ? safe_strdup (g, name) : NULL; drv->name = name ? safe_strdup (g, name) : NULL;
drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL; drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL;
drv->cachemode = cachemode ? safe_strdup (g, cachemode) : 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; return drv;
} }
@@ -123,15 +158,20 @@ create_drive_non_file (guestfs_h *g,
drv->src.u.exportname = safe_strdup (g, exportname); drv->src.u.exportname = safe_strdup (g, exportname);
drv->src.username = username ? safe_strdup (g, username) : NULL; drv->src.username = username ? safe_strdup (g, username) : NULL;
drv->src.secret = secret ? safe_strdup (g, secret) : NULL; drv->src.secret = secret ? safe_strdup (g, secret) : NULL;
drv->src.format = format ? safe_strdup (g, format) : NULL;
drv->readonly = readonly; drv->readonly = readonly;
drv->format = format ? safe_strdup (g, format) : NULL;
drv->iface = iface ? safe_strdup (g, iface) : NULL; drv->iface = iface ? safe_strdup (g, iface) : NULL;
drv->name = name ? safe_strdup (g, name) : NULL; drv->name = name ? safe_strdup (g, name) : NULL;
drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL; drv->disk_label = disk_label ? safe_strdup (g, disk_label) : NULL;
drv->cachemode = cachemode ? safe_strdup (g, cachemode) : 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; return drv;
} }
@@ -523,35 +563,49 @@ free_drive_servers (struct drive_server *servers, size_t nr_servers)
static void static void
free_drive_struct (struct drive *drv) free_drive_struct (struct drive *drv)
{ {
guestfs___free_drive_source (&drv->src); free_drive_source (&drv->src);
free (drv->format); free (drv->overlay);
free (drv->iface); free (drv->iface);
free (drv->name); free (drv->name);
free (drv->disk_label); free (drv->disk_label);
free (drv->cachemode); free (drv->cachemode);
if (drv->priv && drv->free_priv)
drv->free_priv (drv->priv);
free (drv); 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 /* Convert a struct drive to a string for debugging. The caller
* must free this string. * must free this string.
*/ */
static char * static char *
drive_to_string (guestfs_h *g, const struct drive *drv) 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 return safe_asprintf
(g, "%s%s%s%s%s%s%s%s%s%s%s%s", (g, "%s%s%s%s protocol=%s%s%s%s%s%s%s%s%s",
p, drv->src.u.path,
drv->readonly ? " readonly" : "", drv->readonly ? " readonly" : "",
drv->format ? " format=" : "", drv->src.format ? " format=" : "",
drv->format ? : "", drv->src.format ? : "",
protocol_to_string (drv->src.protocol),
drv->iface ? " iface=" : "", drv->iface ? " iface=" : "",
drv->iface ? : "", drv->iface ? : "",
drv->name ? " name=" : "", drv->name ? " name=" : "",
@@ -1176,199 +1230,11 @@ guestfs__debug_drives (guestfs_h *g)
return ret.argv; /* caller frees */ return ret.argv; /* caller frees */
} }
/* The drive_source struct is also used in the backends, so we static void
* also have these utility functions. free_drive_source (struct drive_source *src)
*/
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)
{ {
if (src) { if (src) {
free (src->format);
free (src->u.path); free (src->u.path);
free (src->username); free (src->username);
free (src->secret); free (src->secret);

View File

@@ -209,6 +209,9 @@ struct drive_server {
struct drive_source { struct drive_source {
enum drive_protocol protocol; enum drive_protocol protocol;
/* Format (eg. raw, qcow2). NULL = autodetect. */
char *format;
/* This field is always non-NULL. It may be an empty string. */ /* This field is always non-NULL. It may be an empty string. */
union { union {
char *path; /* path to file (file) */ char *path; /* path to file (file) */
@@ -228,19 +231,27 @@ struct drive_source {
char *secret; char *secret;
}; };
/* There is one 'struct drive' per drive, including hot-plugged drives. */
struct drive { struct drive {
/* Original source of the drive, eg. file:..., http:... */
struct drive_source src; 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; bool readonly;
char *format;
char *iface; char *iface;
char *name; char *name;
char *disk_label; char *disk_label;
char *cachemode; char *cachemode;
/* Data used by the backend. */
void *priv;
void (*free_priv) (void *);
}; };
/* Extra hv parameters (from guestfs_config). */ /* Extra hv parameters (from guestfs_config). */
@@ -262,6 +273,12 @@ struct backend_ops {
*/ */
size_t data_size; 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. */ /* Launch and shut down. */
int (*launch) (guestfs_h *g, void *data, const char *arg); int (*launch) (guestfs_h *g, void *data, const char *arg);
int (*shutdown) (guestfs_h *g, void *data, int check_for_errors); 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___rollback_drives (guestfs_h *g, size_t);
extern void guestfs___add_dummy_appliance_drive (guestfs_h *g); extern void guestfs___add_dummy_appliance_drive (guestfs_h *g);
extern void guestfs___free_drives (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 */ /* appliance.c */
extern int guestfs___build_appliance (guestfs_h *g, char **kernel, char **dtb, char **initrd, char **appliance); 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 #endif
extern void guestfs___cleanup_cmd_close (struct command **); 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_ */ #endif /* GUESTFS_INTERNAL_H_ */

View File

@@ -33,9 +33,12 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
#include <grp.h> #include <grp.h>
#include <assert.h>
#include <pcre.h> #include <pcre.h>
#include <libxml/uri.h>
#include "cloexec.h" #include "cloexec.h"
#include "ignore-value.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 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 *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 #ifdef QEMU_OPTIONS
/* Like 'add_cmdline' but allowing a shell-quoted string of zero or /* Like 'add_cmdline' but allowing a shell-quoted string of zero or
* more options. XXX The unquoting is not very clever. * 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) { ITER_DRIVES (g, i, drv) {
CLEANUP_FREE char *file = NULL, *escaped_file = NULL, *param = NULL; CLEANUP_FREE char *file = NULL, *escaped_file = NULL, *param = NULL;
/* Make the file= parameter. */ if (!drv->overlay) {
file = guestfs___drive_source_qemu_param (g, &drv->src); /* Make the file= parameter. */
escaped_file = qemu_escape_param (g, file); 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 /* Make the first part of the -drive parameter, everything up to
* the if=... at the end. * the if=... at the end.
*/ */
param = safe_asprintf param = safe_asprintf
(g, "file=%s%s,cache=%s%s%s%s%s,id=hd%zu", (g, "file=%s%s,cache=%s%s%s%s%s,id=hd%zu",
escaped_file, escaped_file,
drv->readonly ? ",snapshot=on" : "", drv->readonly ? ",snapshot=on" : "",
drv->cachemode ? drv->cachemode : "writeback", drv->cachemode ? drv->cachemode : "writeback",
drv->format ? ",format=" : "", drv->src.format ? ",format=" : "",
drv->format ? drv->format : "", drv->src.format ? drv->src.format : "",
drv->disk_label ? ",serial=" : "", drv->disk_label ? ",serial=" : "",
drv->disk_label ? drv->disk_label : "", drv->disk_label ? drv->disk_label : "",
i); 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 /* If there's an explicit 'iface', use it. Otherwise default to
* virtio-scsi if available. Otherwise default to virtio-blk. * virtio-scsi if available. Otherwise default to virtio-blk.
@@ -1130,6 +1192,186 @@ qemu_escape_param (guestfs_h *g, const char *param)
return ret; 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 static int
shutdown_direct (guestfs_h *g, void *datav, int check_for_errors) 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 = { static struct backend_ops backend_direct_ops = {
.data_size = sizeof (struct backend_direct_data), .data_size = sizeof (struct backend_direct_data),
.create_cow_overlay = create_cow_overlay_direct,
.launch = launch_direct, .launch = launch_direct,
.shutdown = shutdown_direct, .shutdown = shutdown_direct,
.get_pid = get_pid_direct, .get_pid = get_pid_direct,

View File

@@ -108,18 +108,6 @@ struct backend_libvirt_data {
char name[DOMAIN_NAME_LEN]; /* random name */ 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 /* Parameters passed to construct_libvirt_xml and subfunctions. We
* keep them all in a structure for convenience! * 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_custom_hv (guestfs_h *g);
static int is_blk (const char *path); static int is_blk (const char *path);
static void ignore_errors (void *ignore, virErrorPtr ignore2); 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 set_socket_create_context (guestfs_h *g);
static void clear_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); static void selinux_warning (guestfs_h *g, const char *func, const char *selinux_op, const char *data);
#endif #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 static int
launch_libvirt (guestfs_h *g, void *datav, const char *libvirt_uri) 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; int r;
uint32_t size; uint32_t size;
CLEANUP_FREE void *buf = NULL; CLEANUP_FREE void *buf = NULL;
struct drive *drv;
size_t i;
params.current_proc_is_root = geteuid () == 0; 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. */ /* Note that appliance can be NULL if using the old-style appliance. */
if (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) if (!params.appliance_overlay)
goto cleanup; 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); TRACE0 (launch_build_libvirt_qcow2_overlay_end);
/* Using virtio-serial, we need to create a local Unix domain socket /* 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_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_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 (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_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_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); 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 { \ #define XMLERROR_RET(code,e,ret) do { \
if ((e) == (code)) { \ if ((e) == (code)) { \
perrorf (g, _("error constructing libvirt XML at \"%s\""), \ perrorf (g, _("%s:%d: error constructing libvirt XML at \"%s\""), \
__FILE__, __LINE__, \
#e); \ #e); \
return (ret); \ return (ret); \
} \ } \
@@ -1111,12 +1164,10 @@ construct_libvirt_xml_disk (guestfs_h *g,
xmlTextWriterPtr xo, xmlTextWriterPtr xo,
struct drive *drv, size_t drv_index) struct drive *drv, size_t drv_index)
{ {
char drive_name[64] = "sd";
const char *protocol_str; const char *protocol_str;
char scsi_target[64]; CLEANUP_FREE char *path = NULL;
struct drive_libvirt *drv_priv = (struct drive_libvirt *) drv->priv;
CLEANUP_FREE char *format = NULL;
int is_host_device; int is_host_device;
CLEANUP_FREE char *format = NULL;
/* XXX We probably could support this if we thought about it some more. */ /* XXX We probably could support this if we thought about it some more. */
if (drv->iface) { if (drv->iface) {
@@ -1124,116 +1175,207 @@ construct_libvirt_xml_disk (guestfs_h *g,
return -1; 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, xmlTextWriterStartElement (xo, BAD_CAST "disk"));
XMLERROR (-1, XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "device", xmlTextWriterWriteAttribute (xo, BAD_CAST "device",
BAD_CAST "disk")); BAD_CAST "disk"));
switch (drv_priv->real_src.protocol) { if (drv->overlay) {
case drive_protocol_file: /* Overlay to protect read-only backing disk. The format of the
/* Change the libvirt XML according to whether the host path is * overlay is always qcow2.
* 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_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, XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type", xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
BAD_CAST "network")); BAD_CAST "file"));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source")); XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "source"));
XMLERROR (-1, XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "protocol", xmlTextWriterWriteAttribute (xo, BAD_CAST "file",
BAD_CAST protocol_str)); BAD_CAST drv->overlay));
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;
if (construct_libvirt_xml_disk_source_seclabel (g, data, xo) == -1) if (construct_libvirt_xml_disk_source_seclabel (g, data, xo) == -1)
return -1; return -1;
XMLERROR (-1, xmlTextWriterEndElement (xo)); 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
*/
XMLERROR (-1, xmlTextWriterEndElement (xo));
}
break;
/* libvirt doesn't support the qemu curl driver yet. Give a if (construct_libvirt_xml_disk_target (g, xo, drv_index) == -1)
* reasonable error message instead of trying and failing. return -1;
*/
case drive_protocol_ftp: if (construct_libvirt_xml_disk_driver_qemu (g, xo, "qcow2", "unsafe") == -1)
case drive_protocol_ftps: return -1;
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;
} }
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));
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;
/* 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 (construct_libvirt_xml_disk_target (g, xo, drv_index) == -1)
return -1;
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;
}
if (construct_libvirt_xml_disk_driver_qemu (g, xo, format,
drv->cachemode ? : "writeback")
== -1)
return -1;
}
if (drv->disk_label) {
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "serial"));
XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST drv->disk_label));
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, xmlTextWriterStartElement (xo, BAD_CAST "target"));
XMLERROR (-1, XMLERROR (-1,
@@ -1244,61 +1386,36 @@ construct_libvirt_xml_disk (guestfs_h *g,
BAD_CAST "scsi")); BAD_CAST "scsi"));
XMLERROR (-1, xmlTextWriterEndElement (xo)); 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, xmlTextWriterStartElement (xo, BAD_CAST "driver"));
XMLERROR (-1, XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "name", xmlTextWriterWriteAttribute (xo, BAD_CAST "name",
BAD_CAST "qemu")); BAD_CAST "qemu"));
if (drv_priv->format) { XMLERROR (-1,
XMLERROR (-1, xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
xmlTextWriterWriteAttribute (xo, BAD_CAST "type", BAD_CAST format));
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)
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;
}
XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
BAD_CAST format));
}
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, XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "cache", xmlTextWriterWriteAttribute (xo, BAD_CAST "cache",
BAD_CAST (drv->cachemode ? BAD_CAST cachemode));
drv->cachemode :
"writeback")));
XMLERROR (-1, xmlTextWriterEndElement (xo)); XMLERROR (-1, xmlTextWriterEndElement (xo));
if (drv->disk_label) { return 0;
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "serial")); }
XMLERROR (-1, xmlTextWriterWriteString (xo, BAD_CAST drv->disk_label));
XMLERROR (-1, xmlTextWriterEndElement (xo)); 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, xmlTextWriterStartElement (xo, BAD_CAST "address"));
XMLERROR (-1, XMLERROR (-1,
@@ -1318,8 +1435,6 @@ construct_libvirt_xml_disk (guestfs_h *g,
BAD_CAST "0")); BAD_CAST "0"));
XMLERROR (-1, xmlTextWriterEndElement (xo)); XMLERROR (-1, xmlTextWriterEndElement (xo));
XMLERROR (-1, xmlTextWriterEndElement (xo));
return 0; return 0;
} }
@@ -1398,10 +1513,6 @@ construct_libvirt_xml_appliance (guestfs_h *g,
const struct libvirt_xml_params *params, const struct libvirt_xml_params *params,
xmlTextWriterPtr xo) 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, xmlTextWriterStartElement (xo, BAD_CAST "disk"));
XMLERROR (-1, XMLERROR (-1,
xmlTextWriterWriteAttribute (xo, BAD_CAST "type", xmlTextWriterWriteAttribute (xo, BAD_CAST "type",
@@ -1425,46 +1536,15 @@ construct_libvirt_xml_appliance (guestfs_h *g,
BAD_CAST "scsi")); BAD_CAST "scsi"));
XMLERROR (-1, xmlTextWriterEndElement (xo)); XMLERROR (-1, xmlTextWriterEndElement (xo));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "driver")); if (construct_libvirt_xml_disk_driver_qemu (g, xo, "qcow2", "unsafe") == -1)
XMLERROR (-1, return -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));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "address")); if (construct_libvirt_xml_disk_address (g, xo, params->appliance_index) == -1)
XMLERROR (-1, return -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));
XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "shareable")); XMLERROR (-1, xmlTextWriterStartElement (xo, BAD_CAST "shareable"));
XMLERROR (-1, xmlTextWriterEndElement (xo)); 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)); XMLERROR (-1, xmlTextWriterEndElement (xo));
return 0; return 0;
@@ -1562,143 +1642,6 @@ ignore_errors (void *ignore, virErrorPtr ignore2)
/* empty */ /* 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 static int
shutdown_libvirt (guestfs_h *g, void *datav, int check_for_errors) 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; return -1;
} }
if (make_drive_priv (g, drv, data->selinux_imagelabel) == -1)
return -1;
/* Create the XML for the new disk. */ /* Create the XML for the new disk. */
xml = construct_libvirt_xml_hot_add_disk (g, data, drv, drv_index); xml = construct_libvirt_xml_hot_add_disk (g, data, drv, drv_index);
if (xml == NULL) 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 = { static struct backend_ops backend_libvirt_ops = {
.data_size = sizeof (struct backend_libvirt_data), .data_size = sizeof (struct backend_libvirt_data),
.create_cow_overlay = create_cow_overlay_libvirt,
.launch = launch_libvirt, .launch = launch_libvirt,
.shutdown = shutdown_libvirt, .shutdown = shutdown_libvirt,
.max_disks = max_disks_libvirt, .max_disks = max_disks_libvirt,

View File

@@ -50,7 +50,42 @@ struct backend_uml_data {
}; };
static void print_vmlinux_command_line (guestfs_h *g, char **argv); 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. /* Test for features which are not supported by the UML backend.
* Possibly some of these should just be warnings, not errors. * 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")); error (g, _("uml backend does not support remote drives"));
return false; 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")); error (g, _("uml backend does not support non-raw-format drives"));
return false; return false;
} }
@@ -132,20 +167,10 @@ launch_uml (guestfs_h *g, void *datav, const char *arg)
return -1; return -1;
has_appliance_drive = appliance != NULL; has_appliance_drive = appliance != NULL;
/* Create COW overlays for any readonly drives, and for the root. /* Create COW overlays for the appliance. Note that the documented
* Note that the documented syntax ubd0=cow,orig does not work since * syntax ubd0=cow,orig does not work since kernel 3.3. See:
* kernel 3.3. See:
* http://thread.gmane.org/gmane.linux.uml.devel/13556 * 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) { if (has_appliance_drive) {
appliance_cow = make_cow_overlay (g, appliance); appliance_cow = make_cow_overlay (g, appliance);
if (!appliance_cow) if (!appliance_cow)
@@ -219,10 +244,10 @@ launch_uml (guestfs_h *g, void *datav, const char *arg)
/* Add the drives. */ /* Add the drives. */
ITER_DRIVES (g, i, drv) { ITER_DRIVES (g, i, drv) {
if (!drv->readonly) if (!drv->overlay)
ADD_CMDLINE_PRINTF ("ubd%zu=%s", i, drv->src.u.path); ADD_CMDLINE_PRINTF ("ubd%zu=%s", i, drv->src.u.path);
else 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). */ /* 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; 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, /* This is called from the forked subprocess just before vmlinux runs,
* so it can just print the message straight to stderr, where it will * so it can just print the message straight to stderr, where it will
* be picked up and funnelled through the usual appliance event API. * 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 = { static struct backend_ops backend_uml_ops = {
.data_size = sizeof (struct backend_uml_data), .data_size = sizeof (struct backend_uml_data),
.create_cow_overlay = create_cow_overlay_uml,
.launch = launch_uml, .launch = launch_uml,
.shutdown = shutdown_uml, .shutdown = shutdown_uml,
.get_pid = get_pid_uml, .get_pid = get_pid_uml,

View File

@@ -235,7 +235,10 @@ guestfs___add_libvirt_dom (guestfs_h *g, virDomainPtr dom,
if ((doc = get_domain_xml (g, dom)) == NULL) if ((doc = get_domain_xml (g, dom)) == NULL)
return -1; 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) if (libvirt_selinux_label (g, doc, &label, &imagelabel) == -1)
return -1; return -1;
if (label && imagelabel) { if (label && imagelabel) {