diff --git a/po/POTFILES.in b/po/POTFILES.in index bf0a3b3529..1fd3afdd6f 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -164,6 +164,7 @@ @SRCDIR@src/qemu/qemu_domainjob.c @SRCDIR@src/qemu/qemu_driver.c @SRCDIR@src/qemu/qemu_extdevice.c +@SRCDIR@src/qemu/qemu_fd.c @SRCDIR@src/qemu/qemu_firmware.c @SRCDIR@src/qemu/qemu_hostdev.c @SRCDIR@src/qemu/qemu_hotplug.c diff --git a/src/qemu/meson.build b/src/qemu/meson.build index 3ea084cff8..39f0f615cc 100644 --- a/src/qemu/meson.build +++ b/src/qemu/meson.build @@ -15,6 +15,7 @@ qemu_driver_sources = [ 'qemu_domainjob.c', 'qemu_driver.c', 'qemu_extdevice.c', + 'qemu_fd.c', 'qemu_firmware.c', 'qemu_hostdev.c', 'qemu_hotplug.c', diff --git a/src/qemu/qemu_fd.c b/src/qemu/qemu_fd.c new file mode 100644 index 0000000000..29370a4bb6 --- /dev/null +++ b/src/qemu/qemu_fd.c @@ -0,0 +1,320 @@ +/* + * qemu_fd.c: QEMU fd and fdpass passing helpers + * + * 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 "qemu_fd.h" +#include "qemu_domain.h" + +#include "viralloc.h" +#include "virfile.h" +#include "virlog.h" + +#define VIR_FROM_THIS VIR_FROM_QEMU +VIR_LOG_INIT("qemu.qemu_fd"); + +struct qemuFDPassFD { + int fd; + char *opaque; +}; + +struct _qemuFDPass { + bool useFDSet; + unsigned int fdSetID; + size_t nfds; + struct qemuFDPassFD *fds; + char *prefix; + char *path; + + bool passed; /* passed to qemu via monitor */ +}; + + +void +qemuFDPassFree(qemuFDPass *fdpass) +{ + size_t i; + + if (!fdpass) + return; + + for (i = 0; i < fdpass->nfds; i++) { + VIR_FORCE_CLOSE(fdpass->fds[i].fd); + g_free(fdpass->fds[i].opaque); + } + + g_free(fdpass->fds); + g_free(fdpass->prefix); + g_free(fdpass->path); + g_free(fdpass); +} + + +/** + * qemuFDPassNew: + * @prefix: prefix used for naming the passed FDs + * @dompriv: qemu domain private data + * + * Create a new helper object for passing FDs to QEMU. The instance created + * via 'qemuFDPassNew' will result in the fd passed via a 'fdset' (/dev/fdset/N). + * + * Non-test uses must pass a valid @dompriv. + * + * @prefix is used as prefix for naming the fd in QEMU. + */ +qemuFDPass * +qemuFDPassNew(const char *prefix, + void *dompriv) +{ + qemuDomainObjPrivate *priv = dompriv; + qemuFDPass *fdpass = g_new0(qemuFDPass, 1); + + fdpass->prefix = g_strdup(prefix); + fdpass->useFDSet = true; + + if (priv) + fdpass->fdSetID = qemuDomainFDSetIDNew(priv); + + return fdpass; +} + + +/** + * qemuFDPassNewDirect: + * @prefix: prefix used for naming the passed FDs + * @dompriv: qemu domain private data + * + * Create a new helper object for passing FDs to QEMU. + * + * The instance created via 'qemuFDPassNewDirect' will result in the older + * approach of directly using FD number on the commandline and 'getfd' + * QMP command. + * + * Non-test uses must pass a valid @dompriv. + * + * @prefix is used for naming the FD if needed and is later referenced when + * removing the FDSet via monitor. + */ +qemuFDPass * +qemuFDPassNewDirect(const char *prefix, + void *dompriv G_GNUC_UNUSED) +{ + qemuFDPass *fdpass = g_new0(qemuFDPass, 1); + + fdpass->prefix = g_strdup(prefix); + + return fdpass; +} + + +/** + * qemuFDPassAddFD: + * @fdpass: The fd passing helper struct + * @fd: File descriptor to pass + * @suffix: Name suffix for the file descriptor name + * + * Adds @fd to be passed to qemu when transferring @fdpass to qemu. When @fdpass + * is configured to use FD set mode, multiple file descriptors can be passed by + * calling this function repeatedly. + * + * @suffix is used to build the name of the file descriptor by concatenating + * it with @prefix passed to qemuFDPassNew. @suffix may be NULL, in which case + * it's considered to be an empty string. + * + * Returns 0 on success, -1 on error (when attempting to pass multiple FDs) using + * the 'direct' method. + */ +int +qemuFDPassAddFD(qemuFDPass *fdpass, + int *fd, + const char *suffix) +{ + struct qemuFDPassFD newfd = { .fd = *fd }; + + if (newfd.fd < 0) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("invalid file descriptor")); + return -1; + } + + if (!fdpass->useFDSet && + fdpass->nfds >= 1) { + virReportError(VIR_ERR_INTERNAL_ERROR, "%s", + _("direct FD passing supports only 1 file descriptor")); + return -1; + } + + *fd = -1; + + newfd.opaque = g_strdup_printf("%s%s", fdpass->prefix, NULLSTR_EMPTY(suffix)); + + VIR_APPEND_ELEMENT(fdpass->fds, fdpass->nfds, newfd); + + return 0; +} + + +/** + * qemuFDPassTransferCommand: + * @fdpass: The fd passing helper struct + * @cmd: Command to pass the filedescriptors to + * + * Pass the fds in @fdpass to a commandline object @cmd. @fdpass may be NULL + * in which case this is a no-op. + */ +void +qemuFDPassTransferCommand(qemuFDPass *fdpass, + virCommand *cmd) +{ + size_t i; + + if (!fdpass) + return; + + for (i = 0; i < fdpass->nfds; i++) { + virCommandPassFD(cmd, fdpass->fds[i].fd, VIR_COMMAND_PASS_FD_CLOSE_PARENT); + + if (fdpass->useFDSet) { + g_autofree char *arg = NULL; + + arg = g_strdup_printf("set=%u,fd=%d,opaque=%s", + fdpass->fdSetID, + fdpass->fds[i].fd, + fdpass->fds[i].opaque); + + virCommandAddArgList(cmd, "-add-fd", arg, NULL); + + fdpass->path = g_strdup_printf("/dev/fdset/%u", fdpass->fdSetID); + } else { + fdpass->path = g_strdup_printf("%u", fdpass->fds[i].fd); + } + + fdpass->fds[i].fd = -1; + } +} + + +/** + * qemuFDPassTransferMonitor: + * @fdpass: The fd passing helper struct + * @mon: monitor object + * + * Pass the fds in @fdpass to qemu via the monitor. @fdpass may be NULL + * in which case this is a no-op. Caller needs to enter the monitor context. + */ +int +qemuFDPassTransferMonitor(qemuFDPass *fdpass, + qemuMonitor *mon) +{ + int fdsetid = -1; + size_t i; + + if (!fdpass) + return 0; + + for (i = 0; i < fdpass->nfds; i++) { + if (fdpass->useFDSet) { + qemuMonitorAddFdInfo fdsetinfo; + + if (qemuMonitorAddFileHandleToSet(mon, + fdpass->fds[i].fd, + fdsetid, + fdpass->fds[i].opaque, + &fdsetinfo) < 0) + return -1; + + if (fdsetid == -1) { + fdpass->fdSetID = fdsetid = fdsetinfo.fdset; + fdpass->path = g_strdup_printf("/dev/fdset/%u", fdsetid); + } + } else { + if (qemuMonitorSendFileHandle(mon, + fdpass->fds[i].opaque, + fdpass->fds[i].fd) < 0) + return -1; + + fdpass->path = g_strdup(fdpass->fds[i].opaque); + } + + fdpass->passed = true; + } + + return 0; +} + + +/** + * qemuFDPassTransferMonitorFake: + * @fdpass: The fd passing helper struct + * + * Simulate as if @fdpass was passed via monitor for callers which don't + * actually wish to test that code path. + */ +void +qemuFDPassTransferMonitorFake(qemuFDPass *fdpass) +{ + + if (!fdpass) + return; + + if (fdpass->useFDSet) { + fdpass->path = g_strdup_printf("/dev/fdset/monitor-fake"); + } else { + fdpass->path = g_strdup(fdpass->fds[0].opaque); + } +} + + +/** + * qemuFDPassTransferMonitorRollback: + * @fdpass: The fd passing helper struct + * @mon: monitor object + * + * Rolls back the addition of @fdpass to @mon if it was added originally. + */ +void +qemuFDPassTransferMonitorRollback(qemuFDPass *fdpass, + qemuMonitor *mon) +{ + if (!fdpass || !fdpass->passed) + return; + + if (fdpass->useFDSet) { + ignore_value(qemuMonitorRemoveFdset(mon, fdpass->fdSetID)); + } else { + ignore_value(qemuMonitorCloseFileHandle(mon, fdpass->fds[0].opaque)); + } +} + + +/** + * qemuFDPassGetPath: + * @fdpass: The fd passing helper struct + * + * Returns the path/fd name that is used in qemu to refer to the passed FD. + * Note that it's only valid to call this function after @fdpass was already + * transferred to the command or monitor. + */ +const char * +qemuFDPassGetPath(qemuFDPass *fdpass) +{ + if (!fdpass) + return NULL; + + return fdpass->path; +} diff --git a/src/qemu/qemu_fd.h b/src/qemu/qemu_fd.h new file mode 100644 index 0000000000..6d090392db --- /dev/null +++ b/src/qemu/qemu_fd.h @@ -0,0 +1,58 @@ +/* + * qemu_fd.h: QEMU fd and fdpass passing helpers + * + * 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 + * . + */ + +#pragma once + +#include "vircommand.h" +#include "qemu_monitor.h" + +typedef struct _qemuFDPass qemuFDPass; + +void +qemuFDPassFree(qemuFDPass *fdpass); +G_DEFINE_AUTOPTR_CLEANUP_FUNC(qemuFDPass, qemuFDPassFree); + +qemuFDPass * +qemuFDPassNew(const char *prefix, + void *dompriv); +qemuFDPass * +qemuFDPassNewDirect(const char *prefix, + void *dompriv); + +int +qemuFDPassAddFD(qemuFDPass *fdpass, + int *fd, + const char *suffix); + +void +qemuFDPassTransferCommand(qemuFDPass *fdpass, + virCommand *cmd); + +int +qemuFDPassTransferMonitor(qemuFDPass *fdpass, + qemuMonitor *mon); + +void +qemuFDPassTransferMonitorFake(qemuFDPass *fdpass); + +void +qemuFDPassTransferMonitorRollback(qemuFDPass *fdpass, + qemuMonitor *mon); + +const char * +qemuFDPassGetPath(qemuFDPass *fdpass);