Migration/Memory Pull for 10.2
- PeterX's fix on tls warning for preempt channel when migratino completes - Arun's series to enhance error reporting for vTPM and migration framework - PeterX's patch to cleanup multifd send TLS BYE messages - Juraj's fix on postcopy start state transition when switchover failed - Yanfei's fix to migrate APIC before VFIO-PCI to avoid irq fallbacks - Dan's cleanup to simplify error reporting in qemu_fill_buffer() - PeterM's fix on address space leak when cpu hot plug / unplug - Steve's cpr-exec wholeset -----BEGIN PGP SIGNATURE----- iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCaN/uIhIccGV0ZXJ4QHJl ZGhhdC5jb20ACgkQO1/MzfOr1wZ+mAEA1l2RS9sZS1W3vXQMCNb+Nu8Uo2p+e5Qj Uu6J0WVV+XsBANtzGZk2UM/frqlABywW3/ozJ4qBvIPKo758Mr6/lqUH =asUv -----END PGP SIGNATURE----- Merge tag 'staging-pull-request' of https://gitlab.com/peterx/qemu into staging Migration/Memory Pull for 10.2 - PeterX's fix on tls warning for preempt channel when migratino completes - Arun's series to enhance error reporting for vTPM and migration framework - PeterX's patch to cleanup multifd send TLS BYE messages - Juraj's fix on postcopy start state transition when switchover failed - Yanfei's fix to migrate APIC before VFIO-PCI to avoid irq fallbacks - Dan's cleanup to simplify error reporting in qemu_fill_buffer() - PeterM's fix on address space leak when cpu hot plug / unplug - Steve's cpr-exec wholeset # -----BEGIN PGP SIGNATURE----- # # iIgEABYKADAWIQS5GE3CDMRX2s990ak7X8zN86vXBgUCaN/uIhIccGV0ZXJ4QHJl # ZGhhdC5jb20ACgkQO1/MzfOr1wZ+mAEA1l2RS9sZS1W3vXQMCNb+Nu8Uo2p+e5Qj # Uu6J0WVV+XsBANtzGZk2UM/frqlABywW3/ozJ4qBvIPKo758Mr6/lqUH # =asUv # -----END PGP SIGNATURE----- # gpg: Signature made Fri 03 Oct 2025 08:39:14 AM PDT # gpg: using EDDSA key B9184DC20CC457DACF7DD1A93B5FCCCDF3ABD706 # gpg: issuer "peterx@redhat.com" # gpg: Good signature from "Peter Xu <xzpeter@gmail.com>" [unknown] # gpg: aka "Peter Xu <peterx@redhat.com>" [unknown] # gpg: WARNING: The key's User ID is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: B918 4DC2 0CC4 57DA CF7D D1A9 3B5F CCCD F3AB D706 * tag 'staging-pull-request' of https://gitlab.com/peterx/qemu: (45 commits) migration-test: test cpr-exec vfio: cpr-exec mode migration: cpr-exec docs migration: cpr-exec mode migration: cpr-exec save and load migration: cpr-exec-command parameter oslib: qemu_clear_cloexec migration: add cpr_walk_fd migration: multi-mode notifier migration: simplify error reporting after channel read physmem: Destroy all CPU AddressSpaces on unrealize memory: New AS helper to serialize destroy+free include/system/memory.h: Clarify address_space_destroy() behaviour migration: ensure APIC is loaded prior to VFIO PCI devices migration: Fix state transition in postcopy_start() error handling migration/multifd/tls: Cleanup BYE message processing on sender side migration: HMP: Adjust the order of output fields migration: Make migration_has_failed() work even for CANCELLING io/crypto: Move tls premature termination handling into QIO layer backends/tpm: Propagate vTPM error on migration failure ... Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
This commit is contained in:
commit
bd6aa0d1e5
58 changed files with 1352 additions and 410 deletions
|
|
@ -819,7 +819,8 @@ static int tpm_emulator_get_state_blobs(TPMEmulator *tpm_emu)
|
|||
static int tpm_emulator_set_state_blob(TPMEmulator *tpm_emu,
|
||||
uint32_t type,
|
||||
TPMSizedBuffer *tsb,
|
||||
uint32_t flags)
|
||||
uint32_t flags,
|
||||
Error **errp)
|
||||
{
|
||||
ssize_t n;
|
||||
ptm_setstate pss;
|
||||
|
|
@ -838,17 +839,18 @@ static int tpm_emulator_set_state_blob(TPMEmulator *tpm_emu,
|
|||
/* write the header only */
|
||||
if (tpm_emulator_ctrlcmd(tpm_emu, CMD_SET_STATEBLOB, &pss,
|
||||
offsetof(ptm_setstate, u.req.data), 0, 0) < 0) {
|
||||
error_report("tpm-emulator: could not set state blob type %d : %s",
|
||||
type, strerror(errno));
|
||||
error_setg_errno(errp, errno,
|
||||
"tpm-emulator: could not set state blob type %d",
|
||||
type);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* now the body */
|
||||
n = qemu_chr_fe_write_all(&tpm_emu->ctrl_chr, tsb->buffer, tsb->size);
|
||||
if (n != tsb->size) {
|
||||
error_report("tpm-emulator: Writing the stateblob (type %d) "
|
||||
"failed; could not write %u bytes, but only %zd",
|
||||
type, tsb->size, n);
|
||||
error_setg(errp, "tpm-emulator: Writing the stateblob (type %d) "
|
||||
"failed; could not write %u bytes, but only %zd",
|
||||
type, tsb->size, n);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -856,17 +858,17 @@ static int tpm_emulator_set_state_blob(TPMEmulator *tpm_emu,
|
|||
n = qemu_chr_fe_read_all(&tpm_emu->ctrl_chr,
|
||||
(uint8_t *)&pss, sizeof(pss.u.resp));
|
||||
if (n != sizeof(pss.u.resp)) {
|
||||
error_report("tpm-emulator: Reading response from writing stateblob "
|
||||
"(type %d) failed; expected %zu bytes, got %zd", type,
|
||||
sizeof(pss.u.resp), n);
|
||||
error_setg(errp, "tpm-emulator: Reading response from writing "
|
||||
"stateblob (type %d) failed; expected %zu bytes, "
|
||||
"got %zd", type, sizeof(pss.u.resp), n);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tpm_result = be32_to_cpu(pss.u.resp.tpm_result);
|
||||
if (tpm_result != 0) {
|
||||
error_report("tpm-emulator: Setting the stateblob (type %d) failed "
|
||||
"with a TPM error 0x%x %s", type, tpm_result,
|
||||
tpm_emulator_strerror(tpm_result));
|
||||
error_setg(errp, "tpm-emulator: Setting the stateblob (type %d) "
|
||||
"failed with a TPM error 0x%x %s", type, tpm_result,
|
||||
tpm_emulator_strerror(tpm_result));
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -880,7 +882,7 @@ static int tpm_emulator_set_state_blob(TPMEmulator *tpm_emu,
|
|||
*
|
||||
* Returns a negative errno code in case of error.
|
||||
*/
|
||||
static int tpm_emulator_set_state_blobs(TPMBackend *tb)
|
||||
static int tpm_emulator_set_state_blobs(TPMBackend *tb, Error **errp)
|
||||
{
|
||||
TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
|
||||
TPMBlobBuffers *state_blobs = &tpm_emu->state_blobs;
|
||||
|
|
@ -894,13 +896,13 @@ static int tpm_emulator_set_state_blobs(TPMBackend *tb)
|
|||
|
||||
if (tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_PERMANENT,
|
||||
&state_blobs->permanent,
|
||||
state_blobs->permanent_flags) < 0 ||
|
||||
state_blobs->permanent_flags, errp) < 0 ||
|
||||
tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_VOLATILE,
|
||||
&state_blobs->volatil,
|
||||
state_blobs->volatil_flags) < 0 ||
|
||||
state_blobs->volatil_flags, errp) < 0 ||
|
||||
tpm_emulator_set_state_blob(tpm_emu, PTM_BLOB_TYPE_SAVESTATE,
|
||||
&state_blobs->savestate,
|
||||
state_blobs->savestate_flags) < 0) {
|
||||
state_blobs->savestate_flags, errp) < 0) {
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
|
|
@ -948,12 +950,12 @@ static void tpm_emulator_vm_state_change(void *opaque, bool running,
|
|||
*
|
||||
* Returns negative errno codes in case of error.
|
||||
*/
|
||||
static int tpm_emulator_post_load(void *opaque, int version_id)
|
||||
static int tpm_emulator_post_load(void *opaque, int version_id, Error **errp)
|
||||
{
|
||||
TPMBackend *tb = opaque;
|
||||
int ret;
|
||||
|
||||
ret = tpm_emulator_set_state_blobs(tb);
|
||||
ret = tpm_emulator_set_state_blobs(tb, errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -969,7 +971,7 @@ static const VMStateDescription vmstate_tpm_emulator = {
|
|||
.name = "tpm-emulator",
|
||||
.version_id = 0,
|
||||
.pre_save = tpm_emulator_pre_save,
|
||||
.post_load = tpm_emulator_post_load,
|
||||
.post_load_errp = tpm_emulator_post_load,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT32(state_blobs.permanent_flags, TPMEmulator),
|
||||
VMSTATE_UINT32(state_blobs.permanent.size, TPMEmulator),
|
||||
|
|
|
|||
|
|
@ -552,7 +552,6 @@ ssize_t
|
|||
qcrypto_tls_session_read(QCryptoTLSSession *session,
|
||||
char *buf,
|
||||
size_t len,
|
||||
bool gracefulTermination,
|
||||
Error **errp)
|
||||
{
|
||||
ssize_t ret;
|
||||
|
|
@ -570,9 +569,8 @@ qcrypto_tls_session_read(QCryptoTLSSession *session,
|
|||
if (ret < 0) {
|
||||
if (ret == GNUTLS_E_AGAIN) {
|
||||
return QCRYPTO_TLS_SESSION_ERR_BLOCK;
|
||||
} else if ((ret == GNUTLS_E_PREMATURE_TERMINATION) &&
|
||||
gracefulTermination){
|
||||
return 0;
|
||||
} else if (ret == GNUTLS_E_PREMATURE_TERMINATION) {
|
||||
return QCRYPTO_TLS_SESSION_PREMATURE_TERMINATION;
|
||||
} else {
|
||||
if (session->rerr) {
|
||||
error_propagate(errp, session->rerr);
|
||||
|
|
@ -789,7 +787,6 @@ ssize_t
|
|||
qcrypto_tls_session_read(QCryptoTLSSession *sess,
|
||||
char *buf,
|
||||
size_t len,
|
||||
bool gracefulTermination,
|
||||
Error **errp)
|
||||
{
|
||||
error_setg(errp, "TLS requires GNUTLS support");
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ CPR is the umbrella name for a set of migration modes in which the
|
|||
VM is migrated to a new QEMU instance on the same host. It is
|
||||
intended for use when the goal is to update host software components
|
||||
that run the VM, such as QEMU or even the host kernel. At this time,
|
||||
the cpr-reboot and cpr-transfer modes are available.
|
||||
the cpr-reboot, cpr-transfer, and cpr-exec modes are available.
|
||||
|
||||
Because QEMU is restarted on the same host, with access to the same
|
||||
local devices, CPR is allowed in certain cases where normal migration
|
||||
|
|
@ -324,3 +324,113 @@ descriptors from old to new QEMU. In the future, descriptors for
|
|||
vhost, and char devices could be transferred,
|
||||
preserving those devices and their kernel state without interruption,
|
||||
even if they do not explicitly support live migration.
|
||||
|
||||
cpr-exec mode
|
||||
-------------
|
||||
|
||||
In this mode, QEMU stops the VM, writes VM state to the migration
|
||||
URI, and directly exec's a new version of QEMU on the same host,
|
||||
replacing the original process while retaining its PID. Guest RAM is
|
||||
preserved in place, albeit with new virtual addresses. The user
|
||||
completes the migration by specifying the ``-incoming`` option, and
|
||||
by issuing the ``migrate-incoming`` command if necessary; see details
|
||||
below.
|
||||
|
||||
This mode supports VFIO/IOMMUFD devices by preserving device
|
||||
descriptors and hence kernel state across the exec, even for devices
|
||||
that do not support live migration.
|
||||
|
||||
Because the old and new QEMU instances are not active concurrently,
|
||||
the URI cannot be a type that streams data from one instance to the
|
||||
other.
|
||||
|
||||
This mode does not require a channel of type ``cpr``. The information
|
||||
that is passed over that channel for cpr-transfer mode is instead
|
||||
serialized to a memfd, the number of the fd is saved in the
|
||||
QEMU_CPR_EXEC_STATE environment variable during the exec of new QEMU.
|
||||
and new QEMU mmaps the memfd.
|
||||
|
||||
Usage
|
||||
^^^^^
|
||||
|
||||
Arguments for the new QEMU process are taken from the
|
||||
@cpr-exec-command parameter. The first argument should be the
|
||||
path of a new QEMU binary, or a prefix command that exec's the
|
||||
new QEMU binary, and the arguments should include the ''-incoming''
|
||||
option.
|
||||
|
||||
Memory backend objects must have the ``share=on`` attribute.
|
||||
The VM must be started with the ``-machine aux-ram-share=on`` option.
|
||||
|
||||
Outgoing:
|
||||
* Set the migration mode parameter to ``cpr-exec``.
|
||||
* Set the ``cpr-exec-command`` parameter.
|
||||
* Issue the ``migrate`` command. It is recommended that the URI be
|
||||
a ``file`` type, but one can use other types such as ``exec``,
|
||||
provided the command captures all the data from the outgoing side,
|
||||
and provides all the data to the incoming side.
|
||||
|
||||
Incoming:
|
||||
* You do not need to explicitly start new QEMU. It is started as
|
||||
a side effect of the migrate command above.
|
||||
* If the VM was running when the outgoing ``migrate`` command was
|
||||
issued, then QEMU automatically resumes VM execution.
|
||||
|
||||
Example 1: incoming URI
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
In these examples, we simply restart the same version of QEMU, but in
|
||||
a real scenario one would set a new QEMU binary path in
|
||||
cpr-exec-command.
|
||||
|
||||
::
|
||||
|
||||
# qemu-kvm -monitor stdio
|
||||
-object memory-backend-memfd,id=ram0,size=4G
|
||||
-machine memory-backend=ram0
|
||||
-machine aux-ram-share=on
|
||||
...
|
||||
|
||||
QEMU 10.2.50 monitor - type 'help' for more information
|
||||
(qemu) info status
|
||||
VM status: running
|
||||
(qemu) migrate_set_parameter mode cpr-exec
|
||||
(qemu) migrate_set_parameter cpr-exec-command qemu-kvm ... -incoming file:vm.state
|
||||
(qemu) migrate -d file:vm.state
|
||||
(qemu) QEMU 10.2.50 monitor - type 'help' for more information
|
||||
(qemu) info status
|
||||
VM status: running
|
||||
|
||||
Example 2: incoming defer
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
::
|
||||
|
||||
# qemu-kvm -monitor stdio
|
||||
-object memory-backend-memfd,id=ram0,size=4G
|
||||
-machine memory-backend=ram0
|
||||
-machine aux-ram-share=on
|
||||
...
|
||||
|
||||
QEMU 10.2.50 monitor - type 'help' for more information
|
||||
(qemu) info status
|
||||
VM status: running
|
||||
(qemu) migrate_set_parameter mode cpr-exec
|
||||
(qemu) migrate_set_parameter cpr-exec-command qemu-kvm ... -incoming defer
|
||||
(qemu) migrate -d file:vm.state
|
||||
(qemu) QEMU 10.2.50 monitor - type 'help' for more information
|
||||
(qemu) info status
|
||||
status: paused (inmigrate)
|
||||
(qemu) migrate_incoming file:vm.state
|
||||
(qemu) info status
|
||||
VM status: running
|
||||
|
||||
Caveats
|
||||
^^^^^^^
|
||||
|
||||
cpr-exec mode may not be used with postcopy, background-snapshot,
|
||||
or COLO.
|
||||
|
||||
cpr-exec mode requires permission to use the exec system call, which
|
||||
is denied by certain sandbox options, such as spawn.
|
||||
|
||||
The guest pause time increases for large guest RAM backed by small pages.
|
||||
|
|
|
|||
|
|
@ -444,6 +444,25 @@ The functions to do that are inside a vmstate definition, and are called:
|
|||
This function is called after we save the state of one device
|
||||
(even upon failure, unless the call to pre_save returned an error).
|
||||
|
||||
Following are the errp variants of these functions.
|
||||
|
||||
- ``int (*pre_load_errp)(void *opaque, Error **errp);``
|
||||
|
||||
This function is called before we load the state of one device.
|
||||
|
||||
- ``int (*post_load_errp)(void *opaque, int version_id, Error **errp);``
|
||||
|
||||
This function is called after we load the state of one device.
|
||||
|
||||
- ``int (*pre_save_errp)(void *opaque, Error **errp);``
|
||||
|
||||
This function is called before we save the state of one device.
|
||||
|
||||
New impls should preferentally use 'errp' variants of these
|
||||
methods and existing impls incrementally converted.
|
||||
The variants without 'errp' are intended to be removed
|
||||
once all usage is converted.
|
||||
|
||||
Example: You can look at hpet.c, that uses the first three functions
|
||||
to massage the state that is transferred.
|
||||
|
||||
|
|
|
|||
|
|
@ -1009,7 +1009,7 @@ ERST
|
|||
|
||||
{
|
||||
.name = "migrate_set_parameter",
|
||||
.args_type = "parameter:s,value:s",
|
||||
.args_type = "parameter:s,value:S",
|
||||
.params = "parameter value",
|
||||
.help = "Set the parameter for migration",
|
||||
.cmd = hmp_migrate_set_parameter,
|
||||
|
|
|
|||
|
|
@ -294,6 +294,7 @@ void cpu_exec_unrealizefn(CPUState *cpu)
|
|||
* accel_cpu_common_unrealize, which may free fields using call_rcu.
|
||||
*/
|
||||
accel_cpu_common_unrealize(cpu);
|
||||
cpu_destroy_address_spaces(cpu);
|
||||
}
|
||||
|
||||
static void cpu_common_initfn(Object *obj)
|
||||
|
|
|
|||
|
|
@ -1248,7 +1248,8 @@ static int virtio_gpu_save(QEMUFile *f, void *opaque, size_t size,
|
|||
}
|
||||
qemu_put_be32(f, 0); /* end of list */
|
||||
|
||||
return vmstate_save_state(f, &vmstate_virtio_gpu_scanouts, g, NULL);
|
||||
return vmstate_save_state(f, &vmstate_virtio_gpu_scanouts, g, NULL,
|
||||
&error_fatal);
|
||||
}
|
||||
|
||||
static bool virtio_gpu_load_restore_mapping(VirtIOGPU *g,
|
||||
|
|
@ -1347,7 +1348,7 @@ static int virtio_gpu_load(QEMUFile *f, void *opaque, size_t size,
|
|||
}
|
||||
|
||||
/* load & apply scanout state */
|
||||
vmstate_load_state(f, &vmstate_virtio_gpu_scanouts, g, 1);
|
||||
vmstate_load_state(f, &vmstate_virtio_gpu_scanouts, g, 1, &error_fatal);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -379,6 +379,7 @@ static const VMStateDescription vmstate_apic_common = {
|
|||
.pre_load = apic_pre_load,
|
||||
.pre_save = apic_dispatch_pre_save,
|
||||
.post_load = apic_dispatch_post_load,
|
||||
.priority = MIG_PRI_APIC,
|
||||
.fields = (const VMStateField[]) {
|
||||
VMSTATE_UINT32(apicbase, APICCommonState),
|
||||
VMSTATE_UINT8(id, APICCommonState),
|
||||
|
|
|
|||
|
|
@ -926,7 +926,7 @@ void pci_device_save(PCIDevice *s, QEMUFile *f)
|
|||
* This makes us compatible with old devices
|
||||
* which never set or clear this bit. */
|
||||
s->config[PCI_STATUS] &= ~PCI_STATUS_INTERRUPT;
|
||||
vmstate_save_state(f, &vmstate_pci_device, s, NULL);
|
||||
vmstate_save_state(f, &vmstate_pci_device, s, NULL, &error_fatal);
|
||||
/* Restore the interrupt status bit. */
|
||||
pci_update_irq_status(s);
|
||||
}
|
||||
|
|
@ -934,7 +934,8 @@ void pci_device_save(PCIDevice *s, QEMUFile *f)
|
|||
int pci_device_load(PCIDevice *s, QEMUFile *f)
|
||||
{
|
||||
int ret;
|
||||
ret = vmstate_load_state(f, &vmstate_pci_device, s, s->version_id);
|
||||
ret = vmstate_load_state(f, &vmstate_pci_device, s, s->version_id,
|
||||
&error_fatal);
|
||||
/* Restore the interrupt status bit. */
|
||||
pci_update_irq_status(s);
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -1130,13 +1130,13 @@ static int virtio_ccw_load_queue(DeviceState *d, int n, QEMUFile *f)
|
|||
static void virtio_ccw_save_config(DeviceState *d, QEMUFile *f)
|
||||
{
|
||||
VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
|
||||
vmstate_save_state(f, &vmstate_virtio_ccw_dev, dev, NULL);
|
||||
vmstate_save_state(f, &vmstate_virtio_ccw_dev, dev, NULL, &error_fatal);
|
||||
}
|
||||
|
||||
static int virtio_ccw_load_config(DeviceState *d, QEMUFile *f)
|
||||
{
|
||||
VirtioCcwDevice *dev = VIRTIO_CCW_DEVICE(d);
|
||||
return vmstate_load_state(f, &vmstate_virtio_ccw_dev, dev, 1);
|
||||
return vmstate_load_state(f, &vmstate_virtio_ccw_dev, dev, 1, &error_fatal);
|
||||
}
|
||||
|
||||
static void virtio_ccw_pre_plugged(DeviceState *d, Error **errp)
|
||||
|
|
|
|||
|
|
@ -630,7 +630,7 @@ static void vscsi_save_request(QEMUFile *f, SCSIRequest *sreq)
|
|||
vscsi_req *req = sreq->hba_private;
|
||||
assert(req->active);
|
||||
|
||||
vmstate_save_state(f, &vmstate_spapr_vscsi_req, req, NULL);
|
||||
vmstate_save_state(f, &vmstate_spapr_vscsi_req, req, NULL, &error_fatal);
|
||||
|
||||
trace_spapr_vscsi_save_request(req->qtag, req->cur_desc_num,
|
||||
req->cur_desc_offset);
|
||||
|
|
@ -642,15 +642,17 @@ static void *vscsi_load_request(QEMUFile *f, SCSIRequest *sreq)
|
|||
VSCSIState *s = VIO_SPAPR_VSCSI_DEVICE(bus->qbus.parent);
|
||||
vscsi_req *req;
|
||||
int rc;
|
||||
Error *local_err = NULL;
|
||||
|
||||
assert(sreq->tag < VSCSI_REQ_LIMIT);
|
||||
req = &s->reqs[sreq->tag];
|
||||
assert(!req->active);
|
||||
|
||||
memset(req, 0, sizeof(*req));
|
||||
rc = vmstate_load_state(f, &vmstate_spapr_vscsi_req, req, 1);
|
||||
rc = vmstate_load_state(f, &vmstate_spapr_vscsi_req, req, 1, &local_err);
|
||||
if (rc) {
|
||||
fprintf(stderr, "VSCSI: failed loading request tag#%u\n", sreq->tag);
|
||||
error_report_err(local_err);
|
||||
return NULL;
|
||||
}
|
||||
assert(req->active);
|
||||
|
|
|
|||
|
|
@ -972,7 +972,8 @@ static bool vfio_legacy_attach_device(const char *name, VFIODevice *vbasedev,
|
|||
error_setg(&vbasedev->cpr.mdev_blocker,
|
||||
"CPR does not support vfio mdev %s", vbasedev->name);
|
||||
if (migrate_add_blocker_modes(&vbasedev->cpr.mdev_blocker, errp,
|
||||
MIG_MODE_CPR_TRANSFER, -1) < 0) {
|
||||
MIG_MODE_CPR_TRANSFER, MIG_MODE_CPR_EXEC,
|
||||
-1) < 0) {
|
||||
goto hiod_unref_exit;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ bool vfio_iommufd_cpr_register_iommufd(IOMMUFDBackend *be, Error **errp)
|
|||
|
||||
if (!vfio_cpr_supported(be, cpr_blocker)) {
|
||||
return migrate_add_blocker_modes(cpr_blocker, errp,
|
||||
MIG_MODE_CPR_TRANSFER, -1) == 0;
|
||||
MIG_MODE_CPR_TRANSFER,
|
||||
MIG_MODE_CPR_EXEC, -1) == 0;
|
||||
}
|
||||
|
||||
vmstate_register(NULL, -1, &iommufd_cpr_vmstate, be);
|
||||
|
|
|
|||
|
|
@ -179,16 +179,17 @@ bool vfio_legacy_cpr_register_container(VFIOLegacyContainer *container,
|
|||
|
||||
if (!vfio_cpr_supported(container, cpr_blocker)) {
|
||||
return migrate_add_blocker_modes(cpr_blocker, errp,
|
||||
MIG_MODE_CPR_TRANSFER, -1) == 0;
|
||||
MIG_MODE_CPR_TRANSFER,
|
||||
MIG_MODE_CPR_EXEC, -1) == 0;
|
||||
}
|
||||
|
||||
vfio_cpr_add_kvm_notifier();
|
||||
|
||||
vmstate_register(NULL, -1, &vfio_container_vmstate, container);
|
||||
|
||||
migration_add_notifier_mode(&container->cpr.transfer_notifier,
|
||||
vfio_cpr_fail_notifier,
|
||||
MIG_MODE_CPR_TRANSFER);
|
||||
migration_add_notifier_modes(&container->cpr.transfer_notifier,
|
||||
vfio_cpr_fail_notifier,
|
||||
MIG_MODE_CPR_TRANSFER, MIG_MODE_CPR_EXEC, -1);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -195,9 +195,10 @@ static int vfio_cpr_kvm_close_notifier(NotifierWithReturn *notifier,
|
|||
void vfio_cpr_add_kvm_notifier(void)
|
||||
{
|
||||
if (!kvm_close_notifier.notify) {
|
||||
migration_add_notifier_mode(&kvm_close_notifier,
|
||||
vfio_cpr_kvm_close_notifier,
|
||||
MIG_MODE_CPR_TRANSFER);
|
||||
migration_add_notifier_modes(&kvm_close_notifier,
|
||||
vfio_cpr_kvm_close_notifier,
|
||||
MIG_MODE_CPR_TRANSFER, MIG_MODE_CPR_EXEC,
|
||||
-1);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -282,9 +283,9 @@ static int vfio_cpr_pci_notifier(NotifierWithReturn *notifier,
|
|||
|
||||
void vfio_cpr_pci_register_device(VFIOPCIDevice *vdev)
|
||||
{
|
||||
migration_add_notifier_mode(&vdev->cpr.transfer_notifier,
|
||||
vfio_cpr_pci_notifier,
|
||||
MIG_MODE_CPR_TRANSFER);
|
||||
migration_add_notifier_modes(&vdev->cpr.transfer_notifier,
|
||||
vfio_cpr_pci_notifier,
|
||||
MIG_MODE_CPR_TRANSFER, MIG_MODE_CPR_EXEC, -1);
|
||||
}
|
||||
|
||||
void vfio_cpr_pci_unregister_device(VFIOPCIDevice *vdev)
|
||||
|
|
|
|||
|
|
@ -2821,8 +2821,8 @@ static int vfio_pci_save_config(VFIODevice *vbasedev, QEMUFile *f, Error **errp)
|
|||
{
|
||||
VFIOPCIDevice *vdev = container_of(vbasedev, VFIOPCIDevice, vbasedev);
|
||||
|
||||
return vmstate_save_state_with_err(f, &vmstate_vfio_pci_config, vdev, NULL,
|
||||
errp);
|
||||
return vmstate_save_state(f, &vmstate_vfio_pci_config, vdev, NULL,
|
||||
errp);
|
||||
}
|
||||
|
||||
static int vfio_pci_load_config(VFIODevice *vbasedev, QEMUFile *f)
|
||||
|
|
@ -2831,13 +2831,16 @@ static int vfio_pci_load_config(VFIODevice *vbasedev, QEMUFile *f)
|
|||
PCIDevice *pdev = PCI_DEVICE(vdev);
|
||||
pcibus_t old_addr[PCI_NUM_REGIONS - 1];
|
||||
int bar, ret;
|
||||
Error *local_err = NULL;
|
||||
|
||||
for (bar = 0; bar < PCI_ROM_SLOT; bar++) {
|
||||
old_addr[bar] = pdev->io_regions[bar].addr;
|
||||
}
|
||||
|
||||
ret = vmstate_load_state(f, &vmstate_vfio_pci_config, vdev, 1);
|
||||
ret = vmstate_load_state(f, &vmstate_vfio_pci_config, vdev, 1,
|
||||
&local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@
|
|||
#include "qemu/error-report.h"
|
||||
#include "qemu/log.h"
|
||||
#include "trace.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
static bool virtio_mmio_ioeventfd_enabled(DeviceState *d)
|
||||
{
|
||||
|
|
@ -612,14 +613,14 @@ static void virtio_mmio_save_extra_state(DeviceState *opaque, QEMUFile *f)
|
|||
{
|
||||
VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
|
||||
|
||||
vmstate_save_state(f, &vmstate_virtio_mmio, proxy, NULL);
|
||||
vmstate_save_state(f, &vmstate_virtio_mmio, proxy, NULL, &error_fatal);
|
||||
}
|
||||
|
||||
static int virtio_mmio_load_extra_state(DeviceState *opaque, QEMUFile *f)
|
||||
{
|
||||
VirtIOMMIOProxy *proxy = VIRTIO_MMIO(opaque);
|
||||
|
||||
return vmstate_load_state(f, &vmstate_virtio_mmio, proxy, 1);
|
||||
return vmstate_load_state(f, &vmstate_virtio_mmio, proxy, 1, &error_fatal);
|
||||
}
|
||||
|
||||
static bool virtio_mmio_has_extra_state(DeviceState *opaque)
|
||||
|
|
|
|||
|
|
@ -154,14 +154,14 @@ static void virtio_pci_save_extra_state(DeviceState *d, QEMUFile *f)
|
|||
{
|
||||
VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
|
||||
|
||||
vmstate_save_state(f, &vmstate_virtio_pci, proxy, NULL);
|
||||
vmstate_save_state(f, &vmstate_virtio_pci, proxy, NULL, &error_fatal);
|
||||
}
|
||||
|
||||
static int virtio_pci_load_extra_state(DeviceState *d, QEMUFile *f)
|
||||
{
|
||||
VirtIOPCIProxy *proxy = to_virtio_pci_proxy(d);
|
||||
|
||||
return vmstate_load_state(f, &vmstate_virtio_pci, proxy, 1);
|
||||
return vmstate_load_state(f, &vmstate_virtio_pci, proxy, 1, &error_fatal);
|
||||
}
|
||||
|
||||
static void virtio_pci_save_queue(DeviceState *d, int n, QEMUFile *f)
|
||||
|
|
|
|||
|
|
@ -2992,6 +2992,7 @@ int virtio_save(VirtIODevice *vdev, QEMUFile *f)
|
|||
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
|
||||
uint32_t guest_features_lo = (vdev->guest_features & 0xffffffff);
|
||||
int i;
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (k->save_config) {
|
||||
k->save_config(qbus->parent, f);
|
||||
|
|
@ -3035,14 +3036,15 @@ int virtio_save(VirtIODevice *vdev, QEMUFile *f)
|
|||
}
|
||||
|
||||
if (vdc->vmsd) {
|
||||
int ret = vmstate_save_state(f, vdc->vmsd, vdev, NULL);
|
||||
int ret = vmstate_save_state(f, vdc->vmsd, vdev, NULL, &local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Subsections */
|
||||
return vmstate_save_state(f, &vmstate_virtio, vdev, NULL);
|
||||
return vmstate_save_state(f, &vmstate_virtio, vdev, NULL, &error_fatal);
|
||||
}
|
||||
|
||||
/* A wrapper for use as a VMState .put function */
|
||||
|
|
@ -3235,6 +3237,7 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
|
|||
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
|
||||
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
|
||||
VirtioDeviceClass *vdc = VIRTIO_DEVICE_GET_CLASS(vdev);
|
||||
Error *local_err = NULL;
|
||||
|
||||
/*
|
||||
* We poison the endianness to ensure it does not get used before
|
||||
|
|
@ -3327,15 +3330,17 @@ virtio_load(VirtIODevice *vdev, QEMUFile *f, int version_id)
|
|||
}
|
||||
|
||||
if (vdc->vmsd) {
|
||||
ret = vmstate_load_state(f, vdc->vmsd, vdev, version_id);
|
||||
ret = vmstate_load_state(f, vdc->vmsd, vdev, version_id, &local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
/* Subsections */
|
||||
ret = vmstate_load_state(f, &vmstate_virtio, vdev, 1);
|
||||
ret = vmstate_load_state(f, &vmstate_virtio, vdev, 1, &local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@
|
|||
typedef struct QCryptoTLSSession QCryptoTLSSession;
|
||||
|
||||
#define QCRYPTO_TLS_SESSION_ERR_BLOCK -2
|
||||
#define QCRYPTO_TLS_SESSION_PREMATURE_TERMINATION -3
|
||||
|
||||
/**
|
||||
* qcrypto_tls_session_new:
|
||||
|
|
@ -259,7 +260,6 @@ ssize_t qcrypto_tls_session_write(QCryptoTLSSession *sess,
|
|||
* @sess: the TLS session object
|
||||
* @buf: to fill with plain text received
|
||||
* @len: the length of @buf
|
||||
* @gracefulTermination: treat premature termination as graceful EOF
|
||||
* @errp: pointer to hold returned error object
|
||||
*
|
||||
* Receive up to @len bytes of data from the remote peer
|
||||
|
|
@ -267,22 +267,18 @@ ssize_t qcrypto_tls_session_write(QCryptoTLSSession *sess,
|
|||
* qcrypto_tls_session_set_callbacks(), decrypt it and
|
||||
* store it in @buf.
|
||||
*
|
||||
* If @gracefulTermination is true, then a premature termination
|
||||
* of the TLS session will be treated as indicating EOF, as
|
||||
* opposed to an error.
|
||||
*
|
||||
* It is an error to call this before
|
||||
* qcrypto_tls_session_handshake() returns
|
||||
* QCRYPTO_TLS_HANDSHAKE_COMPLETE
|
||||
*
|
||||
* Returns: the number of bytes received,
|
||||
* or QCRYPTO_TLS_SESSION_ERR_BLOCK if the receive would block,
|
||||
* or -1 on error.
|
||||
* or QCRYPTO_TLS_SESSION_PREMATURE_TERMINATION if a premature termination
|
||||
* is detected, or -1 on error.
|
||||
*/
|
||||
ssize_t qcrypto_tls_session_read(QCryptoTLSSession *sess,
|
||||
char *buf,
|
||||
size_t len,
|
||||
bool gracefulTermination,
|
||||
Error **errp);
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -123,13 +123,13 @@ size_t qemu_ram_pagesize_largest(void);
|
|||
void cpu_address_space_init(CPUState *cpu, int asidx,
|
||||
const char *prefix, MemoryRegion *mr);
|
||||
/**
|
||||
* cpu_address_space_destroy:
|
||||
* @cpu: CPU for which address space needs to be destroyed
|
||||
* @asidx: integer index of this address space
|
||||
* cpu_destroy_address_spaces:
|
||||
* @cpu: CPU for which address spaces need to be destroyed
|
||||
*
|
||||
* Note that with KVM only one address space is supported.
|
||||
* Destroy all address spaces associated with this CPU; this
|
||||
* is called as part of unrealizing the CPU.
|
||||
*/
|
||||
void cpu_address_space_destroy(CPUState *cpu, int asidx);
|
||||
void cpu_destroy_address_spaces(CPUState *cpu);
|
||||
|
||||
void cpu_physical_memory_rw(hwaddr addr, void *buf,
|
||||
hwaddr len, bool is_write);
|
||||
|
|
|
|||
|
|
@ -515,7 +515,6 @@ struct CPUState {
|
|||
QSIMPLEQ_HEAD(, qemu_work_item) work_list;
|
||||
|
||||
struct CPUAddressSpace *cpu_ases;
|
||||
int cpu_ases_count;
|
||||
int num_ases;
|
||||
AddressSpace *as;
|
||||
MemoryRegion *memory;
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ void migrate_start_colo_process(MigrationState *s);
|
|||
bool migration_in_colo_state(void);
|
||||
|
||||
/* loadvm */
|
||||
int migration_incoming_enable_colo(void);
|
||||
int migration_incoming_enable_colo(Error **errp);
|
||||
void migration_incoming_disable_colo(void);
|
||||
bool migration_incoming_colo_enabled(void);
|
||||
bool migration_incoming_in_colo_state(void);
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ void cpr_resave_fd(const char *name, int id, int fd);
|
|||
int cpr_open_fd(const char *path, int flags, const char *name, int id,
|
||||
Error **errp);
|
||||
|
||||
typedef bool (*cpr_walk_fd_cb)(int fd);
|
||||
bool cpr_walk_fd(cpr_walk_fd_cb cb);
|
||||
|
||||
MigMode cpr_get_incoming_mode(void);
|
||||
void cpr_set_incoming_mode(MigMode mode);
|
||||
bool cpr_is_incoming(void);
|
||||
|
|
@ -50,4 +53,11 @@ int cpr_get_fd_param(const char *name, const char *fdname, int index,
|
|||
QEMUFile *cpr_transfer_output(MigrationChannel *channel, Error **errp);
|
||||
QEMUFile *cpr_transfer_input(MigrationChannel *channel, Error **errp);
|
||||
|
||||
void cpr_exec_init(void);
|
||||
QEMUFile *cpr_exec_output(Error **errp);
|
||||
QEMUFile *cpr_exec_input(Error **errp);
|
||||
void cpr_exec_persist_state(QEMUFile *f);
|
||||
bool cpr_exec_has_state(void);
|
||||
void cpr_exec_unpersist_state(void);
|
||||
void cpr_exec_unpreserve_fds(void);
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -95,7 +95,19 @@ void migration_add_notifier(NotifierWithReturn *notify,
|
|||
void migration_add_notifier_mode(NotifierWithReturn *notify,
|
||||
MigrationNotifyFunc func, MigMode mode);
|
||||
|
||||
/*
|
||||
* Same as migration_add_notifier, but applies to all @mode in the argument
|
||||
* list. The list is terminated by -1 or MIG_MODE_ALL. For the latter,
|
||||
* the notifier is added for all modes.
|
||||
*/
|
||||
void migration_add_notifier_modes(NotifierWithReturn *notify,
|
||||
MigrationNotifyFunc func, MigMode mode, ...);
|
||||
|
||||
/*
|
||||
* Remove a notifier from all modes.
|
||||
*/
|
||||
void migration_remove_notifier(NotifierWithReturn *notify);
|
||||
|
||||
void migration_file_set_error(int ret, Error *err);
|
||||
|
||||
/* True if incoming migration entered POSTCOPY_INCOMING_DISCARD */
|
||||
|
|
|
|||
|
|
@ -163,6 +163,7 @@ typedef enum {
|
|||
MIG_PRI_IOMMU, /* Must happen before PCI devices */
|
||||
MIG_PRI_PCI_BUS, /* Must happen before IOMMU */
|
||||
MIG_PRI_VIRTIO_MEM, /* Must happen before IOMMU */
|
||||
MIG_PRI_APIC, /* Must happen before PCI devices */
|
||||
MIG_PRI_GICV3_ITS, /* Must happen before PCI devices */
|
||||
MIG_PRI_GICV3, /* Must happen before the ITS */
|
||||
MIG_PRI_MAX,
|
||||
|
|
@ -200,14 +201,28 @@ struct VMStateDescription {
|
|||
* exclusive. For this reason, also early_setup VMSDs are migrated in a
|
||||
* QEMU_VM_SECTION_FULL section, while save_setup() data is migrated in
|
||||
* a QEMU_VM_SECTION_START section.
|
||||
*
|
||||
* There are duplicate impls of the post/pre save/load hooks.
|
||||
* New impls should preferentally use 'errp' variants of these
|
||||
* methods and existing impls incrementally converted.
|
||||
* The variants without 'errp' are intended to be removed
|
||||
* once all usage is converted.
|
||||
*
|
||||
* For the errp variants,
|
||||
* Returns: 0 on success,
|
||||
* <0 on error where -value is an error number from errno.h
|
||||
*/
|
||||
|
||||
bool early_setup;
|
||||
int version_id;
|
||||
int minimum_version_id;
|
||||
MigrationPriority priority;
|
||||
int (*pre_load)(void *opaque);
|
||||
int (*pre_load_errp)(void *opaque, Error **errp);
|
||||
int (*post_load)(void *opaque, int version_id);
|
||||
int (*post_load_errp)(void *opaque, int version_id, Error **errp);
|
||||
int (*pre_save)(void *opaque);
|
||||
int (*pre_save_errp)(void *opaque, Error **errp);
|
||||
int (*post_save)(void *opaque);
|
||||
bool (*needed)(void *opaque);
|
||||
bool (*dev_unplug_pending)(void *opaque);
|
||||
|
|
@ -1206,10 +1221,8 @@ extern const VMStateInfo vmstate_info_qlist;
|
|||
}
|
||||
|
||||
int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
|
||||
void *opaque, int version_id);
|
||||
void *opaque, int version_id, Error **errp);
|
||||
int vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd,
|
||||
void *opaque, JSONWriter *vmdesc);
|
||||
int vmstate_save_state_with_err(QEMUFile *f, const VMStateDescription *vmsd,
|
||||
void *opaque, JSONWriter *vmdesc, Error **errp);
|
||||
int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
|
||||
void *opaque, JSONWriter *vmdesc,
|
||||
|
|
|
|||
|
|
@ -689,6 +689,15 @@ ssize_t qemu_write_full(int fd, const void *buf, size_t count)
|
|||
void qemu_set_cloexec(int fd);
|
||||
bool qemu_set_blocking(int fd, bool block, Error **errp);
|
||||
|
||||
/*
|
||||
* Clear FD_CLOEXEC for a descriptor.
|
||||
*
|
||||
* The caller must guarantee that no other fork+exec's occur before the
|
||||
* exec that is intended to inherit this descriptor, eg by suspending CPUs
|
||||
* and blocking monitor commands.
|
||||
*/
|
||||
void qemu_clear_cloexec(int fd);
|
||||
|
||||
/* Return a dynamically allocated directory path that is appropriate for storing
|
||||
* local state.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -2727,14 +2727,32 @@ void address_space_init(AddressSpace *as, MemoryRegion *root, const char *name);
|
|||
/**
|
||||
* address_space_destroy: destroy an address space
|
||||
*
|
||||
* Releases all resources associated with an address space. After an address space
|
||||
* is destroyed, its root memory region (given by address_space_init()) may be destroyed
|
||||
* as well.
|
||||
* Releases all resources associated with an address space. After an
|
||||
* address space is destroyed, the reference the AddressSpace had to
|
||||
* its root memory region is dropped, which may result in the
|
||||
* destruction of that memory region as well.
|
||||
*
|
||||
* Note that destruction of the AddressSpace is done via RCU;
|
||||
* it is therefore not valid to free the memory the AddressSpace
|
||||
* struct is in until after that RCU callback has completed.
|
||||
* If you want to g_free() the AddressSpace after destruction you
|
||||
* can do that with address_space_destroy_free().
|
||||
*
|
||||
* @as: address space to be destroyed
|
||||
*/
|
||||
void address_space_destroy(AddressSpace *as);
|
||||
|
||||
/**
|
||||
* address_space_destroy_free: destroy an address space and free it
|
||||
*
|
||||
* This does the same thing as address_space_destroy(), and then also
|
||||
* frees (via g_free()) the AddressSpace itself once the destruction
|
||||
* is complete.
|
||||
*
|
||||
* @as: address space to be destroyed
|
||||
*/
|
||||
void address_space_destroy_free(AddressSpace *as);
|
||||
|
||||
/**
|
||||
* address_space_remove_listeners: unregister all listeners of an address space
|
||||
*
|
||||
|
|
|
|||
|
|
@ -346,6 +346,19 @@ static void qio_channel_tls_finalize(Object *obj)
|
|||
qcrypto_tls_session_free(ioc->session);
|
||||
}
|
||||
|
||||
static bool
|
||||
qio_channel_tls_allow_premature_termination(QIOChannelTLS *tioc, int flags)
|
||||
{
|
||||
if (flags & QIO_CHANNEL_READ_FLAG_RELAXED_EOF) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (qatomic_read(&tioc->shutdown) & QIO_CHANNEL_SHUTDOWN_READ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static ssize_t qio_channel_tls_readv(QIOChannel *ioc,
|
||||
const struct iovec *iov,
|
||||
|
|
@ -364,8 +377,6 @@ static ssize_t qio_channel_tls_readv(QIOChannel *ioc,
|
|||
tioc->session,
|
||||
iov[i].iov_base,
|
||||
iov[i].iov_len,
|
||||
flags & QIO_CHANNEL_READ_FLAG_RELAXED_EOF ||
|
||||
qatomic_load_acquire(&tioc->shutdown) & QIO_CHANNEL_SHUTDOWN_READ,
|
||||
errp);
|
||||
if (ret == QCRYPTO_TLS_SESSION_ERR_BLOCK) {
|
||||
if (got) {
|
||||
|
|
@ -373,6 +384,12 @@ static ssize_t qio_channel_tls_readv(QIOChannel *ioc,
|
|||
} else {
|
||||
return QIO_CHANNEL_ERR_BLOCK;
|
||||
}
|
||||
} else if (ret == QCRYPTO_TLS_SESSION_PREMATURE_TERMINATION) {
|
||||
if (qio_channel_tls_allow_premature_termination(tioc, flags)) {
|
||||
ret = 0;
|
||||
} else {
|
||||
return -1;
|
||||
}
|
||||
} else if (ret < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -686,11 +686,10 @@ static void colo_incoming_process_checkpoint(MigrationIncomingState *mis,
|
|||
|
||||
bql_lock();
|
||||
cpu_synchronize_all_states();
|
||||
ret = qemu_loadvm_state_main(mis->from_src_file, mis);
|
||||
ret = qemu_loadvm_state_main(mis->from_src_file, mis, errp);
|
||||
bql_unlock();
|
||||
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Load VM's live state (ram) error");
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -729,9 +728,8 @@ static void colo_incoming_process_checkpoint(MigrationIncomingState *mis,
|
|||
bql_lock();
|
||||
vmstate_loading = true;
|
||||
colo_flush_ram_cache();
|
||||
ret = qemu_load_device_state(fb);
|
||||
ret = qemu_load_device_state(fb, errp);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "COLO: load device state failed");
|
||||
vmstate_loading = false;
|
||||
bql_unlock();
|
||||
return;
|
||||
|
|
@ -849,10 +847,6 @@ static void *colo_process_incoming_thread(void *opaque)
|
|||
failover_init_state();
|
||||
|
||||
mis->to_src_file = qemu_file_get_return_path(mis->from_src_file);
|
||||
if (!mis->to_src_file) {
|
||||
error_report("COLO incoming thread: Open QEMUFile to_src_file failed");
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* Note: the communication between Primary side and Secondary side
|
||||
* should be sequential, we set the fd to unblocked in migration incoming
|
||||
|
|
|
|||
194
migration/cpr-exec.c
Normal file
194
migration/cpr-exec.c
Normal file
|
|
@ -0,0 +1,194 @@
|
|||
/*
|
||||
* 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);
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "hw/vfio/vfio-device.h"
|
||||
|
|
@ -122,6 +123,19 @@ int cpr_open_fd(const char *path, int flags, const char *name, int id,
|
|||
return fd;
|
||||
}
|
||||
|
||||
bool cpr_walk_fd(cpr_walk_fd_cb cb)
|
||||
{
|
||||
CprFd *elem;
|
||||
|
||||
QLIST_FOREACH(elem, &cpr_state.fds, next) {
|
||||
g_assert(elem->fd >= 0);
|
||||
if (!cb(elem->fd)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/*************************************************************************/
|
||||
static const VMStateDescription vmstate_cpr_state = {
|
||||
.name = CPR_STATE,
|
||||
|
|
@ -173,6 +187,8 @@ int cpr_state_save(MigrationChannel *channel, Error **errp)
|
|||
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 0;
|
||||
}
|
||||
|
|
@ -183,13 +199,16 @@ int cpr_state_save(MigrationChannel *channel, Error **errp)
|
|||
qemu_put_be32(f, QEMU_CPR_FILE_MAGIC);
|
||||
qemu_put_be32(f, QEMU_CPR_FILE_VERSION);
|
||||
|
||||
ret = vmstate_save_state(f, &vmstate_cpr_state, &cpr_state, 0);
|
||||
ret = vmstate_save_state(f, &vmstate_cpr_state, &cpr_state, 0, errp);
|
||||
if (ret) {
|
||||
error_setg(errp, "vmstate_save_state error %d", ret);
|
||||
qemu_fclose(f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (migrate_mode() == MIG_MODE_CPR_EXEC) {
|
||||
cpr_exec_persist_state(f);
|
||||
}
|
||||
|
||||
/*
|
||||
* Close the socket only partially so we can later detect when the other
|
||||
* end closes by getting a HUP event.
|
||||
|
|
@ -208,7 +227,13 @@ int cpr_state_load(MigrationChannel *channel, Error **errp)
|
|||
QEMUFile *f;
|
||||
MigMode mode = 0;
|
||||
|
||||
if (channel) {
|
||||
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);
|
||||
|
|
@ -220,6 +245,7 @@ int cpr_state_load(MigrationChannel *channel, Error **errp)
|
|||
}
|
||||
|
||||
trace_cpr_state_load(MigMode_str(mode));
|
||||
cpr_set_incoming_mode(mode);
|
||||
|
||||
v = qemu_get_be32(f);
|
||||
if (v != QEMU_CPR_FILE_MAGIC) {
|
||||
|
|
@ -234,13 +260,17 @@ int cpr_state_load(MigrationChannel *channel, Error **errp)
|
|||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
ret = vmstate_load_state(f, &vmstate_cpr_state, &cpr_state, 1);
|
||||
ret = vmstate_load_state(f, &vmstate_cpr_state, &cpr_state, 1, errp);
|
||||
if (ret) {
|
||||
error_setg(errp, "vmstate_load_state error %d", ret);
|
||||
qemu_fclose(f);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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).
|
||||
|
|
@ -261,7 +291,7 @@ void cpr_state_close(void)
|
|||
bool cpr_incoming_needed(void *opaque)
|
||||
{
|
||||
MigMode mode = migrate_mode();
|
||||
return mode == MIG_MODE_CPR_TRANSFER;
|
||||
return mode == MIG_MODE_CPR_TRANSFER || mode == MIG_MODE_CPR_EXEC;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ system_ss.add(files(
|
|||
'channel-block.c',
|
||||
'cpr.c',
|
||||
'cpr-transfer.c',
|
||||
'cpr-exec.c',
|
||||
'cpu-throttle.c',
|
||||
'dirtyrate.c',
|
||||
'exec.c',
|
||||
|
|
|
|||
|
|
@ -306,6 +306,18 @@ void hmp_info_migrate_capabilities(Monitor *mon, const QDict *qdict)
|
|||
qapi_free_MigrationCapabilityStatusList(caps);
|
||||
}
|
||||
|
||||
static void monitor_print_cpr_exec_command(Monitor *mon, strList *args)
|
||||
{
|
||||
monitor_printf(mon, "%s:",
|
||||
MigrationParameter_str(MIGRATION_PARAMETER_CPR_EXEC_COMMAND));
|
||||
|
||||
while (args) {
|
||||
monitor_printf(mon, " %s", args->value);
|
||||
args = args->next;
|
||||
}
|
||||
monitor_printf(mon, "\n");
|
||||
}
|
||||
|
||||
void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
|
||||
{
|
||||
MigrationParameters *params;
|
||||
|
|
@ -353,6 +365,10 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
|
|||
monitor_printf(mon, "%s: '%s'\n",
|
||||
MigrationParameter_str(MIGRATION_PARAMETER_TLS_HOSTNAME),
|
||||
params->tls_hostname);
|
||||
assert(params->tls_authz);
|
||||
monitor_printf(mon, "%s: '%s'\n",
|
||||
MigrationParameter_str(MIGRATION_PARAMETER_TLS_AUTHZ),
|
||||
params->tls_authz);
|
||||
assert(params->has_max_bandwidth);
|
||||
monitor_printf(mon, "%s: %" PRIu64 " bytes/second\n",
|
||||
MigrationParameter_str(MIGRATION_PARAMETER_MAX_BANDWIDTH),
|
||||
|
|
@ -361,6 +377,10 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
|
|||
monitor_printf(mon, "%s: %" PRIu64 " bytes/second\n",
|
||||
MigrationParameter_str(MIGRATION_PARAMETER_AVAIL_SWITCHOVER_BANDWIDTH),
|
||||
params->avail_switchover_bandwidth);
|
||||
assert(params->has_max_postcopy_bandwidth);
|
||||
monitor_printf(mon, "%s: %" PRIu64 " bytes/second\n",
|
||||
MigrationParameter_str(MIGRATION_PARAMETER_MAX_POSTCOPY_BANDWIDTH),
|
||||
params->max_postcopy_bandwidth);
|
||||
assert(params->has_downtime_limit);
|
||||
monitor_printf(mon, "%s: %" PRIu64 " ms\n",
|
||||
MigrationParameter_str(MIGRATION_PARAMETER_DOWNTIME_LIMIT),
|
||||
|
|
@ -383,12 +403,6 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
|
|||
monitor_printf(mon, "%s: %" PRIu64 " bytes\n",
|
||||
MigrationParameter_str(MIGRATION_PARAMETER_XBZRLE_CACHE_SIZE),
|
||||
params->xbzrle_cache_size);
|
||||
monitor_printf(mon, "%s: %" PRIu64 "\n",
|
||||
MigrationParameter_str(MIGRATION_PARAMETER_MAX_POSTCOPY_BANDWIDTH),
|
||||
params->max_postcopy_bandwidth);
|
||||
monitor_printf(mon, "%s: '%s'\n",
|
||||
MigrationParameter_str(MIGRATION_PARAMETER_TLS_AUTHZ),
|
||||
params->tls_authz);
|
||||
|
||||
if (params->has_block_bitmap_mapping) {
|
||||
const BitmapMigrationNodeAliasList *bmnal;
|
||||
|
|
@ -435,6 +449,9 @@ void hmp_info_migrate_parameters(Monitor *mon, const QDict *qdict)
|
|||
MIGRATION_PARAMETER_DIRECT_IO),
|
||||
params->direct_io ? "on" : "off");
|
||||
}
|
||||
|
||||
assert(params->has_cpr_exec_command);
|
||||
monitor_print_cpr_exec_command(mon, params->cpr_exec_command);
|
||||
}
|
||||
|
||||
qapi_free_MigrationParameters(params);
|
||||
|
|
@ -716,6 +733,21 @@ void hmp_migrate_set_parameter(Monitor *mon, const QDict *qdict)
|
|||
p->has_direct_io = true;
|
||||
visit_type_bool(v, param, &p->direct_io, &err);
|
||||
break;
|
||||
case MIGRATION_PARAMETER_CPR_EXEC_COMMAND: {
|
||||
g_autofree char **strv = NULL;
|
||||
g_autoptr(GError) gerr = NULL;
|
||||
strList **tail = &p->cpr_exec_command;
|
||||
|
||||
if (!g_shell_parse_argv(valuestr, NULL, &strv, &gerr)) {
|
||||
error_setg(&err, "%s", gerr->message);
|
||||
break;
|
||||
}
|
||||
for (int i = 0; strv[i]; i++) {
|
||||
QAPI_LIST_APPEND(tail, strv[i]);
|
||||
}
|
||||
p->has_cpr_exec_command = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
g_assert_not_reached();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,11 +74,7 @@
|
|||
|
||||
#define INMIGRATE_DEFAULT_EXIT_ON_ERROR true
|
||||
|
||||
static NotifierWithReturnList migration_state_notifiers[] = {
|
||||
NOTIFIER_ELEM_INIT(migration_state_notifiers, MIG_MODE_NORMAL),
|
||||
NOTIFIER_ELEM_INIT(migration_state_notifiers, MIG_MODE_CPR_REBOOT),
|
||||
NOTIFIER_ELEM_INIT(migration_state_notifiers, MIG_MODE_CPR_TRANSFER),
|
||||
};
|
||||
static GSList *migration_state_notifiers[MIG_MODE__MAX];
|
||||
|
||||
/* Messages sent on the return path from destination to source */
|
||||
enum mig_rp_message_type {
|
||||
|
|
@ -337,6 +333,7 @@ void migration_object_init(void)
|
|||
|
||||
ram_mig_init();
|
||||
dirty_bitmap_mig_init();
|
||||
cpr_exec_init();
|
||||
|
||||
/* Initialize cpu throttle timers */
|
||||
cpu_throttle_init();
|
||||
|
|
@ -623,22 +620,22 @@ void migration_incoming_disable_colo(void)
|
|||
migration_colo_enabled = false;
|
||||
}
|
||||
|
||||
int migration_incoming_enable_colo(void)
|
||||
int migration_incoming_enable_colo(Error **errp)
|
||||
{
|
||||
#ifndef CONFIG_REPLICATION
|
||||
error_report("ENABLE_COLO command come in migration stream, but the "
|
||||
"replication module is not built in");
|
||||
error_setg(errp, "ENABLE_COLO command come in migration stream, but the "
|
||||
"replication module is not built in");
|
||||
return -ENOTSUP;
|
||||
#endif
|
||||
|
||||
if (!migrate_colo()) {
|
||||
error_report("ENABLE_COLO command come in migration stream, but x-colo "
|
||||
"capability is not set");
|
||||
error_setg(errp, "ENABLE_COLO command come in migration stream"
|
||||
", but x-colo capability is not set");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ram_block_discard_disable(true)) {
|
||||
error_report("COLO: cannot disable RAM discard");
|
||||
error_setg(errp, "COLO: cannot disable RAM discard");
|
||||
return -EBUSY;
|
||||
}
|
||||
migration_colo_enabled = true;
|
||||
|
|
@ -881,7 +878,7 @@ process_incoming_migration_co(void *opaque)
|
|||
MIGRATION_STATUS_ACTIVE);
|
||||
|
||||
mis->loadvm_co = qemu_coroutine_self();
|
||||
ret = qemu_loadvm_state(mis->from_src_file);
|
||||
ret = qemu_loadvm_state(mis->from_src_file, &local_err);
|
||||
mis->loadvm_co = NULL;
|
||||
|
||||
trace_vmstate_downtime_checkpoint("dst-precopy-loadvm-completed");
|
||||
|
|
@ -908,7 +905,8 @@ process_incoming_migration_co(void *opaque)
|
|||
}
|
||||
|
||||
if (ret < 0) {
|
||||
error_setg(&local_err, "load of migration failed: %s", strerror(-ret));
|
||||
error_prepend(&local_err, "load of migration failed: %s: ",
|
||||
strerror(-ret));
|
||||
goto fail;
|
||||
}
|
||||
|
||||
|
|
@ -935,6 +933,15 @@ fail:
|
|||
}
|
||||
|
||||
exit(EXIT_FAILURE);
|
||||
} else {
|
||||
/*
|
||||
* Report the error here in case that QEMU abruptly exits
|
||||
* when postcopy is enabled.
|
||||
*/
|
||||
WITH_QEMU_LOCK_GUARD(&s->error_mutex) {
|
||||
error_report_err(s->error);
|
||||
s->error = NULL;
|
||||
}
|
||||
}
|
||||
out:
|
||||
/* Pairs with the refcount taken in qmp_migrate_incoming() */
|
||||
|
|
@ -1665,23 +1672,51 @@ void migration_cancel(void)
|
|||
}
|
||||
}
|
||||
|
||||
static int get_modes(MigMode mode, va_list ap);
|
||||
|
||||
static void add_notifiers(NotifierWithReturn *notify, int modes)
|
||||
{
|
||||
for (MigMode mode = 0; mode < MIG_MODE__MAX; mode++) {
|
||||
if (modes & BIT(mode)) {
|
||||
migration_state_notifiers[mode] =
|
||||
g_slist_prepend(migration_state_notifiers[mode], notify);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void migration_add_notifier_modes(NotifierWithReturn *notify,
|
||||
MigrationNotifyFunc func, MigMode mode, ...)
|
||||
{
|
||||
int modes;
|
||||
va_list ap;
|
||||
|
||||
va_start(ap, mode);
|
||||
modes = get_modes(mode, ap);
|
||||
va_end(ap);
|
||||
|
||||
notify->notify = (NotifierWithReturnFunc)func;
|
||||
add_notifiers(notify, modes);
|
||||
}
|
||||
|
||||
void migration_add_notifier_mode(NotifierWithReturn *notify,
|
||||
MigrationNotifyFunc func, MigMode mode)
|
||||
{
|
||||
notify->notify = (NotifierWithReturnFunc)func;
|
||||
notifier_with_return_list_add(&migration_state_notifiers[mode], notify);
|
||||
migration_add_notifier_modes(notify, func, mode, -1);
|
||||
}
|
||||
|
||||
void migration_add_notifier(NotifierWithReturn *notify,
|
||||
MigrationNotifyFunc func)
|
||||
{
|
||||
migration_add_notifier_mode(notify, func, MIG_MODE_NORMAL);
|
||||
migration_add_notifier_modes(notify, func, MIG_MODE_NORMAL, -1);
|
||||
}
|
||||
|
||||
void migration_remove_notifier(NotifierWithReturn *notify)
|
||||
{
|
||||
if (notify->notify) {
|
||||
notifier_with_return_remove(notify);
|
||||
for (MigMode mode = 0; mode < MIG_MODE__MAX; mode++) {
|
||||
migration_blockers[mode] =
|
||||
g_slist_remove(migration_state_notifiers[mode], notify);
|
||||
}
|
||||
notify->notify = NULL;
|
||||
}
|
||||
}
|
||||
|
|
@ -1691,18 +1726,29 @@ int migration_call_notifiers(MigrationState *s, MigrationEventType type,
|
|||
{
|
||||
MigMode mode = s->parameters.mode;
|
||||
MigrationEvent e;
|
||||
NotifierWithReturn *notifier;
|
||||
GSList *elem, *next;
|
||||
int ret;
|
||||
|
||||
e.type = type;
|
||||
ret = notifier_with_return_list_notify(&migration_state_notifiers[mode],
|
||||
&e, errp);
|
||||
assert(!ret || type == MIG_EVENT_PRECOPY_SETUP);
|
||||
return ret;
|
||||
|
||||
for (elem = migration_state_notifiers[mode]; elem; elem = next) {
|
||||
next = elem->next;
|
||||
notifier = (NotifierWithReturn *)elem->data;
|
||||
ret = notifier->notify(notifier, &e, errp);
|
||||
if (ret) {
|
||||
assert(type == MIG_EVENT_PRECOPY_SETUP);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool migration_has_failed(MigrationState *s)
|
||||
{
|
||||
return (s->state == MIGRATION_STATUS_CANCELLED ||
|
||||
return (s->state == MIGRATION_STATUS_CANCELLING ||
|
||||
s->state == MIGRATION_STATUS_CANCELLED ||
|
||||
s->state == MIGRATION_STATUS_FAILED);
|
||||
}
|
||||
|
||||
|
|
@ -1762,7 +1808,8 @@ bool migrate_mode_is_cpr(MigrationState *s)
|
|||
{
|
||||
MigMode mode = s->parameters.mode;
|
||||
return mode == MIG_MODE_CPR_REBOOT ||
|
||||
mode == MIG_MODE_CPR_TRANSFER;
|
||||
mode == MIG_MODE_CPR_TRANSFER ||
|
||||
mode == MIG_MODE_CPR_EXEC;
|
||||
}
|
||||
|
||||
int migrate_init(MigrationState *s, Error **errp)
|
||||
|
|
@ -2111,6 +2158,12 @@ static bool migrate_prepare(MigrationState *s, bool resume, Error **errp)
|
|||
return false;
|
||||
}
|
||||
|
||||
if (migrate_mode() == MIG_MODE_CPR_EXEC &&
|
||||
!s->parameters.has_cpr_exec_command) {
|
||||
error_setg(errp, "cpr-exec mode requires setting cpr-exec-command");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (migration_is_blocked(errp)) {
|
||||
return false;
|
||||
}
|
||||
|
|
@ -2646,12 +2699,9 @@ out:
|
|||
return NULL;
|
||||
}
|
||||
|
||||
static int open_return_path_on_source(MigrationState *ms)
|
||||
static void open_return_path_on_source(MigrationState *ms)
|
||||
{
|
||||
ms->rp_state.from_dst_file = qemu_file_get_return_path(ms->to_dst_file);
|
||||
if (!ms->rp_state.from_dst_file) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
trace_open_return_path_on_source();
|
||||
|
||||
|
|
@ -2660,8 +2710,6 @@ static int open_return_path_on_source(MigrationState *ms)
|
|||
ms->rp_state.rp_thread_created = true;
|
||||
|
||||
trace_open_return_path_on_source_continue();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Return true if error detected, or false otherwise */
|
||||
|
|
@ -2872,8 +2920,9 @@ static int postcopy_start(MigrationState *ms, Error **errp)
|
|||
fail_closefb:
|
||||
qemu_fclose(fb);
|
||||
fail:
|
||||
migrate_set_state(&ms->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
|
||||
MIGRATION_STATUS_FAILED);
|
||||
if (ms->state != MIGRATION_STATUS_CANCELLING) {
|
||||
migrate_set_state(&ms->state, ms->state, MIGRATION_STATUS_FAILED);
|
||||
}
|
||||
migration_block_activate(NULL);
|
||||
migration_call_notifiers(ms, MIG_EVENT_PRECOPY_FAILED, NULL);
|
||||
bql_unlock();
|
||||
|
|
@ -4012,10 +4061,7 @@ void migration_connect(MigrationState *s, Error *error_in)
|
|||
* QEMU uses the return path.
|
||||
*/
|
||||
if (migrate_postcopy_ram() || migrate_return_path()) {
|
||||
if (open_return_path_on_source(s)) {
|
||||
error_setg(&local_err, "Unable to open return-path for postcopy");
|
||||
goto fail;
|
||||
}
|
||||
open_return_path_on_source(s);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -439,6 +439,39 @@ static void multifd_send_set_error(Error *err)
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Gracefully shutdown IOChannels. Only needed for successful migrations on
|
||||
* top of TLS channels. Otherwise it is same to qio_channel_shutdown().
|
||||
*
|
||||
* A successful migration also guarantees multifd sender threads are
|
||||
* properly flushed and halted. It is only safe to send BYE in the
|
||||
* migration thread here when we know there's no other thread writting to
|
||||
* the channel, because GnuTLS doesn't support concurrent writers.
|
||||
*/
|
||||
static void migration_ioc_shutdown_gracefully(QIOChannel *ioc)
|
||||
{
|
||||
g_autoptr(Error) local_err = NULL;
|
||||
|
||||
if (!migration_has_failed(migrate_get_current()) &&
|
||||
object_dynamic_cast((Object *)ioc, TYPE_QIO_CHANNEL_TLS)) {
|
||||
|
||||
/*
|
||||
* The destination expects the TLS session to always be properly
|
||||
* terminated. This helps to detect a premature termination in the
|
||||
* middle of the stream. Note that older QEMUs always break the
|
||||
* connection on the source and the destination always sees
|
||||
* GNUTLS_E_PREMATURE_TERMINATION.
|
||||
*/
|
||||
migration_tls_channel_end(ioc, &local_err);
|
||||
if (local_err) {
|
||||
warn_report("Failed to gracefully terminate TLS connection: %s",
|
||||
error_get_pretty(local_err));
|
||||
}
|
||||
}
|
||||
|
||||
qio_channel_shutdown(ioc, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
|
||||
}
|
||||
|
||||
static void multifd_send_terminate_threads(void)
|
||||
{
|
||||
int i;
|
||||
|
|
@ -460,7 +493,7 @@ static void multifd_send_terminate_threads(void)
|
|||
|
||||
qemu_sem_post(&p->sem);
|
||||
if (p->c) {
|
||||
qio_channel_shutdown(p->c, QIO_CHANNEL_SHUTDOWN_BOTH, NULL);
|
||||
migration_ioc_shutdown_gracefully(p->c);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -547,36 +580,6 @@ void multifd_send_shutdown(void)
|
|||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < migrate_multifd_channels(); i++) {
|
||||
MultiFDSendParams *p = &multifd_send_state->params[i];
|
||||
|
||||
/* thread_created implies the TLS handshake has succeeded */
|
||||
if (p->tls_thread_created && p->thread_created) {
|
||||
Error *local_err = NULL;
|
||||
/*
|
||||
* The destination expects the TLS session to always be
|
||||
* properly terminated. This helps to detect a premature
|
||||
* termination in the middle of the stream. Note that
|
||||
* older QEMUs always break the connection on the source
|
||||
* and the destination always sees
|
||||
* GNUTLS_E_PREMATURE_TERMINATION.
|
||||
*/
|
||||
migration_tls_channel_end(p->c, &local_err);
|
||||
|
||||
/*
|
||||
* The above can return an error in case the migration has
|
||||
* already failed. If the migration succeeded, errors are
|
||||
* not expected but there's no need to kill the source.
|
||||
*/
|
||||
if (local_err && !migration_has_failed(migrate_get_current())) {
|
||||
warn_report(
|
||||
"multifd_send_%d: Failed to terminate TLS connection: %s",
|
||||
p->id, error_get_pretty(local_err));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
multifd_send_terminate_threads();
|
||||
|
||||
for (i = 0; i < migrate_multifd_channels(); i++) {
|
||||
|
|
|
|||
|
|
@ -959,6 +959,9 @@ MigrationParameters *qmp_query_migrate_parameters(Error **errp)
|
|||
params->zero_page_detection = s->parameters.zero_page_detection;
|
||||
params->has_direct_io = true;
|
||||
params->direct_io = s->parameters.direct_io;
|
||||
params->has_cpr_exec_command = true;
|
||||
params->cpr_exec_command = QAPI_CLONE(strList,
|
||||
s->parameters.cpr_exec_command);
|
||||
|
||||
return params;
|
||||
}
|
||||
|
|
@ -993,6 +996,7 @@ void migrate_params_init(MigrationParameters *params)
|
|||
params->has_mode = true;
|
||||
params->has_zero_page_detection = true;
|
||||
params->has_direct_io = true;
|
||||
params->has_cpr_exec_command = true;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -1297,6 +1301,10 @@ static void migrate_params_test_apply(MigrateSetParameters *params,
|
|||
if (params->has_direct_io) {
|
||||
dest->direct_io = params->direct_io;
|
||||
}
|
||||
|
||||
if (params->has_cpr_exec_command) {
|
||||
dest->cpr_exec_command = params->cpr_exec_command;
|
||||
}
|
||||
}
|
||||
|
||||
static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
|
||||
|
|
@ -1429,6 +1437,12 @@ static void migrate_params_apply(MigrateSetParameters *params, Error **errp)
|
|||
if (params->has_direct_io) {
|
||||
s->parameters.direct_io = params->direct_io;
|
||||
}
|
||||
|
||||
if (params->has_cpr_exec_command) {
|
||||
qapi_free_strList(s->parameters.cpr_exec_command);
|
||||
s->parameters.cpr_exec_command =
|
||||
QAPI_CLONE(strList, params->cpr_exec_command);
|
||||
}
|
||||
}
|
||||
|
||||
void qmp_migrate_set_parameters(MigrateSetParameters *params, Error **errp)
|
||||
|
|
|
|||
|
|
@ -681,6 +681,7 @@ out:
|
|||
*/
|
||||
static int init_range(RAMBlock *rb, void *opaque)
|
||||
{
|
||||
Error **errp = opaque;
|
||||
const char *block_name = qemu_ram_get_idstr(rb);
|
||||
void *host_addr = qemu_ram_get_host_addr(rb);
|
||||
ram_addr_t offset = qemu_ram_get_offset(rb);
|
||||
|
|
@ -701,6 +702,8 @@ static int init_range(RAMBlock *rb, void *opaque)
|
|||
* (Precopy will just overwrite this data, so doesn't need the discard)
|
||||
*/
|
||||
if (ram_discard_range(block_name, 0, length)) {
|
||||
error_setg(errp, "failed to discard RAM block %s len=%zu",
|
||||
block_name, length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -749,9 +752,9 @@ static int cleanup_range(RAMBlock *rb, void *opaque)
|
|||
* postcopy later; must be called prior to any precopy.
|
||||
* called from arch_init's similarly named ram_postcopy_incoming_init
|
||||
*/
|
||||
int postcopy_ram_incoming_init(MigrationIncomingState *mis)
|
||||
int postcopy_ram_incoming_init(MigrationIncomingState *mis, Error **errp)
|
||||
{
|
||||
if (foreach_not_ignored_block(init_range, NULL)) {
|
||||
if (foreach_not_ignored_block(init_range, errp)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -1703,7 +1706,7 @@ bool postcopy_ram_supported_by_host(MigrationIncomingState *mis, Error **errp)
|
|||
return false;
|
||||
}
|
||||
|
||||
int postcopy_ram_incoming_init(MigrationIncomingState *mis)
|
||||
int postcopy_ram_incoming_init(MigrationIncomingState *mis, Error **errp)
|
||||
{
|
||||
error_report("postcopy_ram_incoming_init: No OS support");
|
||||
return -1;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ int postcopy_ram_incoming_setup(MigrationIncomingState *mis);
|
|||
* postcopy later; must be called prior to any precopy.
|
||||
* called from ram.c's similarly named ram_postcopy_incoming_init
|
||||
*/
|
||||
int postcopy_ram_incoming_init(MigrationIncomingState *mis);
|
||||
int postcopy_ram_incoming_init(MigrationIncomingState *mis, Error **errp);
|
||||
|
||||
/*
|
||||
* At the end of a migration where postcopy_ram_incoming_init was called.
|
||||
|
|
|
|||
|
|
@ -125,7 +125,6 @@ static QEMUFile *qemu_file_new_impl(QIOChannel *ioc, bool is_writable)
|
|||
|
||||
/*
|
||||
* Result: QEMUFile* for a 'return path' for comms in the opposite direction
|
||||
* NULL if not available
|
||||
*/
|
||||
QEMUFile *qemu_file_get_return_path(QEMUFile *f)
|
||||
{
|
||||
|
|
@ -349,17 +348,13 @@ static ssize_t coroutine_mixed_fn qemu_fill_buffer(QEMUFile *f)
|
|||
} else {
|
||||
qio_channel_wait(f->ioc, G_IO_IN);
|
||||
}
|
||||
} else if (len < 0) {
|
||||
len = -EIO;
|
||||
}
|
||||
} while (len == QIO_CHANNEL_ERR_BLOCK);
|
||||
|
||||
if (len > 0) {
|
||||
f->buf_size += len;
|
||||
} else if (len == 0) {
|
||||
qemu_file_set_error_obj(f, -EIO, local_error);
|
||||
} else {
|
||||
qemu_file_set_error_obj(f, len, local_error);
|
||||
qemu_file_set_error_obj(f, -EIO, local_error);
|
||||
}
|
||||
|
||||
for (int i = 0; i < nfd; i++) {
|
||||
|
|
|
|||
|
|
@ -228,6 +228,7 @@ bool migrate_ram_is_ignored(RAMBlock *block)
|
|||
MigMode mode = migrate_mode();
|
||||
return !qemu_ram_is_migratable(block) ||
|
||||
mode == MIG_MODE_CPR_TRANSFER ||
|
||||
mode == MIG_MODE_CPR_EXEC ||
|
||||
(migrate_ignore_shared() && qemu_ram_is_shared(block)
|
||||
&& qemu_ram_is_named_file(block));
|
||||
}
|
||||
|
|
@ -3575,8 +3576,10 @@ static void colo_init_ram_state(void)
|
|||
* colo cache: this is for secondary VM, we cache the whole
|
||||
* memory of the secondary VM, it is need to hold the global lock
|
||||
* to call this helper.
|
||||
*
|
||||
* Returns zero to indicate success or -1 on error.
|
||||
*/
|
||||
int colo_init_ram_cache(void)
|
||||
int colo_init_ram_cache(Error **errp)
|
||||
{
|
||||
RAMBlock *block;
|
||||
|
||||
|
|
@ -3585,16 +3588,16 @@ int colo_init_ram_cache(void)
|
|||
block->colo_cache = qemu_anon_ram_alloc(block->used_length,
|
||||
NULL, false, false);
|
||||
if (!block->colo_cache) {
|
||||
error_report("%s: Can't alloc memory for COLO cache of block %s,"
|
||||
"size 0x" RAM_ADDR_FMT, __func__, block->idstr,
|
||||
block->used_length);
|
||||
error_setg(errp, "Can't alloc memory for COLO cache of "
|
||||
"block %s, size 0x" RAM_ADDR_FMT,
|
||||
block->idstr, block->used_length);
|
||||
RAMBLOCK_FOREACH_NOT_IGNORED(block) {
|
||||
if (block->colo_cache) {
|
||||
qemu_anon_ram_free(block->colo_cache, block->used_length);
|
||||
block->colo_cache = NULL;
|
||||
}
|
||||
}
|
||||
return -errno;
|
||||
return -1;
|
||||
}
|
||||
if (!machine_dump_guest_core(current_machine)) {
|
||||
qemu_madvise(block->colo_cache, block->used_length,
|
||||
|
|
@ -3716,9 +3719,9 @@ static int ram_load_cleanup(void *opaque)
|
|||
* postcopy-ram. postcopy-ram's similarly names
|
||||
* postcopy_ram_incoming_init does the work.
|
||||
*/
|
||||
int ram_postcopy_incoming_init(MigrationIncomingState *mis)
|
||||
int ram_postcopy_incoming_init(MigrationIncomingState *mis, Error **errp)
|
||||
{
|
||||
return postcopy_ram_incoming_init(mis);
|
||||
return postcopy_ram_incoming_init(mis, errp);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ void ram_postcopy_migrated_memory_release(MigrationState *ms);
|
|||
void ram_postcopy_send_discard_bitmap(MigrationState *ms);
|
||||
/* For incoming postcopy discard */
|
||||
int ram_discard_range(const char *block_name, uint64_t start, size_t length);
|
||||
int ram_postcopy_incoming_init(MigrationIncomingState *mis);
|
||||
int ram_postcopy_incoming_init(MigrationIncomingState *mis, Error **errp);
|
||||
int ram_load_postcopy(QEMUFile *f, int channel);
|
||||
|
||||
void ram_handle_zero(void *host, uint64_t size);
|
||||
|
|
@ -109,7 +109,7 @@ void ramblock_set_file_bmap_atomic(RAMBlock *block, ram_addr_t offset,
|
|||
bool set);
|
||||
|
||||
/* ram cache */
|
||||
int colo_init_ram_cache(void);
|
||||
int colo_init_ram_cache(Error **errp);
|
||||
void colo_flush_ram_cache(void);
|
||||
void colo_release_ram_cache(void);
|
||||
void colo_incoming_start_dirty_log(void);
|
||||
|
|
|
|||
|
|
@ -963,13 +963,20 @@ void vmstate_unregister(VMStateIf *obj, const VMStateDescription *vmsd,
|
|||
}
|
||||
}
|
||||
|
||||
static int vmstate_load(QEMUFile *f, SaveStateEntry *se)
|
||||
static int vmstate_load(QEMUFile *f, SaveStateEntry *se, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
trace_vmstate_load(se->idstr, se->vmsd ? se->vmsd->name : "(old)");
|
||||
if (!se->vmsd) { /* Old style */
|
||||
return se->ops->load_state(f, se->opaque, se->load_version_id);
|
||||
ret = se->ops->load_state(f, se->opaque, se->load_version_id);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Failed to load vmstate version_id: %d, ret: %d",
|
||||
se->load_version_id, ret);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
return vmstate_load_state(f, se->vmsd, se->opaque, se->load_version_id);
|
||||
return vmstate_load_state(f, se->vmsd, se->opaque, se->load_version_id,
|
||||
errp);
|
||||
}
|
||||
|
||||
static void vmstate_save_old_style(QEMUFile *f, SaveStateEntry *se,
|
||||
|
|
@ -1049,8 +1056,8 @@ static int vmstate_save(QEMUFile *f, SaveStateEntry *se, JSONWriter *vmdesc,
|
|||
if (!se->vmsd) {
|
||||
vmstate_save_old_style(f, se, vmdesc);
|
||||
} else {
|
||||
ret = vmstate_save_state_with_err(f, se->vmsd, se->opaque, vmdesc,
|
||||
errp);
|
||||
ret = vmstate_save_state(f, se->vmsd, se->opaque, vmdesc,
|
||||
errp);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -1278,6 +1285,7 @@ void qemu_savevm_state_header(QEMUFile *f)
|
|||
{
|
||||
MigrationState *s = migrate_get_current();
|
||||
JSONWriter *vmdesc = s->vmdesc;
|
||||
Error *local_err = NULL;
|
||||
|
||||
trace_savevm_state_header();
|
||||
qemu_put_be32(f, QEMU_VM_FILE_MAGIC);
|
||||
|
|
@ -1296,7 +1304,11 @@ void qemu_savevm_state_header(QEMUFile *f)
|
|||
json_writer_start_object(vmdesc, "configuration");
|
||||
}
|
||||
|
||||
vmstate_save_state(f, &vmstate_configuration, &savevm_state, vmdesc);
|
||||
vmstate_save_state(f, &vmstate_configuration, &savevm_state,
|
||||
vmdesc, &local_err);
|
||||
if (local_err) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
|
||||
if (vmdesc) {
|
||||
json_writer_end_object(vmdesc);
|
||||
|
|
@ -1905,39 +1917,39 @@ enum LoadVMExitCodes {
|
|||
* quickly.
|
||||
*/
|
||||
static int loadvm_postcopy_handle_advise(MigrationIncomingState *mis,
|
||||
uint16_t len)
|
||||
uint16_t len, Error **errp)
|
||||
{
|
||||
PostcopyState ps = postcopy_state_set(POSTCOPY_INCOMING_ADVISE);
|
||||
uint64_t remote_pagesize_summary, local_pagesize_summary, remote_tps;
|
||||
size_t page_size = qemu_target_page_size();
|
||||
Error *local_err = NULL;
|
||||
|
||||
trace_loadvm_postcopy_handle_advise();
|
||||
if (ps != POSTCOPY_INCOMING_NONE) {
|
||||
error_report("CMD_POSTCOPY_ADVISE in wrong postcopy state (%d)", ps);
|
||||
error_setg(errp, "CMD_POSTCOPY_ADVISE in wrong postcopy state (%d)",
|
||||
ps);
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch (len) {
|
||||
case 0:
|
||||
if (migrate_postcopy_ram()) {
|
||||
error_report("RAM postcopy is enabled but have 0 byte advise");
|
||||
error_setg(errp, "RAM postcopy is enabled but have 0 byte advise");
|
||||
return -EINVAL;
|
||||
}
|
||||
return 0;
|
||||
case 8 + 8:
|
||||
if (!migrate_postcopy_ram()) {
|
||||
error_report("RAM postcopy is disabled but have 16 byte advise");
|
||||
error_setg(errp,
|
||||
"RAM postcopy is disabled but have 16 byte advise");
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
error_report("CMD_POSTCOPY_ADVISE invalid length (%d)", len);
|
||||
error_setg(errp, "CMD_POSTCOPY_ADVISE invalid length (%d)", len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!postcopy_ram_supported_by_host(mis, &local_err)) {
|
||||
error_report_err(local_err);
|
||||
if (!postcopy_ram_supported_by_host(mis, errp)) {
|
||||
postcopy_state_set(POSTCOPY_INCOMING_NONE);
|
||||
return -1;
|
||||
}
|
||||
|
|
@ -1960,9 +1972,10 @@ static int loadvm_postcopy_handle_advise(MigrationIncomingState *mis,
|
|||
* also fails when passed to an older qemu that doesn't
|
||||
* do huge pages.
|
||||
*/
|
||||
error_report("Postcopy needs matching RAM page sizes (s=%" PRIx64
|
||||
" d=%" PRIx64 ")",
|
||||
remote_pagesize_summary, local_pagesize_summary);
|
||||
error_setg(errp,
|
||||
"Postcopy needs matching RAM page sizes "
|
||||
"(s=%" PRIx64 " d=%" PRIx64 ")",
|
||||
remote_pagesize_summary, local_pagesize_summary);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -1972,17 +1985,18 @@ static int loadvm_postcopy_handle_advise(MigrationIncomingState *mis,
|
|||
* Again, some differences could be dealt with, but for now keep it
|
||||
* simple.
|
||||
*/
|
||||
error_report("Postcopy needs matching target page sizes (s=%d d=%zd)",
|
||||
(int)remote_tps, page_size);
|
||||
error_setg(errp,
|
||||
"Postcopy needs matching target page sizes (s=%d d=%zd)",
|
||||
(int)remote_tps, page_size);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (postcopy_notify(POSTCOPY_NOTIFY_INBOUND_ADVISE, &local_err)) {
|
||||
error_report_err(local_err);
|
||||
if (postcopy_notify(POSTCOPY_NOTIFY_INBOUND_ADVISE, errp)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (ram_postcopy_incoming_init(mis)) {
|
||||
if (ram_postcopy_incoming_init(mis, errp) < 0) {
|
||||
error_prepend(errp, "Postcopy RAM incoming init failed: ");
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -1995,7 +2009,7 @@ static int loadvm_postcopy_handle_advise(MigrationIncomingState *mis,
|
|||
* There can be 0..many of these messages, each encoding multiple pages.
|
||||
*/
|
||||
static int loadvm_postcopy_ram_handle_discard(MigrationIncomingState *mis,
|
||||
uint16_t len)
|
||||
uint16_t len, Error **errp)
|
||||
{
|
||||
int tmp;
|
||||
char ramid[256];
|
||||
|
|
@ -2008,6 +2022,7 @@ static int loadvm_postcopy_ram_handle_discard(MigrationIncomingState *mis,
|
|||
/* 1st discard */
|
||||
tmp = postcopy_ram_prepare_discard(mis);
|
||||
if (tmp) {
|
||||
error_setg(errp, "Failed to prepare for RAM discard: %d", tmp);
|
||||
return tmp;
|
||||
}
|
||||
break;
|
||||
|
|
@ -2017,8 +2032,9 @@ static int loadvm_postcopy_ram_handle_discard(MigrationIncomingState *mis,
|
|||
break;
|
||||
|
||||
default:
|
||||
error_report("CMD_POSTCOPY_RAM_DISCARD in wrong postcopy state (%d)",
|
||||
ps);
|
||||
error_setg(errp,
|
||||
"CMD_POSTCOPY_RAM_DISCARD in wrong postcopy state (%d)",
|
||||
ps);
|
||||
return -1;
|
||||
}
|
||||
/* We're expecting a
|
||||
|
|
@ -2027,29 +2043,30 @@ static int loadvm_postcopy_ram_handle_discard(MigrationIncomingState *mis,
|
|||
* then at least 1 16 byte chunk
|
||||
*/
|
||||
if (len < (1 + 1 + 1 + 1 + 2 * 8)) {
|
||||
error_report("CMD_POSTCOPY_RAM_DISCARD invalid length (%d)", len);
|
||||
error_setg(errp, "CMD_POSTCOPY_RAM_DISCARD invalid length (%d)", len);
|
||||
return -1;
|
||||
}
|
||||
|
||||
tmp = qemu_get_byte(mis->from_src_file);
|
||||
if (tmp != postcopy_ram_discard_version) {
|
||||
error_report("CMD_POSTCOPY_RAM_DISCARD invalid version (%d)", tmp);
|
||||
error_setg(errp, "CMD_POSTCOPY_RAM_DISCARD invalid version (%d)", tmp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!qemu_get_counted_string(mis->from_src_file, ramid)) {
|
||||
error_report("CMD_POSTCOPY_RAM_DISCARD Failed to read RAMBlock ID");
|
||||
error_setg(errp,
|
||||
"CMD_POSTCOPY_RAM_DISCARD Failed to read RAMBlock ID");
|
||||
return -1;
|
||||
}
|
||||
tmp = qemu_get_byte(mis->from_src_file);
|
||||
if (tmp != 0) {
|
||||
error_report("CMD_POSTCOPY_RAM_DISCARD missing nil (%d)", tmp);
|
||||
error_setg(errp, "CMD_POSTCOPY_RAM_DISCARD missing nil (%d)", tmp);
|
||||
return -1;
|
||||
}
|
||||
|
||||
len -= 3 + strlen(ramid);
|
||||
if (len % 16) {
|
||||
error_report("CMD_POSTCOPY_RAM_DISCARD invalid length (%d)", len);
|
||||
error_setg(errp, "CMD_POSTCOPY_RAM_DISCARD invalid length (%d)", len);
|
||||
return -1;
|
||||
}
|
||||
trace_loadvm_postcopy_ram_handle_discard_header(ramid, len);
|
||||
|
|
@ -2061,6 +2078,7 @@ static int loadvm_postcopy_ram_handle_discard(MigrationIncomingState *mis,
|
|||
len -= 16;
|
||||
int ret = ram_discard_range(ramid, start_addr, block_length);
|
||||
if (ret) {
|
||||
error_setg(errp, "Failed to discard RAM range %s: %d", ramid, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -2082,6 +2100,7 @@ static void *postcopy_ram_listen_thread(void *opaque)
|
|||
QEMUFile *f = mis->from_src_file;
|
||||
int load_res;
|
||||
MigrationState *migr = migrate_get_current();
|
||||
Error *local_err = NULL;
|
||||
|
||||
object_ref(OBJECT(migr));
|
||||
|
||||
|
|
@ -2098,7 +2117,7 @@ static void *postcopy_ram_listen_thread(void *opaque)
|
|||
qemu_file_set_blocking(f, true, &error_fatal);
|
||||
|
||||
/* TODO: sanity check that only postcopiable data will be loaded here */
|
||||
load_res = qemu_loadvm_state_main(f, mis);
|
||||
load_res = qemu_loadvm_state_main(f, mis, &local_err);
|
||||
|
||||
/*
|
||||
* This is tricky, but, mis->from_src_file can change after it
|
||||
|
|
@ -2124,7 +2143,10 @@ static void *postcopy_ram_listen_thread(void *opaque)
|
|||
__func__, load_res);
|
||||
load_res = 0; /* prevent further exit() */
|
||||
} else {
|
||||
error_report("%s: loadvm failed: %d", __func__, load_res);
|
||||
error_prepend(&local_err,
|
||||
"loadvm failed during postcopy: %d: ", load_res);
|
||||
migrate_set_error(migr, local_err);
|
||||
error_report_err(local_err);
|
||||
migrate_set_state(&mis->state, MIGRATION_STATUS_POSTCOPY_ACTIVE,
|
||||
MIGRATION_STATUS_FAILED);
|
||||
}
|
||||
|
|
@ -2172,15 +2194,16 @@ static void *postcopy_ram_listen_thread(void *opaque)
|
|||
}
|
||||
|
||||
/* After this message we must be able to immediately receive postcopy data */
|
||||
static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis)
|
||||
static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis,
|
||||
Error **errp)
|
||||
{
|
||||
PostcopyState ps = postcopy_state_set(POSTCOPY_INCOMING_LISTENING);
|
||||
Error *local_err = NULL;
|
||||
|
||||
trace_loadvm_postcopy_handle_listen("enter");
|
||||
|
||||
if (ps != POSTCOPY_INCOMING_ADVISE && ps != POSTCOPY_INCOMING_DISCARD) {
|
||||
error_report("CMD_POSTCOPY_LISTEN in wrong postcopy state (%d)", ps);
|
||||
error_setg(errp,
|
||||
"CMD_POSTCOPY_LISTEN in wrong postcopy state (%d)", ps);
|
||||
return -1;
|
||||
}
|
||||
if (ps == POSTCOPY_INCOMING_ADVISE) {
|
||||
|
|
@ -2203,14 +2226,14 @@ static int loadvm_postcopy_handle_listen(MigrationIncomingState *mis)
|
|||
if (migrate_postcopy_ram()) {
|
||||
if (postcopy_ram_incoming_setup(mis)) {
|
||||
postcopy_ram_incoming_cleanup(mis);
|
||||
error_setg(errp, "Failed to setup incoming postcopy RAM blocks");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
trace_loadvm_postcopy_handle_listen("after uffd");
|
||||
|
||||
if (postcopy_notify(POSTCOPY_NOTIFY_INBOUND_LISTEN, &local_err)) {
|
||||
error_report_err(local_err);
|
||||
if (postcopy_notify(POSTCOPY_NOTIFY_INBOUND_LISTEN, errp)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -2263,13 +2286,13 @@ static void loadvm_postcopy_handle_run_bh(void *opaque)
|
|||
}
|
||||
|
||||
/* After all discards we can start running and asking for pages */
|
||||
static int loadvm_postcopy_handle_run(MigrationIncomingState *mis)
|
||||
static int loadvm_postcopy_handle_run(MigrationIncomingState *mis, Error **errp)
|
||||
{
|
||||
PostcopyState ps = postcopy_state_get();
|
||||
|
||||
trace_loadvm_postcopy_handle_run();
|
||||
if (ps != POSTCOPY_INCOMING_LISTENING) {
|
||||
error_report("CMD_POSTCOPY_RUN in wrong postcopy state (%d)", ps);
|
||||
error_setg(errp, "CMD_POSTCOPY_RUN in wrong postcopy state (%d)", ps);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -2327,12 +2350,12 @@ static void migrate_send_rp_req_pages_pending(MigrationIncomingState *mis)
|
|||
}
|
||||
}
|
||||
|
||||
static int loadvm_postcopy_handle_resume(MigrationIncomingState *mis)
|
||||
static void loadvm_postcopy_handle_resume(MigrationIncomingState *mis)
|
||||
{
|
||||
if (mis->state != MIGRATION_STATUS_POSTCOPY_RECOVER) {
|
||||
error_report("%s: illegal resume received", __func__);
|
||||
warn_report("%s: illegal resume received", __func__);
|
||||
/* Don't fail the load, only for this. */
|
||||
return 0;
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -2384,8 +2407,6 @@ static int loadvm_postcopy_handle_resume(MigrationIncomingState *mis)
|
|||
/* Kick the fast ram load thread too */
|
||||
qemu_sem_post(&mis->postcopy_pause_sem_fast_load);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2398,7 +2419,7 @@ static int loadvm_postcopy_handle_resume(MigrationIncomingState *mis)
|
|||
* Returns: Negative values on error
|
||||
*
|
||||
*/
|
||||
static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis)
|
||||
static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis, Error **errp)
|
||||
{
|
||||
int ret;
|
||||
size_t length;
|
||||
|
|
@ -2408,7 +2429,7 @@ static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis)
|
|||
trace_loadvm_handle_cmd_packaged(length);
|
||||
|
||||
if (length > MAX_VM_CMD_PACKAGED_SIZE) {
|
||||
error_report("Unreasonably large packaged state: %zu", length);
|
||||
error_setg(errp, "Unreasonably large packaged state: %zu", length);
|
||||
return -1;
|
||||
}
|
||||
|
||||
|
|
@ -2419,8 +2440,8 @@ static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis)
|
|||
length);
|
||||
if (ret != length) {
|
||||
object_unref(OBJECT(bioc));
|
||||
error_report("CMD_PACKAGED: Buffer receive fail ret=%d length=%zu",
|
||||
ret, length);
|
||||
error_setg(errp, "CMD_PACKAGED: Buffer receive fail ret=%d length=%zu",
|
||||
ret, length);
|
||||
return (ret < 0) ? ret : -EAGAIN;
|
||||
}
|
||||
bioc->usage += length;
|
||||
|
|
@ -2449,7 +2470,7 @@ static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis)
|
|||
qemu_coroutine_yield();
|
||||
} while (1);
|
||||
|
||||
ret = qemu_loadvm_state_main(packf, mis);
|
||||
ret = qemu_loadvm_state_main(packf, mis, errp);
|
||||
trace_loadvm_handle_cmd_packaged_main(ret);
|
||||
qemu_fclose(packf);
|
||||
object_unref(OBJECT(bioc));
|
||||
|
|
@ -2464,32 +2485,35 @@ static int loadvm_handle_cmd_packaged(MigrationIncomingState *mis)
|
|||
* len (1 byte) + ramblock_name (<255 bytes)
|
||||
*/
|
||||
static int loadvm_handle_recv_bitmap(MigrationIncomingState *mis,
|
||||
uint16_t len)
|
||||
uint16_t len, Error **errp)
|
||||
{
|
||||
QEMUFile *file = mis->from_src_file;
|
||||
RAMBlock *rb;
|
||||
char block_name[256];
|
||||
size_t cnt;
|
||||
int ret;
|
||||
|
||||
cnt = qemu_get_counted_string(file, block_name);
|
||||
if (!cnt) {
|
||||
error_report("%s: failed to read block name", __func__);
|
||||
error_setg(errp, "failed to read block name");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Validate before using the data */
|
||||
if (qemu_file_get_error(file)) {
|
||||
return qemu_file_get_error(file);
|
||||
ret = qemu_file_get_error(file);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "loadvm failed: stream error: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (len != cnt + 1) {
|
||||
error_report("%s: invalid payload length (%d)", __func__, len);
|
||||
error_setg(errp, "invalid payload length (%d)", len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
rb = qemu_ram_block_by_name(block_name);
|
||||
if (!rb) {
|
||||
error_report("%s: block '%s' not found", __func__, block_name);
|
||||
error_setg(errp, "block '%s' not found", block_name);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
|
@ -2500,20 +2524,26 @@ static int loadvm_handle_recv_bitmap(MigrationIncomingState *mis,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int loadvm_process_enable_colo(MigrationIncomingState *mis)
|
||||
static int loadvm_process_enable_colo(MigrationIncomingState *mis,
|
||||
Error **errp)
|
||||
{
|
||||
int ret = migration_incoming_enable_colo();
|
||||
ERRP_GUARD();
|
||||
int ret;
|
||||
|
||||
if (!ret) {
|
||||
ret = colo_init_ram_cache();
|
||||
if (ret) {
|
||||
migration_incoming_disable_colo();
|
||||
}
|
||||
ret = migration_incoming_enable_colo(errp);
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = colo_init_ram_cache(errp);
|
||||
if (ret) {
|
||||
error_prepend(errp, "failed to init colo RAM cache: %d: ", ret);
|
||||
migration_incoming_disable_colo();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int loadvm_postcopy_handle_switchover_start(void)
|
||||
static int loadvm_postcopy_handle_switchover_start(Error **errp)
|
||||
{
|
||||
SaveStateEntry *se;
|
||||
|
||||
|
|
@ -2526,6 +2556,7 @@ static int loadvm_postcopy_handle_switchover_start(void)
|
|||
|
||||
ret = se->ops->switchover_start(se->opaque);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Switchover start failed: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -2539,32 +2570,37 @@ static int loadvm_postcopy_handle_switchover_start(void)
|
|||
* LOADVM_QUIT All good, but exit the loop
|
||||
* <0 Error
|
||||
*/
|
||||
static int loadvm_process_command(QEMUFile *f)
|
||||
static int loadvm_process_command(QEMUFile *f, Error **errp)
|
||||
{
|
||||
MigrationIncomingState *mis = migration_incoming_get_current();
|
||||
uint16_t cmd;
|
||||
uint16_t len;
|
||||
uint32_t tmp32;
|
||||
int ret;
|
||||
|
||||
cmd = qemu_get_be16(f);
|
||||
len = qemu_get_be16(f);
|
||||
|
||||
/* Check validity before continue processing of cmds */
|
||||
if (qemu_file_get_error(f)) {
|
||||
return qemu_file_get_error(f);
|
||||
ret = qemu_file_get_error(f);
|
||||
if (ret) {
|
||||
error_setg(errp,
|
||||
"Failed to load VM process command: stream error: %d",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (cmd >= MIG_CMD_MAX || cmd == MIG_CMD_INVALID) {
|
||||
error_report("MIG_CMD 0x%x unknown (len 0x%x)", cmd, len);
|
||||
error_setg(errp, "MIG_CMD 0x%x unknown (len 0x%x)", cmd, len);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
trace_loadvm_process_command(mig_cmd_args[cmd].name, len);
|
||||
|
||||
if (mig_cmd_args[cmd].len != -1 && mig_cmd_args[cmd].len != len) {
|
||||
error_report("%s received with bad length - expecting %zu, got %d",
|
||||
mig_cmd_args[cmd].name,
|
||||
(size_t)mig_cmd_args[cmd].len, len);
|
||||
error_setg(errp, "%s received with bad length - expecting %zu, got %d",
|
||||
mig_cmd_args[cmd].name,
|
||||
(size_t)mig_cmd_args[cmd].len, len);
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
|
|
@ -2576,10 +2612,6 @@ static int loadvm_process_command(QEMUFile *f)
|
|||
return 0;
|
||||
}
|
||||
mis->to_src_file = qemu_file_get_return_path(f);
|
||||
if (!mis->to_src_file) {
|
||||
error_report("CMD_OPEN_RETURN_PATH failed");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Switchover ack is enabled but no device uses it, so send an ACK to
|
||||
|
|
@ -2587,11 +2619,10 @@ static int loadvm_process_command(QEMUFile *f)
|
|||
* been created.
|
||||
*/
|
||||
if (migrate_switchover_ack() && !mis->switchover_ack_pending_num) {
|
||||
int ret = migrate_send_rp_switchover_ack(mis);
|
||||
ret = migrate_send_rp_switchover_ack(mis);
|
||||
if (ret) {
|
||||
error_report(
|
||||
"Could not send switchover ack RP MSG, err %d (%s)", ret,
|
||||
strerror(-ret));
|
||||
error_setg_errno(errp, -ret,
|
||||
"Could not send switchover ack RP MSG");
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -2601,39 +2632,40 @@ static int loadvm_process_command(QEMUFile *f)
|
|||
tmp32 = qemu_get_be32(f);
|
||||
trace_loadvm_process_command_ping(tmp32);
|
||||
if (!mis->to_src_file) {
|
||||
error_report("CMD_PING (0x%x) received with no return path",
|
||||
tmp32);
|
||||
error_setg(errp, "CMD_PING (0x%x) received with no return path",
|
||||
tmp32);
|
||||
return -1;
|
||||
}
|
||||
migrate_send_rp_pong(mis, tmp32);
|
||||
break;
|
||||
|
||||
case MIG_CMD_PACKAGED:
|
||||
return loadvm_handle_cmd_packaged(mis);
|
||||
return loadvm_handle_cmd_packaged(mis, errp);
|
||||
|
||||
case MIG_CMD_POSTCOPY_ADVISE:
|
||||
return loadvm_postcopy_handle_advise(mis, len);
|
||||
return loadvm_postcopy_handle_advise(mis, len, errp);
|
||||
|
||||
case MIG_CMD_POSTCOPY_LISTEN:
|
||||
return loadvm_postcopy_handle_listen(mis);
|
||||
return loadvm_postcopy_handle_listen(mis, errp);
|
||||
|
||||
case MIG_CMD_POSTCOPY_RUN:
|
||||
return loadvm_postcopy_handle_run(mis);
|
||||
return loadvm_postcopy_handle_run(mis, errp);
|
||||
|
||||
case MIG_CMD_POSTCOPY_RAM_DISCARD:
|
||||
return loadvm_postcopy_ram_handle_discard(mis, len);
|
||||
return loadvm_postcopy_ram_handle_discard(mis, len, errp);
|
||||
|
||||
case MIG_CMD_POSTCOPY_RESUME:
|
||||
return loadvm_postcopy_handle_resume(mis);
|
||||
loadvm_postcopy_handle_resume(mis);
|
||||
return 0;
|
||||
|
||||
case MIG_CMD_RECV_BITMAP:
|
||||
return loadvm_handle_recv_bitmap(mis, len);
|
||||
return loadvm_handle_recv_bitmap(mis, len, errp);
|
||||
|
||||
case MIG_CMD_ENABLE_COLO:
|
||||
return loadvm_process_enable_colo(mis);
|
||||
return loadvm_process_enable_colo(mis, errp);
|
||||
|
||||
case MIG_CMD_SWITCHOVER_START:
|
||||
return loadvm_postcopy_handle_switchover_start();
|
||||
return loadvm_postcopy_handle_switchover_start(errp);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
@ -2683,8 +2715,9 @@ static bool check_section_footer(QEMUFile *f, SaveStateEntry *se)
|
|||
}
|
||||
|
||||
static int
|
||||
qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
|
||||
qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
bool trace_downtime = (type == QEMU_VM_SECTION_FULL);
|
||||
uint32_t instance_id, version_id, section_id;
|
||||
int64_t start_ts, end_ts;
|
||||
|
|
@ -2695,8 +2728,8 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
|
|||
/* Read section start */
|
||||
section_id = qemu_get_be32(f);
|
||||
if (!qemu_get_counted_string(f, idstr)) {
|
||||
error_report("Unable to read ID string for section %u",
|
||||
section_id);
|
||||
error_setg(errp, "Unable to read ID string for section %u",
|
||||
section_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
instance_id = qemu_get_be32(f);
|
||||
|
|
@ -2704,8 +2737,7 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
|
|||
|
||||
ret = qemu_file_get_error(f);
|
||||
if (ret) {
|
||||
error_report("%s: Failed to read instance/version ID: %d",
|
||||
__func__, ret);
|
||||
error_setg(errp, "Failed to read instance/version ID: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -2714,17 +2746,17 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
|
|||
/* Find savevm section */
|
||||
se = find_se(idstr, instance_id);
|
||||
if (se == NULL) {
|
||||
error_report("Unknown savevm section or instance '%s' %"PRIu32". "
|
||||
"Make sure that your current VM setup matches your "
|
||||
"saved VM setup, including any hotplugged devices",
|
||||
idstr, instance_id);
|
||||
error_setg(errp, "Unknown section or instance '%s' %"PRIu32". "
|
||||
"Make sure that your current VM setup matches your "
|
||||
"saved VM setup, including any hotplugged devices",
|
||||
idstr, instance_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Validate version */
|
||||
if (version_id > se->version_id) {
|
||||
error_report("savevm: unsupported version %d for '%s' v%d",
|
||||
version_id, idstr, se->version_id);
|
||||
error_setg(errp, "unsupported version %d for '%s' v%d",
|
||||
version_id, idstr, se->version_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
se->load_version_id = version_id;
|
||||
|
|
@ -2732,7 +2764,7 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
|
|||
|
||||
/* Validate if it is a device's state */
|
||||
if (xen_enabled() && se->is_ram) {
|
||||
error_report("loadvm: %s RAM loading not allowed on Xen", idstr);
|
||||
error_setg(errp, "loadvm: %s RAM loading not allowed on Xen", idstr);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
|
@ -2740,10 +2772,11 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
|
|||
start_ts = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
|
||||
}
|
||||
|
||||
ret = vmstate_load(f, se);
|
||||
ret = vmstate_load(f, se, errp);
|
||||
if (ret < 0) {
|
||||
error_report("error while loading state for instance 0x%"PRIx32" of"
|
||||
" device '%s'", instance_id, idstr);
|
||||
error_prepend(errp,
|
||||
"error while loading state for instance 0x%"PRIx32" of"
|
||||
" device '%s': ", instance_id, idstr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -2754,6 +2787,8 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
|
|||
}
|
||||
|
||||
if (!check_section_footer(f, se)) {
|
||||
error_setg(errp, "Section footer error, section_id: %d",
|
||||
section_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
|
@ -2761,7 +2796,7 @@ qemu_loadvm_section_start_full(QEMUFile *f, uint8_t type)
|
|||
}
|
||||
|
||||
static int
|
||||
qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type)
|
||||
qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type, Error **errp)
|
||||
{
|
||||
bool trace_downtime = (type == QEMU_VM_SECTION_END);
|
||||
int64_t start_ts, end_ts;
|
||||
|
|
@ -2773,8 +2808,7 @@ qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type)
|
|||
|
||||
ret = qemu_file_get_error(f);
|
||||
if (ret) {
|
||||
error_report("%s: Failed to read section ID: %d",
|
||||
__func__, ret);
|
||||
error_setg(errp, "Failed to read section ID: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -2785,7 +2819,7 @@ qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type)
|
|||
}
|
||||
}
|
||||
if (se == NULL) {
|
||||
error_report("Unknown savevm section %d", section_id);
|
||||
error_setg(errp, "Unknown section %d", section_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
|
@ -2793,10 +2827,8 @@ qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type)
|
|||
start_ts = qemu_clock_get_us(QEMU_CLOCK_REALTIME);
|
||||
}
|
||||
|
||||
ret = vmstate_load(f, se);
|
||||
ret = vmstate_load(f, se, errp);
|
||||
if (ret < 0) {
|
||||
error_report("error while loading state section id %d(%s)",
|
||||
section_id, se->idstr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -2807,40 +2839,50 @@ qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type)
|
|||
}
|
||||
|
||||
if (!check_section_footer(f, se)) {
|
||||
error_setg(errp, "Section footer error, section_id: %d",
|
||||
section_id);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int qemu_loadvm_state_header(QEMUFile *f)
|
||||
static int qemu_loadvm_state_header(QEMUFile *f, Error **errp)
|
||||
{
|
||||
unsigned int v;
|
||||
int ret;
|
||||
|
||||
v = qemu_get_be32(f);
|
||||
if (v != QEMU_VM_FILE_MAGIC) {
|
||||
error_report("Not a migration stream");
|
||||
error_setg(errp, "Not a migration stream, magic: %x != %x",
|
||||
v, QEMU_VM_FILE_MAGIC);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
v = qemu_get_be32(f);
|
||||
if (v == QEMU_VM_FILE_VERSION_COMPAT) {
|
||||
error_report("SaveVM v2 format is obsolete and don't work anymore");
|
||||
error_setg(errp,
|
||||
"SaveVM v2 format is obsolete and no longer supported");
|
||||
|
||||
return -ENOTSUP;
|
||||
}
|
||||
if (v != QEMU_VM_FILE_VERSION) {
|
||||
error_report("Unsupported migration stream version");
|
||||
error_setg(errp, "Unsupported migration stream version, "
|
||||
"file version %x != %x",
|
||||
v, QEMU_VM_FILE_VERSION);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
if (migrate_get_current()->send_configuration) {
|
||||
if (qemu_get_byte(f) != QEMU_VM_CONFIGURATION) {
|
||||
error_report("Configuration section missing");
|
||||
v = qemu_get_byte(f);
|
||||
if (v != QEMU_VM_CONFIGURATION) {
|
||||
error_setg(errp, "Configuration section missing, %x != %x",
|
||||
v, QEMU_VM_CONFIGURATION);
|
||||
return -EINVAL;
|
||||
}
|
||||
ret = vmstate_load_state(f, &vmstate_configuration, &savevm_state, 0);
|
||||
|
||||
ret = vmstate_load_state(f, &vmstate_configuration, &savevm_state, 0,
|
||||
errp);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -3028,8 +3070,10 @@ static bool postcopy_pause_incoming(MigrationIncomingState *mis)
|
|||
return true;
|
||||
}
|
||||
|
||||
int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis)
|
||||
int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
|
||||
Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
uint8_t section_type;
|
||||
int ret = 0;
|
||||
|
||||
|
|
@ -3037,8 +3081,11 @@ retry:
|
|||
while (true) {
|
||||
section_type = qemu_get_byte(f);
|
||||
|
||||
ret = qemu_file_get_error_obj_any(f, mis->postcopy_qemufile_dst, NULL);
|
||||
ret = qemu_file_get_error_obj_any(f, mis->postcopy_qemufile_dst, errp);
|
||||
if (ret) {
|
||||
error_prepend(errp,
|
||||
"Failed to load section ID: stream error: %d: ",
|
||||
ret);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -3046,20 +3093,20 @@ retry:
|
|||
switch (section_type) {
|
||||
case QEMU_VM_SECTION_START:
|
||||
case QEMU_VM_SECTION_FULL:
|
||||
ret = qemu_loadvm_section_start_full(f, section_type);
|
||||
ret = qemu_loadvm_section_start_full(f, section_type, errp);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
case QEMU_VM_SECTION_PART:
|
||||
case QEMU_VM_SECTION_END:
|
||||
ret = qemu_loadvm_section_part_end(f, section_type);
|
||||
ret = qemu_loadvm_section_part_end(f, section_type, errp);
|
||||
if (ret < 0) {
|
||||
goto out;
|
||||
}
|
||||
break;
|
||||
case QEMU_VM_COMMAND:
|
||||
ret = loadvm_process_command(f);
|
||||
ret = loadvm_process_command(f, errp);
|
||||
trace_qemu_loadvm_state_section_command(ret);
|
||||
if ((ret < 0) || (ret == LOADVM_QUIT)) {
|
||||
goto out;
|
||||
|
|
@ -3069,7 +3116,7 @@ retry:
|
|||
/* This is the end of migration */
|
||||
goto out;
|
||||
default:
|
||||
error_report("Unknown savevm section type %d", section_type);
|
||||
error_setg(errp, "Unknown section type %d", section_type);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -3097,33 +3144,31 @@ out:
|
|||
migrate_postcopy_ram() && postcopy_pause_incoming(mis)) {
|
||||
/* Reset f to point to the newly created channel */
|
||||
f = mis->from_src_file;
|
||||
error_free_or_abort(errp);
|
||||
goto retry;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int qemu_loadvm_state(QEMUFile *f)
|
||||
int qemu_loadvm_state(QEMUFile *f, Error **errp)
|
||||
{
|
||||
MigrationState *s = migrate_get_current();
|
||||
MigrationIncomingState *mis = migration_incoming_get_current();
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
|
||||
if (qemu_savevm_state_blocked(&local_err)) {
|
||||
error_report_err(local_err);
|
||||
if (qemu_savevm_state_blocked(errp)) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
qemu_loadvm_thread_pool_create(mis);
|
||||
|
||||
ret = qemu_loadvm_state_header(f);
|
||||
ret = qemu_loadvm_state_header(f, errp);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (qemu_loadvm_state_setup(f, &local_err) != 0) {
|
||||
error_report_err(local_err);
|
||||
if (qemu_loadvm_state_setup(f, errp) != 0) {
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
|
@ -3133,7 +3178,7 @@ int qemu_loadvm_state(QEMUFile *f)
|
|||
|
||||
cpu_synchronize_all_pre_loadvm();
|
||||
|
||||
ret = qemu_loadvm_state_main(f, mis);
|
||||
ret = qemu_loadvm_state_main(f, mis, errp);
|
||||
qemu_event_set(&mis->main_thread_load_event);
|
||||
|
||||
trace_qemu_loadvm_state_post_main(ret);
|
||||
|
|
@ -3151,8 +3196,15 @@ int qemu_loadvm_state(QEMUFile *f)
|
|||
if (migrate_has_error(migrate_get_current()) ||
|
||||
!qemu_loadvm_thread_pool_wait(s, mis)) {
|
||||
ret = -EINVAL;
|
||||
error_setg(errp,
|
||||
"Error while loading vmstate");
|
||||
} else {
|
||||
ret = qemu_file_get_error(f);
|
||||
if (ret < 0) {
|
||||
error_setg(errp,
|
||||
"Error while loading vmstate: stream error: %d",
|
||||
ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
/*
|
||||
|
|
@ -3201,15 +3253,14 @@ int qemu_loadvm_state(QEMUFile *f)
|
|||
return ret;
|
||||
}
|
||||
|
||||
int qemu_load_device_state(QEMUFile *f)
|
||||
int qemu_load_device_state(QEMUFile *f, Error **errp)
|
||||
{
|
||||
MigrationIncomingState *mis = migration_incoming_get_current();
|
||||
int ret;
|
||||
|
||||
/* Load QEMU_VM_SECTION_FULL section */
|
||||
ret = qemu_loadvm_state_main(f, mis);
|
||||
ret = qemu_loadvm_state_main(f, mis, errp);
|
||||
if (ret < 0) {
|
||||
error_report("Failed to load device state: %d", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -3417,6 +3468,7 @@ void qmp_xen_save_devices_state(const char *filename, bool has_live, bool live,
|
|||
|
||||
void qmp_xen_load_devices_state(const char *filename, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
QEMUFile *f;
|
||||
QIOChannelFile *ioc;
|
||||
int ret;
|
||||
|
|
@ -3438,10 +3490,10 @@ void qmp_xen_load_devices_state(const char *filename, Error **errp)
|
|||
f = qemu_file_new_input(QIO_CHANNEL(ioc));
|
||||
object_unref(OBJECT(ioc));
|
||||
|
||||
ret = qemu_loadvm_state(f);
|
||||
ret = qemu_loadvm_state(f, errp);
|
||||
qemu_fclose(f);
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "loading Xen device state failed");
|
||||
error_prepend(errp, "loading Xen device state failed: ");
|
||||
}
|
||||
migration_incoming_state_destroy();
|
||||
}
|
||||
|
|
@ -3512,13 +3564,12 @@ bool load_snapshot(const char *name, const char *vmstate,
|
|||
ret = -EINVAL;
|
||||
goto err_drain;
|
||||
}
|
||||
ret = qemu_loadvm_state(f);
|
||||
ret = qemu_loadvm_state(f, errp);
|
||||
migration_incoming_state_destroy();
|
||||
|
||||
bdrv_drain_all_end();
|
||||
|
||||
if (ret < 0) {
|
||||
error_setg(errp, "Error %d while loading VM state", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,10 +64,11 @@ void qemu_savevm_send_colo_enable(QEMUFile *f);
|
|||
void qemu_savevm_live_state(QEMUFile *f);
|
||||
int qemu_save_device_state(QEMUFile *f);
|
||||
|
||||
int qemu_loadvm_state(QEMUFile *f);
|
||||
int qemu_loadvm_state(QEMUFile *f, Error **errp);
|
||||
void qemu_loadvm_state_cleanup(MigrationIncomingState *mis);
|
||||
int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis);
|
||||
int qemu_load_device_state(QEMUFile *f);
|
||||
int qemu_loadvm_state_main(QEMUFile *f, MigrationIncomingState *mis,
|
||||
Error **errp);
|
||||
int qemu_load_device_state(QEMUFile *f, Error **errp);
|
||||
int qemu_loadvm_approve_switchover(void);
|
||||
int qemu_savevm_state_complete_precopy_non_iterable(QEMUFile *f,
|
||||
bool in_postcopy);
|
||||
|
|
|
|||
|
|
@ -354,6 +354,7 @@ cpr_state_save(const char *mode) "%s mode"
|
|||
cpr_state_load(const char *mode) "%s mode"
|
||||
cpr_transfer_input(const char *path) "%s"
|
||||
cpr_transfer_output(const char *path) "%s"
|
||||
cpr_exec(void) ""
|
||||
|
||||
# block-dirty-bitmap.c
|
||||
send_bitmap_header_enter(void) ""
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include "qemu/error-report.h"
|
||||
#include "qemu/queue.h"
|
||||
#include "trace.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
/* bool */
|
||||
|
||||
|
|
@ -321,6 +322,10 @@ static int get_fd(QEMUFile *f, void *pv, size_t size,
|
|||
const VMStateField *field)
|
||||
{
|
||||
int32_t *v = pv;
|
||||
if (migrate_mode() == MIG_MODE_CPR_EXEC) {
|
||||
qemu_get_sbe32s(f, v);
|
||||
return 0;
|
||||
}
|
||||
*v = qemu_file_get_fd(f);
|
||||
return 0;
|
||||
}
|
||||
|
|
@ -329,6 +334,10 @@ static int put_fd(QEMUFile *f, void *pv, size_t size,
|
|||
const VMStateField *field, JSONWriter *vmdesc)
|
||||
{
|
||||
int32_t *v = pv;
|
||||
if (migrate_mode() == MIG_MODE_CPR_EXEC) {
|
||||
qemu_put_sbe32s(f, v);
|
||||
return 0;
|
||||
}
|
||||
return qemu_file_put_fd(f, *v);
|
||||
}
|
||||
|
||||
|
|
@ -543,13 +552,17 @@ static int get_tmp(QEMUFile *f, void *pv, size_t size,
|
|||
const VMStateField *field)
|
||||
{
|
||||
int ret;
|
||||
Error *local_err = NULL;
|
||||
const VMStateDescription *vmsd = field->vmsd;
|
||||
int version_id = field->version_id;
|
||||
void *tmp = g_malloc(size);
|
||||
|
||||
/* Writes the parent field which is at the start of the tmp */
|
||||
*(void **)tmp = pv;
|
||||
ret = vmstate_load_state(f, vmsd, tmp, version_id);
|
||||
ret = vmstate_load_state(f, vmsd, tmp, version_id, &local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
g_free(tmp);
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -560,10 +573,14 @@ static int put_tmp(QEMUFile *f, void *pv, size_t size,
|
|||
const VMStateDescription *vmsd = field->vmsd;
|
||||
void *tmp = g_malloc(size);
|
||||
int ret;
|
||||
Error *local_err = NULL;
|
||||
|
||||
/* Writes the parent field which is at the start of the tmp */
|
||||
*(void **)tmp = pv;
|
||||
ret = vmstate_save_state(f, vmsd, tmp, vmdesc);
|
||||
ret = vmstate_save_state(f, vmsd, tmp, vmdesc, &local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
g_free(tmp);
|
||||
|
||||
return ret;
|
||||
|
|
@ -626,6 +643,7 @@ static int get_qtailq(QEMUFile *f, void *pv, size_t unused_size,
|
|||
const VMStateField *field)
|
||||
{
|
||||
int ret = 0;
|
||||
Error *local_err = NULL;
|
||||
const VMStateDescription *vmsd = field->vmsd;
|
||||
/* size of a QTAILQ element */
|
||||
size_t size = field->size;
|
||||
|
|
@ -649,8 +667,9 @@ static int get_qtailq(QEMUFile *f, void *pv, size_t unused_size,
|
|||
|
||||
while (qemu_get_byte(f)) {
|
||||
elm = g_malloc(size);
|
||||
ret = vmstate_load_state(f, vmsd, elm, version_id);
|
||||
ret = vmstate_load_state(f, vmsd, elm, version_id, &local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
return ret;
|
||||
}
|
||||
QTAILQ_RAW_INSERT_TAIL(pv, elm, entry_offset);
|
||||
|
|
@ -669,13 +688,15 @@ static int put_qtailq(QEMUFile *f, void *pv, size_t unused_size,
|
|||
size_t entry_offset = field->start;
|
||||
void *elm;
|
||||
int ret;
|
||||
Error *local_err = NULL;
|
||||
|
||||
trace_put_qtailq(vmsd->name, vmsd->version_id);
|
||||
|
||||
QTAILQ_RAW_FOREACH(elm, pv, entry_offset) {
|
||||
qemu_put_byte(f, true);
|
||||
ret = vmstate_save_state(f, vmsd, elm, vmdesc);
|
||||
ret = vmstate_save_state(f, vmsd, elm, vmdesc, &local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -704,6 +725,7 @@ static gboolean put_gtree_elem(gpointer key, gpointer value, gpointer data)
|
|||
struct put_gtree_data *capsule = (struct put_gtree_data *)data;
|
||||
QEMUFile *f = capsule->f;
|
||||
int ret;
|
||||
Error *local_err = NULL;
|
||||
|
||||
qemu_put_byte(f, true);
|
||||
|
||||
|
|
@ -711,16 +733,20 @@ static gboolean put_gtree_elem(gpointer key, gpointer value, gpointer data)
|
|||
if (!capsule->key_vmsd) {
|
||||
qemu_put_be64(f, (uint64_t)(uintptr_t)(key)); /* direct key */
|
||||
} else {
|
||||
ret = vmstate_save_state(f, capsule->key_vmsd, key, capsule->vmdesc);
|
||||
ret = vmstate_save_state(f, capsule->key_vmsd, key, capsule->vmdesc,
|
||||
&local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
capsule->ret = ret;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/* put the data */
|
||||
ret = vmstate_save_state(f, capsule->val_vmsd, value, capsule->vmdesc);
|
||||
ret = vmstate_save_state(f, capsule->val_vmsd, value, capsule->vmdesc,
|
||||
&local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
capsule->ret = ret;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -772,6 +798,7 @@ static int get_gtree(QEMUFile *f, void *pv, size_t unused_size,
|
|||
GTree *tree = *pval;
|
||||
void *key, *val;
|
||||
int ret = 0;
|
||||
Error *local_err = NULL;
|
||||
|
||||
/* in case of direct key, the key vmsd can be {}, ie. check fields */
|
||||
if (!direct_key && version_id > key_vmsd->version_id) {
|
||||
|
|
@ -803,18 +830,16 @@ static int get_gtree(QEMUFile *f, void *pv, size_t unused_size,
|
|||
key = (void *)(uintptr_t)qemu_get_be64(f);
|
||||
} else {
|
||||
key = g_malloc0(key_size);
|
||||
ret = vmstate_load_state(f, key_vmsd, key, version_id);
|
||||
ret = vmstate_load_state(f, key_vmsd, key, version_id, &local_err);
|
||||
if (ret) {
|
||||
error_report("%s : failed to load %s (%d)",
|
||||
field->name, key_vmsd->name, ret);
|
||||
error_report_err(local_err);
|
||||
goto key_error;
|
||||
}
|
||||
}
|
||||
val = g_malloc0(val_size);
|
||||
ret = vmstate_load_state(f, val_vmsd, val, version_id);
|
||||
ret = vmstate_load_state(f, val_vmsd, val, version_id, &local_err);
|
||||
if (ret) {
|
||||
error_report("%s : failed to load %s (%d)",
|
||||
field->name, val_vmsd->name, ret);
|
||||
error_report_err(local_err);
|
||||
goto val_error;
|
||||
}
|
||||
g_tree_insert(tree, key, val);
|
||||
|
|
@ -851,14 +876,14 @@ static int put_qlist(QEMUFile *f, void *pv, size_t unused_size,
|
|||
size_t entry_offset = field->start;
|
||||
void *elm;
|
||||
int ret;
|
||||
Error *local_err = NULL;
|
||||
|
||||
trace_put_qlist(field->name, vmsd->name, vmsd->version_id);
|
||||
QLIST_RAW_FOREACH(elm, pv, entry_offset) {
|
||||
qemu_put_byte(f, true);
|
||||
ret = vmstate_save_state(f, vmsd, elm, vmdesc);
|
||||
ret = vmstate_save_state(f, vmsd, elm, vmdesc, &local_err);
|
||||
if (ret) {
|
||||
error_report("%s: failed to save %s (%d)", field->name,
|
||||
vmsd->name, ret);
|
||||
error_report_err(local_err);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -872,6 +897,7 @@ static int get_qlist(QEMUFile *f, void *pv, size_t unused_size,
|
|||
const VMStateField *field)
|
||||
{
|
||||
int ret = 0;
|
||||
Error *local_err = NULL;
|
||||
const VMStateDescription *vmsd = field->vmsd;
|
||||
/* size of a QLIST element */
|
||||
size_t size = field->size;
|
||||
|
|
@ -892,10 +918,9 @@ static int get_qlist(QEMUFile *f, void *pv, size_t unused_size,
|
|||
|
||||
while (qemu_get_byte(f)) {
|
||||
elm = g_malloc(size);
|
||||
ret = vmstate_load_state(f, vmsd, elm, version_id);
|
||||
ret = vmstate_load_state(f, vmsd, elm, version_id, &local_err);
|
||||
if (ret) {
|
||||
error_report("%s: failed to load %s (%d)", field->name,
|
||||
vmsd->name, ret);
|
||||
error_report_err(local_err);
|
||||
g_free(elm);
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ static int vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
|
|||
void *opaque, JSONWriter *vmdesc,
|
||||
Error **errp);
|
||||
static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
|
||||
void *opaque);
|
||||
void *opaque, Error **errp);
|
||||
|
||||
/* Whether this field should exist for either save or load the VM? */
|
||||
static bool
|
||||
|
|
@ -132,29 +132,43 @@ static void vmstate_handle_alloc(void *ptr, const VMStateField *field,
|
|||
}
|
||||
|
||||
int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
|
||||
void *opaque, int version_id)
|
||||
void *opaque, int version_id, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
const VMStateField *field = vmsd->fields;
|
||||
int ret = 0;
|
||||
|
||||
trace_vmstate_load_state(vmsd->name, version_id);
|
||||
if (version_id > vmsd->version_id) {
|
||||
error_report("%s: incoming version_id %d is too new "
|
||||
"for local version_id %d",
|
||||
vmsd->name, version_id, vmsd->version_id);
|
||||
error_setg(errp, "%s: incoming version_id %d is too new "
|
||||
"for local version_id %d",
|
||||
vmsd->name, version_id, vmsd->version_id);
|
||||
trace_vmstate_load_state_end(vmsd->name, "too new", -EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (version_id < vmsd->minimum_version_id) {
|
||||
error_report("%s: incoming version_id %d is too old "
|
||||
"for local minimum version_id %d",
|
||||
vmsd->name, version_id, vmsd->minimum_version_id);
|
||||
error_setg(errp, "%s: incoming version_id %d is too old "
|
||||
"for local minimum version_id %d",
|
||||
vmsd->name, version_id, vmsd->minimum_version_id);
|
||||
trace_vmstate_load_state_end(vmsd->name, "too old", -EINVAL);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (vmsd->pre_load) {
|
||||
if (vmsd->pre_load_errp) {
|
||||
ret = vmsd->pre_load_errp(opaque, errp);
|
||||
if (ret < 0) {
|
||||
error_prepend(errp, "pre load hook failed for: '%s', "
|
||||
"version_id: %d, minimum version_id: %d, "
|
||||
"ret: %d: ", vmsd->name, vmsd->version_id,
|
||||
vmsd->minimum_version_id, ret);
|
||||
return ret;
|
||||
}
|
||||
} else if (vmsd->pre_load) {
|
||||
ret = vmsd->pre_load(opaque);
|
||||
if (ret) {
|
||||
error_setg(errp, "pre load hook failed for: '%s', "
|
||||
"version_id: %d, minimum version_id: %d, ret: %d",
|
||||
vmsd->name, vmsd->version_id, vmsd->minimum_version_id,
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -192,13 +206,21 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
|
|||
|
||||
if (inner_field->flags & VMS_STRUCT) {
|
||||
ret = vmstate_load_state(f, inner_field->vmsd, curr_elem,
|
||||
inner_field->vmsd->version_id);
|
||||
inner_field->vmsd->version_id,
|
||||
errp);
|
||||
} else if (inner_field->flags & VMS_VSTRUCT) {
|
||||
ret = vmstate_load_state(f, inner_field->vmsd, curr_elem,
|
||||
inner_field->struct_version_id);
|
||||
inner_field->struct_version_id,
|
||||
errp);
|
||||
} else {
|
||||
ret = inner_field->info->get(f, curr_elem, size,
|
||||
inner_field);
|
||||
if (ret < 0) {
|
||||
error_setg(errp,
|
||||
"Failed to load element of type %s for %s: "
|
||||
"%d", inner_field->info->name,
|
||||
inner_field->name, ret);
|
||||
}
|
||||
}
|
||||
|
||||
/* If we used a fake temp field.. free it now */
|
||||
|
|
@ -208,30 +230,47 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
|
|||
|
||||
if (ret >= 0) {
|
||||
ret = qemu_file_get_error(f);
|
||||
if (ret < 0) {
|
||||
error_setg(errp,
|
||||
"Failed to load %s state: stream error: %d",
|
||||
vmsd->name, ret);
|
||||
}
|
||||
}
|
||||
if (ret < 0) {
|
||||
qemu_file_set_error(f, ret);
|
||||
error_report("Failed to load %s:%s", vmsd->name,
|
||||
field->name);
|
||||
trace_vmstate_load_field_error(field->name, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
} else if (field->flags & VMS_MUST_EXIST) {
|
||||
error_report("Input validation failed: %s/%s",
|
||||
vmsd->name, field->name);
|
||||
error_setg(errp, "Input validation failed: %s/%s version_id: %d",
|
||||
vmsd->name, field->name, vmsd->version_id);
|
||||
return -1;
|
||||
}
|
||||
field++;
|
||||
}
|
||||
assert(field->flags == VMS_END);
|
||||
ret = vmstate_subsection_load(f, vmsd, opaque);
|
||||
ret = vmstate_subsection_load(f, vmsd, opaque, errp);
|
||||
if (ret != 0) {
|
||||
qemu_file_set_error(f, ret);
|
||||
return ret;
|
||||
}
|
||||
if (vmsd->post_load) {
|
||||
if (vmsd->post_load_errp) {
|
||||
ret = vmsd->post_load_errp(opaque, version_id, errp);
|
||||
if (ret < 0) {
|
||||
error_prepend(errp, "post load hook failed for: %s, version_id: "
|
||||
"%d, minimum_version: %d, ret: %d: ", vmsd->name,
|
||||
vmsd->version_id, vmsd->minimum_version_id, ret);
|
||||
}
|
||||
} else if (vmsd->post_load) {
|
||||
ret = vmsd->post_load(opaque, version_id);
|
||||
if (ret < 0) {
|
||||
error_setg(errp,
|
||||
"post load hook failed for: %s, version_id: %d, "
|
||||
"minimum_version: %d, ret: %d",
|
||||
vmsd->name, vmsd->version_id, vmsd->minimum_version_id,
|
||||
ret);
|
||||
}
|
||||
}
|
||||
trace_vmstate_load_state_end(vmsd->name, "end", ret);
|
||||
return ret;
|
||||
|
|
@ -384,12 +423,6 @@ bool vmstate_section_needed(const VMStateDescription *vmsd, void *opaque)
|
|||
|
||||
|
||||
int vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd,
|
||||
void *opaque, JSONWriter *vmdesc_id)
|
||||
{
|
||||
return vmstate_save_state_v(f, vmsd, opaque, vmdesc_id, vmsd->version_id, NULL);
|
||||
}
|
||||
|
||||
int vmstate_save_state_with_err(QEMUFile *f, const VMStateDescription *vmsd,
|
||||
void *opaque, JSONWriter *vmdesc_id, Error **errp)
|
||||
{
|
||||
return vmstate_save_state_v(f, vmsd, opaque, vmdesc_id, vmsd->version_id, errp);
|
||||
|
|
@ -398,12 +431,20 @@ int vmstate_save_state_with_err(QEMUFile *f, const VMStateDescription *vmsd,
|
|||
int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
|
||||
void *opaque, JSONWriter *vmdesc, int version_id, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
int ret = 0;
|
||||
const VMStateField *field = vmsd->fields;
|
||||
|
||||
trace_vmstate_save_state_top(vmsd->name);
|
||||
|
||||
if (vmsd->pre_save) {
|
||||
if (vmsd->pre_save_errp) {
|
||||
ret = vmsd->pre_save_errp(opaque, errp);
|
||||
trace_vmstate_save_state_pre_save_res(vmsd->name, ret);
|
||||
if (ret < 0) {
|
||||
error_prepend(errp, "pre-save for %s failed, ret: %d: ",
|
||||
vmsd->name, ret);
|
||||
}
|
||||
} else if (vmsd->pre_save) {
|
||||
ret = vmsd->pre_save(opaque);
|
||||
trace_vmstate_save_state_pre_save_res(vmsd->name, ret);
|
||||
if (ret) {
|
||||
|
|
@ -490,7 +531,7 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
|
|||
|
||||
if (inner_field->flags & VMS_STRUCT) {
|
||||
ret = vmstate_save_state(f, inner_field->vmsd,
|
||||
curr_elem, vmdesc_loop);
|
||||
curr_elem, vmdesc_loop, errp);
|
||||
} else if (inner_field->flags & VMS_VSTRUCT) {
|
||||
ret = vmstate_save_state_v(f, inner_field->vmsd,
|
||||
curr_elem, vmdesc_loop,
|
||||
|
|
@ -566,8 +607,9 @@ vmstate_get_subsection(const VMStateDescription * const *sub,
|
|||
}
|
||||
|
||||
static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
|
||||
void *opaque)
|
||||
void *opaque, Error **errp)
|
||||
{
|
||||
ERRP_GUARD();
|
||||
trace_vmstate_subsection_load(vmsd->name);
|
||||
|
||||
while (qemu_peek_byte(f, 0) == QEMU_VM_SUBSECTION) {
|
||||
|
|
@ -598,6 +640,8 @@ static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
|
|||
sub_vmsd = vmstate_get_subsection(vmsd->subsections, idstr);
|
||||
if (sub_vmsd == NULL) {
|
||||
trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(lookup)");
|
||||
error_setg(errp, "VM subsection '%s' in '%s' does not exist",
|
||||
idstr, vmsd->name);
|
||||
return -ENOENT;
|
||||
}
|
||||
qemu_file_skip(f, 1); /* subsection */
|
||||
|
|
@ -605,9 +649,12 @@ static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
|
|||
qemu_file_skip(f, len); /* idstr */
|
||||
version_id = qemu_get_be32(f);
|
||||
|
||||
ret = vmstate_load_state(f, sub_vmsd, opaque, version_id);
|
||||
ret = vmstate_load_state(f, sub_vmsd, opaque, version_id, errp);
|
||||
if (ret) {
|
||||
trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(child)");
|
||||
error_prepend(errp,
|
||||
"Loading VM subsection '%s' in '%s' failed: %d: ",
|
||||
idstr, vmsd->name, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
|
@ -646,7 +693,7 @@ static int vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
|
|||
qemu_put_byte(f, len);
|
||||
qemu_put_buffer(f, (uint8_t *)vmsdsub->name, len);
|
||||
qemu_put_be32(f, vmsdsub->version_id);
|
||||
ret = vmstate_save_state_with_err(f, vmsdsub, opaque, vmdesc, errp);
|
||||
ret = vmstate_save_state(f, vmsdsub, opaque, vmdesc, errp);
|
||||
if (ret) {
|
||||
return ret;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -694,9 +694,32 @@
|
|||
# until you issue the `migrate-incoming` command.
|
||||
#
|
||||
# (since 10.0)
|
||||
#
|
||||
# @cpr-exec: The migrate command stops the VM, saves state to the
|
||||
# migration channel, directly exec's a new version of QEMU on the
|
||||
# same host, replacing the original process while retaining its
|
||||
# PID, and loads state from the channel. Guest RAM is preserved
|
||||
# in place. Devices and their pinned pages are also preserved for
|
||||
# VFIO and IOMMUFD.
|
||||
#
|
||||
# Old QEMU starts new QEMU by exec'ing the command specified by
|
||||
# the @cpr-exec-command parameter. The command may be a direct
|
||||
# invocation of new QEMU, or may be a wrapper that exec's the new
|
||||
# QEMU binary.
|
||||
#
|
||||
# Because old QEMU terminates when new QEMU starts, one cannot
|
||||
# stream data between the two, so the channel must be a type,
|
||||
# such as a file, that accepts all data before old QEMU exits.
|
||||
# Otherwise, old QEMU may quietly block writing to the channel.
|
||||
#
|
||||
# Memory-backend objects must have the share=on attribute, but
|
||||
# memory-backend-epc is not supported. The VM must be started
|
||||
# with the '-machine aux-ram-share=on' option.
|
||||
#
|
||||
# (since 10.2)
|
||||
##
|
||||
{ 'enum': 'MigMode',
|
||||
'data': [ 'normal', 'cpr-reboot', 'cpr-transfer' ] }
|
||||
'data': [ 'normal', 'cpr-reboot', 'cpr-transfer', 'cpr-exec' ] }
|
||||
|
||||
##
|
||||
# @ZeroPageDetection:
|
||||
|
|
@ -924,6 +947,10 @@
|
|||
# only has effect if the @mapped-ram capability is enabled.
|
||||
# (Since 9.1)
|
||||
#
|
||||
# @cpr-exec-command: Command to start the new QEMU process when @mode
|
||||
# is @cpr-exec. The first list element is the program's filename,
|
||||
# the remainder its arguments. (Since 10.2)
|
||||
#
|
||||
# Features:
|
||||
#
|
||||
# @unstable: Members @x-checkpoint-delay and
|
||||
|
|
@ -950,7 +977,8 @@
|
|||
'vcpu-dirty-limit',
|
||||
'mode',
|
||||
'zero-page-detection',
|
||||
'direct-io'] }
|
||||
'direct-io',
|
||||
'cpr-exec-command'] }
|
||||
|
||||
##
|
||||
# @MigrateSetParameters:
|
||||
|
|
@ -1105,6 +1133,10 @@
|
|||
# only has effect if the @mapped-ram capability is enabled.
|
||||
# (Since 9.1)
|
||||
#
|
||||
# @cpr-exec-command: Command to start the new QEMU process when @mode
|
||||
# is @cpr-exec. The first list element is the program's filename,
|
||||
# the remainder its arguments. (Since 10.2)
|
||||
#
|
||||
# Features:
|
||||
#
|
||||
# @unstable: Members @x-checkpoint-delay and
|
||||
|
|
@ -1146,7 +1178,8 @@
|
|||
'*vcpu-dirty-limit': 'uint64',
|
||||
'*mode': 'MigMode',
|
||||
'*zero-page-detection': 'ZeroPageDetection',
|
||||
'*direct-io': 'bool' } }
|
||||
'*direct-io': 'bool',
|
||||
'*cpr-exec-command': [ 'str' ]} }
|
||||
|
||||
##
|
||||
# @migrate-set-parameters:
|
||||
|
|
@ -1315,6 +1348,10 @@
|
|||
# only has effect if the @mapped-ram capability is enabled.
|
||||
# (Since 9.1)
|
||||
#
|
||||
# @cpr-exec-command: Command to start the new QEMU process when @mode
|
||||
# is @cpr-exec. The first list element is the program's filename,
|
||||
# the remainder its arguments. (Since 10.2)
|
||||
#
|
||||
# Features:
|
||||
#
|
||||
# @unstable: Members @x-checkpoint-delay and
|
||||
|
|
@ -1353,7 +1390,8 @@
|
|||
'*vcpu-dirty-limit': 'uint64',
|
||||
'*mode': 'MigMode',
|
||||
'*zero-page-detection': 'ZeroPageDetection',
|
||||
'*direct-io': 'bool' } }
|
||||
'*direct-io': 'bool',
|
||||
'*cpr-exec-command': [ 'str' ]} }
|
||||
|
||||
##
|
||||
# @query-migrate-parameters:
|
||||
|
|
|
|||
15
stubs/cpu-destroy-address-spaces.c
Normal file
15
stubs/cpu-destroy-address-spaces.c
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "exec/cpu-common.h"
|
||||
|
||||
/*
|
||||
* user-mode CPUs never create address spaces with
|
||||
* cpu_address_space_init(), so the cleanup function doesn't
|
||||
* need to do anything. We need this stub because cpu-common.c
|
||||
* is built-once so it can't #ifndef CONFIG_USER around the
|
||||
* call; the real function is in physmem.c which is system-only.
|
||||
*/
|
||||
void cpu_destroy_address_spaces(CPUState *cpu)
|
||||
{
|
||||
}
|
||||
|
|
@ -55,6 +55,7 @@ endif
|
|||
if have_user
|
||||
# Symbols that are used by hw/core.
|
||||
stub_ss.add(files('cpu-synchronize-state.c'))
|
||||
stub_ss.add(files('cpu-destroy-address-spaces.c'))
|
||||
|
||||
# Stubs for QAPI events. Those can always be included in the build, but
|
||||
# they are not built at all for --disable-system builds.
|
||||
|
|
|
|||
|
|
@ -3278,7 +3278,14 @@ static void do_address_space_destroy(AddressSpace *as)
|
|||
memory_region_unref(as->root);
|
||||
}
|
||||
|
||||
void address_space_destroy(AddressSpace *as)
|
||||
static void do_address_space_destroy_free(AddressSpace *as)
|
||||
{
|
||||
do_address_space_destroy(as);
|
||||
g_free(as);
|
||||
}
|
||||
|
||||
/* Detach address space from global view, notify all listeners */
|
||||
static void address_space_detach(AddressSpace *as)
|
||||
{
|
||||
MemoryRegion *root = as->root;
|
||||
|
||||
|
|
@ -3293,9 +3300,20 @@ void address_space_destroy(AddressSpace *as)
|
|||
* values to expire before freeing the data.
|
||||
*/
|
||||
as->root = root;
|
||||
}
|
||||
|
||||
void address_space_destroy(AddressSpace *as)
|
||||
{
|
||||
address_space_detach(as);
|
||||
call_rcu(as, do_address_space_destroy, rcu);
|
||||
}
|
||||
|
||||
void address_space_destroy_free(AddressSpace *as)
|
||||
{
|
||||
address_space_detach(as);
|
||||
call_rcu(as, do_address_space_destroy_free, rcu);
|
||||
}
|
||||
|
||||
static const char *memory_region_type(MemoryRegion *mr)
|
||||
{
|
||||
if (mr->alias) {
|
||||
|
|
|
|||
|
|
@ -795,7 +795,6 @@ void cpu_address_space_init(CPUState *cpu, int asidx,
|
|||
|
||||
if (!cpu->cpu_ases) {
|
||||
cpu->cpu_ases = g_new0(CPUAddressSpace, cpu->num_ases);
|
||||
cpu->cpu_ases_count = cpu->num_ases;
|
||||
}
|
||||
|
||||
newas = &cpu->cpu_ases[asidx];
|
||||
|
|
@ -809,30 +808,29 @@ void cpu_address_space_init(CPUState *cpu, int asidx,
|
|||
}
|
||||
}
|
||||
|
||||
void cpu_address_space_destroy(CPUState *cpu, int asidx)
|
||||
void cpu_destroy_address_spaces(CPUState *cpu)
|
||||
{
|
||||
CPUAddressSpace *cpuas;
|
||||
int asidx;
|
||||
|
||||
assert(cpu->cpu_ases);
|
||||
assert(asidx >= 0 && asidx < cpu->num_ases);
|
||||
|
||||
cpuas = &cpu->cpu_ases[asidx];
|
||||
if (tcg_enabled()) {
|
||||
memory_listener_unregister(&cpuas->tcg_as_listener);
|
||||
/* convenience alias just points to some cpu_ases[n] */
|
||||
cpu->as = NULL;
|
||||
|
||||
for (asidx = 0; asidx < cpu->num_ases; asidx++) {
|
||||
cpuas = &cpu->cpu_ases[asidx];
|
||||
if (!cpuas->as) {
|
||||
/* This index was never initialized; no deinit needed */
|
||||
continue;
|
||||
}
|
||||
if (tcg_enabled()) {
|
||||
memory_listener_unregister(&cpuas->tcg_as_listener);
|
||||
}
|
||||
g_clear_pointer(&cpuas->as, address_space_destroy_free);
|
||||
}
|
||||
|
||||
address_space_destroy(cpuas->as);
|
||||
g_free_rcu(cpuas->as, rcu);
|
||||
|
||||
if (asidx == 0) {
|
||||
/* reset the convenience alias for address space 0 */
|
||||
cpu->as = NULL;
|
||||
}
|
||||
|
||||
if (--cpu->cpu_ases_count == 0) {
|
||||
g_free(cpu->cpu_ases);
|
||||
cpu->cpu_ases = NULL;
|
||||
}
|
||||
g_clear_pointer(&cpu->cpu_ases, g_free);
|
||||
}
|
||||
|
||||
AddressSpace *cpu_get_address_space(CPUState *cpu, int asidx)
|
||||
|
|
|
|||
|
|
@ -3837,6 +3837,8 @@ void qemu_init(int argc, char **argv)
|
|||
}
|
||||
qemu_init_displays();
|
||||
accel_setup_post(current_machine);
|
||||
os_setup_post();
|
||||
if (migrate_mode() != MIG_MODE_CPR_EXEC) {
|
||||
os_setup_post();
|
||||
}
|
||||
resume_mux_open();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -113,6 +113,138 @@ static void test_mode_transfer_defer(void)
|
|||
test_mode_transfer_common(true);
|
||||
}
|
||||
|
||||
static void set_cpr_exec_args(QTestState *who, MigrateCommon *args)
|
||||
{
|
||||
g_autofree char *qtest_from_args = NULL;
|
||||
g_autofree char *from_args = NULL;
|
||||
g_autofree char *to_args = NULL;
|
||||
g_autofree char *exec_args = NULL;
|
||||
g_auto(GStrv) argv = NULL;
|
||||
char *from_str, *src, *dst;
|
||||
int ret;
|
||||
|
||||
/*
|
||||
* hide_stderr appends "2>/dev/null" to the command line, but cpr-exec
|
||||
* passes the command-line words to execv, not to the shell, so suppress it
|
||||
* here. fd 2 was already bound in the source VM, and execv preserves it.
|
||||
*/
|
||||
g_assert(args->start.hide_stderr == false);
|
||||
|
||||
ret = migrate_args(&from_args, &to_args, args->listen_uri, &args->start);
|
||||
g_assert(!ret);
|
||||
qtest_from_args = qtest_qemu_args(from_args);
|
||||
|
||||
/*
|
||||
* The generated args may have been formatted using "%s %s" with empty
|
||||
* strings, which can produce consecutive spaces, which g_strsplit would
|
||||
* convert into empty strings. Ditto for leading and trailing space.
|
||||
* De-dup spaces to avoid that.
|
||||
*/
|
||||
|
||||
from_str = src = dst = g_strstrip(qtest_from_args);
|
||||
do {
|
||||
if (*src != ' ' || src[-1] != ' ') {
|
||||
*dst++ = *src;
|
||||
}
|
||||
} while (*src++);
|
||||
|
||||
exec_args = g_strconcat(qtest_qemu_binary(migration_get_env()->qemu_dst),
|
||||
" -incoming defer ", from_str, NULL);
|
||||
argv = g_strsplit(exec_args, " ", -1);
|
||||
migrate_set_parameter_strv(who, "cpr-exec-command", argv);
|
||||
}
|
||||
|
||||
static void wait_for_migration_event(QTestState *who, const char *waitfor)
|
||||
{
|
||||
QDict *rsp, *data;
|
||||
char *status;
|
||||
bool done = false;
|
||||
|
||||
while (!done) {
|
||||
rsp = qtest_qmp_eventwait_ref(who, "MIGRATION");
|
||||
g_assert(qdict_haskey(rsp, "data"));
|
||||
data = qdict_get_qdict(rsp, "data");
|
||||
g_assert(qdict_haskey(data, "status"));
|
||||
status = g_strdup(qdict_get_str(data, "status"));
|
||||
g_assert(strcmp(status, "failed"));
|
||||
done = !strcmp(status, waitfor);
|
||||
qobject_unref(rsp);
|
||||
}
|
||||
}
|
||||
|
||||
static void test_cpr_exec(MigrateCommon *args)
|
||||
{
|
||||
QTestState *from, *to;
|
||||
void *data_hook = NULL;
|
||||
g_autofree char *connect_uri = g_strdup(args->connect_uri);
|
||||
g_autofree char *filename = g_strdup_printf("%s/%s", tmpfs,
|
||||
FILE_TEST_FILENAME);
|
||||
|
||||
if (migrate_start(&from, NULL, args->listen_uri, &args->start)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* Source and dest never run concurrently */
|
||||
g_assert_false(args->live);
|
||||
|
||||
if (args->start_hook) {
|
||||
data_hook = args->start_hook(from, NULL);
|
||||
}
|
||||
|
||||
wait_for_serial("src_serial");
|
||||
set_cpr_exec_args(from, args);
|
||||
migrate_set_capability(from, "events", true);
|
||||
migrate_qmp(from, NULL, connect_uri, NULL, "{}");
|
||||
wait_for_migration_event(from, "completed");
|
||||
|
||||
to = qtest_init_after_exec(from);
|
||||
|
||||
qtest_qmp_assert_success(to, "{ 'execute': 'migrate-incoming',"
|
||||
" 'arguments': { "
|
||||
" 'channels': [ { 'channel-type': 'main',"
|
||||
" 'addr': { 'transport': 'file',"
|
||||
" 'filename': %s,"
|
||||
" 'offset': 0 } } ] } }",
|
||||
filename);
|
||||
wait_for_migration_complete(to);
|
||||
|
||||
wait_for_resume(to, get_dst());
|
||||
/* Device on target is still named src_serial because args do not change */
|
||||
wait_for_serial("src_serial");
|
||||
|
||||
if (args->end_hook) {
|
||||
args->end_hook(from, to, data_hook);
|
||||
}
|
||||
|
||||
migrate_end(from, to, args->result == MIG_TEST_SUCCEED);
|
||||
}
|
||||
|
||||
static void *test_mode_exec_start(QTestState *from, QTestState *to)
|
||||
{
|
||||
assert(!to);
|
||||
migrate_set_parameter_str(from, "mode", "cpr-exec");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void test_mode_exec(void)
|
||||
{
|
||||
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
|
||||
FILE_TEST_FILENAME);
|
||||
g_autofree char *listen_uri = g_strdup_printf("defer");
|
||||
|
||||
MigrateCommon args = {
|
||||
.start.only_source = true,
|
||||
.start.opts_source = "-machine aux-ram-share=on -nodefaults",
|
||||
.start.memory_backend = "-object memory-backend-memfd,id=pc.ram,size=%s"
|
||||
" -machine memory-backend=pc.ram",
|
||||
.connect_uri = uri,
|
||||
.listen_uri = listen_uri,
|
||||
.start_hook = test_mode_exec_start,
|
||||
};
|
||||
|
||||
test_cpr_exec(&args);
|
||||
}
|
||||
|
||||
void migration_test_add_cpr(MigrationTestEnv *env)
|
||||
{
|
||||
tmpfs = env->tmpfs;
|
||||
|
|
@ -135,5 +267,6 @@ void migration_test_add_cpr(MigrationTestEnv *env)
|
|||
migration_test_add("/migration/mode/transfer", test_mode_transfer);
|
||||
migration_test_add("/migration/mode/transfer/defer",
|
||||
test_mode_transfer_defer);
|
||||
migration_test_add("/migration/mode/exec", test_mode_exec);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@
|
|||
#include "../migration/savevm.h"
|
||||
#include "qemu/module.h"
|
||||
#include "io/channel-file.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
static int temp_fd;
|
||||
|
||||
|
|
@ -66,9 +67,13 @@ static QEMUFile *open_test_file(bool write)
|
|||
static void save_vmstate(const VMStateDescription *desc, void *obj)
|
||||
{
|
||||
QEMUFile *f = open_test_file(true);
|
||||
Error *local_err = NULL;
|
||||
|
||||
/* Save file with vmstate */
|
||||
int ret = vmstate_save_state(f, desc, obj, NULL);
|
||||
int ret = vmstate_save_state(f, desc, obj, NULL, &local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
g_assert(!ret);
|
||||
qemu_put_byte(f, QEMU_VM_EOF);
|
||||
g_assert(!qemu_file_get_error(f));
|
||||
|
|
@ -108,14 +113,16 @@ static int load_vmstate_one(const VMStateDescription *desc, void *obj,
|
|||
{
|
||||
QEMUFile *f;
|
||||
int ret;
|
||||
Error *local_err = NULL;
|
||||
|
||||
f = open_test_file(true);
|
||||
qemu_put_buffer(f, wire, size);
|
||||
qemu_fclose(f);
|
||||
|
||||
f = open_test_file(false);
|
||||
ret = vmstate_load_state(f, desc, obj, version);
|
||||
ret = vmstate_load_state(f, desc, obj, version, &local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
g_assert(qemu_file_get_error(f));
|
||||
} else{
|
||||
g_assert(!qemu_file_get_error(f));
|
||||
|
|
@ -355,6 +362,8 @@ static const VMStateDescription vmstate_versioned = {
|
|||
|
||||
static void test_load_v1(void)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
uint8_t buf[] = {
|
||||
0, 0, 0, 10, /* a */
|
||||
0, 0, 0, 30, /* c */
|
||||
|
|
@ -365,7 +374,10 @@ static void test_load_v1(void)
|
|||
|
||||
QEMUFile *loading = open_test_file(false);
|
||||
TestStruct obj = { .b = 200, .e = 500, .f = 600 };
|
||||
vmstate_load_state(loading, &vmstate_versioned, &obj, 1);
|
||||
ret = vmstate_load_state(loading, &vmstate_versioned, &obj, 1, &local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
g_assert(!qemu_file_get_error(loading));
|
||||
g_assert_cmpint(obj.a, ==, 10);
|
||||
g_assert_cmpint(obj.b, ==, 200);
|
||||
|
|
@ -378,6 +390,8 @@ static void test_load_v1(void)
|
|||
|
||||
static void test_load_v2(void)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
uint8_t buf[] = {
|
||||
0, 0, 0, 10, /* a */
|
||||
0, 0, 0, 20, /* b */
|
||||
|
|
@ -391,7 +405,10 @@ static void test_load_v2(void)
|
|||
|
||||
QEMUFile *loading = open_test_file(false);
|
||||
TestStruct obj;
|
||||
vmstate_load_state(loading, &vmstate_versioned, &obj, 2);
|
||||
ret = vmstate_load_state(loading, &vmstate_versioned, &obj, 2, &local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
g_assert_cmpint(obj.a, ==, 10);
|
||||
g_assert_cmpint(obj.b, ==, 20);
|
||||
g_assert_cmpint(obj.c, ==, 30);
|
||||
|
|
@ -425,10 +442,15 @@ static const VMStateDescription vmstate_skipping = {
|
|||
|
||||
static void test_save_noskip(void)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
QEMUFile *fsave = open_test_file(true);
|
||||
TestStruct obj = { .a = 1, .b = 2, .c = 3, .d = 4, .e = 5, .f = 6,
|
||||
.skip_c_e = false };
|
||||
int ret = vmstate_save_state(fsave, &vmstate_skipping, &obj, NULL);
|
||||
int ret = vmstate_save_state(fsave, &vmstate_skipping, &obj, NULL,
|
||||
&local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
g_assert(!ret);
|
||||
g_assert(!qemu_file_get_error(fsave));
|
||||
|
||||
|
|
@ -447,10 +469,15 @@ static void test_save_noskip(void)
|
|||
|
||||
static void test_save_skip(void)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
QEMUFile *fsave = open_test_file(true);
|
||||
TestStruct obj = { .a = 1, .b = 2, .c = 3, .d = 4, .e = 5, .f = 6,
|
||||
.skip_c_e = true };
|
||||
int ret = vmstate_save_state(fsave, &vmstate_skipping, &obj, NULL);
|
||||
int ret = vmstate_save_state(fsave, &vmstate_skipping, &obj, NULL,
|
||||
&local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
g_assert(!ret);
|
||||
g_assert(!qemu_file_get_error(fsave));
|
||||
|
||||
|
|
@ -467,6 +494,8 @@ static void test_save_skip(void)
|
|||
|
||||
static void test_load_noskip(void)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
uint8_t buf[] = {
|
||||
0, 0, 0, 10, /* a */
|
||||
0, 0, 0, 20, /* b */
|
||||
|
|
@ -480,7 +509,10 @@ static void test_load_noskip(void)
|
|||
|
||||
QEMUFile *loading = open_test_file(false);
|
||||
TestStruct obj = { .skip_c_e = false };
|
||||
vmstate_load_state(loading, &vmstate_skipping, &obj, 2);
|
||||
ret = vmstate_load_state(loading, &vmstate_skipping, &obj, 2, &local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
g_assert(!qemu_file_get_error(loading));
|
||||
g_assert_cmpint(obj.a, ==, 10);
|
||||
g_assert_cmpint(obj.b, ==, 20);
|
||||
|
|
@ -493,6 +525,8 @@ static void test_load_noskip(void)
|
|||
|
||||
static void test_load_skip(void)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
uint8_t buf[] = {
|
||||
0, 0, 0, 10, /* a */
|
||||
0, 0, 0, 20, /* b */
|
||||
|
|
@ -504,7 +538,10 @@ static void test_load_skip(void)
|
|||
|
||||
QEMUFile *loading = open_test_file(false);
|
||||
TestStruct obj = { .skip_c_e = true, .c = 300, .e = 500 };
|
||||
vmstate_load_state(loading, &vmstate_skipping, &obj, 2);
|
||||
ret = vmstate_load_state(loading, &vmstate_skipping, &obj, 2, &local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
g_assert(!qemu_file_get_error(loading));
|
||||
g_assert_cmpint(obj.a, ==, 10);
|
||||
g_assert_cmpint(obj.b, ==, 20);
|
||||
|
|
@ -744,6 +781,8 @@ static void test_save_q(void)
|
|||
|
||||
static void test_load_q(void)
|
||||
{
|
||||
int ret;
|
||||
Error *local_err = NULL;
|
||||
TestQtailq obj_q = {
|
||||
.i16 = -512,
|
||||
.i32 = 70000,
|
||||
|
|
@ -773,7 +812,10 @@ static void test_load_q(void)
|
|||
TestQtailq tgt;
|
||||
|
||||
QTAILQ_INIT(&tgt.q);
|
||||
vmstate_load_state(fload, &vmstate_q, &tgt, 1);
|
||||
ret = vmstate_load_state(fload, &vmstate_q, &tgt, 1, &local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
char eof = qemu_get_byte(fload);
|
||||
g_assert(!qemu_file_get_error(fload));
|
||||
g_assert_cmpint(tgt.i16, ==, obj_q.i16);
|
||||
|
|
@ -1115,6 +1157,8 @@ static void diff_iommu(TestGTreeIOMMU *iommu1, TestGTreeIOMMU *iommu2)
|
|||
|
||||
static void test_gtree_load_domain(void)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
TestGTreeDomain *dest_domain = g_new0(TestGTreeDomain, 1);
|
||||
TestGTreeDomain *orig_domain = create_first_domain();
|
||||
QEMUFile *fload, *fsave;
|
||||
|
|
@ -1127,7 +1171,11 @@ static void test_gtree_load_domain(void)
|
|||
|
||||
fload = open_test_file(false);
|
||||
|
||||
vmstate_load_state(fload, &vmstate_domain, dest_domain, 1);
|
||||
ret = vmstate_load_state(fload, &vmstate_domain, dest_domain, 1,
|
||||
&local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
eof = qemu_get_byte(fload);
|
||||
g_assert(!qemu_file_get_error(fload));
|
||||
g_assert_cmpint(orig_domain->id, ==, dest_domain->id);
|
||||
|
|
@ -1230,6 +1278,8 @@ static void test_gtree_save_iommu(void)
|
|||
|
||||
static void test_gtree_load_iommu(void)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
TestGTreeIOMMU *dest_iommu = g_new0(TestGTreeIOMMU, 1);
|
||||
TestGTreeIOMMU *orig_iommu = create_iommu();
|
||||
QEMUFile *fsave, *fload;
|
||||
|
|
@ -1241,7 +1291,10 @@ static void test_gtree_load_iommu(void)
|
|||
qemu_fclose(fsave);
|
||||
|
||||
fload = open_test_file(false);
|
||||
vmstate_load_state(fload, &vmstate_iommu, dest_iommu, 1);
|
||||
ret = vmstate_load_state(fload, &vmstate_iommu, dest_iommu, 1, &local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
eof = qemu_get_byte(fload);
|
||||
g_assert(!qemu_file_get_error(fload));
|
||||
g_assert_cmpint(orig_iommu->id, ==, dest_iommu->id);
|
||||
|
|
@ -1363,6 +1416,8 @@ static void test_save_qlist(void)
|
|||
|
||||
static void test_load_qlist(void)
|
||||
{
|
||||
Error *local_err = NULL;
|
||||
int ret;
|
||||
QEMUFile *fsave, *fload;
|
||||
TestQListContainer *orig_container = alloc_container();
|
||||
TestQListContainer *dest_container = g_new0(TestQListContainer, 1);
|
||||
|
|
@ -1376,7 +1431,11 @@ static void test_load_qlist(void)
|
|||
qemu_fclose(fsave);
|
||||
|
||||
fload = open_test_file(false);
|
||||
vmstate_load_state(fload, &vmstate_container, dest_container, 1);
|
||||
ret = vmstate_load_state(fload, &vmstate_container, dest_container, 1,
|
||||
&local_err);
|
||||
if (ret < 0) {
|
||||
error_report_err(local_err);
|
||||
}
|
||||
eof = qemu_get_byte(fload);
|
||||
g_assert(!qemu_file_get_error(fload));
|
||||
g_assert_cmpint(eof, ==, QEMU_VM_EOF);
|
||||
|
|
|
|||
|
|
@ -992,7 +992,8 @@ static int put_cbinfo(QEMUFile *f, void *pv, size_t size,
|
|||
}
|
||||
}
|
||||
|
||||
return vmstate_save_state(f, &vmstate_cbinfo_array, &cbinfo, vmdesc);
|
||||
return vmstate_save_state(f, &vmstate_cbinfo_array, &cbinfo, vmdesc,
|
||||
&error_fatal);
|
||||
}
|
||||
|
||||
static int get_cbinfo(QEMUFile *f, void *pv, size_t size,
|
||||
|
|
@ -1001,6 +1002,7 @@ static int get_cbinfo(QEMUFile *f, void *pv, size_t size,
|
|||
VDAgentChardev *vd = QEMU_VDAGENT_CHARDEV(pv);
|
||||
struct CBInfoArray cbinfo = {};
|
||||
int i, ret;
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (!have_clipboard(vd)) {
|
||||
return 0;
|
||||
|
|
@ -1008,8 +1010,10 @@ static int get_cbinfo(QEMUFile *f, void *pv, size_t size,
|
|||
|
||||
vdagent_clipboard_peer_register(vd);
|
||||
|
||||
ret = vmstate_load_state(f, &vmstate_cbinfo_array, &cbinfo, 0);
|
||||
ret = vmstate_load_state(f, &vmstate_cbinfo_array, &cbinfo, 0,
|
||||
&local_err);
|
||||
if (ret) {
|
||||
error_report_err(local_err);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -305,6 +305,15 @@ int qemu_socketpair(int domain, int type, int protocol, int sv[2])
|
|||
return ret;
|
||||
}
|
||||
|
||||
void qemu_clear_cloexec(int fd)
|
||||
{
|
||||
int f;
|
||||
f = fcntl(fd, F_GETFD);
|
||||
assert(f != -1);
|
||||
f = fcntl(fd, F_SETFD, f & ~FD_CLOEXEC);
|
||||
assert(f != -1);
|
||||
}
|
||||
|
||||
char *
|
||||
qemu_get_local_state_dir(void)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -219,6 +219,10 @@ void qemu_set_cloexec(int fd)
|
|||
{
|
||||
}
|
||||
|
||||
void qemu_clear_cloexec(int fd)
|
||||
{
|
||||
}
|
||||
|
||||
int qemu_get_thread_id(void)
|
||||
{
|
||||
return GetCurrentThreadId();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue