/*
* 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);
}
static int
qemuFDPassValidate(qemuFDPass *fdpass)
{
size_t i;
if (!fdpass->useFDSet &&
fdpass->nfds > 1) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("direct FD passing supports only 1 file descriptor"));
return -1;
}
for (i = 0; i < fdpass->nfds; i++) {
if (fdpass->fds[i].fd < 0) {
virReportError(VIR_ERR_INTERNAL_ERROR, "%s",
_("invalid file descriptor"));
return -1;
}
}
return 0;
}
/**
* 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);
fdpass->path = g_strdup_printf("/dev/fdset/%u", fdpass->fdSetID);
} else {
fdpass->path = g_strdup_printf("/dev/fdset/monitor-fake");
}
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.
*/
void
qemuFDPassAddFD(qemuFDPass *fdpass,
int *fd,
const char *suffix)
{
struct qemuFDPassFD newfd = { .fd = *fd };
*fd = -1;
newfd.opaque = g_strdup_printf("%s%s", fdpass->prefix, NULLSTR_EMPTY(suffix));
if (!fdpass->useFDSet) {
g_free(fdpass->path);
fdpass->path = g_strdup(newfd.opaque);
}
VIR_APPEND_ELEMENT(fdpass->fds, fdpass->nfds, newfd);
}
/**
* 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.
*/
int
qemuFDPassTransferCommand(qemuFDPass *fdpass,
virCommand *cmd)
{
size_t i;
if (!fdpass)
return 0;
if (qemuFDPassValidate(fdpass) < 0)
return -1;
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);
} else {
/* for monitor use the older FD passing needs the FD number */
g_free(fdpass->path);
fdpass->path = g_strdup_printf("%d", fdpass->fds[i].fd);
}
fdpass->fds[i].fd = -1;
}
return 0;
}
/**
* 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)
{
size_t i;
if (!fdpass)
return 0;
if (qemuFDPassValidate(fdpass) < 0)
return -1;
if (fdpass->useFDSet) {
g_autoptr(qemuMonitorFdsets) fdsets = NULL;
if (qemuMonitorQueryFdsets(mon, &fdsets) < 0)
return -1;
for (i = 0; i < fdsets->nfdsets; i++) {
if (fdsets->fdsets[i].id == fdpass->fdSetID) {
virReportError(VIR_ERR_INTERNAL_ERROR,
_("fdset '%u' is already in use by qemu"),
fdpass->fdSetID);
return -1;
}
}
}
for (i = 0; i < fdpass->nfds; i++) {
if (fdpass->useFDSet) {
if (qemuMonitorAddFileHandleToSet(mon,
fdpass->fds[i].fd,
fdpass->fdSetID,
fdpass->fds[i].opaque) < 0)
return -1;
} else {
if (qemuMonitorSendFileHandle(mon,
fdpass->fds[i].opaque,
fdpass->fds[i].fd) < 0)
return -1;
}
VIR_FORCE_CLOSE(fdpass->fds[i].fd);
fdpass->passed = true;
}
return 0;
}
/**
* 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;
}
struct _qemuFDPassDirect {
int fd;
char *path;
char *name;
bool passed; /* passed to qemu via monitor */
};
void
qemuFDPassDirectFree(qemuFDPassDirect *fdpass)
{
if (!fdpass)
return;
VIR_FORCE_CLOSE(fdpass->fd);
g_free(fdpass->name);
g_free(fdpass->path);
g_free(fdpass);
}
/**
* qemuFDPassDirectNew:
* @name: Name of the fd (for monitor passing use-case)
* @fd: The FD, cleared when passed.
*
* The qemuFDPassDirect helper returned by this helper is used to hold a FD
* passed to qemu either direcly via FD number when used on commandline or the
* 'getfd' QMP command.
*/
qemuFDPassDirect *
qemuFDPassDirectNew(const char *name,
int *fd)
{
qemuFDPassDirect *fdpass = g_new0(qemuFDPassDirect, 1);
fdpass->name = g_strdup(name);
fdpass->fd = *fd;
*fd = -1;
return fdpass;
}
/**
* qemuFDPassDirectTransferCommand:
* @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
qemuFDPassDirectTransferCommand(qemuFDPassDirect *fdpass,
virCommand *cmd)
{
if (!fdpass)
return;
virCommandPassFD(cmd, fdpass->fd, VIR_COMMAND_PASS_FD_CLOSE_PARENT);
fdpass->path = g_strdup_printf("%d", fdpass->fd);
fdpass->fd = -1;
}
/**
* qemuFDPassDirectTransferMonitor:
* @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
qemuFDPassDirectTransferMonitor(qemuFDPassDirect *fdpass,
qemuMonitor *mon)
{
if (!fdpass)
return 0;
if (qemuMonitorSendFileHandle(mon, fdpass->name, fdpass->fd) < 0)
return -1;
fdpass->path = g_strdup(fdpass->name);
VIR_FORCE_CLOSE(fdpass->fd);
fdpass->passed = true;
return 0;
}
/**
* qemuFDPassDirectTransferMonitorRollback:
* @fdpass: The fd passing helper struct
* @mon: monitor object
*
* Rolls back the addition of @fdpass to @mon if it was added originally.
*/
void
qemuFDPassDirectTransferMonitorRollback(qemuFDPassDirect *fdpass,
qemuMonitor *mon)
{
if (!fdpass || !fdpass->passed)
return;
ignore_value(qemuMonitorCloseFileHandle(mon, fdpass->name));
}
/**
* qemuFDPassDirectGetPath:
* @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 *
qemuFDPassDirectGetPath(qemuFDPassDirect *fdpass)
{
if (!fdpass)
return NULL;
return fdpass->path;
}