blob: bca43e9bf3555f14ec3b3dd617daa63d16321284 [file]
/*
* Copyright (c) 2021-2024 Oracle and/or its affiliates.
*
* This work is licensed under the terms of the GNU GPL, version 2 or later.
* See the COPYING file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "qemu/error-report.h"
#include "hw/vfio/vfio-cpr.h"
#include "migration/cpr.h"
#include "migration/misc.h"
#include "migration/options.h"
#include "migration/qemu-file.h"
#include "migration/savevm.h"
#include "migration/vmstate.h"
#include "monitor/monitor.h"
#include "system/runstate.h"
#include "trace.h"
/*************************************************************************/
/* cpr state container for all information to be saved. */
CprState cpr_state;
static GHashTable *cpr_fds_hash;
/****************************************************************************/
typedef struct CprFd {
char *name;
unsigned int namelen;
int id;
int fd;
QLIST_ENTRY(CprFd) next;
} CprFd;
static const VMStateDescription vmstate_cpr_fd = {
.name = "cpr fd",
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_UINT32(namelen, CprFd),
VMSTATE_VBUFFER_ALLOC_UINT32(name, CprFd, 0, NULL, namelen),
VMSTATE_INT32(id, CprFd),
VMSTATE_FD(fd, CprFd),
VMSTATE_END_OF_LIST()
}
};
static guint cpr_fd_hash(gconstpointer v)
{
const CprFd *elem = v;
return g_str_hash(elem->name) ^ elem->id;
}
static gboolean cpr_fd_equal(gconstpointer a, gconstpointer b)
{
const CprFd *elem_a = a;
const CprFd *elem_b = b;
return !strcmp(elem_a->name, elem_b->name) && elem_a->id == elem_b->id;
}
static void cpr_fd_destroy(gpointer data)
{
CprFd *elem = data;
g_free(elem->name);
g_free(elem);
}
static GHashTable *get_cpr_fds_hash(void)
{
if (!cpr_fds_hash) {
cpr_fds_hash = g_hash_table_new_full(cpr_fd_hash, cpr_fd_equal,
cpr_fd_destroy, NULL);
}
return cpr_fds_hash;
}
static void cpr_fd_hash_insert(CprFd *elem)
{
/* Use the same CprFd as key and value. */
g_hash_table_insert(get_cpr_fds_hash(), elem, elem);
}
static int cpr_fd_pre_save(void *opaque)
{
CprState *state = (CprState *)opaque;
GHashTableIter iter;
CprFd *elem;
QLIST_INIT(&state->fds);
g_hash_table_iter_init(&iter, get_cpr_fds_hash());
while (g_hash_table_iter_next(&iter, (gpointer *)&elem, NULL)) {
QLIST_INSERT_HEAD(&state->fds, elem, next);
}
return 0;
}
static int cpr_fd_post_load(void *opaque, int version_id)
{
CprState *state = (CprState *)opaque;
CprFd *elem;
while ((elem = QLIST_FIRST(&state->fds))) {
QLIST_REMOVE(elem, next);
/*
* Preserve legacy QLIST lookup semantics if duplicate keys exist in
* the incoming stream: the first matching entry wins.
*/
if (g_hash_table_contains(get_cpr_fds_hash(), elem)) {
cpr_fd_destroy(elem);
continue;
}
cpr_fd_hash_insert(elem);
}
return 0;
}
void cpr_save_fd(const char *name, int id, int fd)
{
CprFd *elem = g_new0(CprFd, 1);
trace_cpr_save_fd(name, id, fd);
elem->name = g_strdup(name);
elem->namelen = strlen(name) + 1;
elem->id = id;
elem->fd = fd;
cpr_fd_hash_insert(elem);
}
static CprFd *find_fd(const char *name, int id)
{
CprFd key = {
.name = (char *)name,
.id = id,
};
return g_hash_table_lookup(get_cpr_fds_hash(), &key);
}
void cpr_delete_fd(const char *name, int id)
{
CprFd key = {
.name = (char *)name,
.id = id,
};
g_hash_table_remove(get_cpr_fds_hash(), &key);
trace_cpr_delete_fd(name, id);
}
int cpr_find_fd(const char *name, int id)
{
CprFd *elem = find_fd(name, id);
int fd = elem ? elem->fd : -1;
trace_cpr_find_fd(name, id, fd);
return fd;
}
void cpr_resave_fd(const char *name, int id, int fd)
{
CprFd *elem = find_fd(name, id);
int old_fd = elem ? elem->fd : -1;
if (old_fd < 0) {
cpr_save_fd(name, id, fd);
} else if (old_fd != fd) {
error_report("internal error: cpr fd '%s' id %d value %d "
"already saved with a different value %d",
name, id, fd, old_fd);
g_assert_not_reached();
}
}
int cpr_open_fd(const char *path, int flags, const char *name, int id,
Error **errp)
{
int fd = cpr_find_fd(name, id);
if (fd < 0) {
fd = qemu_open(path, flags, errp);
if (fd >= 0) {
cpr_save_fd(name, id, fd);
}
}
return fd;
}
bool cpr_walk_fd(cpr_walk_fd_cb cb)
{
GHashTableIter iter;
CprFd *elem;
g_hash_table_iter_init(&iter, get_cpr_fds_hash());
while (g_hash_table_iter_next(&iter, (gpointer *)&elem, NULL)) {
g_assert(elem->fd >= 0);
if (!cb(elem->fd)) {
return false;
}
}
return true;
}
/*************************************************************************/
static const VMStateDescription vmstate_cpr_state = {
.name = CPR_STATE,
.version_id = 1,
.minimum_version_id = 1,
.pre_save = cpr_fd_pre_save,
.post_load = cpr_fd_post_load,
.fields = (VMStateField[]) {
VMSTATE_QLIST_V(fds, CprState, 1, vmstate_cpr_fd, CprFd, next),
VMSTATE_END_OF_LIST()
},
.subsections = (const VMStateDescription * const []) {
&vmstate_cpr_vfio_devices,
NULL
}
};
/*************************************************************************/
static QEMUFile *cpr_state_file;
QIOChannel *cpr_state_ioc(void)
{
return qemu_file_get_ioc(cpr_state_file);
}
static MigMode incoming_mode = MIG_MODE_NONE;
MigMode cpr_get_incoming_mode(void)
{
return incoming_mode;
}
void cpr_set_incoming_mode(MigMode mode)
{
incoming_mode = mode;
}
bool cpr_is_incoming(void)
{
return incoming_mode != MIG_MODE_NONE;
}
bool cpr_state_save(MigrationChannel *channel, Error **errp)
{
QEMUFile *f;
MigMode mode = migrate_mode();
trace_cpr_state_save(MigMode_str(mode));
if (mode == MIG_MODE_CPR_TRANSFER) {
g_assert(channel);
f = cpr_transfer_output(channel, errp);
} else if (mode == MIG_MODE_CPR_EXEC) {
f = cpr_exec_output(errp);
} else {
return true;
}
if (!f) {
return false;
}
qemu_put_be32(f, QEMU_CPR_FILE_MAGIC);
qemu_put_be32(f, QEMU_CPR_FILE_VERSION);
if (!vmstate_save_vmsd(f, &vmstate_cpr_state, &cpr_state, 0, errp)) {
qemu_fclose(f);
return false;
}
if (migrate_mode() == MIG_MODE_CPR_EXEC) {
if (!cpr_exec_persist_state(f, errp)) {
qemu_fclose(f);
return false;
}
}
/*
* Close the socket only partially so we can later detect when the other
* end closes by getting a HUP event.
*/
qemu_fflush(f);
qio_channel_shutdown(qemu_file_get_ioc(f), QIO_CHANNEL_SHUTDOWN_WRITE,
NULL);
cpr_state_file = f;
return true;
}
bool cpr_state_load(MigrationChannel *channel, Error **errp)
{
uint32_t v;
QEMUFile *f;
MigMode mode = 0;
if (cpr_exec_has_state()) {
mode = MIG_MODE_CPR_EXEC;
f = cpr_exec_input(errp);
if (channel) {
warn_report("ignoring cpr channel for migration mode cpr-exec");
}
} else if (channel) {
mode = MIG_MODE_CPR_TRANSFER;
cpr_set_incoming_mode(mode);
f = cpr_transfer_input(channel, errp);
} else {
return true;
}
if (!f) {
return false;
}
trace_cpr_state_load(MigMode_str(mode));
cpr_set_incoming_mode(mode);
v = qemu_get_be32(f);
if (v != QEMU_CPR_FILE_MAGIC) {
error_setg(errp, "Not a migration stream (bad magic %x)", v);
qemu_fclose(f);
return false;
}
v = qemu_get_be32(f);
if (v != QEMU_CPR_FILE_VERSION) {
error_setg(errp, "Unsupported migration stream version %d", v);
qemu_fclose(f);
return false;
}
if (!vmstate_load_vmsd(f, &vmstate_cpr_state, &cpr_state, 1, errp)) {
qemu_fclose(f);
return false;
}
if (migrate_mode() == MIG_MODE_CPR_EXEC) {
/* Set cloexec to prevent fd leaks from fork until the next cpr-exec */
cpr_exec_unpreserve_fds();
}
/*
* Let the caller decide when to close the socket (and generate a HUP event
* for the sending side).
*/
cpr_state_file = f;
return true;
}
void cpr_state_close(void)
{
if (cpr_state_file) {
qemu_fclose(cpr_state_file);
cpr_state_file = NULL;
}
}
bool cpr_incoming_needed(void *opaque)
{
MigMode mode = migrate_mode();
return mode == MIG_MODE_CPR_TRANSFER || mode == MIG_MODE_CPR_EXEC;
}
/*
* cpr_get_fd_param: find a descriptor and return its value.
*
* @name: CPR name for the descriptor
* @fdname: An integer-valued string, or a name passed to a getfd command
* @index: CPR index of the descriptor
* @errp: returned error message
*
* If CPR is not being performed, then use @fdname to find the fd.
* If CPR is being performed, then ignore @fdname, and look for @name
* and @index in CPR state.
*
* On success returns the fd value, else returns -1.
*/
int cpr_get_fd_param(const char *name, const char *fdname, int index,
Error **errp)
{
ERRP_GUARD();
int fd;
if (cpr_is_incoming()) {
fd = cpr_find_fd(name, index);
if (fd < 0) {
error_setg(errp, "cannot find saved value for fd %s", fdname);
}
} else {
fd = monitor_fd_param(monitor_cur(), fdname, errp);
if (fd >= 0) {
cpr_save_fd(name, index, fd);
} else {
error_prepend(errp, "Could not parse object fd %s:", fdname);
}
}
return fd;
}