/* * Copyright (c) 2021-2025 Oracle and/or its affiliates. * * SPDX-License-Identifier: GPL-2.0-or-later */ #include "qemu/osdep.h" #include "qemu/cutils.h" #include "qemu/error-report.h" #include "qemu/memfd.h" #include "qapi/error.h" #include "qapi/type-helpers.h" #include "io/channel-file.h" #include "io/channel-socket.h" #include "block/block-global-state.h" #include "qemu/main-loop.h" #include "migration/cpr.h" #include "migration/qemu-file.h" #include "migration/migration.h" #include "migration/misc.h" #include "migration/vmstate.h" #include "system/runstate.h" #include "trace.h" #define CPR_EXEC_STATE_NAME "QEMU_CPR_EXEC_STATE" static QEMUFile *qemu_file_new_fd_input(int fd, const char *name) { g_autoptr(QIOChannelFile) fioc = qio_channel_file_new_fd(fd); QIOChannel *ioc = QIO_CHANNEL(fioc); qio_channel_set_name(ioc, name); return qemu_file_new_input(ioc); } static QEMUFile *qemu_file_new_fd_output(int fd, const char *name) { g_autoptr(QIOChannelFile) fioc = qio_channel_file_new_fd(fd); QIOChannel *ioc = QIO_CHANNEL(fioc); qio_channel_set_name(ioc, name); return qemu_file_new_output(ioc); } void cpr_exec_persist_state(QEMUFile *f) { QIOChannelFile *fioc = QIO_CHANNEL_FILE(qemu_file_get_ioc(f)); int mfd = dup(fioc->fd); char val[16]; /* Remember mfd in environment for post-exec load */ qemu_clear_cloexec(mfd); snprintf(val, sizeof(val), "%d", mfd); g_setenv(CPR_EXEC_STATE_NAME, val, 1); } static int cpr_exec_find_state(void) { const char *val = g_getenv(CPR_EXEC_STATE_NAME); int mfd; assert(val); g_unsetenv(CPR_EXEC_STATE_NAME); assert(!qemu_strtoi(val, NULL, 10, &mfd)); return mfd; } bool cpr_exec_has_state(void) { return g_getenv(CPR_EXEC_STATE_NAME) != NULL; } void cpr_exec_unpersist_state(void) { int mfd; const char *val = g_getenv(CPR_EXEC_STATE_NAME); g_unsetenv(CPR_EXEC_STATE_NAME); assert(val); assert(!qemu_strtoi(val, NULL, 10, &mfd)); close(mfd); } QEMUFile *cpr_exec_output(Error **errp) { int mfd; #ifdef CONFIG_LINUX mfd = qemu_memfd_create(CPR_EXEC_STATE_NAME, 0, false, 0, 0, errp); #else mfd = -1; #endif if (mfd < 0) { return NULL; } return qemu_file_new_fd_output(mfd, CPR_EXEC_STATE_NAME); } QEMUFile *cpr_exec_input(Error **errp) { int mfd = cpr_exec_find_state(); lseek(mfd, 0, SEEK_SET); return qemu_file_new_fd_input(mfd, CPR_EXEC_STATE_NAME); } static bool preserve_fd(int fd) { qemu_clear_cloexec(fd); return true; } static bool unpreserve_fd(int fd) { qemu_set_cloexec(fd); return true; } static void cpr_exec_preserve_fds(void) { cpr_walk_fd(preserve_fd); } void cpr_exec_unpreserve_fds(void) { cpr_walk_fd(unpreserve_fd); } static void cpr_exec_cb(void *opaque) { MigrationState *s = migrate_get_current(); char **argv = strv_from_str_list(s->parameters.cpr_exec_command); Error *err = NULL; /* * Clear the close-on-exec flag for all preserved fd's. We cannot do so * earlier because they should not persist across miscellaneous fork and * exec calls that are performed during normal operation. */ cpr_exec_preserve_fds(); trace_cpr_exec(); execvp(argv[0], argv); /* * exec should only fail if argv[0] is bogus, or has a permissions problem, * or the system is very short on resources. */ g_strfreev(argv); cpr_exec_unpreserve_fds(); error_setg_errno(&err, errno, "execvp %s failed", argv[0]); error_report_err(error_copy(err)); migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED); migrate_set_error(s, err); /* Note, we can go from state COMPLETED to FAILED */ migration_call_notifiers(s, MIG_EVENT_PRECOPY_FAILED, NULL); err = NULL; if (!migration_block_activate(&err)) { /* error was already reported */ error_free(err); return; } if (runstate_is_live(s->vm_old_state)) { vm_start(); } } static int cpr_exec_notifier(NotifierWithReturn *notifier, MigrationEvent *e, Error **errp) { MigrationState *s = migrate_get_current(); if (e->type == MIG_EVENT_PRECOPY_DONE) { QEMUBH *cpr_exec_bh = qemu_bh_new(cpr_exec_cb, NULL); assert(s->state == MIGRATION_STATUS_COMPLETED); qemu_bh_schedule(cpr_exec_bh); qemu_notify_event(); } else if (e->type == MIG_EVENT_PRECOPY_FAILED) { cpr_exec_unpersist_state(); } return 0; } void cpr_exec_init(void) { static NotifierWithReturn exec_notifier; migration_add_notifier_mode(&exec_notifier, cpr_exec_notifier, MIG_MODE_CPR_EXEC); }