mirror of
https://github.com/libguestfs/libguestfs.git
synced 2026-03-21 22:53:37 +00:00
This function is now only used in one place, to print the version of
qemu in direct mode, when debugging is enabled.
Remove this function and replace with a direct command invocation
('qemu --version'). We only need to run this command when debugging
is enabled, and we copy all of the output to the debug channel.
I have made the assumption here that qemu -version does not try to
create a display device. (The previous invocation of qemu -help
actually ran 'qemu -display none -help' indicating that this was not
always the case.)
This is actually an improvement on before, since now we get to see the
full output of 'qemu --version'. The new output looks like:
libguestfs: begin testing qemu features
libguestfs: command: run: /usr/bin/qemu-kvm
libguestfs: command: run: \ -version
libguestfs: qemu: QEMU emulator version 10.1.0 (qemu-10.1.0-8.fc44)
libguestfs: qemu: Copyright (c) 2003-2025 Fabrice Bellard and the QEMU Project developers
867 lines
27 KiB
C
867 lines
27 KiB
C
/* libguestfs
|
||
* Copyright (C) 2009-2025 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 <json.h>
|
||
|
||
#include "full-write.h"
|
||
#include "ignore-value.h"
|
||
|
||
#include "guestfs.h"
|
||
#include "guestfs-internal.h"
|
||
#include "guestfs_protocol.h"
|
||
|
||
#define CLEANUP_JSON_OBJECT_PUT \
|
||
__attribute__((cleanup(cleanup_json_object_put)))
|
||
|
||
static void
|
||
cleanup_json_object_put (void *ptr)
|
||
{
|
||
json_object_put (* (json_object **) ptr);
|
||
}
|
||
|
||
struct qemu_data {
|
||
int generation; /* MEMO_GENERATION read from qemu.stat */
|
||
uint64_t prev_size; /* Size of qemu binary when cached. */
|
||
uint64_t prev_mtime; /* mtime of qemu binary when cached. */
|
||
|
||
char *qemu_help; /* Output of qemu -help. */
|
||
char *qemu_devices; /* Output of qemu -device ? */
|
||
char *query_kvm; /* Output of QMP query-kvm. */
|
||
|
||
/* The following fields are derived from the fields above. */
|
||
bool has_kvm; /* If KVM is available. */
|
||
};
|
||
|
||
static char *cache_filename (guestfs_h *g, const char *cachedir, const struct stat *, const char *suffix);
|
||
static int test_qemu_help (guestfs_h *g, struct qemu_data *data);
|
||
static int read_cache_qemu_help (guestfs_h *g, struct qemu_data *data, const char *filename);
|
||
static int write_cache_qemu_help (guestfs_h *g, const struct qemu_data *data, const char *filename);
|
||
static int test_qemu_devices (guestfs_h *g, struct qemu_data *data);
|
||
static int read_cache_qemu_devices (guestfs_h *g, struct qemu_data *data, const char *filename);
|
||
static int write_cache_qemu_devices (guestfs_h *g, const struct qemu_data *data, const char *filename);
|
||
static int test_query_kvm (guestfs_h *g, struct qemu_data *data);
|
||
static int read_cache_query_kvm (guestfs_h *g, struct qemu_data *data, const char *filename);
|
||
static int write_cache_query_kvm (guestfs_h *g, const struct qemu_data *data, const char *filename);
|
||
static int read_cache_qemu_stat (guestfs_h *g, struct qemu_data *data, const char *filename);
|
||
static int write_cache_qemu_stat (guestfs_h *g, const struct qemu_data *data, const char *filename);
|
||
static void parse_has_kvm (guestfs_h *g, const char *, bool *);
|
||
static void read_all (guestfs_h *g, void *retv, const char *buf, size_t len);
|
||
static int generic_read_cache (guestfs_h *g, const char *filename, char **strp);
|
||
static int generic_write_cache (guestfs_h *g, const char *filename, const char *str);
|
||
static int generic_qmp_test (guestfs_h *g, struct qemu_data *data, const char *qmp_command, char **outp);
|
||
|
||
/* This structure abstracts the data we are reading from qemu and how
|
||
* we get it.
|
||
*/
|
||
static const struct qemu_fields {
|
||
const char *name;
|
||
|
||
/* Function to perform the test on g->hv. This must set the correct
|
||
* data->[field] to non-NULL, or else return an error.
|
||
*/
|
||
int (*test) (guestfs_h *g, struct qemu_data *data);
|
||
|
||
/* Functions to read and write the cache file.
|
||
* read_cache returns -1 = error, 0 = no cache, 1 = cache data read.
|
||
* write_cache returns -1 = error, 0 = success.
|
||
*/
|
||
int (*read_cache) (guestfs_h *g, struct qemu_data *data,
|
||
const char *filename);
|
||
int (*write_cache) (guestfs_h *g, const struct qemu_data *data,
|
||
const char *filename);
|
||
} qemu_fields[] = {
|
||
{ "help",
|
||
test_qemu_help, read_cache_qemu_help, write_cache_qemu_help },
|
||
{ "devices",
|
||
test_qemu_devices, read_cache_qemu_devices, write_cache_qemu_devices },
|
||
{ "query-kvm",
|
||
test_query_kvm, read_cache_query_kvm, write_cache_query_kvm },
|
||
};
|
||
#define NR_FIELDS (sizeof qemu_fields / sizeof qemu_fields[0])
|
||
|
||
/* This is saved in the qemu-*.stat file, so if we decide to change the
|
||
* test_qemu memoization format/data in future, we should increment
|
||
* this to discard any memoized data cached by previous versions of
|
||
* libguestfs.
|
||
*/
|
||
#define MEMO_GENERATION 3
|
||
|
||
/**
|
||
* Test that the qemu binary (or wrapper) runs, and do C<qemu -help>
|
||
* and other commands so we can find out the version of qemu and what
|
||
* options this qemu supports.
|
||
*
|
||
* This caches the results in the cachedir so that as long as the qemu
|
||
* binary does not change, calling this is effectively free.
|
||
*/
|
||
struct qemu_data *
|
||
guestfs_int_test_qemu (guestfs_h *g)
|
||
{
|
||
struct stat statbuf;
|
||
struct qemu_data *data;
|
||
CLEANUP_FREE char *cachedir = NULL;
|
||
CLEANUP_FREE char *stat_filename = NULL;
|
||
int r;
|
||
size_t i;
|
||
|
||
if (stat (g->hv, &statbuf) == -1) {
|
||
perrorf (g, "stat: %s", g->hv);
|
||
return NULL;
|
||
}
|
||
|
||
cachedir = guestfs_int_lazy_make_supermin_appliance_dir (g);
|
||
if (cachedir == NULL)
|
||
return NULL;
|
||
|
||
/* Did we previously test the same version of qemu? */
|
||
debug (g, "checking for previously cached test results of %s, in %s",
|
||
g->hv, cachedir);
|
||
|
||
data = safe_calloc (g, 1, sizeof *data);
|
||
|
||
stat_filename = cache_filename (g, cachedir, &statbuf, "stat");
|
||
r = read_cache_qemu_stat (g, data, stat_filename);
|
||
if (r == -1)
|
||
goto error;
|
||
if (r == 0)
|
||
goto do_test;
|
||
|
||
if (data->generation != MEMO_GENERATION ||
|
||
data->prev_size != (uint64_t) statbuf.st_size ||
|
||
data->prev_mtime != (uint64_t) statbuf.st_mtime)
|
||
goto do_test;
|
||
|
||
debug (g, "loading previously cached test results");
|
||
|
||
for (i = 0; i < NR_FIELDS; ++i) {
|
||
CLEANUP_FREE char *filename =
|
||
cache_filename (g, cachedir, &statbuf, qemu_fields[i].name);
|
||
r = qemu_fields[i].read_cache (g, data, filename);
|
||
if (r == -1)
|
||
goto error;
|
||
if (r == 0) {
|
||
/* Cache gone, maybe deleted by the tmp cleaner, so we must run
|
||
* the full tests. We will have a partially filled qemu_data
|
||
* structure. The safest way to deal with that is to free
|
||
* it and start again.
|
||
*/
|
||
guestfs_int_free_qemu_data (data);
|
||
data = safe_calloc (g, 1, sizeof *data);
|
||
goto do_test;
|
||
}
|
||
}
|
||
|
||
goto out;
|
||
|
||
do_test:
|
||
for (i = 0; i < NR_FIELDS; ++i) {
|
||
if (qemu_fields[i].test (g, data) == -1)
|
||
goto error;
|
||
}
|
||
|
||
/* Now memoize the qemu output in the cache directory. */
|
||
debug (g, "saving test results");
|
||
|
||
for (i = 0; i < NR_FIELDS; ++i) {
|
||
CLEANUP_FREE char *filename =
|
||
cache_filename (g, cachedir, &statbuf, qemu_fields[i].name);
|
||
if (qemu_fields[i].write_cache (g, data, filename) == -1)
|
||
goto error;
|
||
}
|
||
|
||
/* Write the qemu.stat file last so that its presence indicates that
|
||
* the qemu.help and qemu.devices files ought to exist.
|
||
*/
|
||
data->generation = MEMO_GENERATION;
|
||
data->prev_size = statbuf.st_size;
|
||
data->prev_mtime = statbuf.st_mtime;
|
||
if (write_cache_qemu_stat (g, data, stat_filename) == -1)
|
||
goto error;
|
||
|
||
out:
|
||
/* Derived fields. */
|
||
parse_has_kvm (g, data->query_kvm, &data->has_kvm);
|
||
|
||
return data;
|
||
|
||
error:
|
||
guestfs_int_free_qemu_data (data);
|
||
return NULL;
|
||
}
|
||
|
||
/**
|
||
* Generate the filenames, for the stat file and the other cache
|
||
* files.
|
||
*
|
||
* By including the size and mtime in the filename we also ensure that
|
||
* the same user can use multiple versions of qemu without conflicts.
|
||
*/
|
||
static char *
|
||
cache_filename (guestfs_h *g, const char *cachedir,
|
||
const struct stat *statbuf, const char *suffix)
|
||
{
|
||
return safe_asprintf (g, "%s/qemu-%" PRIu64 "-%" PRIu64 ".%s",
|
||
cachedir,
|
||
(uint64_t) statbuf->st_size,
|
||
(uint64_t) statbuf->st_mtime,
|
||
suffix);
|
||
}
|
||
|
||
static int
|
||
test_qemu_help (guestfs_h *g, struct qemu_data *data)
|
||
{
|
||
CLEANUP_CMD_CLOSE struct command *cmd = guestfs_int_new_command (g);
|
||
int r;
|
||
|
||
guestfs_int_cmd_add_arg (cmd, g->hv);
|
||
guestfs_int_cmd_add_arg (cmd, "-display");
|
||
guestfs_int_cmd_add_arg (cmd, "none");
|
||
guestfs_int_cmd_add_arg (cmd, "-help");
|
||
guestfs_int_cmd_set_stdout_callback (cmd, read_all, &data->qemu_help,
|
||
CMD_STDOUT_FLAG_WHOLE_BUFFER);
|
||
r = guestfs_int_cmd_run (cmd);
|
||
if (r == -1)
|
||
return -1;
|
||
if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
|
||
guestfs_int_external_command_failed (g, r, g->hv, NULL);
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
read_cache_qemu_help (guestfs_h *g, struct qemu_data *data,
|
||
const char *filename)
|
||
{
|
||
return generic_read_cache (g, filename, &data->qemu_help);
|
||
}
|
||
|
||
static int
|
||
write_cache_qemu_help (guestfs_h *g, const struct qemu_data *data,
|
||
const char *filename)
|
||
{
|
||
return generic_write_cache (g, filename, data->qemu_help);
|
||
}
|
||
|
||
static int
|
||
test_qemu_devices (guestfs_h *g, struct qemu_data *data)
|
||
{
|
||
CLEANUP_CMD_CLOSE struct command *cmd = guestfs_int_new_command (g);
|
||
int r;
|
||
|
||
guestfs_int_cmd_add_arg (cmd, g->hv);
|
||
guestfs_int_cmd_add_arg (cmd, "-display");
|
||
guestfs_int_cmd_add_arg (cmd, "none");
|
||
guestfs_int_cmd_add_arg (cmd, "-machine");
|
||
guestfs_int_cmd_add_arg (cmd,
|
||
#ifdef MACHINE_TYPE
|
||
MACHINE_TYPE ","
|
||
#endif
|
||
"accel=kvm:hvf:tcg");
|
||
guestfs_int_cmd_add_arg (cmd, "-device");
|
||
guestfs_int_cmd_add_arg (cmd, "?");
|
||
guestfs_int_cmd_clear_capture_errors (cmd);
|
||
guestfs_int_cmd_set_stderr_to_stdout (cmd);
|
||
guestfs_int_cmd_set_stdout_callback (cmd, read_all, &data->qemu_devices,
|
||
CMD_STDOUT_FLAG_WHOLE_BUFFER);
|
||
r = guestfs_int_cmd_run (cmd);
|
||
if (r == -1)
|
||
return -1;
|
||
if (!WIFEXITED (r) || WEXITSTATUS (r) != 0) {
|
||
guestfs_int_external_command_failed (g, r, g->hv, NULL);
|
||
return -1;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
static int
|
||
read_cache_qemu_devices (guestfs_h *g, struct qemu_data *data,
|
||
const char *filename)
|
||
{
|
||
return generic_read_cache (g, filename, &data->qemu_devices);
|
||
}
|
||
|
||
static int
|
||
write_cache_qemu_devices (guestfs_h *g, const struct qemu_data *data,
|
||
const char *filename)
|
||
{
|
||
return generic_write_cache (g, filename, data->qemu_devices);
|
||
}
|
||
|
||
static int
|
||
test_query_kvm (guestfs_h *g, struct qemu_data *data)
|
||
{
|
||
return generic_qmp_test (g, data, "query-kvm", &data->query_kvm);
|
||
}
|
||
|
||
static int
|
||
read_cache_query_kvm (guestfs_h *g, struct qemu_data *data,
|
||
const char *filename)
|
||
{
|
||
return generic_read_cache (g, filename, &data->query_kvm);
|
||
}
|
||
|
||
static int
|
||
write_cache_query_kvm (guestfs_h *g, const struct qemu_data *data,
|
||
const char *filename)
|
||
{
|
||
return generic_write_cache (g, filename, data->query_kvm);
|
||
}
|
||
|
||
static int
|
||
read_cache_qemu_stat (guestfs_h *g, struct qemu_data *data,
|
||
const char *filename)
|
||
{
|
||
CLEANUP_FCLOSE FILE *fp = fopen (filename, "r");
|
||
if (fp == NULL) {
|
||
if (errno == ENOENT)
|
||
return 0; /* no cache, run the test instead */
|
||
perrorf (g, "%s", filename);
|
||
return -1;
|
||
}
|
||
|
||
if (fscanf (fp, "%d %" SCNu64 " %" SCNu64,
|
||
&data->generation,
|
||
&data->prev_size,
|
||
&data->prev_mtime) != 3)
|
||
return 0;
|
||
|
||
return 1;
|
||
}
|
||
|
||
static int
|
||
write_cache_qemu_stat (guestfs_h *g, const struct qemu_data *data,
|
||
const char *filename)
|
||
{
|
||
CLEANUP_FCLOSE FILE *fp = fopen (filename, "w");
|
||
if (fp == NULL) {
|
||
perrorf (g, "%s", filename);
|
||
return -1;
|
||
}
|
||
/* The path to qemu is stored for information only, it is not
|
||
* used when we parse the file.
|
||
*/
|
||
if (fprintf (fp, "%d %" PRIu64 " %" PRIu64 " %s\n",
|
||
data->generation,
|
||
data->prev_size,
|
||
data->prev_mtime,
|
||
g->hv) == -1) {
|
||
perrorf (g, "%s: write", filename);
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Parse the json output from QMP query-kvm to find out if KVM is
|
||
* enabled on this machine. Don't fail if parsing is not possible,
|
||
* assume KVM is available.
|
||
*
|
||
* The JSON output looks like:
|
||
* {"return": {"enabled": true, "present": true}}
|
||
*/
|
||
static void
|
||
parse_has_kvm (guestfs_h *g, const char *json, bool *ret)
|
||
{
|
||
CLEANUP_JSON_OBJECT_PUT json_object *tree = NULL;
|
||
json_tokener *tok;
|
||
enum json_tokener_error err;
|
||
json_object *return_node, *enabled_node;
|
||
|
||
*ret = true; /* Assume KVM is enabled. */
|
||
|
||
if (!json)
|
||
return;
|
||
|
||
tok = json_tokener_new ();
|
||
json_tokener_set_flags (tok,
|
||
JSON_TOKENER_STRICT | JSON_TOKENER_VALIDATE_UTF8);
|
||
tree = json_tokener_parse_ex (tok, json, strlen (json));
|
||
err = json_tokener_get_error (tok);
|
||
if (err != json_tokener_success) {
|
||
debug (g, "QMP parse error: %s (ignored)", json_tokener_error_desc (err));
|
||
json_tokener_free (tok);
|
||
return;
|
||
}
|
||
json_tokener_free (tok);
|
||
|
||
return_node = json_object_object_get (tree, "return");
|
||
if (json_object_get_type (return_node) != json_type_object) {
|
||
debug (g, "QMP query-kvm: no \"return\" node (ignored)");
|
||
return;
|
||
}
|
||
enabled_node = json_object_object_get (return_node, "enabled");
|
||
*ret = json_object_get_boolean (enabled_node);
|
||
}
|
||
|
||
/**
|
||
* Generic functions for reading and writing the cache files, used
|
||
* where we are just reading and writing plain text strings.
|
||
*/
|
||
static int
|
||
generic_read_cache (guestfs_h *g, const char *filename, char **strp)
|
||
{
|
||
if (access (filename, R_OK) == -1 && errno == ENOENT)
|
||
return 0; /* no cache, run the test instead */
|
||
if (guestfs_int_read_whole_file (g, filename, strp, NULL) == -1)
|
||
return -1;
|
||
return 1;
|
||
}
|
||
|
||
static int
|
||
generic_write_cache (guestfs_h *g, const char *filename, const char *str)
|
||
{
|
||
CLEANUP_CLOSE int fd = -1;
|
||
size_t len;
|
||
|
||
fd = open (filename, O_WRONLY|O_CREAT|O_TRUNC|O_NOCTTY|O_CLOEXEC, 0666);
|
||
if (fd == -1) {
|
||
perrorf (g, "%s", filename);
|
||
return -1;
|
||
}
|
||
|
||
len = strlen (str);
|
||
if (full_write (fd, str, len) != len) {
|
||
perrorf (g, "%s: write", filename);
|
||
return -1;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
/**
|
||
* Run a generic QMP test on the QEMU binary.
|
||
*/
|
||
static int
|
||
generic_qmp_test (guestfs_h *g, struct qemu_data *data,
|
||
const char *qmp_command,
|
||
char **outp)
|
||
{
|
||
CLEANUP_CMD_CLOSE struct command *cmd = guestfs_int_new_command (g);
|
||
int r, fd;
|
||
CLEANUP_FCLOSE FILE *fp = NULL;
|
||
CLEANUP_FREE char *line = NULL;
|
||
size_t allocsize = 0;
|
||
ssize_t len;
|
||
|
||
guestfs_int_cmd_add_string_unquoted (cmd, "echo ");
|
||
/* QMP is modal. You have to send the qmp_capabilities command first. */
|
||
guestfs_int_cmd_add_string_unquoted (cmd, "'{ \"execute\": \"qmp_capabilities\" }' ");
|
||
guestfs_int_cmd_add_string_unquoted (cmd, "'{ \"execute\": \"");
|
||
guestfs_int_cmd_add_string_unquoted (cmd, qmp_command);
|
||
guestfs_int_cmd_add_string_unquoted (cmd, "\" }' ");
|
||
/* Exit QEMU after sending the commands. */
|
||
guestfs_int_cmd_add_string_unquoted (cmd, "'{ \"execute\": \"quit\" }' ");
|
||
guestfs_int_cmd_add_string_unquoted (cmd, " | ");
|
||
guestfs_int_cmd_add_string_unquoted (cmd, "QEMU_AUDIO_DRV=none ");
|
||
guestfs_int_cmd_add_string_quoted (cmd, g->hv);
|
||
guestfs_int_cmd_add_string_unquoted (cmd, " -display none");
|
||
guestfs_int_cmd_add_string_unquoted (cmd, " -machine ");
|
||
guestfs_int_cmd_add_string_quoted (cmd,
|
||
#ifdef MACHINE_TYPE
|
||
MACHINE_TYPE ","
|
||
#endif
|
||
"accel=kvm:hvf:tcg");
|
||
guestfs_int_cmd_add_string_unquoted (cmd, " -qmp stdio");
|
||
guestfs_int_cmd_clear_capture_errors (cmd);
|
||
|
||
fd = guestfs_int_cmd_pipe_run (cmd, "r");
|
||
if (fd == -1)
|
||
return -1;
|
||
|
||
/* Read the output line by line. We expect to see:
|
||
* line 1: {"QMP": {"version": ... } } # greeting from QMP
|
||
* line 2: {"return": {}} # output from qmp_capabilities
|
||
* line 3: {"return": ... } # the data from our qmp_command
|
||
* line 4: {"return": {}} # output from quit
|
||
* line 5: {"timestamp": ...} # shutdown event
|
||
*/
|
||
fp = fdopen (fd, "r"); /* this will close (fd) at end of scope */
|
||
if (fp == NULL) {
|
||
perrorf (g, "fdopen");
|
||
return -1;
|
||
}
|
||
len = getline (&line, &allocsize, fp); /* line 1 */
|
||
if (len == -1 || strstr (line, "\"QMP\"") == NULL) {
|
||
parse_failure:
|
||
debug (g, "did not understand QMP monitor output from %s (ignored)",
|
||
g->hv);
|
||
/* QMP tests are optional, don't fail if we cannot parse the
|
||
* output. However we MUST return an empty string on non-error
|
||
* paths.
|
||
*/
|
||
*outp = safe_strdup (g, "");
|
||
return 0;
|
||
}
|
||
len = getline (&line, &allocsize, fp); /* line 2 */
|
||
if (len == -1 || strstr (line, "\"return\"") == NULL)
|
||
goto parse_failure;
|
||
len = getline (&line, &allocsize, fp); /* line 3 */
|
||
if (len == -1 || strstr (line, "\"return\"") == NULL)
|
||
goto parse_failure;
|
||
*outp = safe_strdup (g, line);
|
||
/* The other lines we don't care about, so finish parsing here. */
|
||
ignore_value (getline (&line, &allocsize, fp)); /* line 4 */
|
||
ignore_value (getline (&line, &allocsize, fp)); /* line 5 */
|
||
|
||
r = guestfs_int_cmd_pipe_wait (cmd);
|
||
/* QMP tests are optional, don't fail if the tests fail. */
|
||
if (r == -1 || !WIFEXITED (r) || WEXITSTATUS (r) != 0) {
|
||
debug (g, "%s wait failed or unexpected exit status (ignored)", g->hv);
|
||
return 0;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
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 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;
|
||
}
|
||
|
||
bool
|
||
guestfs_int_platform_has_kvm (guestfs_h *g, const struct qemu_data *data)
|
||
{
|
||
return data->has_kvm;
|
||
}
|
||
|
||
/**
|
||
* Escape a qemu parameter.
|
||
*
|
||
* Every C<,> becomes C<,,>. The caller must free the returned string.
|
||
*
|
||
* XXX This functionality is now only used when constructing a
|
||
* qemu-img command in F<lib/create.c>. We should extend the qemuopts
|
||
* library to cover this use case.
|
||
*/
|
||
char *
|
||
guestfs_int_qemu_escape_param (guestfs_h *g, const char *param)
|
||
{
|
||
size_t i;
|
||
const size_t 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_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_STRING_LIST char **hosts = NULL;
|
||
CLEANUP_FREE char *mon_host = NULL, *username = NULL, *secret = NULL;
|
||
const char *auth;
|
||
size_t i;
|
||
|
||
/* Build the list of all the mon hosts. */
|
||
hosts = safe_calloc (g, src->nr_servers + 1, sizeof (char *));
|
||
|
||
for (i = 0; i < src->nr_servers; i++) {
|
||
CLEANUP_FREE char *escaped_host;
|
||
|
||
escaped_host =
|
||
guestfs_int_replace_string (src->servers[i].u.hostname, ":", "\\:");
|
||
if (escaped_host == NULL) g->abort_cb ();
|
||
hosts[i] =
|
||
safe_asprintf (g, "%s\\:%d", escaped_host, src->servers[i].port);
|
||
}
|
||
mon_host = guestfs_int_join_strings ("\\;", hosts);
|
||
|
||
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_ssh:
|
||
return make_uri (g, "ssh", src->username, src->secret,
|
||
&src->servers[0], src->u.exportname);
|
||
}
|
||
|
||
abort ();
|
||
}
|
||
|
||
/**
|
||
* Test if discard is 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)
|
||
{
|
||
/* 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") || STREQ (drv->src.format, "qcow2"))
|
||
/* OK */ ;
|
||
else {
|
||
/* It's possible other formats support discard, but we can enable
|
||
* them on a case-by-case basis.
|
||
*/
|
||
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_iscsi:
|
||
case drive_protocol_nbd:
|
||
case drive_protocol_rbd:
|
||
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:
|
||
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->query_kvm);
|
||
free (data);
|
||
}
|
||
}
|