diff --git a/build-aux/syntax-check.mk b/build-aux/syntax-check.mk index e5623606dd992bc56f321e809caef2bbc286781f..86519b444dd64e29900c0fe135eab495e6d5f75c 100644 --- a/build-aux/syntax-check.mk +++ b/build-aux/syntax-check.mk @@ -2311,7 +2311,7 @@ exclude_file_name_regexp--sc_prohibit_devname = \ ^(tools/virsh.pod|build-aux/syntax-check\.mk|docs/.*)$$ exclude_file_name_regexp--sc_prohibit_virXXXFree = \ - ^(docs/|tests/|examples/|tools/|build-aux/syntax-check\.mk|src/test/test_driver.c|src/libvirt_public.syms|include/libvirt/libvirt-(domain|network|nodedev|storage|stream|secret|nwfilter|interface|domain-snapshot).h|src/libvirt-(domain|qemu|network|nodedev|storage|stream|secret|nwfilter|interface|domain-snapshot).c$$) + ^(docs/|tests/|examples/|tools/|build-aux/syntax-check\.mk|src/test/test_driver.c|src/libvirt_public.syms|include/libvirt/libvirt-(domain|network|nodedev|storage|stream|secret|nwfilter|interface|domain-snapshot).h|src/libvirt-(domain|qemu|network|nodedev|storage|stream|secret|nwfilter|interface|domain-snapshot).c|src/qemu/qemu_shim.c$$) exclude_file_name_regexp--sc_prohibit_sysconf_pagesize = \ ^(build-aux/syntax-check\.mk|src/util/vir(hostmem|util)\.c)$$ diff --git a/docs/Makefile.am b/docs/Makefile.am index 6e4dc68a7b0f638178c9dc23e8c37d93c48cb029..94ae5079ddd4efadd825de8051b6fc701b8702c0 100644 --- a/docs/Makefile.am +++ b/docs/Makefile.am @@ -248,6 +248,11 @@ if WITH_SANLOCK else ! WITH_SANLOCK manpages_rst += manpages/virt-sanlock-cleanup.rst endif ! WITH_SANLOCK +if WITH_QEMU + manpages1_rst += manpages/virt-qemu-run.rst +else ! WITH_QEMU + manpages_rst += manpages/virt-qemu-run.rst +endif ! WITH_QEMU manpages_rst_html_in = \ $(manpages_rst:%.rst=%.html.in) manpages_html = \ diff --git a/docs/manpages/index.rst b/docs/manpages/index.rst index 4945ad59e219c3cf695e4ad626d65ac907f4db52..2e71f8196232d7a2c587a1b95da2f24ebb05ce65 100644 --- a/docs/manpages/index.rst +++ b/docs/manpages/index.rst @@ -19,6 +19,7 @@ Tools * `virt-login-shell(1) `__ - tool to execute a shell within a container * `virt-admin(1) `__ - daemon administration interface * `virsh(1) `__ - management user interface +* `virt-qemu-run(1) `_ + +#. the bug tracker + + `https://libvirt.org/bugs.html `_ + +Alternatively, you may report bugs to your software distributor / vendor. + + +COPYRIGHT +========= + +Copyright (C) 2019 by Red Hat, Inc. + + +LICENSE +======= + +``virt-run-qemu`` is distributed under the terms of the GNU LGPL v2+. +This is free software; see the source for copying conditions. There +is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR +PURPOSE + +SEE ALSO +======== + +virsh(1), `https://libvirt.org/ `_ diff --git a/libvirt.spec.in b/libvirt.spec.in index 5055750d2dca6d5f66d266fae74a408155bcb688..bbf9748582f5e9da21b08da85fd4b705bb1c1a48 100644 --- a/libvirt.spec.in +++ b/libvirt.spec.in @@ -1749,6 +1749,8 @@ exit 0 %{_libdir}/%{name}/connection-driver/libvirt_driver_qemu.so %dir %attr(0711, root, root) %{_localstatedir}/lib/libvirt/swtpm/ %dir %attr(0711, root, root) %{_localstatedir}/log/swtpm/libvirt/qemu/ +%{_bindir}/virt-qemu-run +%{_mandir}/man1/virt-qemu-run.1* %endif %if %{with_lxc} diff --git a/src/Makefile.am b/src/Makefile.am index c9b5eeba47dcc301396201c532fefac6d22e8344..d4042cf7baffd03067de47e02fdfc61370578e90 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -87,6 +87,7 @@ OPENRC_INIT_FILES_IN = OPENRC_CONF_FILES = SYSCONF_FILES = sbin_PROGRAMS = +bin_PROGRAMS = DRIVER_SOURCES = COMMON_UNIT_VARS = \ diff --git a/src/qemu/Makefile.inc.am b/src/qemu/Makefile.inc.am index 967f6e75a297cb9aded105cf9cd13301298a6dab..c6b04c32170e49b19260b5c5ae8c35d329490ab9 100644 --- a/src/qemu/Makefile.inc.am +++ b/src/qemu/Makefile.inc.am @@ -243,3 +243,16 @@ EXTRA_DIST += \ qemu/THREADS.txt \ libvirt_qemu_probes.d \ $(NULL) + +QEMU_SHIM_SOURCES = qemu/qemu_shim.c + +EXTRA_DIST += $(QEMU_SHIM_SOURCES) + +if WITH_QEMU +bin_PROGRAMS += virt-qemu-run + +virt_qemu_run_SOURCES = $(QEMU_SHIM_SOURCES) + +virt_qemu_run_LDADD = libvirt.la +virt_qemu_run_LDFLAGS = -Wl,--export-dynamic +endif WITH_QEMU diff --git a/src/qemu/qemu_shim.c b/src/qemu/qemu_shim.c new file mode 100644 index 0000000000000000000000000000000000000000..f4616f97b24ae29e680ca3ac6c7493658a5560e2 --- /dev/null +++ b/src/qemu/qemu_shim.c @@ -0,0 +1,322 @@ +/* + * 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 + * . + */ + +#include + +#include +#include + +#include "virfile.h" +#include "virstring.h" +#include "virgettext.h" + +#define VIR_FROM_THIS VIR_FROM_QEMU + +static bool eventQuitFlag; +static int eventQuitFD = -1; +static virDomainPtr dom; + +static void * +qemuShimEventLoop(void *opaque G_GNUC_UNUSED) +{ + while (!eventQuitFlag) + virEventRunDefaultImpl(); + + return NULL; +} + +/* Runs in event loop thread context */ +static void +qemuShimEventLoopStop(int watch G_GNUC_UNUSED, + int fd G_GNUC_UNUSED, + int event G_GNUC_UNUSED, + void *opaque G_GNUC_UNUSED) +{ + char c; + ignore_value(read(fd, &c, 1)); + eventQuitFlag = true; +} + +/* 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) + eventQuitFlag = true; + + 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}; + 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\n", + argv[0], deltams()); + + 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); + + if (root == NULL) { + if (!(root = g_dir_make_tmp("libvirt-qemu-shim-XXXXXX", &error))) { + g_printerr("%s: cannot create temporary dir: %s\n", + argv[0], error->message); + return 1; + } + tmproot = 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", 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 regiser for lifecycle events: %s\n", + argv[0], virGetLastErrorMessage()); + goto cleanup; + } + + if (verbose) + g_printerr("%s: %lld: starting guest %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; + } + + dom = virDomainCreateXML(conn, xml, 0); + 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 nowbbbb\n", + argv[0], deltams()); + + 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; +}