From 4e36f2fa9ea048b33b738d7d2ed7c418d7d221bb Mon Sep 17 00:00:00 2001 From: "Richard W.M. Jones" Date: Thu, 12 May 2016 19:01:46 +0100 Subject: [PATCH] lib: Move qemu testing code to a new module called 'qemu.c'. 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 10e02deed..4f1f684a7 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 d2879f49d..bdac5e3e7 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 bcb367b5b..0340687f9 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 689a53876..20f64712a 100644 --- a/src/launch-direct.c +++ b/src/launch-direct.c @@ -42,31 +42,19 @@ #include #include -#include - -#include - #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. */ }; @@ -74,9 +62,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) @@ -293,8 +278,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. @@ -351,19 +339,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 @@ -374,7 +362,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). */ @@ -419,7 +407,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)) @@ -452,7 +440,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"); @@ -460,7 +449,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. */ @@ -583,7 +573,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: @@ -851,6 +842,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) @@ -932,148 +925,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) @@ -1087,379 +938,6 @@ is_openable (guestfs_h *g, const char *path, int flags) return 1; } -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) { @@ -1498,10 +976,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; } @@ -1525,7 +1001,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 000000000..88e0464ae --- /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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#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 so we know + * the version of qemu what options this qemu supports, and + * C 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 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 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). + */ +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. + */ +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. + */ +void +guestfs_int_free_qemu_data (struct qemu_data *data) +{ + if (data) { + free (data->qemu_help); + free (data->qemu_devices); + free (data); + } +}