mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-22 07:03:38 +00:00
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.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -125,6 +125,7 @@ libguestfs_la_SOURCES = \
|
||||
osinfo.c \
|
||||
private-data.c \
|
||||
proto.c \
|
||||
qemu.c \
|
||||
stringsbuf.c \
|
||||
structs-compare.c \
|
||||
structs-copy.c \
|
||||
|
||||
@@ -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 *);
|
||||
|
||||
|
||||
@@ -42,31 +42,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. */
|
||||
};
|
||||
@@ -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 */
|
||||
|
||||
573
src/qemu.c
Normal file
573
src/qemu.c
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user