| /* | |
| * 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); | |
| } | |
| bool cpr_exec_persist_state(QEMUFile *f, Error **errp) | |
| { | |
| QIOChannelFile *fioc = QIO_CHANNEL_FILE(qemu_file_get_ioc(f)); | |
| /* coverity[leaked_storage] - mfd intentionally kept open across exec() */ | |
| 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); | |
| if (!g_setenv(CPR_EXEC_STATE_NAME, val, 1)) { | |
| error_setg(errp, "Setting env %s = %s failed", CPR_EXEC_STATE_NAME, val); | |
| return false; | |
| } | |
| return true; | |
| } | |
| 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. | |
| */ | |
| error_setg_errno(&err, errno, "execvp %s failed", argv[0]); | |
| g_clear_pointer(&argv, g_strfreev); | |
| cpr_exec_unpreserve_fds(); | |
| error_report_err(error_copy(err)); | |
| migrate_set_state(&s->state, s->state, MIGRATION_STATUS_FAILED); | |
| migrate_error_propagate(s, err); | |
| /* We must reset the error because it'll be reused later */ | |
| err = NULL; | |
| /* Note, we can go from state COMPLETED to FAILED */ | |
| migration_call_notifiers(MIG_EVENT_FAILED, 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_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_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); | |
| } |