libvirt/src/qemu/qemu_shim.c
Michal Privoznik 52a937d8a2 qemu_shim: Don't hang if failed to start domain
The qemu shim spawns a separate thread in which the event loop is
ran. The virEventRunDefaultImpl() call is wrapped in a while()
loop, just like it should. There are few lines of code around
which try to ensure that domain is destroyed (when quitting) and
that the last round of event loop is ran after the
virDomainDestroy() call. Only after that the loop is quit from
and the thread quits.

However, if domain creation fails, there is no @dom to call
destroy over, the @quit flag is never set and while() never
exits. Set the flag regardless of @dom pointer.

Resolves: https://bugzilla.redhat.com/show_bug.cgi?id=1920337
Signed-off-by: Michal Privoznik <mprivozn@redhat.com>
Reviewed-by: Andrea Bolognani <abologna@redhat.com>
2021-03-12 17:05:52 +01:00

407 lines
12 KiB
C

/*
* qemu_shim.c: standalone binary for running QEMU instances
*
* Copyright (C) 2019 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.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 <stdio.h>
#include <stdbool.h>
#include <unistd.h>
#include "virfile.h"
#include "virstring.h"
#include "virgettext.h"
#include "virthread.h"
#define VIR_FROM_THIS VIR_FROM_QEMU
static GMutex eventLock;
static bool eventPreventQuitFlag;
static bool eventQuitFlag;
static int eventQuitFD = -1;
static virDomainPtr dom;
/* Runs in event loop thread context */
static void *
qemuShimEventLoop(void *opaque G_GNUC_UNUSED)
{
bool quit = false;
while (!quit) {
g_mutex_lock(&eventLock);
if (eventQuitFlag && !eventPreventQuitFlag) {
quit = true;
if (dom) {
virDomainDestroy(dom);
}
}
g_mutex_unlock(&eventLock);
virEventRunDefaultImpl();
}
return NULL;
}
/* Runs in any thread context */
static bool
qemuShimEventLoopPreventQuit(void)
{
bool quitting;
g_mutex_lock(&eventLock);
quitting = eventQuitFlag;
if (!quitting)
eventPreventQuitFlag = true;
g_mutex_unlock(&eventLock);
return quitting;
}
/* Runs in any thread context */
static bool
qemuShimEventLoopAllowQuit(void)
{
bool quitting;
g_mutex_lock(&eventLock);
eventPreventQuitFlag = false;
/* kick the event loop thread again immediately */
quitting = eventQuitFlag;
if (quitting)
ignore_value(safewrite(eventQuitFD, "c", 1));
g_mutex_unlock(&eventLock);
return quitting;
}
/* Runs in event loop thread context */
static void
qemuShimEventLoopStop(int watch G_GNUC_UNUSED,
int fd,
int event G_GNUC_UNUSED,
void *opaque G_GNUC_UNUSED)
{
char c;
ignore_value(read(fd, &c, 1));
g_mutex_lock(&eventLock);
eventQuitFlag = true;
g_mutex_unlock(&eventLock);
}
/* Runs in event loop thread context */
static int
qemuShimDomShutdown(virConnectPtr econn G_GNUC_UNUSED,
virDomainPtr edom G_GNUC_UNUSED,
int event,
int detail G_GNUC_UNUSED,
void *opaque G_GNUC_UNUSED)
{
if (event == VIR_DOMAIN_EVENT_STOPPED) {
g_mutex_lock(&eventLock);
eventQuitFlag = true;
g_mutex_unlock(&eventLock);
}
return 0;
}
/* Runs in unknown thread context */
static void
qemuShimSigShutdown(int sig G_GNUC_UNUSED)
{
if (dom)
virDomainDestroy(dom);
ignore_value(safewrite(eventQuitFD, "c", 1));
}
static void
qemuShimQuench(void *userData G_GNUC_UNUSED,
virErrorPtr error G_GNUC_UNUSED)
{
}
int main(int argc, char **argv)
{
GThread *eventLoopThread = NULL;
virConnectPtr conn = NULL;
virConnectPtr sconn = NULL;
g_autofree char *xml = NULL;
g_autofree char *uri = NULL;
g_autofree char *suri = NULL;
const char *root = NULL;
g_autofree char *escaped = NULL;
bool tmproot = false;
int ret = 1;
g_autoptr(GError) error = NULL;
g_auto(GStrv) secrets = NULL;
gboolean verbose = false;
gboolean debug = false;
GStrv tmpsecrets;
GOptionContext *ctx;
GOptionEntry entries[] = {
{ "secret", 's', 0, G_OPTION_ARG_STRING_ARRAY, &secrets, "Load secret file", "SECRET-XML-FILE,SECRET-VALUE-FILE" },
{ "root", 'r', 0, G_OPTION_ARG_STRING, &root, "Root directory", "DIR" },
{ "debug", 'd', 0, G_OPTION_ARG_NONE, &debug, "Debug output", NULL },
{ "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose, "Verbose output", NULL },
{ 0 }
};
int quitfd[2] = {-1, -1};
bool quitting;
long long start = g_get_monotonic_time();
#define deltams() ((long long)g_get_monotonic_time() - start)
ctx = g_option_context_new("- run a standalone QEMU process");
g_option_context_add_main_entries(ctx, entries, PACKAGE);
if (!g_option_context_parse(ctx, &argc, &argv, &error)) {
g_printerr("%s: option parsing failed: %s\n",
argv[0], error->message);
return 1;
}
if (argc != 2) {
g_autofree char *help = g_option_context_get_help(ctx, TRUE, NULL);
g_printerr("%s", help);
return 1;
}
if (verbose)
g_printerr("%s: %lld: initializing libvirt %llu\n",
argv[0], deltams(), virThreadSelfID());
if (virInitialize() < 0) {
g_printerr("%s: cannot initialize libvirt\n", argv[0]);
return 1;
}
if (virGettextInitialize() < 0) {
g_printerr("%s: cannot initialize libvirt translations\n", argv[0]);
return 1;
}
virSetErrorFunc(NULL, qemuShimQuench);
if (verbose)
g_printerr("%s: %lld: initializing signal handlers\n",
argv[0], deltams());
signal(SIGTERM, qemuShimSigShutdown);
signal(SIGINT, qemuShimSigShutdown);
signal(SIGQUIT, qemuShimSigShutdown);
signal(SIGHUP, qemuShimSigShutdown);
signal(SIGPIPE, SIG_IGN);
if (root == NULL) {
if (!(root = g_dir_make_tmp("virt-qemu-run-XXXXXX", &error))) {
g_printerr("%s: cannot create temporary dir: %s\n",
argv[0], error->message);
return 1;
}
tmproot = true;
} else if (g_mkdir_with_parents(root, 0755) < 0) {
g_printerr("%s: cannot create dir: %s\n",
argv[0], g_strerror(errno));
goto cleanup;
}
if (chmod(root, 0755) < 0) {
g_printerr("%s: cannot chmod temporary dir: %s\n",
argv[0], g_strerror(errno));
goto cleanup;
}
escaped = g_uri_escape_string(root, NULL, true);
virFileActivateDirOverrideForProg(argv[0]);
if (verbose)
g_printerr("%s: %lld: preparing event loop thread\n",
argv[0], deltams());
virEventRegisterDefaultImpl();
if (pipe(quitfd) < 0) {
g_printerr("%s: cannot create event loop pipe: %s",
argv[0], g_strerror(errno));
goto cleanup;
}
if (virEventAddHandle(quitfd[0], VIR_EVENT_HANDLE_READABLE, qemuShimEventLoopStop, NULL, NULL) < 0) {
VIR_FORCE_CLOSE(quitfd[0]);
VIR_FORCE_CLOSE(quitfd[1]);
quitfd[0] = quitfd[1] = -1;
g_printerr("%s: cannot register event loop handle: %s",
argv[0], virGetLastErrorMessage());
goto cleanup;
}
eventQuitFD = quitfd[1];
eventLoopThread = g_thread_new("event-loop", qemuShimEventLoop, NULL);
if (secrets && *secrets) {
suri = g_strdup_printf("secret:///embed?root=%s", escaped);
if (verbose)
g_printerr("%s: %lld: opening %s\n",
argv[0], deltams(), suri);
sconn = virConnectOpen(suri);
if (!sconn) {
g_printerr("%s: cannot open %s: %s\n",
argv[0], suri, virGetLastErrorMessage());
goto cleanup;
}
tmpsecrets = secrets;
while (tmpsecrets && *tmpsecrets) {
g_auto(GStrv) bits = g_strsplit(*tmpsecrets, ",", 2);
g_autofree char *sxml = NULL;
g_autofree char *value = NULL;
virSecretPtr sec;
size_t nvalue;
if (!bits || bits[0] == NULL || bits[1] == NULL) {
g_printerr("%s: expected a pair of filenames for --secret argument\n",
argv[0]);
goto cleanup;
}
if (verbose)
g_printerr("%s: %lld: loading secret %s and %s\n",
argv[0], deltams(), bits[0], bits[1]);
if (!g_file_get_contents(bits[0], &sxml, NULL, &error)) {
g_printerr("%s: cannot read secret XML %s: %s\n",
argv[0], bits[0], error->message);
goto cleanup;
}
if (!g_file_get_contents(bits[1], &value, &nvalue, &error)) {
g_printerr("%s: cannot read secret value %s: %s\n",
argv[0], bits[1], error->message);
goto cleanup;
}
if (!(sec = virSecretDefineXML(sconn, sxml, 0))) {
g_printerr("%s: cannot define secret %s: %s\n",
argv[0], bits[0], virGetLastErrorMessage());
goto cleanup;
}
if (virSecretSetValue(sec, (unsigned char *)value, nvalue, 0) < 0) {
virSecretFree(sec);
g_printerr("%s: cannot set value for secret %s: %s\n",
argv[0], bits[0], virGetLastErrorMessage());
goto cleanup;
}
virSecretFree(sec);
tmpsecrets++;
}
}
uri = g_strdup_printf("qemu:///embed?root=%s", escaped);
if (verbose)
g_printerr("%s: %lld: opening %s\n",
argv[0], deltams(), uri);
conn = virConnectOpen(uri);
if (!conn) {
g_printerr("%s: cannot open %s: %s\n",
argv[0], uri, virGetLastErrorMessage());
goto cleanup;
}
if (virConnectDomainEventRegisterAny(
conn, dom, VIR_DOMAIN_EVENT_ID_LIFECYCLE,
VIR_DOMAIN_EVENT_CALLBACK(qemuShimDomShutdown),
NULL, NULL) < 0) {
g_printerr("%s: cannot register for lifecycle events: %s\n",
argv[0], virGetLastErrorMessage());
goto cleanup;
}
if (verbose)
g_printerr("%s: %lld: fetching guest config %s\n",
argv[0], deltams(), argv[1]);
if (!g_file_get_contents(argv[1], &xml, NULL, &error)) {
g_printerr("%s: cannot read %s: %s\n",
argv[0], argv[1], error->message);
goto cleanup;
}
if (verbose)
g_printerr("%s: %lld: starting guest %s\n",
argv[0], deltams(), argv[1]);
/*
* If the user issues a ctrl-C at this time, we need to
* let the virDomainCreateXML call complete, so that we
* can then clean up the guest correctly. We must also
* ensure that the event loop doesn't quit yet, because
* it might be needed to complete VM startup & shutdown
* during the cleanup.
*/
quitting = qemuShimEventLoopPreventQuit();
if (quitting)
goto cleanup;
dom = virDomainCreateXML(conn, xml, 0);
quitting = qemuShimEventLoopAllowQuit();
if (!dom) {
g_printerr("%s: cannot start VM: %s\n",
argv[0], virGetLastErrorMessage());
goto cleanup;
}
if (verbose)
g_printerr("%s: %lld: guest running, Ctrl-C to stop now\n",
argv[0], deltams());
if (quitting)
goto cleanup;
if (debug) {
g_autofree char *newxml = NULL;
newxml = virDomainGetXMLDesc(dom, 0);
g_printerr("%s: XML: %s\n", argv[0], newxml);
}
ret = 0;
cleanup:
if (ret != 0 && eventQuitFD != -1)
ignore_value(safewrite(eventQuitFD, "c", 1));
if (eventLoopThread != NULL && (ret == 0 || eventQuitFD != -1))
g_thread_join(eventLoopThread);
VIR_FORCE_CLOSE(quitfd[0]);
VIR_FORCE_CLOSE(quitfd[1]);
if (dom != NULL)
virDomainFree(dom);
if (sconn != NULL)
virConnectClose(sconn);
if (conn != NULL)
virConnectClose(conn);
if (tmproot)
virFileDeleteTree(root);
if (verbose)
g_printerr("%s: %lld: cleaned up, exiting\n",
argv[0], deltams());
return ret;
}