2022-07-05 20:40:16 +00:00
|
|
|
/*
|
|
|
|
* qemu_nbdkit.c: helpers for using nbdkit
|
|
|
|
*
|
|
|
|
* 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.1 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, see
|
|
|
|
* <http://www.gnu.org/licenses/>.
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <config.h>
|
|
|
|
#include <glib.h>
|
meson: Improve nbdkit configurability
Currently, nbdkit support will automatically be enabled as long as
the pidfd_open(2) syscall is available. Optionally, libnbd is used
to generate more user-friendly error messages.
In theory this is all good, since use of nbdkit is supposed to be
transparent to the user. In practice, however, there is a problem:
if support for it is enabled at build time and the necessary
runtime components are installed, nbdkit will always be preferred,
with no way for the user to opt out.
This will arguably be fine in the long run, but right now none of
the platforms that we target ships with a SELinux policy that
allows libvirt to launch nbdkit, and the AppArmor policy that we
maintain ourselves hasn't been updated either.
So, in practice, as of today having nbdkit installed on the host
makes network disks completely unusable unless you're willing to
compromise the overall security of the system by disabling
SELinux/AppArmor.
In order to make the transition smoother, provide a convenient
way for users and distro packagers to disable nbdkit support at
compile time until SELinux and AppArmor are ready.
In the process, detection is completely overhauled. libnbd is
made mandatory when nbdkit support is enabled, since availability
across operating systems is comparable and offering users the
option to make error messages worse doesn't make a lot of sense;
we also make sure that an explicit request from the user to
enable/disable nbdkit support is either complied with, or results
in a build failure when that's not possible. Last but not least,
we avoid linking against libnbd when nbdkit support is disabled.
At the RPM level, we disable the feature when building against
anything older than Fedora 40, which still doesn't have the
necessary SELinux bits but will hopefully gain them by the time
it's released. We also allow nbdkit support to be disabled at
build time the same way as other optional features, that is, by
passing "--define '_without_nbdkit 1'" to rpmbuild. Finally, if
nbdkit support has been disabled, installing libvirt will no
longer drag it in as a (weak) dependency.
Signed-off-by: Andrea Bolognani <abologna@redhat.com>
Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com>
2023-10-04 22:37:09 +00:00
|
|
|
#if WITH_NBDKIT
|
qemu: try to connect to nbdkit early to detect errors
When using nbdkit to serve a network disk source, the nbdkit process
will start and wait for an nbd connection before actually attempting to
connect to the (remote) disk location. Because of this, nbdkit will not
report an error until after qemu is launched and tries to read from the
disk. This results in a fairly user-unfriendly error saying that qemu
was unable to start because "Requested export not available".
Ideally we'd like to be able to tell the user *why* the export is not
available, but this sort of information is only available to nbdkit, not
qemu. It could be because the url was incorrect, or because of an
authentication failure, or one of many other possibilities.
To make this friendlier for users and easier to detect
misconfigurations, try to connect to nbdkit immediately after starting
nbdkit and before we try to start qemu. This requires adding a
dependency on libnbd. If an error occurs when connecting to nbdkit, read
back from the nbdkit error log and provide that information in the error
report from qemuNbdkitProcessStart().
User-visible change demonstrated below:
Previous error:
$ virsh start nbdkit-test
2023-01-18 19:47:45.778+0000: 30895: error : virNetClientProgramDispatchError:172 : internal
error: process exited while connecting to monitor: 2023-01-18T19:47:45.704658Z
qemu-system-x86_64: -blockdev {"driver":"nbd","server":{"type":"unix",
"path":"/var/lib/libvirt/qemu/domain-1-nbdkit-test/nbdkit-libvirt-1-storage.socket"},
"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}: Requested export not
available
error: Failed to start domain 'nbdkit-test'
error: internal error: process exited while connecting to monitor: 2023-01-18T19:47:45.704658Z
qemu-system-x86_64: -blockdev {"driver":"nbd","server":{"type":"unix",
"path":"/var/lib/libvirt/qemu/domain-1-nbdkit-test/nbdkit-libvirt-1-storage.socket"},
"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}: Requested export not
available
After this change:
$ virsh start nbdkit-test
2023-01-18 19:44:36.242+0000: 30895: error : virNetClientProgramDispatchError:172 : internal
error: Failed to connect to nbdkit for 'http://localhost:8888/nonexistent.iso': nbdkit: curl[1]:
error: problem doing HEAD request to fetch size of URL [http://localhost:8888/nonexistent.iso]:
HTTP response code said error: The requested URL returned error: 404
error: Failed to start domain 'nbdkit-test'
error: internal error: Failed to connect to nbdkit for 'http://localhost:8888/nonexistent.iso]:
error: problem doing HEAD request to fetch size of URL [http://localhost:8888/nonexistent.iso]:
HTTP response code said error: The requested URL returned error: 404
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
2022-12-16 23:10:49 +00:00
|
|
|
# include <libnbd.h>
|
|
|
|
#endif
|
2022-10-05 17:03:33 +00:00
|
|
|
#include <sys/syscall.h>
|
2022-07-05 20:40:16 +00:00
|
|
|
|
|
|
|
#include "vircommand.h"
|
|
|
|
#include "virerror.h"
|
|
|
|
#include "virlog.h"
|
|
|
|
#include "virpidfile.h"
|
2022-07-08 19:20:20 +00:00
|
|
|
#include "virtime.h"
|
2022-07-05 20:40:16 +00:00
|
|
|
#include "virutil.h"
|
|
|
|
#include "qemu_block.h"
|
|
|
|
#include "qemu_conf.h"
|
|
|
|
#include "qemu_domain.h"
|
|
|
|
#include "qemu_extdevice.h"
|
|
|
|
#include "qemu_nbdkit.h"
|
2022-08-19 22:21:52 +00:00
|
|
|
#define LIBVIRT_QEMU_NBDKITPRIV_H_ALLOW
|
|
|
|
#include "qemu_nbdkitpriv.h"
|
2022-10-05 17:03:33 +00:00
|
|
|
#include "qemu_process.h"
|
2022-07-05 20:40:16 +00:00
|
|
|
#include "qemu_security.h"
|
|
|
|
|
|
|
|
#include <fcntl.h>
|
|
|
|
|
|
|
|
#define VIR_FROM_THIS VIR_FROM_QEMU
|
|
|
|
|
|
|
|
VIR_LOG_INIT("qemu.nbdkit");
|
|
|
|
|
2022-10-05 17:03:33 +00:00
|
|
|
#if WITH_NBDKIT
|
|
|
|
# define WITHOUT_NBDKIT_UNUSED
|
|
|
|
#else
|
|
|
|
# define WITHOUT_NBDKIT_UNUSED G_GNUC_UNUSED
|
|
|
|
#endif
|
|
|
|
|
2022-07-05 20:40:16 +00:00
|
|
|
VIR_ENUM_IMPL(qemuNbdkitCaps,
|
|
|
|
QEMU_NBDKIT_CAPS_LAST,
|
|
|
|
/* 0 */
|
|
|
|
"plugin-curl", /* QEMU_NBDKIT_CAPS_PLUGIN_CURL */
|
|
|
|
"plugin-ssh", /* QEMU_NBDKIT_CAPS_PLUGIN_SSH */
|
|
|
|
"filter-readahead", /* QEMU_NBDKIT_CAPS_FILTER_READAHEAD */
|
|
|
|
);
|
|
|
|
|
|
|
|
struct _qemuNbdkitCaps {
|
|
|
|
GObject parent;
|
|
|
|
|
|
|
|
char *path;
|
|
|
|
char *version;
|
2022-12-09 16:29:25 +00:00
|
|
|
char *filterDir;
|
|
|
|
char *pluginDir;
|
|
|
|
|
2022-07-21 22:16:40 +00:00
|
|
|
time_t ctime;
|
|
|
|
time_t libvirtCtime;
|
|
|
|
time_t pluginDirMtime;
|
|
|
|
time_t filterDirMtime;
|
|
|
|
unsigned int libvirtVersion;
|
2022-07-05 20:40:16 +00:00
|
|
|
|
|
|
|
virBitmap *flags;
|
|
|
|
};
|
|
|
|
G_DEFINE_TYPE(qemuNbdkitCaps, qemu_nbdkit_caps, G_TYPE_OBJECT);
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemuNbdkitCheckCommandCap(qemuNbdkitCaps *nbdkit,
|
|
|
|
virCommand *cmd,
|
|
|
|
qemuNbdkitCapsFlags cap)
|
|
|
|
{
|
|
|
|
if (virCommandRun(cmd, NULL) != 0)
|
|
|
|
return;
|
|
|
|
|
|
|
|
VIR_DEBUG("Setting nbdkit capability %i", cap);
|
|
|
|
ignore_value(virBitmapSetBit(nbdkit->flags, cap));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemuNbdkitQueryFilter(qemuNbdkitCaps *nbdkit,
|
|
|
|
const char *filter,
|
|
|
|
qemuNbdkitCapsFlags cap)
|
|
|
|
{
|
|
|
|
g_autoptr(virCommand) cmd = virCommandNewArgList(nbdkit->path,
|
|
|
|
"--version",
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
virCommandAddArgPair(cmd, "--filter", filter);
|
|
|
|
|
|
|
|
/* use null plugin to check for filter */
|
|
|
|
virCommandAddArg(cmd, "null");
|
|
|
|
|
|
|
|
qemuNbdkitCheckCommandCap(nbdkit, cmd, cap);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemuNbdkitQueryPlugin(qemuNbdkitCaps *nbdkit,
|
|
|
|
const char *plugin,
|
|
|
|
qemuNbdkitCapsFlags cap)
|
|
|
|
{
|
|
|
|
g_autoptr(virCommand) cmd = virCommandNewArgList(nbdkit->path,
|
|
|
|
plugin,
|
|
|
|
"--version",
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
qemuNbdkitCheckCommandCap(nbdkit, cmd, cap);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemuNbdkitCapsQueryPlugins(qemuNbdkitCaps *nbdkit)
|
|
|
|
{
|
|
|
|
qemuNbdkitQueryPlugin(nbdkit, "curl", QEMU_NBDKIT_CAPS_PLUGIN_CURL);
|
|
|
|
qemuNbdkitQueryPlugin(nbdkit, "ssh", QEMU_NBDKIT_CAPS_PLUGIN_SSH);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemuNbdkitCapsQueryFilters(qemuNbdkitCaps *nbdkit)
|
|
|
|
{
|
|
|
|
qemuNbdkitQueryFilter(nbdkit, "readahead",
|
|
|
|
QEMU_NBDKIT_CAPS_FILTER_READAHEAD);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
2022-12-09 16:29:25 +00:00
|
|
|
qemuNbdkitCapsQueryBuildConfig(qemuNbdkitCaps *nbdkit)
|
2022-07-05 20:40:16 +00:00
|
|
|
{
|
2022-12-09 16:29:25 +00:00
|
|
|
size_t i;
|
|
|
|
g_autofree char *output = NULL;
|
|
|
|
g_auto(GStrv) lines = NULL;
|
|
|
|
const char *line;
|
2022-07-05 20:40:16 +00:00
|
|
|
g_autoptr(virCommand) cmd = virCommandNewArgList(nbdkit->path,
|
2022-12-09 16:29:25 +00:00
|
|
|
"--dump-config",
|
2022-07-05 20:40:16 +00:00
|
|
|
NULL);
|
|
|
|
|
2022-12-09 16:29:25 +00:00
|
|
|
virCommandSetOutputBuffer(cmd, &output);
|
2022-07-05 20:40:16 +00:00
|
|
|
|
|
|
|
if (virCommandRun(cmd, NULL) != 0)
|
|
|
|
return -1;
|
|
|
|
|
2022-12-09 16:29:25 +00:00
|
|
|
lines = g_strsplit(output, "\n", 0);
|
|
|
|
if (!lines)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
for (i = 0; (line = lines[i]); i++) {
|
|
|
|
const char *key;
|
|
|
|
const char *val;
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
p = strchr(line, '=');
|
|
|
|
if (!p)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
*p = '\0';
|
|
|
|
key = line;
|
|
|
|
val = p + 1;
|
|
|
|
|
|
|
|
VIR_DEBUG("Got nbdkit config value %s=%s", key, val);
|
|
|
|
|
|
|
|
if (STREQ(key, "version"))
|
|
|
|
nbdkit->version = g_strdup(val);
|
|
|
|
else if (STREQ(key, "filterdir"))
|
|
|
|
nbdkit->filterDir = g_strdup(val);
|
|
|
|
else if (STREQ(key, "plugindir"))
|
|
|
|
nbdkit->pluginDir = g_strdup(val);
|
|
|
|
}
|
2022-07-05 20:40:16 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemuNbdkitCapsFinalize(GObject *object)
|
|
|
|
{
|
|
|
|
qemuNbdkitCaps *nbdkit = QEMU_NBDKIT_CAPS(object);
|
|
|
|
|
|
|
|
g_clear_pointer(&nbdkit->path, g_free);
|
|
|
|
g_clear_pointer(&nbdkit->version, g_free);
|
2022-12-09 16:29:25 +00:00
|
|
|
g_clear_pointer(&nbdkit->filterDir, g_free);
|
|
|
|
g_clear_pointer(&nbdkit->pluginDir, g_free);
|
2022-07-05 20:40:16 +00:00
|
|
|
g_clear_pointer(&nbdkit->flags, virBitmapFree);
|
|
|
|
|
|
|
|
G_OBJECT_CLASS(qemu_nbdkit_caps_parent_class)->finalize(object);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
qemu_nbdkit_caps_init(qemuNbdkitCaps *caps)
|
|
|
|
{
|
|
|
|
caps->flags = virBitmapNew(QEMU_NBDKIT_CAPS_LAST);
|
|
|
|
caps->version = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemu_nbdkit_caps_class_init(qemuNbdkitCapsClass *klass)
|
|
|
|
{
|
|
|
|
GObjectClass *obj = G_OBJECT_CLASS(klass);
|
|
|
|
|
|
|
|
obj->finalize = qemuNbdkitCapsFinalize;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
qemuNbdkitCaps *
|
|
|
|
qemuNbdkitCapsNew(const char *path)
|
|
|
|
{
|
|
|
|
qemuNbdkitCaps *caps = g_object_new(QEMU_TYPE_NBDKIT_CAPS, NULL);
|
|
|
|
caps->path = g_strdup(path);
|
|
|
|
|
|
|
|
return caps;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-21 22:16:40 +00:00
|
|
|
static time_t
|
|
|
|
qemuNbdkitGetDirMtime(const char *moddir)
|
|
|
|
{
|
|
|
|
struct stat st;
|
|
|
|
|
|
|
|
if (stat(moddir, &st) < 0) {
|
|
|
|
VIR_DEBUG("Failed to stat nbdkit module directory '%s': %s",
|
|
|
|
moddir,
|
|
|
|
g_strerror(errno));
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return st.st_mtime;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-21 22:25:16 +00:00
|
|
|
static void
|
2022-07-05 20:40:16 +00:00
|
|
|
qemuNbdkitCapsQuery(qemuNbdkitCaps *caps)
|
|
|
|
{
|
2022-07-21 22:16:40 +00:00
|
|
|
struct stat st;
|
|
|
|
|
|
|
|
if (stat(caps->path, &st) < 0) {
|
|
|
|
VIR_DEBUG("Failed to stat nbdkit binary '%s': %s",
|
|
|
|
caps->path,
|
|
|
|
g_strerror(errno));
|
|
|
|
caps->ctime = 0;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-12-09 16:29:25 +00:00
|
|
|
qemuNbdkitCapsQueryBuildConfig(caps);
|
|
|
|
qemuNbdkitCapsQueryPlugins(caps);
|
|
|
|
qemuNbdkitCapsQueryFilters(caps);
|
|
|
|
|
2022-07-21 22:16:40 +00:00
|
|
|
caps->ctime = st.st_ctime;
|
2022-12-09 16:29:25 +00:00
|
|
|
caps->filterDirMtime = qemuNbdkitGetDirMtime(caps->filterDir);
|
|
|
|
caps->pluginDirMtime = qemuNbdkitGetDirMtime(caps->pluginDir);
|
2022-07-21 22:16:40 +00:00
|
|
|
caps->libvirtCtime = virGetSelfLastChanged();
|
|
|
|
caps->libvirtVersion = LIBVIR_VERSION_NUMBER;
|
2022-07-05 20:40:16 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool
|
|
|
|
qemuNbdkitCapsGet(qemuNbdkitCaps *nbdkitCaps,
|
|
|
|
qemuNbdkitCapsFlags flag)
|
|
|
|
{
|
|
|
|
return virBitmapIsBitSet(nbdkitCaps->flags, flag);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
qemuNbdkitCapsSet(qemuNbdkitCaps *nbdkitCaps,
|
|
|
|
qemuNbdkitCapsFlags flag)
|
|
|
|
{
|
|
|
|
ignore_value(virBitmapSetBit(nbdkitCaps->flags, flag));
|
|
|
|
}
|
2022-07-21 22:25:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
virNbkditCapsCheckModdir(const char *moddir,
|
|
|
|
time_t expectedMtime)
|
|
|
|
{
|
|
|
|
time_t mtime = qemuNbdkitGetDirMtime(moddir);
|
|
|
|
|
|
|
|
if (mtime != expectedMtime) {
|
|
|
|
VIR_DEBUG("Outdated capabilities for nbdkit: module directory '%s' changed (%lld vs %lld)",
|
|
|
|
moddir, (long long)mtime, (long long)expectedMtime);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static bool
|
|
|
|
virNbdkitCapsIsValid(void *data,
|
2022-07-08 19:30:30 +00:00
|
|
|
void *privData)
|
2022-07-21 22:25:16 +00:00
|
|
|
{
|
|
|
|
qemuNbdkitCaps *nbdkitCaps = data;
|
|
|
|
struct stat st;
|
2022-07-08 19:30:30 +00:00
|
|
|
/* when run under test, we will use privData as a signal to indicate that
|
|
|
|
* we shouldn't touch the filesystem */
|
|
|
|
bool skipValidation = (privData != NULL);
|
|
|
|
|
|
|
|
if (skipValidation)
|
|
|
|
return true;
|
2022-07-21 22:25:16 +00:00
|
|
|
|
|
|
|
if (!nbdkitCaps->path)
|
|
|
|
return true;
|
|
|
|
|
2022-12-09 16:29:25 +00:00
|
|
|
if (!virNbkditCapsCheckModdir(nbdkitCaps->pluginDir, nbdkitCaps->pluginDirMtime))
|
2022-07-21 22:25:16 +00:00
|
|
|
return false;
|
2022-12-09 16:29:25 +00:00
|
|
|
if (!virNbkditCapsCheckModdir(nbdkitCaps->filterDir, nbdkitCaps->filterDirMtime))
|
2022-07-21 22:25:16 +00:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (nbdkitCaps->libvirtCtime != virGetSelfLastChanged() ||
|
|
|
|
nbdkitCaps->libvirtVersion != LIBVIR_VERSION_NUMBER) {
|
|
|
|
VIR_DEBUG("Outdated capabilities for '%s': libvirt changed (%lld vs %lld, %lu vs %lu)",
|
|
|
|
nbdkitCaps->path,
|
|
|
|
(long long)nbdkitCaps->libvirtCtime,
|
|
|
|
(long long)virGetSelfLastChanged(),
|
|
|
|
(unsigned long)nbdkitCaps->libvirtVersion,
|
|
|
|
(unsigned long)LIBVIR_VERSION_NUMBER);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (stat(nbdkitCaps->path, &st) < 0) {
|
|
|
|
VIR_DEBUG("Failed to stat nbdkit binary '%s': %s",
|
|
|
|
nbdkitCaps->path,
|
|
|
|
g_strerror(errno));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (st.st_ctime != nbdkitCaps->ctime) {
|
|
|
|
VIR_DEBUG("Outdated capabilities for '%s': nbdkit binary changed (%lld vs %lld)",
|
|
|
|
nbdkitCaps->path,
|
|
|
|
(long long)st.st_ctime, (long long)nbdkitCaps->ctime);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void*
|
|
|
|
virNbdkitCapsNewData(const char *binary,
|
2022-07-08 19:30:30 +00:00
|
|
|
void *privData)
|
2022-07-21 22:25:16 +00:00
|
|
|
{
|
2022-07-08 19:30:30 +00:00
|
|
|
/* when run under test, we will use privData as a signal to indicate that
|
|
|
|
* we shouldn't touch the filesystem */
|
|
|
|
bool skipNewData = (privData != NULL);
|
|
|
|
qemuNbdkitCaps *caps = NULL;
|
|
|
|
|
|
|
|
if (skipNewData)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
caps = qemuNbdkitCapsNew(binary);
|
2022-07-21 22:25:16 +00:00
|
|
|
qemuNbdkitCapsQuery(caps);
|
|
|
|
|
|
|
|
return caps;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-08-05 20:24:08 +00:00
|
|
|
static int
|
|
|
|
qemuNbdkitCapsValidateBinary(qemuNbdkitCaps *nbdkitCaps,
|
|
|
|
xmlXPathContextPtr ctxt)
|
|
|
|
{
|
|
|
|
g_autofree char *str = NULL;
|
|
|
|
|
|
|
|
if (!(str = virXPathString("string(./path)", ctxt))) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("missing path in nbdkit capabilities cache"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (STRNEQ(str, nbdkitCaps->path)) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("Expected caps for '%1$s' but saw '%2$s'"),
|
|
|
|
nbdkitCaps->path, str);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuNbdkitCapsParseFlags(qemuNbdkitCaps *nbdkitCaps,
|
|
|
|
xmlXPathContextPtr ctxt)
|
|
|
|
{
|
|
|
|
g_autofree xmlNodePtr *nodes = NULL;
|
|
|
|
size_t i;
|
|
|
|
int n;
|
|
|
|
|
|
|
|
if ((n = virXPathNodeSet("./flag", ctxt, &nodes)) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
|
|
|
|
_("failed to parse qemu capabilities flags"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
VIR_DEBUG("Got flags %d", n);
|
|
|
|
for (i = 0; i < n; i++) {
|
|
|
|
unsigned int flag;
|
|
|
|
|
|
|
|
if (virXMLPropEnum(nodes[i], "name", qemuNbdkitCapsTypeFromString,
|
|
|
|
VIR_XML_PROP_REQUIRED, &flag) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
qemuNbdkitCapsSet(nbdkitCaps, flag);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Parsing a doc that looks like
|
|
|
|
*
|
|
|
|
* <nbdkitCaps>
|
|
|
|
* <path>/some/path</path>
|
|
|
|
* <nbdkitctime>234235253</nbdkitctime>
|
|
|
|
* <plugindirmtime>234235253</plugindirmtime>
|
|
|
|
* <filterdirmtime>234235253</filterdirmtime>
|
|
|
|
* <selfctime>234235253</selfctime>
|
|
|
|
* <selfvers>1002016</selfvers>
|
|
|
|
* <flag name='foo'/>
|
|
|
|
* <flag name='bar'/>
|
|
|
|
* ...
|
|
|
|
* </nbdkitCaps>
|
|
|
|
*
|
|
|
|
* Returns 0 on success, 1 if outdated, -1 on error
|
|
|
|
*/
|
|
|
|
static int
|
|
|
|
qemuNbdkitCapsLoadCache(qemuNbdkitCaps *nbdkitCaps,
|
|
|
|
const char *filename)
|
|
|
|
{
|
|
|
|
g_autoptr(xmlDoc) doc = NULL;
|
|
|
|
g_autoptr(xmlXPathContext) ctxt = NULL;
|
|
|
|
long long int l;
|
|
|
|
|
|
|
|
if (!(doc = virXMLParse(filename, NULL, NULL, "nbdkitCaps", &ctxt, NULL, false)))
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virXPathLongLong("string(./selfctime)", ctxt, &l) < 0) {
|
|
|
|
VIR_DEBUG("missing selfctime in nbdkit capabilities XML");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
nbdkitCaps->libvirtCtime = (time_t)l;
|
|
|
|
|
|
|
|
nbdkitCaps->libvirtVersion = 0;
|
|
|
|
virXPathUInt("string(./selfvers)", ctxt, &nbdkitCaps->libvirtVersion);
|
|
|
|
|
|
|
|
if (nbdkitCaps->libvirtCtime != virGetSelfLastChanged() ||
|
|
|
|
nbdkitCaps->libvirtVersion != LIBVIR_VERSION_NUMBER) {
|
|
|
|
VIR_DEBUG("Outdated capabilities in %s: libvirt changed (%lld vs %lld, %lu vs %lu), stopping load",
|
|
|
|
nbdkitCaps->path,
|
|
|
|
(long long)nbdkitCaps->libvirtCtime,
|
|
|
|
(long long)virGetSelfLastChanged(),
|
|
|
|
(unsigned long)nbdkitCaps->libvirtVersion,
|
|
|
|
(unsigned long)LIBVIR_VERSION_NUMBER);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (qemuNbdkitCapsValidateBinary(nbdkitCaps, ctxt) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if (virXPathLongLong("string(./nbdkitctime)", ctxt, &l) < 0) {
|
|
|
|
VIR_DEBUG("missing nbdkitctime in nbdkit capabilities XML");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
nbdkitCaps->ctime = (time_t)l;
|
|
|
|
|
2022-12-09 16:29:25 +00:00
|
|
|
if ((nbdkitCaps->pluginDir = virXPathString("string(./plugindir)", ctxt)) == NULL) {
|
|
|
|
VIR_DEBUG("missing plugindir in nbdkit capabilities cache");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-08-05 20:24:08 +00:00
|
|
|
if (virXPathLongLong("string(./plugindirmtime)", ctxt, &l) < 0) {
|
|
|
|
VIR_DEBUG("missing plugindirmtime in nbdkit capabilities XML");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
nbdkitCaps->pluginDirMtime = (time_t)l;
|
|
|
|
|
2022-12-09 16:29:25 +00:00
|
|
|
if ((nbdkitCaps->filterDir = virXPathString("string(./filterdir)", ctxt)) == NULL) {
|
|
|
|
VIR_DEBUG("missing filterdir in nbdkit capabilities cache");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2022-08-05 20:24:08 +00:00
|
|
|
if (virXPathLongLong("string(./filterdirmtime)", ctxt, &l) < 0) {
|
|
|
|
VIR_DEBUG("missing filterdirmtime in nbdkit capabilities XML");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
nbdkitCaps->filterDirMtime = (time_t)l;
|
|
|
|
|
|
|
|
if (qemuNbdkitCapsParseFlags(nbdkitCaps, ctxt) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
if ((nbdkitCaps->version = virXPathString("string(./version)", ctxt)) == NULL) {
|
|
|
|
VIR_DEBUG("missing version in nbdkit capabilities cache");
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void*
|
|
|
|
virNbdkitCapsLoadFile(const char *filename,
|
|
|
|
const char *binary,
|
|
|
|
void *privData G_GNUC_UNUSED,
|
|
|
|
bool *outdated)
|
|
|
|
{
|
|
|
|
g_autoptr(qemuNbdkitCaps) nbdkitCaps = qemuNbdkitCapsNew(binary);
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!nbdkitCaps)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
ret = qemuNbdkitCapsLoadCache(nbdkitCaps, filename);
|
|
|
|
if (ret < 0)
|
|
|
|
return NULL;
|
|
|
|
if (ret == 1) {
|
|
|
|
*outdated = true;
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return g_steal_pointer(&nbdkitCaps);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static char*
|
|
|
|
qemuNbdkitCapsFormatCache(qemuNbdkitCaps *nbdkitCaps)
|
|
|
|
{
|
|
|
|
g_auto(virBuffer) buf = VIR_BUFFER_INITIALIZER;
|
|
|
|
size_t i;
|
|
|
|
|
|
|
|
virBufferAddLit(&buf, "<nbdkitCaps>\n");
|
|
|
|
virBufferAdjustIndent(&buf, 2);
|
|
|
|
|
|
|
|
virBufferEscapeString(&buf, "<path>%s</path>\n",
|
|
|
|
nbdkitCaps->path);
|
|
|
|
virBufferAsprintf(&buf, "<nbdkitctime>%lu</nbdkitctime>\n",
|
|
|
|
nbdkitCaps->ctime);
|
2022-12-09 16:29:25 +00:00
|
|
|
virBufferEscapeString(&buf, "<plugindir>%s</plugindir>\n",
|
|
|
|
nbdkitCaps->pluginDir);
|
2022-08-05 20:24:08 +00:00
|
|
|
virBufferAsprintf(&buf, "<plugindirmtime>%lu</plugindirmtime>\n",
|
|
|
|
nbdkitCaps->pluginDirMtime);
|
2022-12-09 16:29:25 +00:00
|
|
|
virBufferEscapeString(&buf, "<filterdir>%s</filterdir>\n",
|
|
|
|
nbdkitCaps->filterDir);
|
2022-08-05 20:24:08 +00:00
|
|
|
virBufferAsprintf(&buf, "<filterdirmtime>%lu</filterdirmtime>\n",
|
|
|
|
nbdkitCaps->filterDirMtime);
|
|
|
|
virBufferAsprintf(&buf, "<selfctime>%lu</selfctime>\n",
|
|
|
|
nbdkitCaps->libvirtCtime);
|
|
|
|
virBufferAsprintf(&buf, "<selfvers>%u</selfvers>\n",
|
|
|
|
nbdkitCaps->libvirtVersion);
|
|
|
|
|
|
|
|
for (i = 0; i < QEMU_NBDKIT_CAPS_LAST; i++) {
|
|
|
|
if (qemuNbdkitCapsGet(nbdkitCaps, i)) {
|
|
|
|
virBufferAsprintf(&buf, "<flag name='%s'/>\n",
|
|
|
|
qemuNbdkitCapsTypeToString(i));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
virBufferAsprintf(&buf, "<version>%s</version>\n",
|
|
|
|
nbdkitCaps->version);
|
|
|
|
|
|
|
|
virBufferAdjustIndent(&buf, -2);
|
|
|
|
virBufferAddLit(&buf, "</nbdkitCaps>\n");
|
|
|
|
|
|
|
|
return virBufferContentAndReset(&buf);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
virNbdkitCapsSaveFile(void *data,
|
|
|
|
const char *filename,
|
|
|
|
void *privData G_GNUC_UNUSED)
|
|
|
|
{
|
|
|
|
qemuNbdkitCaps *nbdkitCaps = data;
|
|
|
|
g_autofree char *xml = NULL;
|
|
|
|
|
|
|
|
xml = qemuNbdkitCapsFormatCache(nbdkitCaps);
|
|
|
|
|
|
|
|
if (virFileWriteStr(filename, xml, 0600) < 0) {
|
|
|
|
virReportSystemError(errno,
|
|
|
|
_("Failed to save '%1$s' for '%2$s'"),
|
|
|
|
filename, nbdkitCaps->path);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
VIR_DEBUG("Saved caps '%s' for '%s' with (%lu, %lu)",
|
|
|
|
filename, nbdkitCaps->path,
|
|
|
|
nbdkitCaps->ctime,
|
|
|
|
nbdkitCaps->libvirtCtime);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-21 22:25:16 +00:00
|
|
|
virFileCacheHandlers nbdkitCapsCacheHandlers = {
|
|
|
|
.isValid = virNbdkitCapsIsValid,
|
|
|
|
.newData = virNbdkitCapsNewData,
|
2022-08-05 20:24:08 +00:00
|
|
|
.loadFile = virNbdkitCapsLoadFile,
|
|
|
|
.saveFile = virNbdkitCapsSaveFile,
|
2022-07-21 22:25:16 +00:00
|
|
|
.privFree = NULL,
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
virFileCache*
|
|
|
|
qemuNbdkitCapsCacheNew(const char *cachedir)
|
|
|
|
{
|
|
|
|
g_autofree char *dir = g_build_filename(cachedir, "nbdkitcapabilities", NULL);
|
|
|
|
return virFileCacheNew(dir, "xml", &nbdkitCapsCacheHandlers);
|
|
|
|
}
|
2022-07-05 22:00:11 +00:00
|
|
|
|
|
|
|
|
2022-10-05 17:03:33 +00:00
|
|
|
int
|
|
|
|
qemuNbdkitProcessRestart(qemuNbdkitProcess *proc,
|
|
|
|
virDomainObj *vm)
|
|
|
|
{
|
|
|
|
qemuDomainObjPrivate *vmpriv = vm->privateData;
|
|
|
|
virQEMUDriver *driver = vmpriv->driver;
|
|
|
|
|
|
|
|
/* clean up resources associated with process */
|
2022-12-22 19:04:51 +00:00
|
|
|
qemuNbdkitProcessStop(proc, vm);
|
2022-10-05 17:03:33 +00:00
|
|
|
|
|
|
|
return qemuNbdkitProcessStart(proc, vm, driver);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if WITH_NBDKIT
|
|
|
|
typedef struct {
|
|
|
|
qemuNbdkitProcess *proc;
|
|
|
|
virDomainObj *vm;
|
|
|
|
} qemuNbdkitProcessEventData;
|
|
|
|
|
|
|
|
|
|
|
|
static qemuNbdkitProcessEventData*
|
|
|
|
qemuNbdkitProcessEventDataNew(qemuNbdkitProcess *proc,
|
|
|
|
virDomainObj *vm)
|
|
|
|
{
|
|
|
|
qemuNbdkitProcessEventData *d = g_new(qemuNbdkitProcessEventData, 1);
|
|
|
|
d->proc = proc;
|
|
|
|
d->vm = virObjectRef(vm);
|
|
|
|
return d;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemuNbdkitProcessEventDataFree(qemuNbdkitProcessEventData *d)
|
|
|
|
{
|
|
|
|
virObjectUnref(d->vm);
|
|
|
|
g_free(d);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemuNbdkitProcessPidfdCb(int watch G_GNUC_UNUSED,
|
|
|
|
int fd,
|
|
|
|
int events G_GNUC_UNUSED,
|
|
|
|
void *opaque)
|
|
|
|
{
|
|
|
|
qemuNbdkitProcessEventData *d = opaque;
|
|
|
|
|
|
|
|
VIR_FORCE_CLOSE(fd);
|
|
|
|
/* submit an event so that it is handled in the per-vm event thread */
|
|
|
|
qemuProcessHandleNbdkitExit(d->proc, d->vm);
|
|
|
|
}
|
|
|
|
#endif /* WITH_NBDKIT */
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuNbdkitProcessStartMonitor(qemuNbdkitProcess *proc WITHOUT_NBDKIT_UNUSED,
|
|
|
|
virDomainObj *vm WITHOUT_NBDKIT_UNUSED)
|
|
|
|
{
|
|
|
|
#if WITH_NBDKIT
|
|
|
|
int pidfd;
|
|
|
|
qemuNbdkitProcessEventData *data;
|
|
|
|
|
|
|
|
pidfd = syscall(SYS_pidfd_open, proc->pid, 0);
|
|
|
|
if (pidfd < 0) {
|
|
|
|
virReportSystemError(errno, _("pidfd_open failed for %1$i"), proc->pid);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
data = qemuNbdkitProcessEventDataNew(proc, vm);
|
|
|
|
if ((proc->eventwatch = virEventAddHandle(pidfd,
|
|
|
|
VIR_EVENT_HANDLE_READABLE,
|
|
|
|
qemuNbdkitProcessPidfdCb,
|
|
|
|
data,
|
|
|
|
(virFreeCallback)qemuNbdkitProcessEventDataFree)) < 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("failed to monitor nbdkit process %1$i"),
|
|
|
|
proc->pid);
|
|
|
|
VIR_FORCE_CLOSE(pidfd);
|
|
|
|
qemuNbdkitProcessEventDataFree(data);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
VIR_DEBUG("Monitoring nbdkit process %i for exit", proc->pid);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
#else
|
|
|
|
/* This should not be reachable */
|
|
|
|
virReportError(VIR_ERR_NO_SUPPORT, "%s",
|
|
|
|
_("nbdkit support is not enabled"));
|
|
|
|
return -1;
|
|
|
|
#endif /* WITH_NBDKIT */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
qemuNbdkitProcessStopMonitor(qemuNbdkitProcess *proc WITHOUT_NBDKIT_UNUSED)
|
|
|
|
{
|
|
|
|
#if WITH_NBDKIT
|
|
|
|
if (proc->eventwatch > 0) {
|
|
|
|
virEventRemoveHandle(proc->eventwatch);
|
|
|
|
proc->eventwatch = 0;
|
|
|
|
}
|
|
|
|
#endif /* WITH_NBDKIT */
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-05 22:00:11 +00:00
|
|
|
static qemuNbdkitProcess *
|
|
|
|
qemuNbdkitProcessNew(virStorageSource *source,
|
|
|
|
const char *pidfile,
|
|
|
|
const char *socketfile)
|
|
|
|
{
|
|
|
|
qemuNbdkitProcess *nbdkit = g_new0(qemuNbdkitProcess, 1);
|
|
|
|
/* weak reference -- source owns this object, so it will always outlive us */
|
|
|
|
nbdkit->source = source;
|
|
|
|
nbdkit->user = -1;
|
|
|
|
nbdkit->group = -1;
|
|
|
|
nbdkit->pid = -1;
|
|
|
|
nbdkit->pidfile = g_strdup(pidfile);
|
|
|
|
nbdkit->socketfile = g_strdup(socketfile);
|
|
|
|
|
|
|
|
return nbdkit;
|
|
|
|
}
|
|
|
|
|
2022-07-08 15:40:06 +00:00
|
|
|
/**
|
|
|
|
* qemuNbdkitReconnectStorageSource:
|
|
|
|
* @source: a storage source
|
|
|
|
* @pidfile: a pidfile for an nbdkit process
|
|
|
|
* @socketfile: the socket file associated with the nbdkit process
|
|
|
|
*
|
|
|
|
* This function constructs a new qemuNbdkitProcess object with the given values for @pidfile and
|
|
|
|
* @socketfile and stores it in @source. This is intended to be called when the libvirt daemon is
|
|
|
|
* restarted and tries to reconnect to all currently-running domains. Since this function is called
|
|
|
|
* from the code that parses the current daemon state, it should not perform any filesystem
|
|
|
|
* operations, or anything else that might fail. Additional initialization will be done later by
|
|
|
|
* calling qemuNbdkitStorageSourceManageProcess().
|
|
|
|
*/
|
|
|
|
void
|
|
|
|
qemuNbdkitReconnectStorageSource(virStorageSource *source,
|
|
|
|
const char *pidfile,
|
|
|
|
const char *socketfile)
|
|
|
|
{
|
|
|
|
qemuDomainStorageSourcePrivate *srcpriv = qemuDomainStorageSourcePrivateFetch(source);
|
|
|
|
|
|
|
|
if (srcpriv->nbdkitProcess) {
|
|
|
|
VIR_WARN("source already has an nbdkit process");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
srcpriv->nbdkitProcess = qemuNbdkitProcessNew(source, pidfile, socketfile);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-10-05 17:03:33 +00:00
|
|
|
static int
|
|
|
|
qemuNbdkitStorageSourceManageProcessOne(virStorageSource *source,
|
|
|
|
virDomainObj *vm)
|
2022-07-08 15:40:06 +00:00
|
|
|
{
|
|
|
|
qemuDomainStorageSourcePrivate *srcpriv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(source);
|
2022-10-05 17:03:33 +00:00
|
|
|
qemuDomainObjPrivate *vmpriv = vm->privateData;
|
2022-07-08 15:40:06 +00:00
|
|
|
qemuNbdkitProcess *proc;
|
|
|
|
|
|
|
|
if (!srcpriv)
|
2022-10-05 17:03:33 +00:00
|
|
|
return 0;
|
2022-07-08 15:40:06 +00:00
|
|
|
|
|
|
|
proc = srcpriv->nbdkitProcess;
|
|
|
|
|
|
|
|
if (!proc)
|
2022-10-05 17:03:33 +00:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (!proc->caps)
|
|
|
|
proc->caps = qemuGetNbdkitCaps(vmpriv->driver);
|
2022-07-08 15:40:06 +00:00
|
|
|
|
|
|
|
if (proc->pid <= 0) {
|
|
|
|
if (virPidFileReadPath(proc->pidfile, &proc->pid) < 0) {
|
2022-10-05 17:03:33 +00:00
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("Unable to read pidfile '%1$s'"),
|
|
|
|
proc->pidfile);
|
|
|
|
return -1;
|
2022-07-08 15:40:06 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-10-05 17:03:33 +00:00
|
|
|
if (virProcessKill(proc->pid, 0) < 0) {
|
|
|
|
VIR_DEBUG("nbdkit process %i is not alive", proc->pid);
|
|
|
|
return qemuNbdkitProcessRestart(proc, vm);
|
|
|
|
}
|
|
|
|
|
|
|
|
return qemuNbdkitProcessStartMonitor(proc, vm);
|
2022-07-08 15:40:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* qemuNbdkitStorageSourceManageProcess:
|
|
|
|
* @source: a storage source
|
|
|
|
* @vm: the vm that owns this storage source
|
|
|
|
*
|
|
|
|
* This function re-enables monitoring of any nbdkit processes associated with the backing chain of
|
|
|
|
* @source. It is intended to be called after libvirt restarts and has loaded its current state from
|
|
|
|
* disk and is attempting to re-connect to active domains.
|
|
|
|
*/
|
2022-10-05 17:03:33 +00:00
|
|
|
int
|
|
|
|
qemuNbdkitStorageSourceManageProcess(virStorageSource *source,
|
|
|
|
virDomainObj *vm)
|
2022-07-08 15:40:06 +00:00
|
|
|
{
|
|
|
|
virStorageSource *backing;
|
|
|
|
for (backing = source; backing != NULL; backing = backing->backingStore)
|
2022-10-05 17:03:33 +00:00
|
|
|
if (qemuNbdkitStorageSourceManageProcessOne(backing, vm) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
2022-07-08 15:40:06 +00:00
|
|
|
}
|
|
|
|
|
2022-07-05 22:00:11 +00:00
|
|
|
|
|
|
|
bool
|
2022-10-05 17:03:33 +00:00
|
|
|
qemuNbdkitInitStorageSource(qemuNbdkitCaps *caps WITHOUT_NBDKIT_UNUSED,
|
|
|
|
virStorageSource *source WITHOUT_NBDKIT_UNUSED,
|
|
|
|
char *statedir WITHOUT_NBDKIT_UNUSED,
|
|
|
|
const char *alias WITHOUT_NBDKIT_UNUSED,
|
|
|
|
uid_t user WITHOUT_NBDKIT_UNUSED,
|
|
|
|
gid_t group WITHOUT_NBDKIT_UNUSED)
|
2022-07-05 22:00:11 +00:00
|
|
|
{
|
2022-10-05 17:03:33 +00:00
|
|
|
#if !WITH_NBDKIT
|
|
|
|
/* if nbdkit support is not enabled, don't construct the object so the
|
|
|
|
* calling function will fall back to qemu storage drivers */
|
|
|
|
return false;
|
|
|
|
#else
|
2022-07-05 22:00:11 +00:00
|
|
|
qemuDomainStorageSourcePrivate *srcPriv = qemuDomainStorageSourcePrivateFetch(source);
|
|
|
|
g_autofree char *pidname = g_strdup_printf("nbdkit-%s.pid", alias);
|
|
|
|
g_autofree char *socketname = g_strdup_printf("nbdkit-%s.socket", alias);
|
|
|
|
g_autofree char *pidfile = g_build_filename(statedir, pidname, NULL);
|
|
|
|
g_autofree char *socketfile = g_build_filename(statedir, socketname, NULL);
|
|
|
|
qemuNbdkitProcess *proc;
|
|
|
|
|
|
|
|
if (srcPriv->nbdkitProcess)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
switch (source->protocol) {
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_HTTP:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_HTTPS:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_FTP:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_FTPS:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_TFTP:
|
|
|
|
if (!virBitmapIsBitSet(caps->flags, QEMU_NBDKIT_CAPS_PLUGIN_CURL))
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
|
|
|
if (!virBitmapIsBitSet(caps->flags, QEMU_NBDKIT_CAPS_PLUGIN_SSH))
|
|
|
|
return false;
|
|
|
|
break;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NBD:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_VXHS:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NFS:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_LAST:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
proc = qemuNbdkitProcessNew(source, pidfile, socketfile);
|
|
|
|
proc->caps = g_object_ref(caps);
|
|
|
|
proc->user = user;
|
|
|
|
proc->group = group;
|
|
|
|
|
|
|
|
srcPriv->nbdkitProcess = proc;
|
|
|
|
|
|
|
|
return true;
|
2022-10-05 17:03:33 +00:00
|
|
|
#endif /* WITH_NBDKIT */
|
2022-07-05 22:00:11 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-08 22:13:34 +00:00
|
|
|
int
|
|
|
|
qemuNbdkitStartStorageSource(virQEMUDriver *driver,
|
|
|
|
virDomainObj *vm,
|
|
|
|
virStorageSource *src)
|
|
|
|
{
|
|
|
|
virStorageSource *backing;
|
|
|
|
|
|
|
|
for (backing = src; backing != NULL; backing = backing->backingStore) {
|
|
|
|
qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
|
|
|
|
|
|
|
if (priv && priv->nbdkitProcess &&
|
|
|
|
qemuNbdkitProcessStart(priv->nbdkitProcess, vm, driver) < 0)
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void
|
2022-12-22 19:04:51 +00:00
|
|
|
qemuNbdkitStopStorageSource(virStorageSource *src,
|
|
|
|
virDomainObj *vm)
|
2022-07-08 22:13:34 +00:00
|
|
|
{
|
|
|
|
virStorageSource *backing;
|
|
|
|
|
|
|
|
for (backing = src; backing != NULL; backing = backing->backingStore) {
|
|
|
|
qemuDomainStorageSourcePrivate *priv = QEMU_DOMAIN_STORAGE_SOURCE_PRIVATE(src);
|
|
|
|
|
|
|
|
if (priv && priv->nbdkitProcess &&
|
2022-12-22 19:04:51 +00:00
|
|
|
qemuNbdkitProcessStop(priv->nbdkitProcess, vm) < 0)
|
2023-09-25 15:02:20 +00:00
|
|
|
VIR_WARN("Unable to stop nbdkit for storage source '%s'", qemuBlockStorageSourceGetStorageNodename(src));
|
2022-07-08 22:13:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-08-18 21:27:46 +00:00
|
|
|
static int
|
|
|
|
qemuNbdkitCommandPassDataByPipe(virCommand *cmd,
|
|
|
|
const char *argName,
|
|
|
|
unsigned char **buf,
|
|
|
|
size_t buflen)
|
|
|
|
{
|
|
|
|
g_autofree char *fdfmt = NULL;
|
|
|
|
int fd = virCommandSetSendBuffer(cmd, buf, buflen);
|
|
|
|
|
|
|
|
if (fd < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
/* some nbdkit arguments accept a variation where nbdkit will read the data
|
|
|
|
* from a file descriptor, e.g. password=-FD */
|
|
|
|
fdfmt = g_strdup_printf("-%i", fd);
|
|
|
|
virCommandAddArgPair(cmd, argName, fdfmt);
|
|
|
|
|
|
|
|
virCommandDoAsyncIO(cmd);
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-12-21 21:42:02 +00:00
|
|
|
static int
|
|
|
|
qemuNbdkitProcessBuildCommandAuth(virStorageAuthDef *authdef,
|
|
|
|
virCommand *cmd)
|
|
|
|
{
|
|
|
|
g_autoptr(virConnect) conn = NULL;
|
|
|
|
g_autofree uint8_t *secret = NULL;
|
|
|
|
size_t secretlen = 0;
|
|
|
|
int secrettype;
|
|
|
|
|
|
|
|
if (!authdef)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if ((secrettype = virSecretUsageTypeFromString(authdef->secrettype)) < 0) {
|
|
|
|
virReportError(VIR_ERR_CONFIG_UNSUPPORTED,
|
|
|
|
_("invalid secret type %1$s"),
|
|
|
|
authdef->secrettype);
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
conn = virGetConnectSecret();
|
2023-09-20 11:45:59 +00:00
|
|
|
if (!conn)
|
|
|
|
return -1;
|
|
|
|
|
2022-12-21 21:42:02 +00:00
|
|
|
if (virSecretGetSecretString(conn,
|
|
|
|
&authdef->seclookupdef,
|
|
|
|
secrettype,
|
|
|
|
&secret,
|
|
|
|
&secretlen) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
virCommandAddArgPair(cmd, "user", authdef->username);
|
|
|
|
|
|
|
|
if (qemuNbdkitCommandPassDataByPipe(cmd, "password",
|
|
|
|
&secret, secretlen) < 0)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-08 19:20:20 +00:00
|
|
|
static int
|
|
|
|
qemuNbdkitProcessBuildCommandCurl(qemuNbdkitProcess *proc,
|
|
|
|
virCommand *cmd)
|
|
|
|
{
|
|
|
|
g_autoptr(virURI) uri = qemuBlockStorageSourceGetURI(proc->source);
|
|
|
|
g_autofree char *uristring = virURIFormat(uri);
|
|
|
|
|
|
|
|
/* nbdkit plugin name */
|
|
|
|
virCommandAddArg(cmd, "curl");
|
|
|
|
if (proc->source->protocol == VIR_STORAGE_NET_PROTOCOL_HTTP) {
|
|
|
|
/* allow http to be upgraded to https via e.g. redirect */
|
|
|
|
virCommandAddArgPair(cmd, "protocols", "http,https");
|
|
|
|
} else {
|
|
|
|
virCommandAddArgPair(cmd, "protocols",
|
|
|
|
virStorageNetProtocolTypeToString(proc->source->protocol));
|
|
|
|
}
|
|
|
|
virCommandAddArgPair(cmd, "url", uristring);
|
|
|
|
|
2022-12-21 21:42:02 +00:00
|
|
|
if (proc->source->auth && qemuNbdkitProcessBuildCommandAuth(proc->source->auth, cmd) < 0)
|
|
|
|
return -1;
|
2022-07-08 19:20:20 +00:00
|
|
|
|
2022-08-18 21:27:46 +00:00
|
|
|
/* Create a pipe to send the cookies to the nbdkit process. */
|
|
|
|
if (proc->source->ncookies) {
|
|
|
|
g_autofree char *cookies = qemuBlockStorageSourceGetCookieString(proc->source);
|
|
|
|
|
|
|
|
if (qemuNbdkitCommandPassDataByPipe(cmd, "cookie",
|
|
|
|
(unsigned char**)&cookies,
|
|
|
|
strlen(cookies)) < 0)
|
|
|
|
return -1;
|
2022-07-08 19:20:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (proc->source->sslverify == VIR_TRISTATE_BOOL_NO) {
|
|
|
|
virCommandAddArgPair(cmd, "sslverify", "false");
|
|
|
|
}
|
|
|
|
|
|
|
|
if (proc->source->timeout > 0) {
|
|
|
|
g_autofree char *timeout = g_strdup_printf("%llu", proc->source->timeout);
|
|
|
|
virCommandAddArgPair(cmd, "timeout", timeout);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
qemuNbdkitProcessBuildCommandSSH(qemuNbdkitProcess *proc,
|
|
|
|
virCommand *cmd)
|
|
|
|
{
|
|
|
|
virStorageNetHostDef *host = &proc->source->hosts[0];
|
|
|
|
g_autofree char *portstr = g_strdup_printf("%u", host->port);
|
|
|
|
|
|
|
|
/* nbdkit plugin name */
|
|
|
|
virCommandAddArg(cmd, "ssh");
|
|
|
|
|
|
|
|
virCommandAddArgPair(cmd, "host", host->name);
|
|
|
|
virCommandAddArgPair(cmd, "port", portstr);
|
|
|
|
virCommandAddArgPair(cmd, "path", proc->source->path);
|
|
|
|
|
2022-12-21 21:42:02 +00:00
|
|
|
if (proc->source->auth) {
|
|
|
|
if (qemuNbdkitProcessBuildCommandAuth(proc->source->auth, cmd) < 0)
|
|
|
|
return -1;
|
2022-12-22 22:56:47 +00:00
|
|
|
} else {
|
|
|
|
if (proc->source->ssh_keyfile)
|
|
|
|
virCommandAddArgPair(cmd, "identity", proc->source->ssh_keyfile);
|
|
|
|
|
|
|
|
if (proc->source->ssh_user)
|
|
|
|
virCommandAddArgPair(cmd, "user", proc->source->ssh_user);
|
2022-12-21 21:42:02 +00:00
|
|
|
}
|
2022-07-08 19:20:20 +00:00
|
|
|
|
2023-07-14 20:54:03 +00:00
|
|
|
if (proc->source->ssh_agent)
|
|
|
|
virCommandAddEnvPair(cmd, "SSH_AUTH_SOCK", proc->source->ssh_agent);
|
|
|
|
|
2022-07-08 19:20:20 +00:00
|
|
|
if (proc->source->ssh_host_key_check_disabled)
|
|
|
|
virCommandAddArgPair(cmd, "verify-remote-host", "false");
|
|
|
|
|
2022-12-22 19:04:51 +00:00
|
|
|
if (proc->source->ssh_known_hosts_file)
|
|
|
|
virCommandAddArgPair(cmd, "known-hosts", proc->source->ssh_known_hosts_file);
|
|
|
|
|
2022-07-08 19:20:20 +00:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-08-19 22:21:52 +00:00
|
|
|
virCommand *
|
2022-07-08 19:20:20 +00:00
|
|
|
qemuNbdkitProcessBuildCommand(qemuNbdkitProcess *proc)
|
|
|
|
{
|
|
|
|
g_autoptr(virCommand) cmd = virCommandNewArgList(proc->caps->path,
|
|
|
|
"--unix",
|
|
|
|
proc->socketfile,
|
|
|
|
"--foreground",
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
if (proc->source->readonly)
|
|
|
|
virCommandAddArg(cmd, "--readonly");
|
|
|
|
|
|
|
|
if (qemuNbdkitCapsGet(proc->caps, QEMU_NBDKIT_CAPS_FILTER_READAHEAD) &&
|
|
|
|
proc->source->readahead > 0)
|
|
|
|
virCommandAddArgPair(cmd, "--filter", "readahead");
|
|
|
|
|
|
|
|
switch (proc->source->protocol) {
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_HTTP:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_HTTPS:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_FTP:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_FTPS:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_TFTP:
|
|
|
|
if (qemuNbdkitProcessBuildCommandCurl(proc, cmd) < 0)
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SSH:
|
|
|
|
if (qemuNbdkitProcessBuildCommandSSH(proc, cmd) < 0)
|
|
|
|
return NULL;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NONE:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NBD:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_RBD:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_SHEEPDOG:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_GLUSTER:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_ISCSI:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_VXHS:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_NFS:
|
|
|
|
case VIR_STORAGE_NET_PROTOCOL_LAST:
|
|
|
|
virReportError(VIR_ERR_NO_SUPPORT,
|
|
|
|
_("protocol '%1$s' is not supported by nbdkit"),
|
|
|
|
virStorageNetProtocolTypeToString(proc->source->protocol));
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
virCommandDaemonize(cmd);
|
|
|
|
|
|
|
|
return g_steal_pointer(&cmd);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-05 22:00:11 +00:00
|
|
|
void
|
|
|
|
qemuNbdkitProcessFree(qemuNbdkitProcess *proc)
|
|
|
|
{
|
2022-10-05 17:03:33 +00:00
|
|
|
qemuNbdkitProcessStopMonitor(proc);
|
|
|
|
|
2022-07-05 22:00:11 +00:00
|
|
|
g_clear_pointer(&proc->pidfile, g_free);
|
|
|
|
g_clear_pointer(&proc->socketfile, g_free);
|
|
|
|
g_clear_object(&proc->caps);
|
|
|
|
g_free(proc);
|
|
|
|
}
|
2022-07-08 19:20:20 +00:00
|
|
|
|
|
|
|
|
2022-07-08 22:13:34 +00:00
|
|
|
int
|
|
|
|
qemuNbdkitProcessSetupCgroup(qemuNbdkitProcess *proc,
|
|
|
|
virCgroup *cgroup)
|
|
|
|
{
|
|
|
|
return virCgroupAddProcess(cgroup, proc->pid);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2022-07-08 19:20:20 +00:00
|
|
|
int
|
|
|
|
qemuNbdkitProcessStart(qemuNbdkitProcess *proc,
|
|
|
|
virDomainObj *vm,
|
|
|
|
virQEMUDriver *driver)
|
|
|
|
{
|
|
|
|
g_autoptr(virCommand) cmd = NULL;
|
|
|
|
int rc;
|
|
|
|
int exitstatus = 0;
|
|
|
|
g_autofree char *errbuf = NULL;
|
|
|
|
virTimeBackOffVar timebackoff;
|
|
|
|
g_autoptr(virURI) uri = NULL;
|
|
|
|
g_autofree char *uristring = NULL;
|
2022-12-12 21:12:33 +00:00
|
|
|
g_autofree char *basename = g_strdup_printf("%s-nbdkit-%i", vm->def->name, proc->source->id);
|
|
|
|
int logfd = -1;
|
|
|
|
g_autoptr(qemuLogContext) logContext = NULL;
|
meson: Improve nbdkit configurability
Currently, nbdkit support will automatically be enabled as long as
the pidfd_open(2) syscall is available. Optionally, libnbd is used
to generate more user-friendly error messages.
In theory this is all good, since use of nbdkit is supposed to be
transparent to the user. In practice, however, there is a problem:
if support for it is enabled at build time and the necessary
runtime components are installed, nbdkit will always be preferred,
with no way for the user to opt out.
This will arguably be fine in the long run, but right now none of
the platforms that we target ships with a SELinux policy that
allows libvirt to launch nbdkit, and the AppArmor policy that we
maintain ourselves hasn't been updated either.
So, in practice, as of today having nbdkit installed on the host
makes network disks completely unusable unless you're willing to
compromise the overall security of the system by disabling
SELinux/AppArmor.
In order to make the transition smoother, provide a convenient
way for users and distro packagers to disable nbdkit support at
compile time until SELinux and AppArmor are ready.
In the process, detection is completely overhauled. libnbd is
made mandatory when nbdkit support is enabled, since availability
across operating systems is comparable and offering users the
option to make error messages worse doesn't make a lot of sense;
we also make sure that an explicit request from the user to
enable/disable nbdkit support is either complied with, or results
in a build failure when that's not possible. Last but not least,
we avoid linking against libnbd when nbdkit support is disabled.
At the RPM level, we disable the feature when building against
anything older than Fedora 40, which still doesn't have the
necessary SELinux bits but will hopefully gain them by the time
it's released. We also allow nbdkit support to be disabled at
build time the same way as other optional features, that is, by
passing "--define '_without_nbdkit 1'" to rpmbuild. Finally, if
nbdkit support has been disabled, installing libvirt will no
longer drag it in as a (weak) dependency.
Signed-off-by: Andrea Bolognani <abologna@redhat.com>
Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com>
2023-10-04 22:37:09 +00:00
|
|
|
#if WITH_NBDKIT
|
qemu: try to connect to nbdkit early to detect errors
When using nbdkit to serve a network disk source, the nbdkit process
will start and wait for an nbd connection before actually attempting to
connect to the (remote) disk location. Because of this, nbdkit will not
report an error until after qemu is launched and tries to read from the
disk. This results in a fairly user-unfriendly error saying that qemu
was unable to start because "Requested export not available".
Ideally we'd like to be able to tell the user *why* the export is not
available, but this sort of information is only available to nbdkit, not
qemu. It could be because the url was incorrect, or because of an
authentication failure, or one of many other possibilities.
To make this friendlier for users and easier to detect
misconfigurations, try to connect to nbdkit immediately after starting
nbdkit and before we try to start qemu. This requires adding a
dependency on libnbd. If an error occurs when connecting to nbdkit, read
back from the nbdkit error log and provide that information in the error
report from qemuNbdkitProcessStart().
User-visible change demonstrated below:
Previous error:
$ virsh start nbdkit-test
2023-01-18 19:47:45.778+0000: 30895: error : virNetClientProgramDispatchError:172 : internal
error: process exited while connecting to monitor: 2023-01-18T19:47:45.704658Z
qemu-system-x86_64: -blockdev {"driver":"nbd","server":{"type":"unix",
"path":"/var/lib/libvirt/qemu/domain-1-nbdkit-test/nbdkit-libvirt-1-storage.socket"},
"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}: Requested export not
available
error: Failed to start domain 'nbdkit-test'
error: internal error: process exited while connecting to monitor: 2023-01-18T19:47:45.704658Z
qemu-system-x86_64: -blockdev {"driver":"nbd","server":{"type":"unix",
"path":"/var/lib/libvirt/qemu/domain-1-nbdkit-test/nbdkit-libvirt-1-storage.socket"},
"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}: Requested export not
available
After this change:
$ virsh start nbdkit-test
2023-01-18 19:44:36.242+0000: 30895: error : virNetClientProgramDispatchError:172 : internal
error: Failed to connect to nbdkit for 'http://localhost:8888/nonexistent.iso': nbdkit: curl[1]:
error: problem doing HEAD request to fetch size of URL [http://localhost:8888/nonexistent.iso]:
HTTP response code said error: The requested URL returned error: 404
error: Failed to start domain 'nbdkit-test'
error: internal error: Failed to connect to nbdkit for 'http://localhost:8888/nonexistent.iso]:
error: problem doing HEAD request to fetch size of URL [http://localhost:8888/nonexistent.iso]:
HTTP response code said error: The requested URL returned error: 404
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
2022-12-16 23:10:49 +00:00
|
|
|
struct nbd_handle *nbd = NULL;
|
|
|
|
#endif
|
2022-07-08 19:20:20 +00:00
|
|
|
|
|
|
|
if (!(cmd = qemuNbdkitProcessBuildCommand(proc)))
|
|
|
|
return -1;
|
|
|
|
|
2022-12-12 21:12:33 +00:00
|
|
|
if (!(logContext = qemuLogContextNew(driver, vm, basename))) {
|
|
|
|
virLastErrorPrefixMessage("%s", _("can't connect to virtlogd"));
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
logfd = qemuLogContextGetWriteFD(logContext);
|
|
|
|
|
2023-09-25 15:02:20 +00:00
|
|
|
VIR_DEBUG("starting nbdkit process for %s", qemuBlockStorageSourceGetStorageNodename(proc->source));
|
2022-12-12 21:12:33 +00:00
|
|
|
virCommandSetErrorFD(cmd, &logfd);
|
|
|
|
virCommandSetOutputFD(cmd, &logfd);
|
2022-07-08 19:20:20 +00:00
|
|
|
virCommandSetPidFile(cmd, proc->pidfile);
|
|
|
|
|
|
|
|
if (qemuExtDeviceLogCommand(driver, vm, cmd, "nbdkit") < 0)
|
|
|
|
goto error;
|
|
|
|
|
2022-12-22 22:56:47 +00:00
|
|
|
if (proc->source->ssh_keyfile &&
|
|
|
|
qemuSecurityDomainSetPathLabel(driver, vm, proc->source->ssh_keyfile, false) < 0)
|
|
|
|
goto error;
|
|
|
|
|
2023-07-14 20:54:03 +00:00
|
|
|
if (proc->source->ssh_agent &&
|
|
|
|
qemuSecurityDomainSetPathLabel(driver, vm, proc->source->ssh_agent, false) < 0)
|
|
|
|
goto error;
|
|
|
|
|
2022-12-22 19:04:51 +00:00
|
|
|
if (proc->source->ssh_known_hosts_file &&
|
|
|
|
qemuSecurityDomainSetPathLabel(driver, vm, proc->source->ssh_known_hosts_file, false) < 0)
|
|
|
|
goto error;
|
|
|
|
|
2022-07-08 19:20:20 +00:00
|
|
|
if (qemuSecurityCommandRun(driver, vm, cmd, proc->user, proc->group, true, &exitstatus) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
if (exitstatus != 0) {
|
|
|
|
virReportError(VIR_ERR_INTERNAL_ERROR,
|
|
|
|
_("Could not start 'nbdkit'. exitstatus: %1$d"), exitstatus);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((rc = virPidFileReadPath(proc->pidfile, &proc->pid)) < 0) {
|
|
|
|
virReportSystemError(-rc,
|
|
|
|
_("Failed to read pidfile %1$s"),
|
|
|
|
proc->pidfile);
|
|
|
|
goto error;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (virTimeBackOffStart(&timebackoff, 1, 1000) < 0)
|
|
|
|
goto error;
|
|
|
|
|
|
|
|
while (virTimeBackOffWait(&timebackoff)) {
|
2022-10-05 17:03:33 +00:00
|
|
|
if (virFileExists(proc->socketfile)) {
|
meson: Improve nbdkit configurability
Currently, nbdkit support will automatically be enabled as long as
the pidfd_open(2) syscall is available. Optionally, libnbd is used
to generate more user-friendly error messages.
In theory this is all good, since use of nbdkit is supposed to be
transparent to the user. In practice, however, there is a problem:
if support for it is enabled at build time and the necessary
runtime components are installed, nbdkit will always be preferred,
with no way for the user to opt out.
This will arguably be fine in the long run, but right now none of
the platforms that we target ships with a SELinux policy that
allows libvirt to launch nbdkit, and the AppArmor policy that we
maintain ourselves hasn't been updated either.
So, in practice, as of today having nbdkit installed on the host
makes network disks completely unusable unless you're willing to
compromise the overall security of the system by disabling
SELinux/AppArmor.
In order to make the transition smoother, provide a convenient
way for users and distro packagers to disable nbdkit support at
compile time until SELinux and AppArmor are ready.
In the process, detection is completely overhauled. libnbd is
made mandatory when nbdkit support is enabled, since availability
across operating systems is comparable and offering users the
option to make error messages worse doesn't make a lot of sense;
we also make sure that an explicit request from the user to
enable/disable nbdkit support is either complied with, or results
in a build failure when that's not possible. Last but not least,
we avoid linking against libnbd when nbdkit support is disabled.
At the RPM level, we disable the feature when building against
anything older than Fedora 40, which still doesn't have the
necessary SELinux bits but will hopefully gain them by the time
it's released. We also allow nbdkit support to be disabled at
build time the same way as other optional features, that is, by
passing "--define '_without_nbdkit 1'" to rpmbuild. Finally, if
nbdkit support has been disabled, installing libvirt will no
longer drag it in as a (weak) dependency.
Signed-off-by: Andrea Bolognani <abologna@redhat.com>
Reviewed-by: Jonathon Jongsma <jjongsma@redhat.com>
2023-10-04 22:37:09 +00:00
|
|
|
#if WITH_NBDKIT
|
qemu: try to connect to nbdkit early to detect errors
When using nbdkit to serve a network disk source, the nbdkit process
will start and wait for an nbd connection before actually attempting to
connect to the (remote) disk location. Because of this, nbdkit will not
report an error until after qemu is launched and tries to read from the
disk. This results in a fairly user-unfriendly error saying that qemu
was unable to start because "Requested export not available".
Ideally we'd like to be able to tell the user *why* the export is not
available, but this sort of information is only available to nbdkit, not
qemu. It could be because the url was incorrect, or because of an
authentication failure, or one of many other possibilities.
To make this friendlier for users and easier to detect
misconfigurations, try to connect to nbdkit immediately after starting
nbdkit and before we try to start qemu. This requires adding a
dependency on libnbd. If an error occurs when connecting to nbdkit, read
back from the nbdkit error log and provide that information in the error
report from qemuNbdkitProcessStart().
User-visible change demonstrated below:
Previous error:
$ virsh start nbdkit-test
2023-01-18 19:47:45.778+0000: 30895: error : virNetClientProgramDispatchError:172 : internal
error: process exited while connecting to monitor: 2023-01-18T19:47:45.704658Z
qemu-system-x86_64: -blockdev {"driver":"nbd","server":{"type":"unix",
"path":"/var/lib/libvirt/qemu/domain-1-nbdkit-test/nbdkit-libvirt-1-storage.socket"},
"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}: Requested export not
available
error: Failed to start domain 'nbdkit-test'
error: internal error: process exited while connecting to monitor: 2023-01-18T19:47:45.704658Z
qemu-system-x86_64: -blockdev {"driver":"nbd","server":{"type":"unix",
"path":"/var/lib/libvirt/qemu/domain-1-nbdkit-test/nbdkit-libvirt-1-storage.socket"},
"node-name":"libvirt-1-storage","auto-read-only":true,"discard":"unmap"}: Requested export not
available
After this change:
$ virsh start nbdkit-test
2023-01-18 19:44:36.242+0000: 30895: error : virNetClientProgramDispatchError:172 : internal
error: Failed to connect to nbdkit for 'http://localhost:8888/nonexistent.iso': nbdkit: curl[1]:
error: problem doing HEAD request to fetch size of URL [http://localhost:8888/nonexistent.iso]:
HTTP response code said error: The requested URL returned error: 404
error: Failed to start domain 'nbdkit-test'
error: internal error: Failed to connect to nbdkit for 'http://localhost:8888/nonexistent.iso]:
error: problem doing HEAD request to fetch size of URL [http://localhost:8888/nonexistent.iso]:
HTTP response code said error: The requested URL returned error: 404
Signed-off-by: Jonathon Jongsma <jjongsma@redhat.com>
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
2022-12-16 23:10:49 +00:00
|
|
|
/* if the disk source was misconfigured, nbdkit will not produce an error
|
|
|
|
* until somebody connects to the socket and tries to access the nbd
|
|
|
|
* export. This results in poor user experience because the only error we
|
|
|
|
* would get from qemu is something like "Requested export not available".
|
|
|
|
* So let's try to access it ourselves so that we can error out early and
|
|
|
|
* provide a useful message to the user.
|
|
|
|
*/
|
|
|
|
nbd = nbd_create();
|
|
|
|
if (nbd_connect_unix(nbd, proc->socketfile) < 0) {
|
|
|
|
VIR_WARN("nbd_connect_unix failed: %s", nbd_get_error());
|
|
|
|
nbd_close(nbd);
|
|
|
|
goto errorlog;
|
|
|
|
}
|
|
|
|
nbd_close(nbd);
|
|
|
|
|
|
|
|
#endif
|
2022-10-05 17:03:33 +00:00
|
|
|
if (qemuNbdkitProcessStartMonitor(proc, vm) < 0)
|
|
|
|
goto error;
|
2022-07-08 19:20:20 +00:00
|
|
|
return 0;
|
2022-10-05 17:03:33 +00:00
|
|
|
}
|
2022-07-08 19:20:20 +00:00
|
|
|
|
|
|
|
if (virProcessKill(proc->pid, 0) == 0)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
VIR_WARN("nbdkit died unexpectedly");
|
|
|
|
goto errorlog;
|
|
|
|
}
|
|
|
|
|
|
|
|
VIR_WARN("nbdkit socket did not show up");
|
|
|
|
|
|
|
|
errorlog:
|
|
|
|
if ((uri = qemuBlockStorageSourceGetURI(proc->source)))
|
|
|
|
uristring = virURIFormat(uri);
|
|
|
|
|
2022-12-12 21:12:33 +00:00
|
|
|
if (qemuLogContextReadFiltered(logContext, &errbuf, 1024) < 0)
|
|
|
|
VIR_WARN("Unable to read from nbdkit log");
|
|
|
|
|
2022-07-08 19:20:20 +00:00
|
|
|
virReportError(VIR_ERR_OPERATION_FAILED,
|
|
|
|
_("Failed to connect to nbdkit for '%1$s': %2$s"),
|
|
|
|
NULLSTR(uristring), NULLSTR(errbuf));
|
|
|
|
|
|
|
|
error:
|
2022-12-22 19:04:51 +00:00
|
|
|
qemuNbdkitProcessStop(proc, vm);
|
2022-07-08 19:20:20 +00:00
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
int
|
2022-12-22 19:04:51 +00:00
|
|
|
qemuNbdkitProcessStop(qemuNbdkitProcess *proc,
|
|
|
|
virDomainObj *vm)
|
2022-07-08 19:20:20 +00:00
|
|
|
{
|
2022-12-22 19:04:51 +00:00
|
|
|
qemuDomainObjPrivate *vmpriv = vm->privateData;
|
|
|
|
virQEMUDriver *driver = vmpriv->driver;
|
|
|
|
|
2022-10-05 17:03:33 +00:00
|
|
|
qemuNbdkitProcessStopMonitor(proc);
|
|
|
|
|
2022-12-22 19:04:51 +00:00
|
|
|
if (proc->source->ssh_known_hosts_file)
|
|
|
|
qemuSecurityDomainRestorePathLabel(driver, vm, proc->source->ssh_known_hosts_file);
|
|
|
|
|
2022-12-22 22:56:47 +00:00
|
|
|
if (proc->source->ssh_keyfile)
|
|
|
|
qemuSecurityDomainRestorePathLabel(driver, vm, proc->source->ssh_keyfile);
|
|
|
|
|
2023-07-14 20:54:03 +00:00
|
|
|
if (proc->source->ssh_agent)
|
|
|
|
qemuSecurityDomainRestorePathLabel(driver, vm, proc->source->ssh_agent);
|
|
|
|
|
2022-07-08 19:20:20 +00:00
|
|
|
if (proc->pid < 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
VIR_DEBUG("Stopping nbdkit process %i", proc->pid);
|
|
|
|
virProcessKill(proc->pid, SIGTERM);
|
|
|
|
|
|
|
|
unlink(proc->pidfile);
|
|
|
|
unlink(proc->socketfile);
|
|
|
|
proc->pid = -1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|