This is code motion, but I have cleaned up and formalized the
interface between this module and other parts of the library.
Also this adds documentation to the interface.
---
docs/C_SOURCE_FILES | 1 +
src/Makefile.am | 1 +
src/guestfs-internal.h | 16 +-
src/launch-direct.c | 582 +++----------------------------------------------
src/qemu.c | 573 ++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 619 insertions(+), 554 deletions(-)
create mode 100644 src/qemu.c
diff --git a/docs/C_SOURCE_FILES b/docs/C_SOURCE_FILES
index 10e02de..4f1f684 100644
--- a/docs/C_SOURCE_FILES
+++ b/docs/C_SOURCE_FILES
@@ -253,6 +253,7 @@ src/mountable.c
src/osinfo.c
src/private-data.c
src/proto.c
+src/qemu.c
src/stringsbuf.c
src/structs-cleanup.c
src/structs-compare.c
diff --git a/src/Makefile.am b/src/Makefile.am
index d2879f4..bdac5e3 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -125,6 +125,7 @@ libguestfs_la_SOURCES = \
osinfo.c \
private-data.c \
proto.c \
+ qemu.c \
stringsbuf.c \
structs-compare.c \
structs-copy.c \
diff --git a/src/guestfs-internal.h b/src/guestfs-internal.h
index 0b207b2..6004890 100644
--- a/src/guestfs-internal.h
+++ b/src/guestfs-internal.h
@@ -906,11 +906,6 @@ extern char *guestfs_int_cmd_get_pipe_errors (struct command *cmd);
#endif
extern void guestfs_int_cleanup_cmd_close (struct command **);
-/* launch-direct.c */
-extern char *guestfs_int_drive_source_qemu_param (guestfs_h *g, const struct drive_source
*src);
-extern bool guestfs_int_discard_possible (guestfs_h *g, struct drive *drv, const struct
version *qemu_version);
-extern char *guestfs_int_qemu_escape_param (guestfs_h *g, const char *param);
-
/* launch-*.c constructors */
void guestfs_int_init_direct_backend (void) __attribute__((constructor));
#ifdef HAVE_LIBVIRT_BACKEND
@@ -919,6 +914,17 @@ void guestfs_int_init_libvirt_backend (void)
__attribute__((constructor));
void guestfs_int_init_uml_backend (void) __attribute__((constructor));
void guestfs_int_init_unix_backend (void) __attribute__((constructor));
+/* qemu.c */
+struct qemu_data;
+extern struct qemu_data *guestfs_int_test_qemu (guestfs_h *g, struct version
*qemu_version);
+extern int guestfs_int_qemu_supports (guestfs_h *g, const struct qemu_data *, const char
*option);
+extern int guestfs_int_qemu_supports_device (guestfs_h *g, const struct qemu_data *,
const char *device_name);
+extern int guestfs_int_qemu_supports_virtio_scsi (guestfs_h *g, struct qemu_data *, const
struct version *qemu_version);
+extern char *guestfs_int_drive_source_qemu_param (guestfs_h *g, const struct drive_source
*src);
+extern bool guestfs_int_discard_possible (guestfs_h *g, struct drive *drv, const struct
version *qemu_version);
+extern char *guestfs_int_qemu_escape_param (guestfs_h *g, const char *param);
+extern void guestfs_int_free_qemu_data (struct qemu_data *);
+
/* guid.c */
extern int guestfs_int_validate_guid (const char *);
diff --git a/src/launch-direct.c b/src/launch-direct.c
index 64967d2..30cf01a 100644
--- a/src/launch-direct.c
+++ b/src/launch-direct.c
@@ -40,31 +40,19 @@
#include <string.h>
#include <libintl.h>
-#include <pcre.h>
-
-#include <libxml/uri.h>
-
#include "cloexec.h"
-#include "ignore-value.h"
#include "guestfs.h"
#include "guestfs-internal.h"
#include "guestfs_protocol.h"
-COMPILE_REGEXP (re_major_minor, "(\\d+)\\.(\\d+)", 0)
-
/* Per-handle data. */
struct backend_direct_data {
- pid_t pid; /* Qemu PID. */
- pid_t recoverypid; /* Recovery process PID. */
+ pid_t pid; /* Qemu PID. */
+ pid_t recoverypid; /* Recovery process PID. */
- char *qemu_help; /* Output of qemu -help. */
- char *qemu_devices; /* Output of qemu -device ? */
-
- /* qemu version (0, 0 if unable to parse). */
- struct version qemu_version;
-
- int virtio_scsi; /* See function qemu_supports_virtio_scsi */
+ struct version qemu_version; /* qemu version (0 if unable to parse). */
+ struct qemu_data *qemu_data; /* qemu -help output etc. */
char guestfsd_sock[UNIX_PATH_MAX]; /* Path to daemon socket. */
};
@@ -72,9 +60,6 @@ struct backend_direct_data {
static int is_openable (guestfs_h *g, const char *path, int flags);
static char *make_appliance_dev (guestfs_h *g, int virtio_scsi);
static void print_qemu_command_line (guestfs_h *g, char **argv);
-static int qemu_supports (guestfs_h *g, struct backend_direct_data *, const char
*option);
-static int qemu_supports_device (guestfs_h *g, struct backend_direct_data *, const char
*device_name);
-static int qemu_supports_virtio_scsi (guestfs_h *g, struct backend_direct_data *);
static char *
create_cow_overlay_direct (guestfs_h *g, void *datav, struct drive *drv)
@@ -291,8 +276,11 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
debug (g, "begin testing qemu features");
/* Get qemu help text and version. */
- if (qemu_supports (g, data, NULL) == -1)
- goto cleanup0;
+ if (data->qemu_data == NULL) {
+ data->qemu_data = guestfs_int_test_qemu (g, &data->qemu_version);
+ if (data->qemu_data == NULL)
+ goto cleanup0;
+ }
/* Using virtio-serial, we need to create a local Unix domain socket
* for qemu to connect to.
@@ -349,19 +337,19 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
* strings to it so we don't need to check for the specific virtio
* feature.
*/
- if (qemu_supports (g, data, "-global")) {
+ if (guestfs_int_qemu_supports (g, data->qemu_data, "-global")) {
ADD_CMDLINE ("-global");
ADD_CMDLINE (VIRTIO_BLK ".scsi=off");
}
- if (qemu_supports (g, data, "-nodefconfig"))
+ if (guestfs_int_qemu_supports (g, data->qemu_data, "-nodefconfig"))
ADD_CMDLINE ("-nodefconfig");
/* This oddly named option doesn't actually enable FIPS. It just
* causes qemu to do the right thing if FIPS is enabled in the
* kernel. So like libvirt, we pass it unconditionally.
*/
- if (qemu_supports (g, data, "-enable-fips"))
+ if (guestfs_int_qemu_supports (g, data->qemu_data, "-enable-fips"))
ADD_CMDLINE ("-enable-fips");
/* Newer versions of qemu (from around 2009/12) changed the
@@ -372,7 +360,7 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
* called -nodefaults which gets rid of all this default crud, so
* let's use that to avoid this and any future surprises.
*/
- if (qemu_supports (g, data, "-nodefaults"))
+ if (guestfs_int_qemu_supports (g, data->qemu_data, "-nodefaults"))
ADD_CMDLINE ("-nodefaults");
/* This disables the host-side display (SDL, Gtk). */
@@ -417,7 +405,7 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
/* These are recommended settings, see RHBZ#1053847. */
ADD_CMDLINE ("-rtc");
ADD_CMDLINE ("driftfix=slew");
- if (qemu_supports (g, data, "-no-hpet")) {
+ if (guestfs_int_qemu_supports (g, data->qemu_data, "-no-hpet")) {
ADD_CMDLINE ("-no-hpet");
}
if (!guestfs_int_version_ge (&data->qemu_version, 1, 3, 0))
@@ -450,7 +438,8 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
* isn't strictly necessary but means we won't need to hang around
* when needing entropy.
*/
- if (qemu_supports_device (g, data, "virtio-rng-pci")) {
+ if (guestfs_int_qemu_supports_device (g, data->qemu_data,
+ "virtio-rng-pci")) {
ADD_CMDLINE ("-object");
ADD_CMDLINE ("rng-random,filename=/dev/urandom,id=rng0");
ADD_CMDLINE ("-device");
@@ -458,7 +447,8 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
}
/* Add drives */
- virtio_scsi = qemu_supports_virtio_scsi (g, data);
+ virtio_scsi = guestfs_int_qemu_supports_virtio_scsi (g, data->qemu_data,
+ &data->qemu_version);
if (virtio_scsi) {
/* Create the virtio-scsi bus. */
@@ -581,7 +571,8 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
ADD_CMDLINE ("stdio");
if (g->verbose &&
- qemu_supports_device (g, data, "Serial Graphics Adapter")) {
+ guestfs_int_qemu_supports_device (g, data->qemu_data,
+ "Serial Graphics Adapter")) {
/* Use sgabios instead of vgabios. This means we'll see BIOS
* messages on the serial port, and also works around this bug
* in qemu 1.1.0:
@@ -849,6 +840,8 @@ launch_direct (guestfs_h *g, void *datav, const char *arg)
data->pid = 0;
data->recoverypid = 0;
memset (&g->launch_t, 0, sizeof g->launch_t);
+ guestfs_int_free_qemu_data (data->qemu_data);
+ data->qemu_data = NULL;
cleanup0:
if (daemon_accept_sock >= 0)
@@ -930,148 +923,6 @@ print_qemu_command_line (guestfs_h *g, char **argv)
fputc ('\n', stderr);
}
-static void parse_qemu_version (guestfs_h *g, struct backend_direct_data *data);
-static void read_all (guestfs_h *g, void *retv, const char *buf, size_t len);
-
-/* Test qemu binary (or wrapper) runs, and do 'qemu -help' so we know
- * the version of qemu what options this qemu supports.
- */
-static int
-test_qemu (guestfs_h *g, struct backend_direct_data *data)
-{
- CLEANUP_CMD_CLOSE struct command *cmd1 = guestfs_int_new_command (g);
- CLEANUP_CMD_CLOSE struct command *cmd2 = guestfs_int_new_command (g);
- int r;
-
- free (data->qemu_help);
- data->qemu_help = NULL;
- free (data->qemu_devices);
- data->qemu_devices = NULL;
-
- guestfs_int_cmd_add_arg (cmd1, g->hv);
- guestfs_int_cmd_add_arg (cmd1, "-display");
- guestfs_int_cmd_add_arg (cmd1, "none");
- guestfs_int_cmd_add_arg (cmd1, "-help");
- guestfs_int_cmd_set_stdout_callback (cmd1, read_all, &data->qemu_help,
- CMD_STDOUT_FLAG_WHOLE_BUFFER);
- r = guestfs_int_cmd_run (cmd1);
- if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0)
- goto error;
-
- parse_qemu_version (g, data);
-
- guestfs_int_cmd_add_arg (cmd2, g->hv);
- guestfs_int_cmd_add_arg (cmd2, "-display");
- guestfs_int_cmd_add_arg (cmd2, "none");
- guestfs_int_cmd_add_arg (cmd2, "-machine");
- guestfs_int_cmd_add_arg (cmd2,
-#ifdef MACHINE_TYPE
- MACHINE_TYPE ","
-#endif
- "accel=kvm:tcg");
- guestfs_int_cmd_add_arg (cmd2, "-device");
- guestfs_int_cmd_add_arg (cmd2, "?");
- guestfs_int_cmd_clear_capture_errors (cmd2);
- guestfs_int_cmd_set_stderr_to_stdout (cmd2);
- guestfs_int_cmd_set_stdout_callback (cmd2, read_all, &data->qemu_devices,
- CMD_STDOUT_FLAG_WHOLE_BUFFER);
- r = guestfs_int_cmd_run (cmd2);
- if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0)
- goto error;
-
- return 0;
-
- error:
- if (r == -1)
- return -1;
-
- guestfs_int_external_command_failed (g, r, g->hv, NULL);
- return -1;
-}
-
-/* Parse the first line of data->qemu_help (if not NULL) into the
- * major and minor version of qemu, but don't fail if parsing is not
- * possible.
- */
-static void
-parse_qemu_version (guestfs_h *g, struct backend_direct_data *data)
-{
- CLEANUP_FREE char *major_s = NULL, *minor_s = NULL;
- int major_i, minor_i;
-
- version_init_null (&data->qemu_version);
-
- if (!data->qemu_help)
- return;
-
- if (!match2 (g, data->qemu_help, re_major_minor, &major_s, &minor_s)) {
- parse_failed:
- debug (g, "%s: failed to parse qemu version string from the first line of the
output of '%s -help'. When reporting this bug please include the -help
output.",
- __func__, g->hv);
- return;
- }
-
- major_i = guestfs_int_parse_unsigned_int (g, major_s);
- if (major_i == -1)
- goto parse_failed;
-
- minor_i = guestfs_int_parse_unsigned_int (g, minor_s);
- if (minor_i == -1)
- goto parse_failed;
-
- guestfs_int_version_from_values (&data->qemu_version, major_i, minor_i, 0);
-
- debug (g, "qemu version %d.%d", major_i, minor_i);
-}
-
-static void
-read_all (guestfs_h *g, void *retv, const char *buf, size_t len)
-{
- char **ret = retv;
-
- *ret = safe_strndup (g, buf, len);
-}
-
-/* Test if option is supported by qemu command line (just by grepping
- * the help text).
- *
- * The first time this is used, it has to run the external qemu
- * binary. If that fails, it returns -1.
- *
- * To just do the first-time run of the qemu binary, call this with
- * option == NULL, in which case it will return -1 if there was an
- * error doing that.
- */
-static int
-qemu_supports (guestfs_h *g, struct backend_direct_data *data,
- const char *option)
-{
- if (!data->qemu_help) {
- if (test_qemu (g, data) == -1)
- return -1;
- }
-
- if (option == NULL)
- return 1;
-
- return strstr (data->qemu_help, option) != NULL;
-}
-
-/* Test if device is supported by qemu (currently just greps the -device ?
- * output).
- */
-static int
-qemu_supports_device (guestfs_h *g, struct backend_direct_data *data,
- const char *device_name)
-{
- if (!data->qemu_devices) {
- if (test_qemu (g, data) == -1)
- return -1;
- }
-
- return strstr (data->qemu_devices, device_name) != NULL;
-}
-
/* Check if a file can be opened. */
static int
is_openable (guestfs_h *g, const char *path, int flags)
@@ -1086,379 +937,6 @@ is_openable (guestfs_h *g, const char *path, int flags)
}
static int
-old_or_broken_virtio_scsi (guestfs_h *g, struct backend_direct_data *data)
-{
- /* qemu 1.1 claims to support virtio-scsi but in reality it's broken. */
- if (!guestfs_int_version_ge (&data->qemu_version, 1, 2, 0))
- return 1;
-
- return 0;
-}
-
-/* Returns 1 = use virtio-scsi, or 0 = use virtio-blk. */
-static int
-qemu_supports_virtio_scsi (guestfs_h *g, struct backend_direct_data *data)
-{
- int r;
-
- if (!data->qemu_help) {
- if (test_qemu (g, data) == -1)
- return 0; /* safe option? */
- }
-
- /* data->virtio_scsi has these values:
- * 0 = untested (after handle creation)
- * 1 = supported
- * 2 = not supported (use virtio-blk)
- * 3 = test failed (use virtio-blk)
- */
- if (data->virtio_scsi == 0) {
- if (old_or_broken_virtio_scsi (g, data))
- data->virtio_scsi = 2;
- else {
- r = qemu_supports_device (g, data, VIRTIO_SCSI);
- if (r > 0)
- data->virtio_scsi = 1;
- else if (r == 0)
- data->virtio_scsi = 2;
- else
- data->virtio_scsi = 3;
- }
- }
-
- return data->virtio_scsi == 1;
-}
-
-/**
- * Escape a qemu parameter.
- *
- * Every C<,> becomes C<,,>. The caller must free the returned string.
- */
-char *
-guestfs_int_qemu_escape_param (guestfs_h *g, const char *param)
-{
- size_t i, len = strlen (param);
- char *p, *ret;
-
- ret = p = safe_malloc (g, len*2 + 1); /* max length of escaped name*/
- for (i = 0; i < len; ++i) {
- *p++ = param[i];
- if (param[i] == ',')
- *p++ = ',';
- }
- *p = '\0';
-
- return ret;
-}
-
-static char *
-make_uri (guestfs_h *g, const char *scheme, const char *user,
- const char *password,
- struct drive_server *server, const char *path)
-{
- xmlURI uri = { .scheme = (char *) scheme,
- .user = (char *) user };
- CLEANUP_FREE char *query = NULL;
- CLEANUP_FREE char *pathslash = NULL;
- CLEANUP_FREE char *userauth = NULL;
-
- /* Need to add a leading '/' to URI paths since xmlSaveUri doesn't. */
- if (path != NULL && path[0] != '/') {
- pathslash = safe_asprintf (g, "/%s", path);
- uri.path = pathslash;
- }
- else
- uri.path = (char *) path;
-
- /* Rebuild user:password. */
- if (user != NULL && password != NULL) {
- /* Keep the string in an own variable so it can be freed automatically. */
- userauth = safe_asprintf (g, "%s:%s", user, password);
- uri.user = userauth;
- }
-
- 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_int_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->secret,
- &src->servers[0], src->u.exportname);
-
- case drive_protocol_ftps:
- return make_uri (g, "ftps", src->username, src->secret,
- &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, NULL,
- &src->servers[0], src->u.exportname);
- case drive_transport_tcp:
- return make_uri (g, "gluster+tcp", NULL, NULL,
- &src->servers[0], src->u.exportname);
- case drive_transport_unix:
- return make_uri (g, "gluster+unix", NULL, NULL,
- &src->servers[0], NULL);
- }
-
- case drive_protocol_http:
- return make_uri (g, "http", src->username, src->secret,
- &src->servers[0], src->u.exportname);
-
- case drive_protocol_https:
- return make_uri (g, "https", src->username, src->secret,
- &src->servers[0], src->u.exportname);
-
- case drive_protocol_iscsi: {
- CLEANUP_FREE char *escaped_hostname = NULL;
- CLEANUP_FREE char *escaped_target = NULL;
- CLEANUP_FREE char *userauth = NULL;
- char port_str[16];
- char *ret;
-
- escaped_hostname =
- (char *) xmlURIEscapeStr(BAD_CAST src->servers[0].u.hostname,
- BAD_CAST "");
- /* The target string must keep slash as it is, as exportname contains
- * "iqn/lun".
- */
- escaped_target =
- (char *) xmlURIEscapeStr(BAD_CAST src->u.exportname, BAD_CAST "/");
- if (src->username != NULL && src->secret != NULL)
- userauth = safe_asprintf (g, "%s%%%s@", src->username,
src->secret);
- if (src->servers[0].port != 0)
- snprintf (port_str, sizeof port_str, ":%d", src->servers[0].port);
-
- ret = safe_asprintf (g, "iscsi://%s%s%s/%s",
- userauth != NULL ? userauth : "",
- escaped_hostname,
- src->servers[0].port != 0 ? port_str : "",
- escaped_target);
-
- return ret;
- }
-
- 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
- ret = safe_asprintf (g, "%s:exportname=%s", p, src->u.exportname);
-
- return ret;
- }
-
- case drive_protocol_rbd: {
- CLEANUP_FREE char *mon_host = NULL, *username = NULL, *secret = NULL;
- const char *auth;
- size_t n = 0;
- size_t i, j;
-
- /* build the list of all the mon hosts */
- 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";
-
- return safe_asprintf (g, "rbd:%s%s%s%s%s%s",
- src->u.exportname,
- src->nr_servers > 0 ? ":mon_host=" :
"",
- src->nr_servers > 0 ? mon_host : "",
- username ? username : "",
- auth,
- secret ? secret : "");
- }
-
- case drive_protocol_sheepdog:
- if (src->nr_servers == 0)
- return safe_asprintf (g, "sheepdog:%s", src->u.exportname);
- 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);
-
- case drive_protocol_ssh:
- return make_uri (g, "ssh", src->username, src->secret,
- &src->servers[0], src->u.exportname);
-
- case drive_protocol_tftp:
- return make_uri (g, "tftp", src->username, src->secret,
- &src->servers[0], src->u.exportname);
- }
-
- abort ();
-}
-
-/* Test if discard is both supported by qemu AND possible with the
- * underlying file or device. This returns 1 if discard is possible.
- * It returns 0 if not possible and sets the error to the reason why.
- *
- * This function is called when the user set discard == "enable".
- */
-bool
-guestfs_int_discard_possible (guestfs_h *g, struct drive *drv,
- const struct version *qemu_version)
-{
- /* qemu >= 1.5. This was the first version that supported the
- * discard option on -drive at all.
- */
- bool qemu15 = guestfs_int_version_ge (qemu_version, 1, 5, 0);
- /* qemu >= 1.6. This was the first version that supported unmap on
- * qcow2 backing files.
- */
- bool qemu16 = guestfs_int_version_ge (qemu_version, 1, 6, 0);
-
- if (!qemu15)
- NOT_SUPPORTED (g, false,
- _("discard cannot be enabled on this drive: "
- "qemu < 1.5"));
-
- /* If it's an overlay, discard is not possible (on the underlying
- * file). This has probably been caught earlier since we already
- * checked that the drive is !readonly. Nevertheless ...
- */
- if (drv->overlay)
- NOT_SUPPORTED (g, false,
- _("discard cannot be enabled on this drive: "
- "the drive has a read-only overlay"));
-
- /* Look at the source format. */
- if (drv->src.format == NULL) {
- /* We could autodetect the format, but we don't ... yet. XXX */
- NOT_SUPPORTED (g, false,
- _("discard cannot be enabled on this drive: "
- "you have to specify the format of the file"));
- }
- else if (STREQ (drv->src.format, "raw"))
- /* OK */ ;
- else if (STREQ (drv->src.format, "qcow2")) {
- if (!qemu16)
- NOT_SUPPORTED (g, false,
- _("discard cannot be enabled on this drive: "
- "qemu < 1.6 cannot do discard on qcow2 files"));
- }
- else {
- /* It's possible in future other formats will support discard, but
- * currently (qemu 1.7) none of them do.
- */
- NOT_SUPPORTED (g, false,
- _("discard cannot be enabled on this drive: "
- "qemu does not support discard for '%s' format
files"),
- drv->src.format);
- }
-
- switch (drv->src.protocol) {
- /* Protocols which support discard. */
- case drive_protocol_file:
- case drive_protocol_gluster:
- case drive_protocol_iscsi:
- case drive_protocol_nbd:
- case drive_protocol_rbd:
- case drive_protocol_sheepdog: /* XXX depends on server version */
- break;
-
- /* Protocols which don't support discard. */
- case drive_protocol_ftp:
- case drive_protocol_ftps:
- case drive_protocol_http:
- case drive_protocol_https:
- case drive_protocol_ssh:
- case drive_protocol_tftp:
- NOT_SUPPORTED (g, -1,
- _("discard cannot be enabled on this drive: "
- "protocol '%s' does not support discard"),
- guestfs_int_drive_protocol_to_string (drv->src.protocol));
- }
-
- return true;
-}
-
-static int
shutdown_direct (guestfs_h *g, void *datav, int check_for_errors)
{
struct backend_direct_data *data = datav;
@@ -1490,10 +968,8 @@ shutdown_direct (guestfs_h *g, void *datav, int check_for_errors)
data->guestfsd_sock[0] = '\0';
}
- free (data->qemu_help);
- data->qemu_help = NULL;
- free (data->qemu_devices);
- data->qemu_devices = NULL;
+ guestfs_int_free_qemu_data (data->qemu_data);
+ data->qemu_data = NULL;
return ret;
}
@@ -1517,7 +993,15 @@ max_disks_direct (guestfs_h *g, void *datav)
{
struct backend_direct_data *data = datav;
- if (qemu_supports_virtio_scsi (g, data))
+ /* Get qemu help text and version. */
+ if (data->qemu_data == NULL) {
+ data->qemu_data = guestfs_int_test_qemu (g, &data->qemu_version);
+ if (data->qemu_data == NULL)
+ return -1;
+ }
+
+ if (guestfs_int_qemu_supports_virtio_scsi (g, data->qemu_data,
+ &data->qemu_version))
return 255;
else
return 27; /* conservative estimate */
diff --git a/src/qemu.c b/src/qemu.c
new file mode 100644
index 0000000..88e0464
--- /dev/null
+++ b/src/qemu.c
@@ -0,0 +1,573 @@
+/* libguestfs
+ * Copyright (C) 2009-2016 Red Hat Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * Functions to handle qemu versions and features.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <inttypes.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <assert.h>
+#include <libintl.h>
+
+#include <libxml/uri.h>
+
+#include <pcre.h>
+
+#include "ignore-value.h"
+
+#include "guestfs.h"
+#include "guestfs-internal.h"
+#include "guestfs_protocol.h"
+
+COMPILE_REGEXP (re_major_minor, "(\\d+)\\.(\\d+)", 0)
+
+struct qemu_data {
+ char *qemu_help; /* Output of qemu -help. */
+ char *qemu_devices; /* Output of qemu -device ? */
+
+ int virtio_scsi; /* See function
+ guestfs_int_qemu_supports_virtio_scsi */
+};
+
+static void parse_qemu_version (guestfs_h *g, const char *, struct version
*qemu_version);
+static void read_all (guestfs_h *g, void *retv, const char *buf, size_t len);
+
+/**
+ * Test qemu binary (or wrapper) runs, and do C<qemu -help> so we know
+ * the version of qemu what options this qemu supports, and
+ * C<qemu -device ?> so we know what devices are available.
+ *
+ * The version number of qemu (from the C<-help> output) is saved in
+ * C<&qemu_version>.
+ */
+struct qemu_data *
+guestfs_int_test_qemu (guestfs_h *g, struct version *qemu_version)
+{
+ CLEANUP_CMD_CLOSE struct command *cmd1 = guestfs_int_new_command (g);
+ CLEANUP_CMD_CLOSE struct command *cmd2 = guestfs_int_new_command (g);
+ int r;
+ struct qemu_data *data;
+
+ data = safe_calloc (g, 1, sizeof *data);
+
+ guestfs_int_cmd_add_arg (cmd1, g->hv);
+ guestfs_int_cmd_add_arg (cmd1, "-display");
+ guestfs_int_cmd_add_arg (cmd1, "none");
+ guestfs_int_cmd_add_arg (cmd1, "-help");
+ guestfs_int_cmd_set_stdout_callback (cmd1, read_all, &data->qemu_help,
+ CMD_STDOUT_FLAG_WHOLE_BUFFER);
+ r = guestfs_int_cmd_run (cmd1);
+ if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0)
+ goto error;
+
+ parse_qemu_version (g, data->qemu_help, qemu_version);
+
+ guestfs_int_cmd_add_arg (cmd2, g->hv);
+ guestfs_int_cmd_add_arg (cmd2, "-display");
+ guestfs_int_cmd_add_arg (cmd2, "none");
+ guestfs_int_cmd_add_arg (cmd2, "-machine");
+ guestfs_int_cmd_add_arg (cmd2,
+#ifdef MACHINE_TYPE
+ MACHINE_TYPE ","
+#endif
+ "accel=kvm:tcg");
+ guestfs_int_cmd_add_arg (cmd2, "-device");
+ guestfs_int_cmd_add_arg (cmd2, "?");
+ guestfs_int_cmd_clear_capture_errors (cmd2);
+ guestfs_int_cmd_set_stderr_to_stdout (cmd2);
+ guestfs_int_cmd_set_stdout_callback (cmd2, read_all, &data->qemu_devices,
+ CMD_STDOUT_FLAG_WHOLE_BUFFER);
+ r = guestfs_int_cmd_run (cmd2);
+ if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0)
+ goto error;
+
+ return data;
+
+ error:
+ free (data);
+
+ if (r == -1)
+ return NULL;
+
+ guestfs_int_external_command_failed (g, r, g->hv, NULL);
+ return NULL;
+}
+
+/**
+ * Parse the first line of C<qemu_help> into the major and minor
+ * version of qemu, but don't fail if parsing is not possible.
+ */
+static void
+parse_qemu_version (guestfs_h *g, const char *qemu_help,
+ struct version *qemu_version)
+{
+ CLEANUP_FREE char *major_s = NULL, *minor_s = NULL;
+ int major_i, minor_i;
+
+ version_init_null (qemu_version);
+
+ if (!match2 (g, qemu_help, re_major_minor, &major_s, &minor_s)) {
+ parse_failed:
+ debug (g, "%s: failed to parse qemu version string from the first line of the
output of '%s -help'. When reporting this bug please include the -help
output.",
+ __func__, g->hv);
+ return;
+ }
+
+ major_i = guestfs_int_parse_unsigned_int (g, major_s);
+ if (major_i == -1)
+ goto parse_failed;
+
+ minor_i = guestfs_int_parse_unsigned_int (g, minor_s);
+ if (minor_i == -1)
+ goto parse_failed;
+
+ guestfs_int_version_from_values (qemu_version, major_i, minor_i, 0);
+
+ debug (g, "qemu version %d.%d", major_i, minor_i);
+}
+
+static void
+read_all (guestfs_h *g, void *retv, const char *buf, size_t len)
+{
+ char **ret = retv;
+
+ *ret = safe_strndup (g, buf, len);
+}
+
+/**
+ * Test if option is supported by qemu command line (just by grepping
+ * the help text).
+ */
+int
+guestfs_int_qemu_supports (guestfs_h *g, const struct qemu_data *data,
+ const char *option)
+{
+ return strstr (data->qemu_help, option) != NULL;
+}
+
+/**
+ * Test if device is supported by qemu (currently just greps the
+ * C<qemu -device ?> output).
+ */
+int
+guestfs_int_qemu_supports_device (guestfs_h *g,
+ const struct qemu_data *data,
+ const char *device_name)
+{
+ return strstr (data->qemu_devices, device_name) != NULL;
+}
+
+static int
+old_or_broken_virtio_scsi (const struct version *qemu_version)
+{
+ /* qemu 1.1 claims to support virtio-scsi but in reality it's broken. */
+ if (!guestfs_int_version_ge (qemu_version, 1, 2, 0))
+ return 1;
+
+ return 0;
+}
+
+/**
+ * Test if qemu supports virtio-scsi.
+ *
+ * Returns C<1> = use virtio-scsi, or C<0> = use virtio-blk.
+ */
+int
+guestfs_int_qemu_supports_virtio_scsi (guestfs_h *g, struct qemu_data *data,
+ const struct version *qemu_version)
+{
+ int r;
+
+ /* data->virtio_scsi has these values:
+ * 0 = untested (after handle creation)
+ * 1 = supported
+ * 2 = not supported (use virtio-blk)
+ * 3 = test failed (use virtio-blk)
+ */
+ if (data->virtio_scsi == 0) {
+ if (old_or_broken_virtio_scsi (qemu_version))
+ data->virtio_scsi = 2;
+ else {
+ r = guestfs_int_qemu_supports_device (g, data, VIRTIO_SCSI);
+ if (r > 0)
+ data->virtio_scsi = 1;
+ else if (r == 0)
+ data->virtio_scsi = 2;
+ else
+ data->virtio_scsi = 3;
+ }
+ }
+
+ return data->virtio_scsi == 1;
+}
+
+/**
+ * Escape a qemu parameter.
+ *
+ * Every C<,> becomes C<,,>. The caller must free the returned string.
+ */
+char *
+guestfs_int_qemu_escape_param (guestfs_h *g, const char *param)
+{
+ size_t i, len = strlen (param);
+ char *p, *ret;
+
+ ret = p = safe_malloc (g, len*2 + 1); /* max length of escaped name*/
+ for (i = 0; i < len; ++i) {
+ *p++ = param[i];
+ if (param[i] == ',')
+ *p++ = ',';
+ }
+ *p = '\0';
+
+ return ret;
+}
+
+static char *
+make_uri (guestfs_h *g, const char *scheme, const char *user,
+ const char *password,
+ struct drive_server *server, const char *path)
+{
+ xmlURI uri = { .scheme = (char *) scheme,
+ .user = (char *) user };
+ CLEANUP_FREE char *query = NULL;
+ CLEANUP_FREE char *pathslash = NULL;
+ CLEANUP_FREE char *userauth = NULL;
+
+ /* Need to add a leading '/' to URI paths since xmlSaveUri doesn't. */
+ if (path != NULL && path[0] != '/') {
+ pathslash = safe_asprintf (g, "/%s", path);
+ uri.path = pathslash;
+ }
+ else
+ uri.path = (char *) path;
+
+ /* Rebuild user:password. */
+ if (user != NULL && password != NULL) {
+ /* Keep the string in an own variable so it can be freed automatically. */
+ userauth = safe_asprintf (g, "%s:%s", user, password);
+ uri.user = userauth;
+ }
+
+ 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.
+ *
+ * Note that the qemu parameter is the bit after C<"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 L<qemu-img(1)>).
+ */
+char *
+guestfs_int_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->secret,
+ &src->servers[0], src->u.exportname);
+
+ case drive_protocol_ftps:
+ return make_uri (g, "ftps", src->username, src->secret,
+ &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, NULL,
+ &src->servers[0], src->u.exportname);
+ case drive_transport_tcp:
+ return make_uri (g, "gluster+tcp", NULL, NULL,
+ &src->servers[0], src->u.exportname);
+ case drive_transport_unix:
+ return make_uri (g, "gluster+unix", NULL, NULL,
+ &src->servers[0], NULL);
+ }
+
+ case drive_protocol_http:
+ return make_uri (g, "http", src->username, src->secret,
+ &src->servers[0], src->u.exportname);
+
+ case drive_protocol_https:
+ return make_uri (g, "https", src->username, src->secret,
+ &src->servers[0], src->u.exportname);
+
+ case drive_protocol_iscsi: {
+ CLEANUP_FREE char *escaped_hostname = NULL;
+ CLEANUP_FREE char *escaped_target = NULL;
+ CLEANUP_FREE char *userauth = NULL;
+ char port_str[16];
+ char *ret;
+
+ escaped_hostname =
+ (char *) xmlURIEscapeStr(BAD_CAST src->servers[0].u.hostname,
+ BAD_CAST "");
+ /* The target string must keep slash as it is, as exportname contains
+ * "iqn/lun".
+ */
+ escaped_target =
+ (char *) xmlURIEscapeStr(BAD_CAST src->u.exportname, BAD_CAST "/");
+ if (src->username != NULL && src->secret != NULL)
+ userauth = safe_asprintf (g, "%s%%%s@", src->username,
src->secret);
+ if (src->servers[0].port != 0)
+ snprintf (port_str, sizeof port_str, ":%d", src->servers[0].port);
+
+ ret = safe_asprintf (g, "iscsi://%s%s%s/%s",
+ userauth != NULL ? userauth : "",
+ escaped_hostname,
+ src->servers[0].port != 0 ? port_str : "",
+ escaped_target);
+
+ return ret;
+ }
+
+ 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
+ ret = safe_asprintf (g, "%s:exportname=%s", p, src->u.exportname);
+
+ return ret;
+ }
+
+ case drive_protocol_rbd: {
+ CLEANUP_FREE char *mon_host = NULL, *username = NULL, *secret = NULL;
+ const char *auth;
+ size_t n = 0;
+ size_t i, j;
+
+ /* build the list of all the mon hosts */
+ 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";
+
+ return safe_asprintf (g, "rbd:%s%s%s%s%s%s",
+ src->u.exportname,
+ src->nr_servers > 0 ? ":mon_host=" :
"",
+ src->nr_servers > 0 ? mon_host : "",
+ username ? username : "",
+ auth,
+ secret ? secret : "");
+ }
+
+ case drive_protocol_sheepdog:
+ if (src->nr_servers == 0)
+ return safe_asprintf (g, "sheepdog:%s", src->u.exportname);
+ 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);
+
+ case drive_protocol_ssh:
+ return make_uri (g, "ssh", src->username, src->secret,
+ &src->servers[0], src->u.exportname);
+
+ case drive_protocol_tftp:
+ return make_uri (g, "tftp", src->username, src->secret,
+ &src->servers[0], src->u.exportname);
+ }
+
+ abort ();
+}
+
+/**
+ * Test if discard is both supported by qemu AND possible with the
+ * underlying file or device. This returns C<1> if discard is
+ * possible. It returns C<0> if not possible and sets the error to
+ * the reason why.
+ *
+ * This function is called when the user set C<discard == "enable">.
+ */
+bool
+guestfs_int_discard_possible (guestfs_h *g, struct drive *drv,
+ const struct version *qemu_version)
+{
+ /* qemu >= 1.5. This was the first version that supported the
+ * discard option on -drive at all.
+ */
+ bool qemu15 = guestfs_int_version_ge (qemu_version, 1, 5, 0);
+ /* qemu >= 1.6. This was the first version that supported unmap on
+ * qcow2 backing files.
+ */
+ bool qemu16 = guestfs_int_version_ge (qemu_version, 1, 6, 0);
+
+ if (!qemu15)
+ NOT_SUPPORTED (g, false,
+ _("discard cannot be enabled on this drive: "
+ "qemu < 1.5"));
+
+ /* If it's an overlay, discard is not possible (on the underlying
+ * file). This has probably been caught earlier since we already
+ * checked that the drive is !readonly. Nevertheless ...
+ */
+ if (drv->overlay)
+ NOT_SUPPORTED (g, false,
+ _("discard cannot be enabled on this drive: "
+ "the drive has a read-only overlay"));
+
+ /* Look at the source format. */
+ if (drv->src.format == NULL) {
+ /* We could autodetect the format, but we don't ... yet. XXX */
+ NOT_SUPPORTED (g, false,
+ _("discard cannot be enabled on this drive: "
+ "you have to specify the format of the file"));
+ }
+ else if (STREQ (drv->src.format, "raw"))
+ /* OK */ ;
+ else if (STREQ (drv->src.format, "qcow2")) {
+ if (!qemu16)
+ NOT_SUPPORTED (g, false,
+ _("discard cannot be enabled on this drive: "
+ "qemu < 1.6 cannot do discard on qcow2 files"));
+ }
+ else {
+ /* It's possible in future other formats will support discard, but
+ * currently (qemu 1.7) none of them do.
+ */
+ NOT_SUPPORTED (g, false,
+ _("discard cannot be enabled on this drive: "
+ "qemu does not support discard for '%s' format
files"),
+ drv->src.format);
+ }
+
+ switch (drv->src.protocol) {
+ /* Protocols which support discard. */
+ case drive_protocol_file:
+ case drive_protocol_gluster:
+ case drive_protocol_iscsi:
+ case drive_protocol_nbd:
+ case drive_protocol_rbd:
+ case drive_protocol_sheepdog: /* XXX depends on server version */
+ break;
+
+ /* Protocols which don't support discard. */
+ case drive_protocol_ftp:
+ case drive_protocol_ftps:
+ case drive_protocol_http:
+ case drive_protocol_https:
+ case drive_protocol_ssh:
+ case drive_protocol_tftp:
+ NOT_SUPPORTED (g, -1,
+ _("discard cannot be enabled on this drive: "
+ "protocol '%s' does not support discard"),
+ guestfs_int_drive_protocol_to_string (drv->src.protocol));
+ }
+
+ return true;
+}
+
+/**
+ * Free the C<struct qemu_data>.
+ */
+void
+guestfs_int_free_qemu_data (struct qemu_data *data)
+{
+ if (data) {
+ free (data->qemu_help);
+ free (data->qemu_devices);
+ free (data);
+ }
+}
--
2.7.4