libvirt/src/qemu/qemu_shim.c
Pavel Hrdina b2b1702341 src: add missing headers to various files
All these headers are indirectly included provided by virfile.h having
virstoragefile.h which will be removed in the following patch.

Signed-off-by: Pavel Hrdina <phrdina@redhat.com>
Reviewed-by: Peter Krempa <pkrempa@redhat.com>
2021-01-06 13:15:17 +01:00

399 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) {
if (dom) {
virDomainDestroy(dom);
quit = true;
}
}
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;
char *root = 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;
if (chmod(root, 0755) < 0) {
g_printerr("%s: cannot chown temporary dir: %s\n",
argv[0], g_strerror(errno));
goto cleanup;
}
}
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", root);
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", root);
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;
}