| /* | |
| * 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; | |
| } |