mirror of https://gitlab.com/qemu-project/qemu
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
4.6 KiB
C
195 lines
4.6 KiB
C
/*
|
|
* 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);
|
|
}
|