* [PULL 01/43] checkpatch: Allow spaces after all coroutine annotations
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 02/43] tests/functional: Make socat wait longer in migration exec test Fabiano Rosas
` (42 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Markus Armbruster, Kevin Wolf
The coroutine annotations may be used in the declaration of function
pointers, which triggers checkpatch due to the space before the
parentheses. E.g:
int coroutine_fn (*run)(Job *job, Error **errp);
^
The coroutine_fn annotation is already included in the list of terms
where spaces are allowed. Add the other coroutine annotations:
coroutine_mixed_fn and no_coroutine_fn.
Reviewed-by: Markus Armbruster <armbru@redhat.com>
Reviewed-by: Kevin Wolf <kwolf@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260420191356.4439-1-farosas@suse.de
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
scripts/checkpatch.pl | 1 +
1 file changed, 1 insertion(+)
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index b77bd69328..e1cf2ff23b 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -2441,6 +2441,7 @@ sub process {
if ($name =~ /^(?:
if|for|while|switch|return|case|
volatile|__volatile__|coroutine_fn|
+ coroutine_mixed_fn|no_coroutine_fn|
__attribute__|format|__extension__|
asm|__asm__)$/x)
{
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 02/43] tests/functional: Make socat wait longer in migration exec test
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
2026-04-23 19:19 ` [PULL 01/43] checkpatch: Allow spaces after all coroutine annotations Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 03/43] migration: vmstate_save_state_v: fix double error_setg Fabiano Rosas
` (41 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Thomas Huth, Philippe Mathieu-Daudé
The migration_with_exec test is failing sporadically for all
architectures due to a race when the destination socat process takes
too long to start listening while the source process is already
issuing connect().
The race is inherent because the exec: migration spawns the
to-be-exec'ed command asynchronously and returns from the
migrate-incoming command. The localhost-only testcase is not
representative of the majority of migrations. In a real scenario
between two different hosts that race wouldn't happen.
Fix the testcase by configuring the source socat command to wait
indefinitely while trying to connect.
Reviewed-by: Thomas Huth <thuth@redhat.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Link: https://lore.kernel.org/qemu-devel/20260422230001.3168-1-farosas@suse.de
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
tests/functional/migration.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/tests/functional/migration.py b/tests/functional/migration.py
index 144f091ba8..3b7674af3b 100644
--- a/tests/functional/migration.py
+++ b/tests/functional/migration.py
@@ -85,5 +85,5 @@ def migration_with_exec(self):
with Ports() as ports:
free_port = self._get_free_port(ports)
dst_uri = 'exec:socat TCP-LISTEN:%u -' % free_port
- src_uri = 'exec:socat - TCP:localhost:%u' % free_port
+ src_uri = 'exec:socat - TCP:localhost:%u,forever' % free_port
self.migrate(dst_uri, src_uri)
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 03/43] migration: vmstate_save_state_v: fix double error_setg
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
2026-04-23 19:19 ` [PULL 01/43] checkpatch: Allow spaces after all coroutine annotations Fabiano Rosas
2026-04-23 19:19 ` [PULL 02/43] tests/functional: Make socat wait longer in migration exec test Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 04/43] migration: make vmstate_save_state_v() static Fabiano Rosas
` (40 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
We may call error_setg twice on same errp if inner
vmstate_save_state_v() or vmstate_save_state() call fails. Next we will
crash on assertion in error_setv().
Fixes: 848a0503422d043 "migration: Update error description outside migration.c"
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-2-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate.c | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 4d28364f7b..fccd030dfd 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -539,6 +539,9 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
} else {
ret = inner_field->info->put(f, curr_elem, size,
inner_field, vmdesc_loop);
+ if (ret < 0) {
+ error_setg(errp, "put failed");
+ }
}
written_bytes = qemu_file_transferred(f) - old_offset;
@@ -551,8 +554,8 @@ int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
}
if (ret) {
- error_setg(errp, "Save of field %s/%s failed",
- vmsd->name, field->name);
+ error_prepend(errp, "Save of field %s/%s failed: ",
+ vmsd->name, field->name);
if (vmsd->post_save) {
vmsd->post_save(opaque);
}
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 04/43] migration: make vmstate_save_state_v() static
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (2 preceding siblings ...)
2026-04-23 19:19 ` [PULL 03/43] migration: vmstate_save_state_v: fix double error_setg Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 05/43] migration: make .post_save() a void function Fabiano Rosas
` (39 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
It's used only in vmstate.c.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-3-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/migration/vmstate.h | 3 ---
migration/vmstate.c | 19 ++++++++++---------
2 files changed, 10 insertions(+), 12 deletions(-)
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 62c2abd0c4..78d97151bd 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -1244,9 +1244,6 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, int version_id, Error **errp);
int vmstate_save_state(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,
- int version_id, Error **errp);
bool vmstate_section_needed(const VMStateDescription *vmsd, void *opaque);
diff --git a/migration/vmstate.c b/migration/vmstate.c
index fccd030dfd..651c3fe011 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -420,15 +420,9 @@ bool vmstate_section_needed(const VMStateDescription *vmsd, void *opaque)
return true;
}
-
-int vmstate_save_state(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);
-}
-
-int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
- void *opaque, JSONWriter *vmdesc, int version_id, Error **errp)
+static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
+ void *opaque, JSONWriter *vmdesc,
+ int version_id, Error **errp)
{
ERRP_GUARD();
int ret = 0;
@@ -608,6 +602,13 @@ vmstate_get_subsection(const VMStateDescription * const *sub,
return NULL;
}
+int vmstate_save_state(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);
+}
+
static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, Error **errp)
{
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 05/43] migration: make .post_save() a void function
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (3 preceding siblings ...)
2026-04-23 19:19 ` [PULL 04/43] migration: make vmstate_save_state_v() static Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 06/43] migration: vmstate_load_state(): add some newlines Fabiano Rosas
` (38 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy, Zhao Liu
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
All other handlers now have _errp() variants. Should we go this way
for .post_save()? Actually it's rather strange, when the vmstate do
successful preparations in .pre_save(), then successfully save all
sections and subsections, end then fail when all the state is
successfully transferred to the target.
Happily, we have only three .post_save() realizations, all always
successful. Let's make this a rule.
Also note, that we call .post_save() in two places, and handle
its (theoretical) failure inconsistently. Fix that too.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Zhao Liu <zhao1.liu@intel.com> #rust
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-4-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
docs/devel/migration/main.rst | 2 +-
hw/ppc/spapr_pci.c | 3 +--
include/migration/vmstate.h | 8 +++++++-
migration/savevm.c | 3 +--
migration/vmstate.c | 12 +++---------
rust/migration/src/migratable.rs | 6 ++----
rust/migration/src/vmstate.rs | 12 +++++++-----
target/arm/machine.c | 4 +---
8 files changed, 23 insertions(+), 27 deletions(-)
diff --git a/docs/devel/migration/main.rst b/docs/devel/migration/main.rst
index 234d280249..2de7050764 100644
--- a/docs/devel/migration/main.rst
+++ b/docs/devel/migration/main.rst
@@ -439,7 +439,7 @@ The functions to do that are inside a vmstate definition, and are called:
This function is called before we save the state of one device.
-- ``int (*post_save)(void *opaque);``
+- ``void (*post_save)(void *opaque);``
This function is called after we save the state of one device
(even upon failure, unless the call to pre_save returned an error).
diff --git a/hw/ppc/spapr_pci.c b/hw/ppc/spapr_pci.c
index ea998bdff1..1dc3b02659 100644
--- a/hw/ppc/spapr_pci.c
+++ b/hw/ppc/spapr_pci.c
@@ -2093,14 +2093,13 @@ static int spapr_pci_pre_save(void *opaque)
return 0;
}
-static int spapr_pci_post_save(void *opaque)
+static void spapr_pci_post_save(void *opaque)
{
SpaprPhbState *sphb = opaque;
g_free(sphb->msi_devs);
sphb->msi_devs = NULL;
sphb->msi_devs_num = 0;
- return 0;
}
static int spapr_pci_post_load(void *opaque, int version_id)
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 78d97151bd..9d42cf7a64 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -223,7 +223,13 @@ struct VMStateDescription {
bool (*post_load_errp)(void *opaque, int version_id, Error **errp);
int (*pre_save)(void *opaque);
bool (*pre_save_errp)(void *opaque, Error **errp);
- int (*post_save)(void *opaque);
+
+ /*
+ * Unless .pre_save() fails, .post_save() is called after saving
+ * fields and subsections. It should not fail because at this
+ * point the state has potentially already been transferred.
+ */
+ void (*post_save)(void *opaque);
bool (*needed)(void *opaque);
bool (*dev_unplug_pending)(void *opaque);
diff --git a/migration/savevm.c b/migration/savevm.c
index dd58f2a705..699d2c9f8b 100644
--- a/migration/savevm.c
+++ b/migration/savevm.c
@@ -321,14 +321,13 @@ static int configuration_pre_save(void *opaque)
return 0;
}
-static int configuration_post_save(void *opaque)
+static void configuration_post_save(void *opaque)
{
SaveState *state = opaque;
g_free(state->capabilities);
state->capabilities = NULL;
state->caps_count = 0;
- return 0;
}
static int configuration_pre_load(void *opaque)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 651c3fe011..5111e7a141 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -550,10 +550,7 @@ static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
if (ret) {
error_prepend(errp, "Save of field %s/%s failed: ",
vmsd->name, field->name);
- if (vmsd->post_save) {
- vmsd->post_save(opaque);
- }
- return ret;
+ goto out;
}
/* Compressed arrays only care about the first element */
@@ -578,12 +575,9 @@ static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
ret = vmstate_subsection_save(f, vmsd, opaque, vmdesc, errp);
+out:
if (vmsd->post_save) {
- int ps_ret = vmsd->post_save(opaque);
- if (!ret && ps_ret) {
- ret = ps_ret;
- error_setg(errp, "post-save failed: %s", vmsd->name);
- }
+ vmsd->post_save(opaque);
}
return ret;
}
diff --git a/rust/migration/src/migratable.rs b/rust/migration/src/migratable.rs
index 7748aac2f2..b9e5e1fc15 100644
--- a/rust/migration/src/migratable.rs
+++ b/rust/migration/src/migratable.rs
@@ -406,10 +406,8 @@ fn pre_save(&self) -> Result<(), InvalidError> {
Ok(())
}
- fn post_save(&self) -> Result<(), InvalidError> {
- let state = unsafe { Box::from_raw(self.migration_state.replace(ptr::null_mut())) };
- drop(state);
- Ok(())
+ fn post_save(&self) {
+ let _ = unsafe { Box::from_raw(self.migration_state.replace(ptr::null_mut())) };
}
fn pre_load(&self) -> Result<(), InvalidError> {
diff --git a/rust/migration/src/vmstate.rs b/rust/migration/src/vmstate.rs
index edc7c70265..f34a36f680 100644
--- a/rust/migration/src/vmstate.rs
+++ b/rust/migration/src/vmstate.rs
@@ -492,6 +492,11 @@ fn from(_value: InvalidError) -> Errno {
into_neg_errno(result)
}
+unsafe extern "C" fn vmstate_post_save_cb<T, F: for<'a> FnCall<(&'a T,), ()>>(opaque: *mut c_void) {
+ // SAFETY: the function is used in T's implementation of VMState.
+ F::call((unsafe { &*(opaque.cast::<T>()) },));
+}
+
unsafe extern "C" fn vmstate_post_load_cb<
T,
F: for<'a> FnCall<(&'a T, u8), Result<(), impl Into<Errno>>>,
@@ -597,12 +602,9 @@ pub const fn pre_save<F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>>
}
#[must_use]
- pub const fn post_save<F: for<'a> FnCall<(&'a T,), Result<(), impl Into<Errno>>>>(
- mut self,
- _f: &F,
- ) -> Self {
+ pub const fn post_save<F: for<'a> FnCall<(&'a T,), ()>>(mut self, _f: &F) -> Self {
self.0.post_save = if F::IS_SOME {
- Some(vmstate_no_version_cb::<T, F>)
+ Some(vmstate_post_save_cb::<T, F>)
} else {
None
};
diff --git a/target/arm/machine.c b/target/arm/machine.c
index b0e499515c..50d80ffb68 100644
--- a/target/arm/machine.c
+++ b/target/arm/machine.c
@@ -998,7 +998,7 @@ static int cpu_pre_save(void *opaque)
return 0;
}
-static int cpu_post_save(void *opaque)
+static void cpu_post_save(void *opaque)
{
ARMCPU *cpu = opaque;
@@ -1008,8 +1008,6 @@ static int cpu_post_save(void *opaque)
cpu->cpreg_vmstate_indexes = NULL;
cpu->cpreg_vmstate_values = NULL;
-
- return 0;
}
static int cpu_pre_load(void *opaque)
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 06/43] migration: vmstate_load_state(): add some newlines
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (4 preceding siblings ...)
2026-04-23 19:19 ` [PULL 05/43] migration: make .post_save() a void function Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 07/43] migration: vmstate_save/load_state(): refactor tracing errors Fabiano Rosas
` (37 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Split logical blocks by newlines, that simplify reading the code.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-5-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 5111e7a141..dd7cd27993 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -139,6 +139,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
int ret = 0;
trace_vmstate_load_state(vmsd->name, version_id);
+
if (version_id > vmsd->version_id) {
error_setg(errp, "%s: incoming version_id %d is too new "
"for local version_id %d",
@@ -146,6 +147,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
trace_vmstate_load_state_end(vmsd->name, "too new", -EINVAL);
return -EINVAL;
}
+
if (version_id < vmsd->minimum_version_id) {
error_setg(errp, "%s: incoming version_id %d is too old "
"for local minimum version_id %d",
@@ -153,6 +155,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
trace_vmstate_load_state_end(vmsd->name, "too old", -EINVAL);
return -EINVAL;
}
+
if (vmsd->pre_load_errp) {
if (!vmsd->pre_load_errp(opaque, errp)) {
error_prepend(errp, "pre load hook failed for: '%s', "
@@ -171,9 +174,12 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
return ret;
}
}
+
while (field->name) {
bool exists = vmstate_field_exists(vmsd, field, opaque, version_id);
+
trace_vmstate_load_state_field(vmsd->name, field->name, exists);
+
if (exists) {
void *first_elem = opaque + field->offset;
int i, n_elems = vmstate_n_elems(opaque, field);
@@ -184,6 +190,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
first_elem = *(void **)first_elem;
assert(first_elem || !n_elems || !size);
}
+
for (i = 0; i < n_elems; i++) {
void *curr_elem = first_elem + size * i;
const VMStateField *inner_field;
@@ -235,6 +242,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
vmsd->name, ret);
}
}
+
if (ret < 0) {
qemu_file_set_error(f, ret);
trace_vmstate_load_field_error(field->name, ret);
@@ -249,11 +257,13 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
field++;
}
assert(field->flags == VMS_END);
+
ret = vmstate_subsection_load(f, vmsd, opaque, errp);
if (ret != 0) {
qemu_file_set_error(f, ret);
return ret;
}
+
if (vmsd->post_load_errp) {
if (!vmsd->post_load_errp(opaque, version_id, errp)) {
error_prepend(errp, "post load hook failed for: %s, version_id: "
@@ -271,7 +281,9 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
ret);
}
}
+
trace_vmstate_load_state_end(vmsd->name, "end", ret);
+
return ret;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 07/43] migration: vmstate_save/load_state(): refactor tracing errors
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (5 preceding siblings ...)
2026-04-23 19:19 ` [PULL 06/43] migration: vmstate_load_state(): add some newlines Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 08/43] migration: factor out vmstate_pre_save() from vmstate_save_state() Fabiano Rosas
` (36 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
To simplify further changes (convertion to bool+errp APIs),
let's rework some error paths:
- get rid of int ret in traces, as we are moving to bool+errp APIs
- split traces to _fail / _success (seems better than add boolean
result to the message).
- prefer short error paths (return immediately on error)
- around trace_vmstate_load_field_error(), do not call
qemu_file_set_error(), if the erroc comes from qemu_file_get_error()
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-6-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/trace-events | 6 ++++--
migration/vmstate.c | 25 +++++++++++++++----------
2 files changed, 19 insertions(+), 12 deletions(-)
diff --git a/migration/trace-events b/migration/trace-events
index 60e5087e38..27459dfb78 100644
--- a/migration/trace-events
+++ b/migration/trace-events
@@ -56,13 +56,15 @@ postcopy_page_req_sync(void *host_addr) "sync page req %p"
# vmstate.c
vmstate_load_field_error(const char *field, int ret) "field \"%s\" load failed, ret = %d"
vmstate_load_state(const char *name, int version_id) "%s v%d"
-vmstate_load_state_end(const char *name, const char *reason, int val) "%s %s/%d"
+vmstate_load_state_fail(const char *name, const char *reason) "%s %s"
+vmstate_load_state_success(const char *name) "%s"
vmstate_load_state_field(const char *name, const char *field, bool exists) "%s:%s exists=%d"
vmstate_n_elems(const char *name, int n_elems) "%s: %d"
vmstate_subsection_load(const char *parent) "%s"
vmstate_subsection_load_bad(const char *parent, const char *sub, const char *sub2) "%s: %s/%s"
vmstate_subsection_load_good(const char *parent) "%s"
-vmstate_save_state_pre_save_res(const char *name, int res) "%s/%d"
+vmstate_save_state_pre_save_fail(const char *name) "%s"
+vmstate_save_state_pre_save_success(const char *name) "%s"
vmstate_save_state_loop(const char *name, const char *field, int n_elems) "%s/%s[%d]"
vmstate_save_state_top(const char *idstr) "%s"
vmstate_subsection_save_loop(const char *name, const char *sub) "%s/%s"
diff --git a/migration/vmstate.c b/migration/vmstate.c
index dd7cd27993..87e1f04959 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -144,7 +144,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
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);
+ trace_vmstate_load_state_fail(vmsd->name, "too new");
return -EINVAL;
}
@@ -152,7 +152,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
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);
+ trace_vmstate_load_state_fail(vmsd->name, "too old");
return -EINVAL;
}
@@ -240,10 +240,10 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
error_setg(errp,
"Failed to load %s state: stream error: %d",
vmsd->name, ret);
+ trace_vmstate_load_field_error(field->name, ret);
+ return ret;
}
- }
-
- if (ret < 0) {
+ } else {
qemu_file_set_error(f, ret);
trace_vmstate_load_field_error(field->name, ret);
return ret;
@@ -269,7 +269,8 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
error_prepend(errp, "post load hook failed for: %s, version_id: "
"%d, minimum_version: %d: ", vmsd->name,
vmsd->version_id, vmsd->minimum_version_id);
- ret = -EINVAL;
+ trace_vmstate_load_state_fail(vmsd->name, "post-load");
+ return -EINVAL;
}
} else if (vmsd->post_load) {
ret = vmsd->post_load(opaque, version_id);
@@ -279,12 +280,14 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
"minimum_version: %d, ret: %d",
vmsd->name, vmsd->version_id, vmsd->minimum_version_id,
ret);
+ trace_vmstate_load_state_fail(vmsd->name, "post-load");
+ return ret;
}
}
- trace_vmstate_load_state_end(vmsd->name, "end", ret);
+ trace_vmstate_load_state_success(vmsd->name);
- return ret;
+ return 0;
}
static int vmfield_name_num(const VMStateField *start,
@@ -444,20 +447,22 @@ static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
if (vmsd->pre_save_errp) {
ret = vmsd->pre_save_errp(opaque, errp) ? 0 : -EINVAL;
- trace_vmstate_save_state_pre_save_res(vmsd->name, ret);
if (ret < 0) {
error_prepend(errp, "pre-save for %s failed: ", vmsd->name);
+ trace_vmstate_save_state_pre_save_fail(vmsd->name);
return ret;
}
} else if (vmsd->pre_save) {
ret = vmsd->pre_save(opaque);
- trace_vmstate_save_state_pre_save_res(vmsd->name, ret);
if (ret) {
error_setg(errp, "pre-save failed: %s", vmsd->name);
+ trace_vmstate_save_state_pre_save_fail(vmsd->name);
return ret;
}
}
+ trace_vmstate_save_state_pre_save_success(vmsd->name);
+
if (vmdesc) {
json_writer_str(vmdesc, "vmsd_name", vmsd->name);
json_writer_int64(vmdesc, "version", version_id);
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 08/43] migration: factor out vmstate_pre_save() from vmstate_save_state()
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (6 preceding siblings ...)
2026-04-23 19:19 ` [PULL 07/43] migration: vmstate_save/load_state(): refactor tracing errors Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 09/43] migration: factor out vmstate_save_field() " Fabiano Rosas
` (35 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Simplify vmstate_save_state() which is rather big, and simplify further
refactoring.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-7-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate.c | 39 ++++++++++++++++++++++++---------------
1 file changed, 24 insertions(+), 15 deletions(-)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 87e1f04959..605b5c59c7 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -435,32 +435,41 @@ bool vmstate_section_needed(const VMStateDescription *vmsd, void *opaque)
return true;
}
-static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
- void *opaque, JSONWriter *vmdesc,
- int version_id, Error **errp)
+static bool vmstate_pre_save(const VMStateDescription *vmsd, void *opaque,
+ Error **errp)
{
ERRP_GUARD();
- int ret = 0;
- const VMStateField *field = vmsd->fields;
-
- trace_vmstate_save_state_top(vmsd->name);
if (vmsd->pre_save_errp) {
- ret = vmsd->pre_save_errp(opaque, errp) ? 0 : -EINVAL;
- if (ret < 0) {
+ if (!vmsd->pre_save_errp(opaque, errp)) {
error_prepend(errp, "pre-save for %s failed: ", vmsd->name);
- trace_vmstate_save_state_pre_save_fail(vmsd->name);
- return ret;
+ return false;
}
} else if (vmsd->pre_save) {
- ret = vmsd->pre_save(opaque);
- if (ret) {
+ if (vmsd->pre_save(opaque) < 0) {
error_setg(errp, "pre-save failed: %s", vmsd->name);
- trace_vmstate_save_state_pre_save_fail(vmsd->name);
- return ret;
+ return false;
}
}
+ return true;
+}
+
+static 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 (!vmstate_pre_save(vmsd, opaque, errp)) {
+ trace_vmstate_save_state_pre_save_fail(vmsd->name);
+ return -EINVAL;
+ }
+
trace_vmstate_save_state_pre_save_success(vmsd->name);
if (vmdesc) {
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 09/43] migration: factor out vmstate_save_field() from vmstate_save_state()
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (7 preceding siblings ...)
2026-04-23 19:19 ` [PULL 08/43] migration: factor out vmstate_pre_save() from vmstate_save_state() Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 10/43] migration: factor out vmstate_pre_load() from vmstate_load_state() Fabiano Rosas
` (34 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Simplify vmstate_save_state() which is rather big, and simplify further
refactoring.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-8-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate.c | 40 +++++++++++++++++++++++++---------------
1 file changed, 25 insertions(+), 15 deletions(-)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 605b5c59c7..2ce2652712 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -26,6 +26,9 @@ static int vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
Error **errp);
static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, Error **errp);
+static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
+ void *opaque, JSONWriter *vmdesc,
+ int version_id, Error **errp);
/* Whether this field should exist for either save or load the VM? */
static bool
@@ -455,6 +458,26 @@ static bool vmstate_pre_save(const VMStateDescription *vmsd, void *opaque,
return true;
}
+static bool vmstate_save_field(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field,
+ JSONWriter *vmdesc, Error **errp)
+{
+ if (field->flags & VMS_STRUCT) {
+ return vmstate_save_state(f, field->vmsd, pv, vmdesc, errp) >= 0;
+ } else if (field->flags & VMS_VSTRUCT) {
+ return vmstate_save_state_v(f, field->vmsd, pv, vmdesc,
+ field->struct_version_id,
+ errp) >= 0;
+ }
+
+ if (field->info->put(f, pv, size, field, vmdesc) < 0) {
+ error_setg(errp, "put failed");
+ return false;
+ }
+
+ return true;
+}
+
static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, JSONWriter *vmdesc,
int version_id, Error **errp)
@@ -548,21 +571,8 @@ static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
vmsd_desc_field_start(vmsd, vmdesc_loop, inner_field,
i, max_elems);
- if (inner_field->flags & VMS_STRUCT) {
- ret = vmstate_save_state(f, inner_field->vmsd,
- 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,
- inner_field->struct_version_id,
- errp);
- } else {
- ret = inner_field->info->put(f, curr_elem, size,
- inner_field, vmdesc_loop);
- if (ret < 0) {
- error_setg(errp, "put failed");
- }
- }
+ ret = vmstate_save_field(f, curr_elem, size, inner_field,
+ vmdesc_loop, errp) ? 0 : -EINVAL;
written_bytes = qemu_file_transferred(f) - old_offset;
vmsd_desc_field_end(vmsd, vmdesc_loop, inner_field,
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 10/43] migration: factor out vmstate_pre_load() from vmstate_load_state()
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (8 preceding siblings ...)
2026-04-23 19:19 ` [PULL 09/43] migration: factor out vmstate_save_field() " Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 11/43] migration: factor out vmstate_load_field() " Fabiano Rosas
` (33 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Simplify vmstate_load_state() which is rather big, and simplify further
refactoring.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-9-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate.c | 46 ++++++++++++++++++++++++++++-----------------
1 file changed, 29 insertions(+), 17 deletions(-)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 2ce2652712..35dbe1550e 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -134,6 +134,33 @@ static void vmstate_handle_alloc(void *ptr, const VMStateField *field,
}
}
+static bool vmstate_pre_load(const VMStateDescription *vmsd, void *opaque,
+ Error **errp)
+{
+ ERRP_GUARD();
+
+ if (vmsd->pre_load_errp) {
+ if (!vmsd->pre_load_errp(opaque, errp)) {
+ error_prepend(errp, "pre load hook failed for: '%s', "
+ "version_id: %d, minimum version_id: %d: ",
+ vmsd->name, vmsd->version_id,
+ vmsd->minimum_version_id);
+ return false;
+ }
+ } else if (vmsd->pre_load) {
+ int 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 false;
+ }
+ }
+
+ return true;
+}
+
int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, int version_id, Error **errp)
{
@@ -159,23 +186,8 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
return -EINVAL;
}
- if (vmsd->pre_load_errp) {
- if (!vmsd->pre_load_errp(opaque, errp)) {
- error_prepend(errp, "pre load hook failed for: '%s', "
- "version_id: %d, minimum version_id: %d: ",
- vmsd->name, vmsd->version_id,
- vmsd->minimum_version_id);
- return -EINVAL;
- }
- } 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;
- }
+ if (!vmstate_pre_load(vmsd, opaque, errp)) {
+ return -EINVAL;
}
while (field->name) {
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 11/43] migration: factor out vmstate_load_field() from vmstate_load_state()
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (9 preceding siblings ...)
2026-04-23 19:19 ` [PULL 10/43] migration: factor out vmstate_pre_load() from vmstate_load_state() Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 12/43] migration: factor out vmstate_post_load() " Fabiano Rosas
` (32 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Simplify vmstate_load_state() which is rather big, and simplify further
refactoring.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-10-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate.c | 41 +++++++++++++++++++++++------------------
1 file changed, 23 insertions(+), 18 deletions(-)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 35dbe1550e..32944c0444 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -161,6 +161,27 @@ static bool vmstate_pre_load(const VMStateDescription *vmsd, void *opaque,
return true;
}
+static bool vmstate_load_field(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
+{
+ if (field->flags & VMS_STRUCT) {
+ return vmstate_load_state(f, field->vmsd, pv, field->vmsd->version_id,
+ errp) >= 0;
+ } else if (field->flags & VMS_VSTRUCT) {
+ return vmstate_load_state(f, field->vmsd, pv, field->struct_version_id,
+ errp) >= 0;
+ }
+
+ if (field->info->get(f, pv, size, field) < 0) {
+ error_setg(errp,
+ "Failed to load element of type %s for %s",
+ field->info->name, field->name);
+ return false;
+ }
+
+ return true;
+}
+
int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, int version_id, Error **errp)
{
@@ -225,24 +246,8 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
inner_field = field;
}
- if (inner_field->flags & VMS_STRUCT) {
- ret = vmstate_load_state(f, inner_field->vmsd, curr_elem,
- 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,
- 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);
- }
- }
+ ret = vmstate_load_field(f, curr_elem, size, inner_field,
+ errp) ? 0 : -EINVAL;
/* If we used a fake temp field.. free it now */
if (inner_field != field) {
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 12/43] migration: factor out vmstate_post_load() from vmstate_load_state()
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (10 preceding siblings ...)
2026-04-23 19:19 ` [PULL 11/43] migration: factor out vmstate_load_field() " Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 13/43] migration: convert vmstate_subsection_save/load functions to bool Fabiano Rosas
` (31 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Simplify vmstate_load_state() which is rather big, and simplify further
refactoring.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-11-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate.c | 49 +++++++++++++++++++++++++++------------------
1 file changed, 30 insertions(+), 19 deletions(-)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 32944c0444..53b57c09d8 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -182,6 +182,33 @@ static bool vmstate_load_field(QEMUFile *f, void *pv, size_t size,
return true;
}
+static bool vmstate_post_load(const VMStateDescription *vmsd,
+ void *opaque, int version_id, Error **errp)
+{
+ ERRP_GUARD();
+
+ if (vmsd->post_load_errp) {
+ if (!vmsd->post_load_errp(opaque, version_id, errp)) {
+ error_prepend(errp, "post load hook failed for: %s, version_id: "
+ "%d, minimum_version: %d: ", vmsd->name,
+ vmsd->version_id, vmsd->minimum_version_id);
+ return false;
+ }
+ } else if (vmsd->post_load) {
+ int 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);
+ return false;
+ }
+ }
+
+ return true;
+}
+
int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, int version_id, Error **errp)
{
@@ -284,25 +311,9 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
return ret;
}
- if (vmsd->post_load_errp) {
- if (!vmsd->post_load_errp(opaque, version_id, errp)) {
- error_prepend(errp, "post load hook failed for: %s, version_id: "
- "%d, minimum_version: %d: ", vmsd->name,
- vmsd->version_id, vmsd->minimum_version_id);
- trace_vmstate_load_state_fail(vmsd->name, "post-load");
- return -EINVAL;
- }
- } 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_fail(vmsd->name, "post-load");
- return ret;
- }
+ if (!vmstate_post_load(vmsd, opaque, version_id, errp)) {
+ trace_vmstate_load_state_fail(vmsd->name, "post-load");
+ return -EINVAL;
}
trace_vmstate_load_state_success(vmsd->name);
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 13/43] migration: convert vmstate_subsection_save/load functions to bool
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (11 preceding siblings ...)
2026-04-23 19:19 ` [PULL 12/43] migration: factor out vmstate_post_load() " Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 14/43] migration: VMStateInfo: introduce new handlers with errp Fabiano Rosas
` (30 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Convert them to bool return value, as preparation to further
convertion of vmstate_save/load_state().
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-12-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate.c | 57 +++++++++++++++++++++------------------------
1 file changed, 26 insertions(+), 31 deletions(-)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 53b57c09d8..8825aa3b1f 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -21,11 +21,11 @@
#include "qemu/error-report.h"
#include "trace.h"
-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, Error **errp);
+static bool vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
+ void *opaque, JSONWriter *vmdesc,
+ Error **errp);
+static bool vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
+ void *opaque, Error **errp);
static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, JSONWriter *vmdesc,
int version_id, Error **errp);
@@ -305,10 +305,9 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
}
assert(field->flags == VMS_END);
- ret = vmstate_subsection_load(f, vmsd, opaque, errp);
- if (ret != 0) {
- qemu_file_set_error(f, ret);
- return ret;
+ if (!vmstate_subsection_load(f, vmsd, opaque, errp)) {
+ qemu_file_set_error(f, -EINVAL);
+ return -EINVAL;
}
if (!vmstate_post_load(vmsd, opaque, version_id, errp)) {
@@ -637,7 +636,7 @@ static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
json_writer_end_array(vmdesc);
}
- ret = vmstate_subsection_save(f, vmsd, opaque, vmdesc, errp);
+ ret = vmstate_subsection_save(f, vmsd, opaque, vmdesc, errp) ? 0 : -EINVAL;
out:
if (vmsd->post_save) {
@@ -667,15 +666,14 @@ int vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd,
errp);
}
-static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
- void *opaque, Error **errp)
+static bool vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
+ void *opaque, Error **errp)
{
ERRP_GUARD();
trace_vmstate_subsection_load(vmsd->name);
while (qemu_peek_byte(f, 0) == QEMU_VM_SUBSECTION) {
char idstr[256], *idstr_ret;
- int ret;
uint8_t version_id, len, size;
const VMStateDescription *sub_vmsd;
@@ -683,12 +681,12 @@ static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
if (len < strlen(vmsd->name) + 1) {
/* subsection name has to be "section_name/a" */
trace_vmstate_subsection_load_bad(vmsd->name, "(short)", "");
- return 0;
+ return true;
}
size = qemu_peek_buffer(f, (uint8_t **)&idstr_ret, len, 2);
if (size != len) {
trace_vmstate_subsection_load_bad(vmsd->name, "(peek fail)", "");
- return 0;
+ return true;
}
memcpy(idstr, idstr_ret, size);
idstr[size] = 0;
@@ -696,41 +694,39 @@ static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
if (strncmp(vmsd->name, idstr, strlen(vmsd->name)) != 0) {
trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(prefix)");
/* it doesn't have a valid subsection name */
- return 0;
+ return true;
}
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;
+ return false;
}
qemu_file_skip(f, 1); /* subsection */
qemu_file_skip(f, 1); /* len */
qemu_file_skip(f, len); /* idstr */
version_id = qemu_get_be32(f);
- ret = vmstate_load_state(f, sub_vmsd, opaque, version_id, errp);
- if (ret) {
+ if (vmstate_load_state(f, sub_vmsd, opaque, version_id, errp) < 0) {
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;
+ "Loading VM subsection '%s' in '%s' failed: ",
+ idstr, vmsd->name);
+ return false;
}
}
trace_vmstate_subsection_load_good(vmsd->name);
- return 0;
+ return true;
}
-static int vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
- void *opaque, JSONWriter *vmdesc,
- Error **errp)
+static bool vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
+ void *opaque, JSONWriter *vmdesc,
+ Error **errp)
{
const VMStateDescription * const *sub = vmsd->subsections;
bool vmdesc_has_subsections = false;
- int ret = 0;
trace_vmstate_subsection_save_top(vmsd->name);
while (sub && *sub) {
@@ -754,9 +750,8 @@ 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(f, vmsdsub, opaque, vmdesc, errp);
- if (ret) {
- return ret;
+ if (vmstate_save_state(f, vmsdsub, opaque, vmdesc, errp) < 0) {
+ return false;
}
if (vmdesc) {
@@ -770,5 +765,5 @@ static int vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
json_writer_end_array(vmdesc);
}
- return ret;
+ return true;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 14/43] migration: VMStateInfo: introduce new handlers with errp
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (12 preceding siblings ...)
2026-04-23 19:19 ` [PULL 13/43] migration: convert vmstate_subsection_save/load functions to bool Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 15/43] migration: introduce vmstate_load_vmsd() and vmstate_save_vmsd() Fabiano Rosas
` (29 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Add new APIs with errp, to allow handlers report good
error messages. We'll convert existing handlers soon.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-13-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/migration/vmstate.h | 17 ++++++++++++++---
migration/vmstate.c | 4 ++++
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 9d42cf7a64..65683d8d87 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -32,12 +32,16 @@
typedef struct VMStateInfo VMStateInfo;
typedef struct VMStateField VMStateField;
-/* VMStateInfo allows customized migration of objects that don't fit in
+/*
+ * VMStateInfo allows customized migration of objects that don't fit in
* any category in VMStateFlags. Additional information is always passed
- * into get and put in terms of field and vmdesc parameters. However
+ * into load and save in terms of field and vmdesc parameters. However
* these two parameters should only be used in cases when customized
* handling is needed, such as QTAILQ. For primitive data types such as
- * integer, field and vmdesc parameters should be ignored inside get/put.
+ * integer, field and vmdesc parameters should be ignored inside load/save.
+ *
+ * @get and @put are deprecated copies of @load and @save. For new interfaces
+ * use @load and @save.
*/
struct VMStateInfo {
const char *name;
@@ -46,6 +50,13 @@ struct VMStateInfo {
int coroutine_mixed_fn (*put)(QEMUFile *f, void *pv, size_t size,
const VMStateField *field,
JSONWriter *vmdesc);
+ bool coroutine_mixed_fn (*load)(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field,
+ Error **errp);
+ bool coroutine_mixed_fn (*save)(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field,
+ JSONWriter *vmdesc,
+ Error **errp);
};
enum VMStateFlags {
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 8825aa3b1f..f16626d7d1 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -170,6 +170,8 @@ static bool vmstate_load_field(QEMUFile *f, void *pv, size_t size,
} else if (field->flags & VMS_VSTRUCT) {
return vmstate_load_state(f, field->vmsd, pv, field->struct_version_id,
errp) >= 0;
+ } else if (field->info->load) {
+ return field->info->load(f, pv, size, field, errp);
}
if (field->info->get(f, pv, size, field) < 0) {
@@ -495,6 +497,8 @@ static bool vmstate_save_field(QEMUFile *f, void *pv, size_t size,
return vmstate_save_state_v(f, field->vmsd, pv, vmdesc,
field->struct_version_id,
errp) >= 0;
+ } else if (field->info->save) {
+ return field->info->save(f, pv, size, field, vmdesc, errp);
}
if (field->info->put(f, pv, size, field, vmdesc) < 0) {
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 15/43] migration: introduce vmstate_load_vmsd() and vmstate_save_vmsd()
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (13 preceding siblings ...)
2026-04-23 19:19 ` [PULL 14/43] migration: VMStateInfo: introduce new handlers with errp Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 16/43] migration/cpr: move to new migration APIs Fabiano Rosas
` (28 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Introduce new APIs, returning bool.
The analysis
https://lore.kernel.org/qemu-devel/aQDdRn8t0B8oE3gf@x1.local/
shows, that vmstate_load_state() return value actually only
used to check for success, specific errno values doesn't make
sense.
With this commit we introduce new functions with modern bool
interface, and in following commits we'll update the
code base to use them, starting from migration/ code, and
finally we will remove old vmstate_load_state() and
vmstate_save_state().
This patch reworks existing functions to new one, so that
old interfaces are simple wrappers, which will be easy to
remove later.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-14-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/migration/vmstate.h | 9 ++++
migration/vmstate.c | 89 ++++++++++++++++++++-----------------
2 files changed, 58 insertions(+), 40 deletions(-)
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 65683d8d87..66c2e87bd3 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -1257,10 +1257,19 @@ extern const VMStateInfo vmstate_info_qlist;
.flags = VMS_END, \
}
+/*
+ * vmstate_load_state() and vmstate_save_state() are
+ * depreacated, use vmstate_load_vmsd() and vmstate_save_vmsd()
+ * instead.
+ */
int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, int version_id, Error **errp);
int vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, JSONWriter *vmdesc, Error **errp);
+bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
+ void *opaque, int version_id, Error **errp);
+bool vmstate_save_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
+ void *opaque, JSONWriter *vmdesc, Error **errp);
bool vmstate_section_needed(const VMStateDescription *vmsd, void *opaque);
diff --git a/migration/vmstate.c b/migration/vmstate.c
index f16626d7d1..e98b5f5346 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -26,7 +26,7 @@ static bool vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
Error **errp);
static bool vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, Error **errp);
-static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
+static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, JSONWriter *vmdesc,
int version_id, Error **errp);
@@ -165,11 +165,11 @@ static bool vmstate_load_field(QEMUFile *f, void *pv, size_t size,
const VMStateField *field, Error **errp)
{
if (field->flags & VMS_STRUCT) {
- return vmstate_load_state(f, field->vmsd, pv, field->vmsd->version_id,
- errp) >= 0;
+ return vmstate_load_vmsd(f, field->vmsd, pv, field->vmsd->version_id,
+ errp);
} else if (field->flags & VMS_VSTRUCT) {
- return vmstate_load_state(f, field->vmsd, pv, field->struct_version_id,
- errp) >= 0;
+ return vmstate_load_vmsd(f, field->vmsd, pv, field->struct_version_id,
+ errp);
} else if (field->info->load) {
return field->info->load(f, pv, size, field, errp);
}
@@ -211,12 +211,11 @@ static bool vmstate_post_load(const VMStateDescription *vmsd,
return true;
}
-int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
+bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
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);
@@ -225,7 +224,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
"for local version_id %d",
vmsd->name, version_id, vmsd->version_id);
trace_vmstate_load_state_fail(vmsd->name, "too new");
- return -EINVAL;
+ return false;
}
if (version_id < vmsd->minimum_version_id) {
@@ -233,11 +232,11 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
"for local minimum version_id %d",
vmsd->name, version_id, vmsd->minimum_version_id);
trace_vmstate_load_state_fail(vmsd->name, "too old");
- return -EINVAL;
+ return false;
}
if (!vmstate_pre_load(vmsd, opaque, errp)) {
- return -EINVAL;
+ return false;
}
while (field->name) {
@@ -257,6 +256,7 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
}
for (i = 0; i < n_elems; i++) {
+ bool ok;
void *curr_elem = first_elem + size * i;
const VMStateField *inner_field;
@@ -275,33 +275,32 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
inner_field = field;
}
- ret = vmstate_load_field(f, curr_elem, size, inner_field,
- errp) ? 0 : -EINVAL;
+ ok = vmstate_load_field(f, curr_elem, size, inner_field, errp);
/* If we used a fake temp field.. free it now */
if (inner_field != field) {
g_clear_pointer((gpointer *)&inner_field, g_free);
}
- if (ret >= 0) {
- ret = qemu_file_get_error(f);
+ if (ok) {
+ int ret = qemu_file_get_error(f);
if (ret < 0) {
error_setg(errp,
"Failed to load %s state: stream error: %d",
vmsd->name, ret);
trace_vmstate_load_field_error(field->name, ret);
- return ret;
+ return false;
}
} else {
- qemu_file_set_error(f, ret);
- trace_vmstate_load_field_error(field->name, ret);
- return ret;
+ qemu_file_set_error(f, -EINVAL);
+ trace_vmstate_load_field_error(field->name, -EINVAL);
+ return false;
}
}
} else if (field->flags & VMS_MUST_EXIST) {
error_setg(errp, "Input validation failed: %s/%s version_id: %d",
vmsd->name, field->name, vmsd->version_id);
- return -1;
+ return false;
}
field++;
}
@@ -309,17 +308,16 @@ int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
if (!vmstate_subsection_load(f, vmsd, opaque, errp)) {
qemu_file_set_error(f, -EINVAL);
- return -EINVAL;
+ return false;
}
if (!vmstate_post_load(vmsd, opaque, version_id, errp)) {
trace_vmstate_load_state_fail(vmsd->name, "post-load");
- return -EINVAL;
+ return false;
}
trace_vmstate_load_state_success(vmsd->name);
-
- return 0;
+ return true;
}
static int vmfield_name_num(const VMStateField *start,
@@ -492,11 +490,10 @@ static bool vmstate_save_field(QEMUFile *f, void *pv, size_t size,
JSONWriter *vmdesc, Error **errp)
{
if (field->flags & VMS_STRUCT) {
- return vmstate_save_state(f, field->vmsd, pv, vmdesc, errp) >= 0;
+ return vmstate_save_vmsd(f, field->vmsd, pv, vmdesc, errp);
} else if (field->flags & VMS_VSTRUCT) {
- return vmstate_save_state_v(f, field->vmsd, pv, vmdesc,
- field->struct_version_id,
- errp) >= 0;
+ return vmstate_save_vmsd_v(f, field->vmsd, pv, vmdesc,
+ field->struct_version_id, errp);
} else if (field->info->save) {
return field->info->save(f, pv, size, field, vmdesc, errp);
}
@@ -509,19 +506,19 @@ static bool vmstate_save_field(QEMUFile *f, void *pv, size_t size,
return true;
}
-static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
+static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, JSONWriter *vmdesc,
int version_id, Error **errp)
{
ERRP_GUARD();
- int ret = 0;
+ bool ok = true;
const VMStateField *field = vmsd->fields;
trace_vmstate_save_state_top(vmsd->name);
if (!vmstate_pre_save(vmsd, opaque, errp)) {
trace_vmstate_save_state_pre_save_fail(vmsd->name);
- return -EINVAL;
+ return false;
}
trace_vmstate_save_state_pre_save_success(vmsd->name);
@@ -602,8 +599,8 @@ static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
vmsd_desc_field_start(vmsd, vmdesc_loop, inner_field,
i, max_elems);
- ret = vmstate_save_field(f, curr_elem, size, inner_field,
- vmdesc_loop, errp) ? 0 : -EINVAL;
+ ok = vmstate_save_field(f, curr_elem, size, inner_field,
+ vmdesc_loop, errp);
written_bytes = qemu_file_transferred(f) - old_offset;
vmsd_desc_field_end(vmsd, vmdesc_loop, inner_field,
@@ -614,7 +611,7 @@ static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
g_clear_pointer((gpointer *)&inner_field, g_free);
}
- if (ret) {
+ if (!ok) {
error_prepend(errp, "Save of field %s/%s failed: ",
vmsd->name, field->name);
goto out;
@@ -640,13 +637,13 @@ static int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
json_writer_end_array(vmdesc);
}
- ret = vmstate_subsection_save(f, vmsd, opaque, vmdesc, errp) ? 0 : -EINVAL;
+ ok = vmstate_subsection_save(f, vmsd, opaque, vmdesc, errp);
out:
if (vmsd->post_save) {
vmsd->post_save(opaque);
}
- return ret;
+ return ok;
}
static const VMStateDescription *
@@ -663,11 +660,11 @@ vmstate_get_subsection(const VMStateDescription * const *sub,
return NULL;
}
-int vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd,
+bool vmstate_save_vmsd(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);
+ return vmstate_save_vmsd_v(f, vmsd, opaque, vmdesc_id, vmsd->version_id,
+ errp);
}
static bool vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
@@ -712,7 +709,7 @@ static bool vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
qemu_file_skip(f, len); /* idstr */
version_id = qemu_get_be32(f);
- if (vmstate_load_state(f, sub_vmsd, opaque, version_id, errp) < 0) {
+ if (!vmstate_load_vmsd(f, sub_vmsd, opaque, version_id, errp)) {
trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(child)");
error_prepend(errp,
"Loading VM subsection '%s' in '%s' failed: ",
@@ -754,7 +751,7 @@ static bool 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);
- if (vmstate_save_state(f, vmsdsub, opaque, vmdesc, errp) < 0) {
+ if (!vmstate_save_vmsd(f, vmsdsub, opaque, vmdesc, errp)) {
return false;
}
@@ -771,3 +768,15 @@ static bool vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
return true;
}
+
+int vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd,
+ void *opaque, JSONWriter *vmdesc_id, Error **errp)
+{
+ return vmstate_save_vmsd(f, vmsd, opaque, vmdesc_id, errp) ? 0 : -EINVAL;
+}
+
+int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
+ void *opaque, int version_id, Error **errp)
+{
+ return vmstate_load_vmsd(f, vmsd, opaque, version_id, errp) ? 0 : -EINVAL;
+}
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 16/43] migration/cpr: move to new migration APIs
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (14 preceding siblings ...)
2026-04-23 19:19 ` [PULL 15/43] migration: introduce vmstate_load_vmsd() and vmstate_save_vmsd() Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 17/43] migration/savevm: " Fabiano Rosas
` (27 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-15-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/migration/cpr.h | 2 +-
migration/cpr.c | 22 +++++++++-------------
2 files changed, 10 insertions(+), 14 deletions(-)
diff --git a/include/migration/cpr.h b/include/migration/cpr.h
index 5850fd1788..96ce26e711 100644
--- a/include/migration/cpr.h
+++ b/include/migration/cpr.h
@@ -43,7 +43,7 @@ void cpr_set_incoming_mode(MigMode mode);
bool cpr_is_incoming(void);
bool cpr_state_save(MigrationChannel *channel, Error **errp);
-int cpr_state_load(MigrationChannel *channel, Error **errp);
+bool cpr_state_load(MigrationChannel *channel, Error **errp);
void cpr_state_close(void);
struct QIOChannel *cpr_state_ioc(void);
diff --git a/migration/cpr.c b/migration/cpr.c
index a0b37007f5..05266dfcfd 100644
--- a/migration/cpr.c
+++ b/migration/cpr.c
@@ -178,7 +178,6 @@ bool cpr_is_incoming(void)
bool cpr_state_save(MigrationChannel *channel, Error **errp)
{
- int ret;
QEMUFile *f;
MigMode mode = migrate_mode();
@@ -199,8 +198,7 @@ bool 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, errp);
- if (ret) {
+ if (!vmstate_save_vmsd(f, &vmstate_cpr_state, &cpr_state, 0, errp)) {
qemu_fclose(f);
return false;
}
@@ -223,9 +221,8 @@ bool cpr_state_save(MigrationChannel *channel, Error **errp)
return true;
}
-int cpr_state_load(MigrationChannel *channel, Error **errp)
+bool cpr_state_load(MigrationChannel *channel, Error **errp)
{
- int ret;
uint32_t v;
QEMUFile *f;
MigMode mode = 0;
@@ -241,10 +238,10 @@ int cpr_state_load(MigrationChannel *channel, Error **errp)
cpr_set_incoming_mode(mode);
f = cpr_transfer_input(channel, errp);
} else {
- return 0;
+ return true;
}
if (!f) {
- return -1;
+ return false;
}
trace_cpr_state_load(MigMode_str(mode));
@@ -254,19 +251,18 @@ int cpr_state_load(MigrationChannel *channel, Error **errp)
if (v != QEMU_CPR_FILE_MAGIC) {
error_setg(errp, "Not a migration stream (bad magic %x)", v);
qemu_fclose(f);
- return -EINVAL;
+ return false;
}
v = qemu_get_be32(f);
if (v != QEMU_CPR_FILE_VERSION) {
error_setg(errp, "Unsupported migration stream version %d", v);
qemu_fclose(f);
- return -ENOTSUP;
+ return false;
}
- ret = vmstate_load_state(f, &vmstate_cpr_state, &cpr_state, 1, errp);
- if (ret) {
+ if (!vmstate_load_vmsd(f, &vmstate_cpr_state, &cpr_state, 1, errp)) {
qemu_fclose(f);
- return ret;
+ return false;
}
if (migrate_mode() == MIG_MODE_CPR_EXEC) {
@@ -280,7 +276,7 @@ int cpr_state_load(MigrationChannel *channel, Error **errp)
*/
cpr_state_file = f;
- return ret;
+ return true;
}
void cpr_state_close(void)
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 17/43] migration/savevm: move to new migration APIs
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (15 preceding siblings ...)
2026-04-23 19:19 ` [PULL 16/43] migration/cpr: move to new migration APIs Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 18/43] hw/s390x/css: drop use of .err_hint for vmstate Fabiano Rosas
` (26 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-16-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/savevm.c | 105 ++++++++++++++++++++++++---------------------
1 file changed, 55 insertions(+), 50 deletions(-)
diff --git a/migration/savevm.c b/migration/savevm.c
index 699d2c9f8b..8115203b51 100644
--- a/migration/savevm.c
+++ b/migration/savevm.c
@@ -205,27 +205,28 @@ void timer_get(QEMUFile *f, QEMUTimer *ts)
* Not in vmstate.c to not add qemu-timer.c as dependency to vmstate.c
*/
-static int get_timer(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_timer(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
QEMUTimer *v = pv;
timer_get(f, v);
- return 0;
+ return true;
}
-static int put_timer(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_timer(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
QEMUTimer *v = pv;
timer_put(f, v);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_timer = {
.name = "timer",
- .get = get_timer,
- .put = put_timer,
+ .load = load_timer,
+ .save = save_timer,
};
@@ -297,7 +298,7 @@ static uint32_t get_validatable_capabilities_count(void)
return result;
}
-static int configuration_pre_save(void *opaque)
+static bool configuration_pre_save(void *opaque, Error **errp)
{
SaveState *state = opaque;
const char *current_name = MACHINE_GET_CLASS(current_machine)->name;
@@ -318,7 +319,7 @@ static int configuration_pre_save(void *opaque)
}
state->uuid = qemu_uuid;
- return 0;
+ return true;
}
static void configuration_post_save(void *opaque)
@@ -330,7 +331,7 @@ static void configuration_post_save(void *opaque)
state->caps_count = 0;
}
-static int configuration_pre_load(void *opaque)
+static bool configuration_pre_load(void *opaque, Error **errp)
{
SaveState *state = opaque;
@@ -339,7 +340,7 @@ static int configuration_pre_load(void *opaque)
* minimum possible value for this CPU.
*/
state->target_page_bits = migration_legacy_page_bits();
- return 0;
+ return true;
}
static bool configuration_validate_capabilities(SaveState *state)
@@ -376,28 +377,31 @@ static bool configuration_validate_capabilities(SaveState *state)
return ret;
}
-static int configuration_post_load(void *opaque, int version_id)
+static bool configuration_post_load(void *opaque, int version_id, Error **errp)
{
SaveState *state = opaque;
const char *current_name = MACHINE_GET_CLASS(current_machine)->name;
- int ret = 0;
+ bool ok = true;
if (strncmp(state->name, current_name, state->len) != 0) {
- error_report("Machine type received is '%.*s' and local is '%s'",
- (int) state->len, state->name, current_name);
- ret = -EINVAL;
+ error_setg(errp,
+ "Machine type received is '%.*s' and local is '%s'",
+ (int) state->len, state->name, current_name);
+ ok = false;
goto out;
}
if (state->target_page_bits != qemu_target_page_bits()) {
- error_report("Received TARGET_PAGE_BITS is %d but local is %d",
- state->target_page_bits, qemu_target_page_bits());
- ret = -EINVAL;
+ error_setg(errp,
+ "Received TARGET_PAGE_BITS is %d but local is %d",
+ state->target_page_bits, qemu_target_page_bits());
+ ok = false;
goto out;
}
if (!configuration_validate_capabilities(state)) {
- ret = -EINVAL;
+ error_setg(errp, "Failed to validate capabilities");
+ ok = false;
goto out;
}
@@ -409,11 +413,12 @@ out:
state->capabilities = NULL;
state->caps_count = 0;
- return ret;
+ return ok;
}
-static int get_capability(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_capability(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field,
+ Error **errp)
{
MigrationCapability *capability = pv;
char capability_str[UINT8_MAX + 1];
@@ -426,15 +431,16 @@ static int get_capability(QEMUFile *f, void *pv, size_t size,
for (i = 0; i < MIGRATION_CAPABILITY__MAX; i++) {
if (!strcmp(MigrationCapability_str(i), capability_str)) {
*capability = i;
- return 0;
+ return true;
}
}
- error_report("Received unknown capability %s", capability_str);
- return -EINVAL;
+ error_setg(errp, "Received unknown capability %s", capability_str);
+ return false;
}
-static int put_capability(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_capability(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
MigrationCapability *capability = pv;
const char *capability_str = MigrationCapability_str(*capability);
@@ -443,13 +449,13 @@ static int put_capability(QEMUFile *f, void *pv, size_t size,
qemu_put_byte(f, len);
qemu_put_buffer(f, (uint8_t *)capability_str, len);
- return 0;
+ return true;
}
static const VMStateInfo vmstate_info_capability = {
.name = "capability",
- .get = get_capability,
- .put = put_capability,
+ .load = load_capability,
+ .save = save_capability,
};
/* The target-page-bits subsection is present only if the
@@ -539,9 +545,9 @@ static const VMStateDescription vmstate_uuid = {
static const VMStateDescription vmstate_configuration = {
.name = "configuration",
.version_id = 1,
- .pre_load = configuration_pre_load,
- .post_load = configuration_post_load,
- .pre_save = configuration_pre_save,
+ .pre_load_errp = configuration_pre_load,
+ .post_load_errp = configuration_post_load,
+ .pre_save_errp = configuration_pre_save,
.post_save = configuration_post_save,
.fields = (const VMStateField[]) {
VMSTATE_UINT32(len, SaveState),
@@ -969,8 +975,13 @@ static int vmstate_load(QEMUFile *f, SaveStateEntry *se, Error **errp)
}
return ret;
}
- return vmstate_load_state(f, se->vmsd, se->opaque, se->load_version_id,
- errp);
+
+ if (!vmstate_load_vmsd(f, se->vmsd, se->opaque, se->load_version_id,
+ errp)) {
+ return -EINVAL;
+ }
+
+ return 0;
}
static void vmstate_save_old_style(QEMUFile *f, SaveStateEntry *se,
@@ -1028,8 +1039,6 @@ static void save_section_footer(QEMUFile *f, SaveStateEntry *se)
static int vmstate_save(QEMUFile *f, SaveStateEntry *se, JSONWriter *vmdesc,
Error **errp)
{
- int ret;
-
if ((!se->ops || !se->ops->save_state) && !se->vmsd) {
return 0;
}
@@ -1050,10 +1059,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(f, se->vmsd, se->opaque, vmdesc,
- errp);
- if (ret) {
- return ret;
+ if (!vmstate_save_vmsd(f, se->vmsd, se->opaque, vmdesc, errp)) {
+ return -EINVAL;
}
}
@@ -1311,8 +1318,8 @@ static void qemu_savevm_send_configuration(MigrationState *s, QEMUFile *f)
json_writer_start_object(vmdesc, "configuration");
}
- vmstate_save_state(f, &vmstate_configuration, &savevm_state,
- vmdesc, &local_err);
+ vmstate_save_vmsd(f, &vmstate_configuration, &savevm_state,
+ vmdesc, &local_err);
if (local_err) {
error_report_err(local_err);
}
@@ -2721,7 +2728,6 @@ qemu_loadvm_section_part_end(QEMUFile *f, uint8_t type, Error **errp)
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) {
@@ -2752,10 +2758,9 @@ static int qemu_loadvm_state_header(QEMUFile *f, Error **errp)
return -EINVAL;
}
- ret = vmstate_load_state(f, &vmstate_configuration, &savevm_state, 0,
- errp);
- if (ret) {
- return ret;
+ if (!vmstate_load_vmsd(f, &vmstate_configuration, &savevm_state, 0,
+ errp)) {
+ return -EINVAL;
}
}
return 0;
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 18/43] hw/s390x/css: drop use of .err_hint for vmstate
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (16 preceding siblings ...)
2026-04-23 19:19 ` [PULL 17/43] migration/savevm: " Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 19/43] migration: drop VMStateField.err_hint Fabiano Rosas
` (25 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy, Eric Farman
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
That's the only usage through the whole base. Doesn't
worth keeping the whole complexity. And 2.7 machines were
long ago.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Eric Farman <farman@linux.ibm.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-17-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
hw/s390x/css.c | 15 ++++++++-------
1 file changed, 8 insertions(+), 7 deletions(-)
diff --git a/hw/s390x/css.c b/hw/s390x/css.c
index 04ce3178a0..df5f0bc27f 100644
--- a/hw/s390x/css.c
+++ b/hw/s390x/css.c
@@ -192,10 +192,6 @@ static const VMStateDescription vmstate_schdev_orb = {
static int subch_dev_post_load(void *opaque, int version_id);
static int subch_dev_pre_save(void *opaque);
-const char err_hint_devno[] = "Devno mismatch, tried to load wrong section!"
- " Likely reason: some sequences of plug and unplug can break"
- " migration for machine versions prior to 2.7 (known design flaw).";
-
const VMStateDescription vmstate_subch_dev = {
.name = "s390_subch_dev",
.version_id = 1,
@@ -203,10 +199,15 @@ const VMStateDescription vmstate_subch_dev = {
.post_load = subch_dev_post_load,
.pre_save = subch_dev_pre_save,
.fields = (const VMStateField[]) {
- VMSTATE_UINT8_EQUAL(cssid, SubchDev, "Bug!"),
- VMSTATE_UINT8_EQUAL(ssid, SubchDev, "Bug!"),
+ VMSTATE_UINT8_EQUAL(cssid, SubchDev, NULL),
+ VMSTATE_UINT8_EQUAL(ssid, SubchDev, NULL),
VMSTATE_UINT16(migrated_schid, SubchDev),
- VMSTATE_UINT16_EQUAL(devno, SubchDev, err_hint_devno),
+ /*
+ * If devno mismatch on target, it may be due to some
+ * sequences of plug and unplug breaks migration for
+ * machine versions prior to 2.7 (known design flaw).
+ */
+ VMSTATE_UINT16_EQUAL(devno, SubchDev, NULL),
VMSTATE_BOOL(thinint_active, SubchDev),
VMSTATE_STRUCT(curr_status, SubchDev, 0, vmstate_schib, SCHIB),
VMSTATE_UINT8_ARRAY(sense_data, SubchDev, 32),
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 19/43] migration: drop VMStateField.err_hint
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (17 preceding siblings ...)
2026-04-23 19:19 ` [PULL 18/43] hw/s390x/css: drop use of .err_hint for vmstate Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 20/43] migration/vmstate-types: move to new migration APIs Fabiano Rosas
` (24 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Xu, Vladimir Sementsov-Ogievskiy, Eric Farman,
Akihiko Odaki
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
The field is unused, all users of VMSTATE_*_EQUAL pass _err_hint=NULL.
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Acked-by: Eric Farman <farman@linux.ibm.com> # s390
Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-18-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
hw/block/fdc.c | 2 +-
hw/display/qxl.c | 4 ++--
hw/display/vga.c | 2 +-
hw/display/virtio-gpu.c | 2 +-
hw/display/vmware_vga.c | 2 +-
hw/i386/vmmouse.c | 2 +-
hw/ide/ahci.c | 2 +-
hw/intc/openpic.c | 2 +-
hw/intc/spapr_xive.c | 2 +-
hw/intc/xics.c | 2 +-
hw/intc/xive.c | 2 +-
hw/nvram/eeprom93xx.c | 2 +-
hw/pci/pci.c | 2 +-
hw/pci/pcie_aer.c | 2 +-
hw/ppc/spapr_iommu.c | 2 +-
hw/ppc/spapr_pci.c | 4 ++--
hw/ppc/spapr_vio.c | 4 ++--
hw/s390x/css.c | 6 +++---
hw/usb/hcd-uhci.c | 2 +-
include/migration/vmstate.h | 36 +++++++++++++++++-------------------
migration/vmstate-types.c | 15 ---------------
target/ppc/machine.c | 6 +++---
22 files changed, 44 insertions(+), 61 deletions(-)
diff --git a/hw/block/fdc.c b/hw/block/fdc.c
index 4585640af9..2c1681b7d0 100644
--- a/hw/block/fdc.c
+++ b/hw/block/fdc.c
@@ -1051,7 +1051,7 @@ const VMStateDescription vmstate_fdc = {
VMSTATE_UINT8(config, FDCtrl),
VMSTATE_UINT8(lock, FDCtrl),
VMSTATE_UINT8(pwrd, FDCtrl),
- VMSTATE_UINT8_EQUAL(num_floppies, FDCtrl, NULL),
+ VMSTATE_UINT8_EQUAL(num_floppies, FDCtrl),
VMSTATE_STRUCT_ARRAY(drives, FDCtrl, MAX_FD, 1,
vmstate_fdrive, FDrive),
VMSTATE_END_OF_LIST()
diff --git a/hw/display/qxl.c b/hw/display/qxl.c
index 3d4b563556..f8fd7ee069 100644
--- a/hw/display/qxl.c
+++ b/hw/display/qxl.c
@@ -2442,12 +2442,12 @@ static const VMStateDescription qxl_vmstate = {
VMSTATE_UINT32(last_release_offset, PCIQXLDevice),
VMSTATE_UINT32(mode, PCIQXLDevice),
VMSTATE_UINT32(ssd.unique, PCIQXLDevice),
- VMSTATE_INT32_EQUAL(num_memslots, PCIQXLDevice, NULL),
+ VMSTATE_INT32_EQUAL(num_memslots, PCIQXLDevice),
VMSTATE_STRUCT_ARRAY(guest_slots, PCIQXLDevice, NUM_MEMSLOTS, 0,
qxl_memslot, struct guest_slots),
VMSTATE_STRUCT(guest_primary.surface, PCIQXLDevice, 0,
qxl_surface, QXLSurfaceCreate),
- VMSTATE_INT32_EQUAL(ssd.num_surfaces, PCIQXLDevice, NULL),
+ VMSTATE_INT32_EQUAL(ssd.num_surfaces, PCIQXLDevice),
VMSTATE_VARRAY_INT32(guest_surfaces.cmds, PCIQXLDevice,
ssd.num_surfaces, 0,
vmstate_info_uint64, uint64_t),
diff --git a/hw/display/vga.c b/hw/display/vga.c
index ee7d97b5c2..0d69a53f27 100644
--- a/hw/display/vga.c
+++ b/hw/display/vga.c
@@ -2160,7 +2160,7 @@ const VMStateDescription vmstate_vga_common = {
VMSTATE_BUFFER(palette, VGACommonState),
VMSTATE_INT32(bank_offset, VGACommonState),
- VMSTATE_UINT8_EQUAL(is_vbe_vmstate, VGACommonState, NULL),
+ VMSTATE_UINT8_EQUAL(is_vbe_vmstate, VGACommonState),
VMSTATE_UINT16(vbe_index, VGACommonState),
VMSTATE_UINT16_ARRAY(vbe_regs, VGACommonState, VBE_DISPI_INDEX_NB),
VMSTATE_UINT32(vbe_start_addr, VGACommonState),
diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
index b998ce8324..dbb72bbb22 100644
--- a/hw/display/virtio-gpu.c
+++ b/hw/display/virtio-gpu.c
@@ -1223,7 +1223,7 @@ static const VMStateDescription vmstate_virtio_gpu_scanouts = {
.fields = (const VMStateField[]) {
VMSTATE_INT32(parent_obj.enable, struct VirtIOGPU),
VMSTATE_UINT32_EQUAL(parent_obj.conf.max_outputs,
- struct VirtIOGPU, NULL),
+ struct VirtIOGPU),
VMSTATE_STRUCT_VARRAY_UINT32(parent_obj.scanout, struct VirtIOGPU,
parent_obj.conf.max_outputs, 1,
vmstate_virtio_gpu_scanout,
diff --git a/hw/display/vmware_vga.c b/hw/display/vmware_vga.c
index c2c6bc76e9..2b95787ddf 100644
--- a/hw/display/vmware_vga.c
+++ b/hw/display/vmware_vga.c
@@ -1209,7 +1209,7 @@ static const VMStateDescription vmstate_vmware_vga_internal = {
.minimum_version_id = 0,
.post_load = vmsvga_post_load,
.fields = (const VMStateField[]) {
- VMSTATE_INT32_EQUAL(new_depth, struct vmsvga_state_s, NULL),
+ VMSTATE_INT32_EQUAL(new_depth, struct vmsvga_state_s),
VMSTATE_INT32(enable, struct vmsvga_state_s),
VMSTATE_INT32(config, struct vmsvga_state_s),
VMSTATE_INT32(cursor.id, struct vmsvga_state_s),
diff --git a/hw/i386/vmmouse.c b/hw/i386/vmmouse.c
index c1aeeca0c9..417c3aecb9 100644
--- a/hw/i386/vmmouse.c
+++ b/hw/i386/vmmouse.c
@@ -278,7 +278,7 @@ static const VMStateDescription vmstate_vmmouse = {
.minimum_version_id = 0,
.post_load = vmmouse_post_load,
.fields = (const VMStateField[]) {
- VMSTATE_INT32_EQUAL(queue_size, VMMouseState, NULL),
+ VMSTATE_INT32_EQUAL(queue_size, VMMouseState),
VMSTATE_UINT32_ARRAY(queue, VMMouseState, VMMOUSE_QUEUE_SIZE),
VMSTATE_UINT16(nb_queue, VMMouseState),
VMSTATE_UINT16(status, VMMouseState),
diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c
index 08f51c8e36..c2b4432b94 100644
--- a/hw/ide/ahci.c
+++ b/hw/ide/ahci.c
@@ -1795,7 +1795,7 @@ const VMStateDescription vmstate_ahci = {
VMSTATE_UINT32(control_regs.impl, AHCIState),
VMSTATE_UINT32(control_regs.version, AHCIState),
VMSTATE_UINT32(idp_index, AHCIState),
- VMSTATE_UINT32_EQUAL(ports, AHCIState, NULL),
+ VMSTATE_UINT32_EQUAL(ports, AHCIState),
VMSTATE_END_OF_LIST()
},
};
diff --git a/hw/intc/openpic.c b/hw/intc/openpic.c
index cd353a04f5..99d2a1e65e 100644
--- a/hw/intc/openpic.c
+++ b/hw/intc/openpic.c
@@ -1477,7 +1477,7 @@ static const VMStateDescription vmstate_openpic = {
VMSTATE_UINT32(max_irq, OpenPICState),
VMSTATE_STRUCT_VARRAY_UINT32(src, OpenPICState, max_irq, 0,
vmstate_openpic_irqsource, IRQSource),
- VMSTATE_UINT32_EQUAL(nb_cpus, OpenPICState, NULL),
+ VMSTATE_UINT32_EQUAL(nb_cpus, OpenPICState),
VMSTATE_STRUCT_VARRAY_UINT32(dst, OpenPICState, nb_cpus, 0,
vmstate_openpic_irqdest, IRQDest),
VMSTATE_STRUCT_ARRAY(timers, OpenPICState, OPENPIC_MAX_TMR, 0,
diff --git a/hw/intc/spapr_xive.c b/hw/intc/spapr_xive.c
index 76ab476f59..c30dace4e2 100644
--- a/hw/intc/spapr_xive.c
+++ b/hw/intc/spapr_xive.c
@@ -578,7 +578,7 @@ static const VMStateDescription vmstate_spapr_xive = {
.pre_save = vmstate_spapr_xive_pre_save,
.post_load = NULL, /* handled at the machine level */
.fields = (const VMStateField[]) {
- VMSTATE_UINT32_EQUAL(nr_irqs, SpaprXive, NULL),
+ VMSTATE_UINT32_EQUAL(nr_irqs, SpaprXive),
VMSTATE_STRUCT_VARRAY_POINTER_UINT32(eat, SpaprXive, nr_irqs,
vmstate_spapr_xive_eas, XiveEAS),
VMSTATE_STRUCT_VARRAY_POINTER_UINT32(endt, SpaprXive, nr_ends,
diff --git a/hw/intc/xics.c b/hw/intc/xics.c
index 1d40c4386d..c0a252d051 100644
--- a/hw/intc/xics.c
+++ b/hw/intc/xics.c
@@ -668,7 +668,7 @@ static const VMStateDescription vmstate_ics = {
.post_load = ics_post_load,
.fields = (const VMStateField[]) {
/* Sanity check */
- VMSTATE_UINT32_EQUAL(nr_irqs, ICSState, NULL),
+ VMSTATE_UINT32_EQUAL(nr_irqs, ICSState),
VMSTATE_STRUCT_VARRAY_POINTER_UINT32(irqs, ICSState, nr_irqs,
vmstate_ics_irq,
diff --git a/hw/intc/xive.c b/hw/intc/xive.c
index d702b58bd0..f473e6ac77 100644
--- a/hw/intc/xive.c
+++ b/hw/intc/xive.c
@@ -1565,7 +1565,7 @@ static const VMStateDescription vmstate_xive_source = {
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
- VMSTATE_UINT32_EQUAL(nr_irqs, XiveSource, NULL),
+ VMSTATE_UINT32_EQUAL(nr_irqs, XiveSource),
VMSTATE_VBUFFER_UINT32(status, XiveSource, 1, NULL, nr_irqs),
VMSTATE_END_OF_LIST()
},
diff --git a/hw/nvram/eeprom93xx.c b/hw/nvram/eeprom93xx.c
index a8fd60a8fb..73b3d248d4 100644
--- a/hw/nvram/eeprom93xx.c
+++ b/hw/nvram/eeprom93xx.c
@@ -144,7 +144,7 @@ static const VMStateDescription vmstate_eeprom = {
VMSTATE_UINT8(addrbits, eeprom_t),
VMSTATE_UINT16_HACK_TEST(size, eeprom_t, is_old_eeprom_version),
VMSTATE_UNUSED_TEST(is_old_eeprom_version, 1),
- VMSTATE_UINT16_EQUAL_V(size, eeprom_t, EEPROM_VERSION, NULL),
+ VMSTATE_UINT16_EQUAL_V(size, eeprom_t, EEPROM_VERSION),
VMSTATE_UINT16(data, eeprom_t),
VMSTATE_VARRAY_UINT16_UNSAFE(contents, eeprom_t, size, 0,
vmstate_info_uint16, uint16_t),
diff --git a/hw/pci/pci.c b/hw/pci/pci.c
index 2c3657d00d..a0dbb81fd9 100644
--- a/hw/pci/pci.c
+++ b/hw/pci/pci.c
@@ -105,7 +105,7 @@ static const VMStateDescription vmstate_pcibus = {
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
- VMSTATE_INT32_EQUAL(nirq, PCIBus, NULL),
+ VMSTATE_INT32_EQUAL(nirq, PCIBus),
VMSTATE_VARRAY_INT32(irq_count, PCIBus,
nirq, 0, vmstate_info_int32,
int32_t),
diff --git a/hw/pci/pcie_aer.c b/hw/pci/pcie_aer.c
index 2c85a78fcd..22497b1fd6 100644
--- a/hw/pci/pcie_aer.c
+++ b/hw/pci/pcie_aer.c
@@ -820,7 +820,7 @@ const VMStateDescription vmstate_pcie_aer_log = {
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_UINT16(log_num, PCIEAERLog),
- VMSTATE_UINT16_EQUAL(log_max, PCIEAERLog, NULL),
+ VMSTATE_UINT16_EQUAL(log_max, PCIEAERLog),
VMSTATE_VALIDATE("log_num <= log_max", pcie_aer_state_log_num_valid),
VMSTATE_STRUCT_VARRAY_POINTER_UINT16(log, PCIEAERLog, log_num,
vmstate_pcie_aer_err, PCIEAERErr),
diff --git a/hw/ppc/spapr_iommu.c b/hw/ppc/spapr_iommu.c
index c2432a0c00..e6264b0785 100644
--- a/hw/ppc/spapr_iommu.c
+++ b/hw/ppc/spapr_iommu.c
@@ -285,7 +285,7 @@ static const VMStateDescription vmstate_spapr_tce_table = {
.post_load = spapr_tce_table_post_load,
.fields = (const VMStateField []) {
/* Sanity check */
- VMSTATE_UINT32_EQUAL(liobn, SpaprTceTable, NULL),
+ VMSTATE_UINT32_EQUAL(liobn, SpaprTceTable),
/* IOMMU state */
VMSTATE_UINT32(mig_nb_table, SpaprTceTable),
diff --git a/hw/ppc/spapr_pci.c b/hw/ppc/spapr_pci.c
index 1dc3b02659..c1d4b7806e 100644
--- a/hw/ppc/spapr_pci.c
+++ b/hw/ppc/spapr_pci.c
@@ -2051,7 +2051,7 @@ static const VMStateDescription vmstate_spapr_pci_lsi = {
.version_id = 1,
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
- VMSTATE_UINT32_EQUAL(irq, SpaprPciLsi, NULL),
+ VMSTATE_UINT32_EQUAL(irq, SpaprPciLsi),
VMSTATE_END_OF_LIST()
},
@@ -2129,7 +2129,7 @@ static const VMStateDescription vmstate_spapr_pci = {
.post_save = spapr_pci_post_save,
.post_load = spapr_pci_post_load,
.fields = (const VMStateField[]) {
- VMSTATE_UINT64_EQUAL(buid, SpaprPhbState, NULL),
+ VMSTATE_UINT64_EQUAL(buid, SpaprPhbState),
VMSTATE_STRUCT_ARRAY(lsi_table, SpaprPhbState, PCI_NUM_PINS, 0,
vmstate_spapr_pci_lsi, SpaprPciLsi),
VMSTATE_INT32(msi_devs_num, SpaprPhbState),
diff --git a/hw/ppc/spapr_vio.c b/hw/ppc/spapr_vio.c
index 501e82a766..3f05081bad 100644
--- a/hw/ppc/spapr_vio.c
+++ b/hw/ppc/spapr_vio.c
@@ -609,8 +609,8 @@ const VMStateDescription vmstate_spapr_vio = {
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
/* Sanity check */
- VMSTATE_UINT32_EQUAL(reg, SpaprVioDevice, NULL),
- VMSTATE_UINT32_EQUAL(irq, SpaprVioDevice, NULL),
+ VMSTATE_UINT32_EQUAL(reg, SpaprVioDevice),
+ VMSTATE_UINT32_EQUAL(irq, SpaprVioDevice),
/* General VIO device state */
VMSTATE_UINT64(signal_state, SpaprVioDevice),
diff --git a/hw/s390x/css.c b/hw/s390x/css.c
index df5f0bc27f..ecd28fed5c 100644
--- a/hw/s390x/css.c
+++ b/hw/s390x/css.c
@@ -199,15 +199,15 @@ const VMStateDescription vmstate_subch_dev = {
.post_load = subch_dev_post_load,
.pre_save = subch_dev_pre_save,
.fields = (const VMStateField[]) {
- VMSTATE_UINT8_EQUAL(cssid, SubchDev, NULL),
- VMSTATE_UINT8_EQUAL(ssid, SubchDev, NULL),
+ VMSTATE_UINT8_EQUAL(cssid, SubchDev),
+ VMSTATE_UINT8_EQUAL(ssid, SubchDev),
VMSTATE_UINT16(migrated_schid, SubchDev),
/*
* If devno mismatch on target, it may be due to some
* sequences of plug and unplug breaks migration for
* machine versions prior to 2.7 (known design flaw).
*/
- VMSTATE_UINT16_EQUAL(devno, SubchDev, NULL),
+ VMSTATE_UINT16_EQUAL(devno, SubchDev),
VMSTATE_BOOL(thinint_active, SubchDev),
VMSTATE_STRUCT(curr_status, SubchDev, 0, vmstate_schib, SCHIB),
VMSTATE_UINT8_ARRAY(sense_data, SubchDev, 32),
diff --git a/hw/usb/hcd-uhci.c b/hw/usb/hcd-uhci.c
index b2224c7f76..a7b9fe1317 100644
--- a/hw/usb/hcd-uhci.c
+++ b/hw/usb/hcd-uhci.c
@@ -364,7 +364,7 @@ static const VMStateDescription vmstate_uhci = {
.post_load = uhci_post_load,
.fields = (const VMStateField[]) {
VMSTATE_PCI_DEVICE(dev, UHCIState),
- VMSTATE_UINT8_EQUAL(num_ports_vmstate, UHCIState, NULL),
+ VMSTATE_UINT8_EQUAL(num_ports_vmstate, UHCIState),
VMSTATE_STRUCT_ARRAY(ports, UHCIState, UHCI_PORTS, 1,
vmstate_uhci_port, UHCIPort),
VMSTATE_UINT16(cmd, UHCIState),
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 66c2e87bd3..d4a39aa794 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -182,7 +182,6 @@ typedef enum {
struct VMStateField {
const char *name;
- const char *err_hint;
size_t offset;
size_t size;
size_t start;
@@ -358,9 +357,8 @@ extern const VMStateInfo vmstate_info_qlist;
}
#define VMSTATE_SINGLE_FULL(_field, _state, _test, _version, _info, \
- _type, _err_hint) { \
+ _type) { \
.name = (stringify(_field)), \
- .err_hint = (_err_hint), \
.version_id = (_version), \
.field_exists = (_test), \
.size = sizeof(_type), \
@@ -1022,35 +1020,35 @@ extern const VMStateInfo vmstate_info_qlist;
#endif
-#define VMSTATE_UINT8_EQUAL(_f, _s, _err_hint) \
+#define VMSTATE_UINT8_EQUAL(_f, _s) \
VMSTATE_SINGLE_FULL(_f, _s, 0, 0, \
- vmstate_info_uint8_equal, uint8_t, _err_hint)
+ vmstate_info_uint8_equal, uint8_t)
-#define VMSTATE_UINT16_EQUAL(_f, _s, _err_hint) \
+#define VMSTATE_UINT16_EQUAL(_f, _s) \
VMSTATE_SINGLE_FULL(_f, _s, 0, 0, \
- vmstate_info_uint16_equal, uint16_t, _err_hint)
+ vmstate_info_uint16_equal, uint16_t)
-#define VMSTATE_UINT16_EQUAL_V(_f, _s, _v, _err_hint) \
+#define VMSTATE_UINT16_EQUAL_V(_f, _s, _v) \
VMSTATE_SINGLE_FULL(_f, _s, 0, _v, \
- vmstate_info_uint16_equal, uint16_t, _err_hint)
+ vmstate_info_uint16_equal, uint16_t)
-#define VMSTATE_INT32_EQUAL(_f, _s, _err_hint) \
+#define VMSTATE_INT32_EQUAL(_f, _s) \
VMSTATE_SINGLE_FULL(_f, _s, 0, 0, \
- vmstate_info_int32_equal, int32_t, _err_hint)
+ vmstate_info_int32_equal, int32_t)
-#define VMSTATE_UINT32_EQUAL_V(_f, _s, _v, _err_hint) \
+#define VMSTATE_UINT32_EQUAL_V(_f, _s, _v) \
VMSTATE_SINGLE_FULL(_f, _s, 0, _v, \
- vmstate_info_uint32_equal, uint32_t, _err_hint)
+ vmstate_info_uint32_equal, uint32_t)
-#define VMSTATE_UINT32_EQUAL(_f, _s, _err_hint) \
- VMSTATE_UINT32_EQUAL_V(_f, _s, 0, _err_hint)
+#define VMSTATE_UINT32_EQUAL(_f, _s) \
+ VMSTATE_UINT32_EQUAL_V(_f, _s, 0)
-#define VMSTATE_UINT64_EQUAL_V(_f, _s, _v, _err_hint) \
+#define VMSTATE_UINT64_EQUAL_V(_f, _s, _v) \
VMSTATE_SINGLE_FULL(_f, _s, 0, _v, \
- vmstate_info_uint64_equal, uint64_t, _err_hint)
+ vmstate_info_uint64_equal, uint64_t)
-#define VMSTATE_UINT64_EQUAL(_f, _s, _err_hint) \
- VMSTATE_UINT64_EQUAL_V(_f, _s, 0, _err_hint)
+#define VMSTATE_UINT64_EQUAL(_f, _s) \
+ VMSTATE_UINT64_EQUAL_V(_f, _s, 0)
#define VMSTATE_INT32_POSITIVE_LE(_f, _s) \
VMSTATE_SINGLE(_f, _s, 0, vmstate_info_int32_le, int32_t)
diff --git a/migration/vmstate-types.c b/migration/vmstate-types.c
index 89cb211472..033a2685c8 100644
--- a/migration/vmstate-types.c
+++ b/migration/vmstate-types.c
@@ -131,9 +131,6 @@ static int get_int32_equal(QEMUFile *f, void *pv, size_t size,
return 0;
}
error_report("%" PRIx32 " != %" PRIx32, *v, v2);
- if (field->err_hint) {
- error_printf("%s\n", field->err_hint);
- }
return -EINVAL;
}
@@ -280,9 +277,6 @@ static int get_uint32_equal(QEMUFile *f, void *pv, size_t size,
return 0;
}
error_report("%" PRIx32 " != %" PRIx32, *v, v2);
- if (field->err_hint) {
- error_printf("%s\n", field->err_hint);
- }
return -EINVAL;
}
@@ -391,9 +385,6 @@ static int get_uint64_equal(QEMUFile *f, void *pv, size_t size,
return 0;
}
error_report("%" PRIx64 " != %" PRIx64, *v, v2);
- if (field->err_hint) {
- error_printf("%s\n", field->err_hint);
- }
return -EINVAL;
}
@@ -417,9 +408,6 @@ static int get_uint8_equal(QEMUFile *f, void *pv, size_t size,
return 0;
}
error_report("%x != %x", *v, v2);
- if (field->err_hint) {
- error_printf("%s\n", field->err_hint);
- }
return -EINVAL;
}
@@ -443,9 +431,6 @@ static int get_uint16_equal(QEMUFile *f, void *pv, size_t size,
return 0;
}
error_report("%x != %x", *v, v2);
- if (field->err_hint) {
- error_printf("%s\n", field->err_hint);
- }
return -EINVAL;
}
diff --git a/target/ppc/machine.c b/target/ppc/machine.c
index 49cfdc6d67..9eae0ff647 100644
--- a/target/ppc/machine.c
+++ b/target/ppc/machine.c
@@ -564,7 +564,7 @@ static const VMStateDescription vmstate_tlb6xx = {
.minimum_version_id = 1,
.needed = tlb6xx_needed,
.fields = (const VMStateField[]) {
- VMSTATE_INT32_EQUAL(env.nb_tlb, PowerPCCPU, NULL),
+ VMSTATE_INT32_EQUAL(env.nb_tlb, PowerPCCPU),
VMSTATE_STRUCT_VARRAY_POINTER_INT32(env.tlb.tlb6, PowerPCCPU,
env.nb_tlb,
vmstate_tlb6xx_entry,
@@ -603,7 +603,7 @@ static const VMStateDescription vmstate_tlbemb = {
.minimum_version_id = 1,
.needed = tlbemb_needed,
.fields = (const VMStateField[]) {
- VMSTATE_INT32_EQUAL(env.nb_tlb, PowerPCCPU, NULL),
+ VMSTATE_INT32_EQUAL(env.nb_tlb, PowerPCCPU),
VMSTATE_STRUCT_VARRAY_POINTER_INT32(env.tlb.tlbe, PowerPCCPU,
env.nb_tlb,
vmstate_tlbemb_entry,
@@ -639,7 +639,7 @@ static const VMStateDescription vmstate_tlbmas = {
.minimum_version_id = 1,
.needed = tlbmas_needed,
.fields = (const VMStateField[]) {
- VMSTATE_INT32_EQUAL(env.nb_tlb, PowerPCCPU, NULL),
+ VMSTATE_INT32_EQUAL(env.nb_tlb, PowerPCCPU),
VMSTATE_STRUCT_VARRAY_POINTER_INT32(env.tlb.tlbm, PowerPCCPU,
env.nb_tlb,
vmstate_tlbmas_entry,
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 20/43] migration/vmstate-types: move to new migration APIs
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (18 preceding siblings ...)
2026-04-23 19:19 ` [PULL 19/43] migration: drop VMStateField.err_hint Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 21/43] migration: Tweak description of migration property multifd-compression Fabiano Rosas
` (23 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Vladimir Sementsov-Ogievskiy
From: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Signed-off-by: Vladimir Sementsov-Ogievskiy <vsementsov@yandex-team.ru>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260304212303.667141-19-vsementsov@yandex-team.ru
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/trace-events | 24 +-
migration/vmstate-types.c | 627 +++++++++++++++++++-------------------
2 files changed, 327 insertions(+), 324 deletions(-)
diff --git a/migration/trace-events b/migration/trace-events
index 27459dfb78..34143b14b4 100644
--- a/migration/trace-events
+++ b/migration/trace-events
@@ -72,20 +72,20 @@ vmstate_subsection_save_top(const char *idstr) "%s"
vmstate_field_exists(const char *vmsd, const char *name, int field_version, int version, int result) "%s:%s field_version %d version %d result %d"
# vmstate-types.c
-get_qtailq(const char *name, int version_id) "%s v%d"
-get_qtailq_end(const char *name, const char *reason, int val) "%s %s/%d"
-put_qtailq(const char *name, int version_id) "%s v%d"
-put_qtailq_end(const char *name, const char *reason) "%s %s"
+load_qtailq(const char *name, int version_id) "%s v%d"
+load_qtailq_end(const char *name) "%s"
+save_qtailq(const char *name, int version_id) "%s v%d"
+save_qtailq_end(const char *name) "%s"
-get_gtree(const char *field_name, const char *key_vmsd_name, const char *val_vmsd_name, uint32_t nnodes) "%s(%s/%s) nnodes=%d"
-get_gtree_end(const char *field_name, const char *key_vmsd_name, const char *val_vmsd_name, int ret) "%s(%s/%s) %d"
-put_gtree(const char *field_name, const char *key_vmsd_name, const char *val_vmsd_name, uint32_t nnodes) "%s(%s/%s) nnodes=%d"
-put_gtree_end(const char *field_name, const char *key_vmsd_name, const char *val_vmsd_name, int ret) "%s(%s/%s) %d"
+load_gtree(const char *field_name, const char *key_vmsd_name, const char *val_vmsd_name, uint32_t nnodes) "%s(%s/%s) nnodes=%d"
+load_gtree_end(const char *field_name, const char *key_vmsd_name, const char *val_vmsd_name) "%s(%s/%s)"
+save_gtree(const char *field_name, const char *key_vmsd_name, const char *val_vmsd_name, uint32_t nnodes) "%s(%s/%s) nnodes=%d"
+save_gtree_end(const char *field_name, const char *key_vmsd_name, const char *val_vmsd_name) "%s(%s/%s)"
-get_qlist(const char *field_name, const char *vmsd_name, int version_id) "%s(%s v%d)"
-get_qlist_end(const char *field_name, const char *vmsd_name) "%s(%s)"
-put_qlist(const char *field_name, const char *vmsd_name, int version_id) "%s(%s v%d)"
-put_qlist_end(const char *field_name, const char *vmsd_name) "%s(%s)"
+load_qlist(const char *field_name, const char *vmsd_name, int version_id) "%s(%s v%d)"
+load_qlist_end(const char *field_name, const char *vmsd_name) "%s(%s)"
+save_qlist(const char *field_name, const char *vmsd_name, int version_id) "%s(%s v%d)"
+save_qlist_end(const char *field_name, const char *vmsd_name) "%s(%s)"
# qemu-file.c
qemu_file_fclose(void) ""
diff --git a/migration/vmstate-types.c b/migration/vmstate-types.c
index 033a2685c8..23f3433696 100644
--- a/migration/vmstate-types.c
+++ b/migration/vmstate-types.c
@@ -23,129 +23,135 @@
/* bool */
-static int get_bool(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_bool(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
bool *v = pv;
*v = qemu_get_byte(f);
- return 0;
+ return true;
}
-static int put_bool(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_bool(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
bool *v = pv;
qemu_put_byte(f, *v);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_bool = {
.name = "bool",
- .get = get_bool,
- .put = put_bool,
+ .load = load_bool,
+ .save = save_bool,
};
/* 8 bit int */
-static int get_int8(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_int8(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
int8_t *v = pv;
qemu_get_s8s(f, v);
- return 0;
+ return true;
}
-static int put_int8(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_int8(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
int8_t *v = pv;
qemu_put_s8s(f, v);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_int8 = {
.name = "int8",
- .get = get_int8,
- .put = put_int8,
+ .load = load_int8,
+ .save = save_int8,
};
/* 16 bit int */
-static int get_int16(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_int16(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
int16_t *v = pv;
qemu_get_sbe16s(f, v);
- return 0;
+ return true;
}
-static int put_int16(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_int16(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
int16_t *v = pv;
qemu_put_sbe16s(f, v);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_int16 = {
.name = "int16",
- .get = get_int16,
- .put = put_int16,
+ .load = load_int16,
+ .save = save_int16,
};
/* 32 bit int */
-static int get_int32(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_int32(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
int32_t *v = pv;
qemu_get_sbe32s(f, v);
- return 0;
+ return true;
}
-static int put_int32(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_int32(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
int32_t *v = pv;
qemu_put_sbe32s(f, v);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_int32 = {
.name = "int32",
- .get = get_int32,
- .put = put_int32,
+ .load = load_int32,
+ .save = save_int32,
};
/* 32 bit int. See that the received value is the same than the one
in the field */
-static int get_int32_equal(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_int32_equal(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
+ ERRP_GUARD();
int32_t *v = pv;
int32_t v2;
qemu_get_sbe32s(f, &v2);
if (*v == v2) {
- return 0;
+ return true;
}
- error_report("%" PRIx32 " != %" PRIx32, *v, v2);
- return -EINVAL;
+
+ error_setg(errp, "%" PRIx32 " != %" PRIx32, *v, v2);
+ return false;
}
const VMStateInfo vmstate_info_int32_equal = {
.name = "int32 equal",
- .get = get_int32_equal,
- .put = put_int32,
+ .load = load_int32_equal,
+ .save = save_int32,
};
/* 32 bit int. Check that the received value is non-negative
* and less than or equal to the one in the field.
*/
-static int get_int32_le(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_int32_le(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
int32_t *cur = pv;
int32_t loaded;
@@ -153,348 +159,373 @@ static int get_int32_le(QEMUFile *f, void *pv, size_t size,
if (loaded >= 0 && loaded <= *cur) {
*cur = loaded;
- return 0;
+ return true;
}
- error_report("Invalid value %" PRId32
- " expecting positive value <= %" PRId32,
- loaded, *cur);
- return -EINVAL;
+
+ error_setg(errp, "Invalid value %" PRId32
+ " expecting positive value <= %" PRId32,
+ loaded, *cur);
+ return false;
}
const VMStateInfo vmstate_info_int32_le = {
.name = "int32 le",
- .get = get_int32_le,
- .put = put_int32,
+ .load = load_int32_le,
+ .save = save_int32,
};
/* 64 bit int */
-static int get_int64(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_int64(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
int64_t *v = pv;
qemu_get_sbe64s(f, v);
- return 0;
+ return true;
}
-static int put_int64(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_int64(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
int64_t *v = pv;
qemu_put_sbe64s(f, v);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_int64 = {
.name = "int64",
- .get = get_int64,
- .put = put_int64,
+ .load = load_int64,
+ .save = save_int64,
};
/* 8 bit unsigned int */
-static int get_uint8(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_uint8(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
uint8_t *v = pv;
qemu_get_8s(f, v);
- return 0;
+ return true;
}
-static int put_uint8(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_uint8(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
uint8_t *v = pv;
qemu_put_8s(f, v);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_uint8 = {
.name = "uint8",
- .get = get_uint8,
- .put = put_uint8,
+ .load = load_uint8,
+ .save = save_uint8,
};
/* 16 bit unsigned int */
-static int get_uint16(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_uint16(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
uint16_t *v = pv;
qemu_get_be16s(f, v);
- return 0;
+ return true;
}
-static int put_uint16(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_uint16(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
uint16_t *v = pv;
qemu_put_be16s(f, v);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_uint16 = {
.name = "uint16",
- .get = get_uint16,
- .put = put_uint16,
+ .load = load_uint16,
+ .save = save_uint16,
};
/* 32 bit unsigned int */
-static int get_uint32(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_uint32(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
uint32_t *v = pv;
qemu_get_be32s(f, v);
- return 0;
+ return true;
}
-static int put_uint32(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_uint32(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
uint32_t *v = pv;
qemu_put_be32s(f, v);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_uint32 = {
.name = "uint32",
- .get = get_uint32,
- .put = put_uint32,
+ .load = load_uint32,
+ .save = save_uint32,
};
/* 32 bit uint. See that the received value is the same than the one
in the field */
-static int get_uint32_equal(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_uint32_equal(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
+ ERRP_GUARD();
uint32_t *v = pv;
uint32_t v2;
qemu_get_be32s(f, &v2);
if (*v == v2) {
- return 0;
+ return true;
}
- error_report("%" PRIx32 " != %" PRIx32, *v, v2);
- return -EINVAL;
+
+ error_setg(errp, "%" PRIx32 " != %" PRIx32, *v, v2);
+ return false;
}
const VMStateInfo vmstate_info_uint32_equal = {
.name = "uint32 equal",
- .get = get_uint32_equal,
- .put = put_uint32,
+ .load = load_uint32_equal,
+ .save = save_uint32,
};
/* 64 bit unsigned int */
-static int get_uint64(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_uint64(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
uint64_t *v = pv;
qemu_get_be64s(f, v);
- return 0;
+ return true;
}
-static int put_uint64(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_uint64(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
uint64_t *v = pv;
qemu_put_be64s(f, v);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_uint64 = {
.name = "uint64",
- .get = get_uint64,
- .put = put_uint64,
+ .load = load_uint64,
+ .save = save_uint64,
};
/* File descriptor communicated via SCM_RIGHTS */
-static int get_fd(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_fd(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
int32_t *v = pv;
if (migrate_mode() == MIG_MODE_CPR_EXEC) {
qemu_get_sbe32s(f, v);
- return 0;
+ return true;
}
- return qemu_file_get_fd(f, v);
+ return qemu_file_get_fd(f, v) >= 0;
}
-static int put_fd(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_fd(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
int32_t *v = pv;
+
if (migrate_mode() == MIG_MODE_CPR_EXEC) {
qemu_put_sbe32s(f, v);
- return 0;
+ return true;
}
- return qemu_file_put_fd(f, *v);
+
+ return qemu_file_put_fd(f, *v) >= 0;
}
const VMStateInfo vmstate_info_fd = {
.name = "fd",
- .get = get_fd,
- .put = put_fd,
+ .load = load_fd,
+ .save = save_fd,
};
-static int get_nullptr(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_nullptr(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
if (qemu_get_byte(f) == VMS_NULLPTR_MARKER) {
- return 0;
+ return true;
}
- error_report("vmstate: get_nullptr expected VMS_NULLPTR_MARKER");
- return -EINVAL;
+
+ error_setg(errp, "vmstate: load_nullptr expected VMS_NULLPTR_MARKER");
+ return false;
}
-static int put_nullptr(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_nullptr(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
if (pv == NULL) {
qemu_put_byte(f, VMS_NULLPTR_MARKER);
- return 0;
+ return true;
}
- error_report("vmstate: put_nullptr must be called with pv == NULL");
- return -EINVAL;
+
+ error_setg(errp, "vmstate: save_nullptr must be called with pv == NULL");
+ return false;
}
const VMStateInfo vmstate_info_nullptr = {
.name = "nullptr",
- .get = get_nullptr,
- .put = put_nullptr,
+ .load = load_nullptr,
+ .save = save_nullptr,
};
/* 64 bit unsigned int. See that the received value is the same than the one
in the field */
-static int get_uint64_equal(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_uint64_equal(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
+ ERRP_GUARD();
uint64_t *v = pv;
uint64_t v2;
+
qemu_get_be64s(f, &v2);
if (*v == v2) {
- return 0;
+ return true;
}
- error_report("%" PRIx64 " != %" PRIx64, *v, v2);
- return -EINVAL;
+
+ error_setg(errp, "%" PRIx64 " != %" PRIx64, *v, v2);
+ return false;
}
const VMStateInfo vmstate_info_uint64_equal = {
.name = "int64 equal",
- .get = get_uint64_equal,
- .put = put_uint64,
+ .load = load_uint64_equal,
+ .save = save_uint64,
};
/* 8 bit int. See that the received value is the same than the one
in the field */
-static int get_uint8_equal(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_uint8_equal(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
+ ERRP_GUARD();
uint8_t *v = pv;
uint8_t v2;
+
qemu_get_8s(f, &v2);
if (*v == v2) {
- return 0;
+ return true;
}
- error_report("%x != %x", *v, v2);
- return -EINVAL;
+
+ error_setg(errp, "%x != %x", *v, v2);
+ return false;
}
const VMStateInfo vmstate_info_uint8_equal = {
.name = "uint8 equal",
- .get = get_uint8_equal,
- .put = put_uint8,
+ .load = load_uint8_equal,
+ .save = save_uint8,
};
/* 16 bit unsigned int int. See that the received value is the same than the one
in the field */
-static int get_uint16_equal(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_uint16_equal(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
+ ERRP_GUARD();
uint16_t *v = pv;
uint16_t v2;
+
qemu_get_be16s(f, &v2);
if (*v == v2) {
- return 0;
+ return true;
}
- error_report("%x != %x", *v, v2);
- return -EINVAL;
+
+ error_setg(errp, "%x != %x", *v, v2);
+ return false;
}
const VMStateInfo vmstate_info_uint16_equal = {
.name = "uint16 equal",
- .get = get_uint16_equal,
- .put = put_uint16,
+ .load = load_uint16_equal,
+ .save = save_uint16,
};
/* CPU_DoubleU type */
-static int get_cpudouble(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_cpudouble(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
CPU_DoubleU *v = pv;
qemu_get_be32s(f, &v->l.upper);
qemu_get_be32s(f, &v->l.lower);
- return 0;
+ return true;
}
-static int put_cpudouble(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_cpudouble(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
CPU_DoubleU *v = pv;
qemu_put_be32s(f, &v->l.upper);
qemu_put_be32s(f, &v->l.lower);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_cpudouble = {
.name = "CPU_Double_U",
- .get = get_cpudouble,
- .put = put_cpudouble,
+ .load = load_cpudouble,
+ .save = save_cpudouble,
};
/* uint8_t buffers */
-static int get_buffer(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_buffer(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
uint8_t *v = pv;
qemu_get_buffer(f, v, size);
- return 0;
+ return true;
}
-static int put_buffer(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_buffer(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
uint8_t *v = pv;
qemu_put_buffer(f, v, size);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_buffer = {
.name = "buffer",
- .get = get_buffer,
- .put = put_buffer,
+ .load = load_buffer,
+ .save = save_buffer,
};
/* unused buffers: space that was used for some fields that are
not useful anymore */
-static int get_unused_buffer(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_unused_buffer(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
uint8_t buf[1024];
int block_len;
@@ -504,11 +535,13 @@ static int get_unused_buffer(QEMUFile *f, void *pv, size_t size,
size -= block_len;
qemu_get_buffer(f, buf, block_len);
}
- return 0;
+
+ return true;
}
-static int put_unused_buffer(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_unused_buffer(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
static const uint8_t buf[1024];
int block_len;
@@ -519,13 +552,13 @@ static int put_unused_buffer(QEMUFile *f, void *pv, size_t size,
qemu_put_buffer(f, buf, block_len);
}
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_unused_buffer = {
.name = "unused_buffer",
- .get = get_unused_buffer,
- .put = put_unused_buffer,
+ .load = load_unused_buffer,
+ .save = save_unused_buffer,
};
/* vmstate_info_tmp, see VMSTATE_WITH_TMP, the idea is that we allocate
@@ -534,48 +567,34 @@ const VMStateInfo vmstate_info_unused_buffer = {
* in fields that don't really exist in the parent but need to be in the
* stream.
*/
-static int get_tmp(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_tmp(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
- int ret;
- Error *local_err = NULL;
const VMStateDescription *vmsd = field->vmsd;
int version_id = field->version_id;
- void *tmp = g_malloc(size);
+ g_autofree 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, &local_err);
- if (ret < 0) {
- error_report_err(local_err);
- }
- g_free(tmp);
- return ret;
+ return vmstate_load_vmsd(f, vmsd, tmp, version_id, errp);
}
-static int put_tmp(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_tmp(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
const VMStateDescription *vmsd = field->vmsd;
- void *tmp = g_malloc(size);
- int ret;
- Error *local_err = NULL;
+ g_autofree void *tmp = g_malloc(size);
/* Writes the parent field which is at the start of the tmp */
*(void **)tmp = pv;
- ret = vmstate_save_state(f, vmsd, tmp, vmdesc, &local_err);
- if (ret) {
- error_report_err(local_err);
- }
- g_free(tmp);
-
- return ret;
+ return vmstate_save_vmsd(f, vmsd, tmp, vmdesc, errp);
}
const VMStateInfo vmstate_info_tmp = {
.name = "tmp",
- .get = get_tmp,
- .put = put_tmp,
+ .load = load_tmp,
+ .save = save_tmp,
};
/* bitmaps (as defined by bitmap.h). Note that size here is the size
@@ -585,11 +604,12 @@ const VMStateInfo vmstate_info_tmp = {
*/
/* This is the number of 64 bit words sent over the wire */
#define BITS_TO_U64S(nr) DIV_ROUND_UP(nr, 64)
-static int get_bitmap(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field)
+static bool load_bitmap(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
unsigned long *bmp = pv;
int i, idx = 0;
+
for (i = 0; i < BITS_TO_U64S(size); i++) {
uint64_t w = qemu_get_be64(f);
bmp[idx++] = w;
@@ -597,14 +617,17 @@ static int get_bitmap(QEMUFile *f, void *pv, size_t size,
bmp[idx++] = w >> 32;
}
}
- return 0;
+
+ return true;
}
-static int put_bitmap(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_bitmap(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
unsigned long *bmp = pv;
int i, idx = 0;
+
for (i = 0; i < BITS_TO_U64S(size); i++) {
uint64_t w = bmp[idx++];
if (sizeof(unsigned long) == 4 && idx < BITS_TO_LONGS(size)) {
@@ -613,23 +636,21 @@ static int put_bitmap(QEMUFile *f, void *pv, size_t size,
qemu_put_be64(f, w);
}
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_bitmap = {
.name = "bitmap",
- .get = get_bitmap,
- .put = put_bitmap,
+ .load = load_bitmap,
+ .save = save_bitmap,
};
/* get for QTAILQ
* meta data about the QTAILQ is encoded in a VMStateField structure
*/
-static int get_qtailq(QEMUFile *f, void *pv, size_t unused_size,
- const VMStateField *field)
+static bool load_qtailq(QEMUFile *f, void *pv, size_t unused_size,
+ const VMStateField *field, Error **errp)
{
- int ret = 0;
- Error *local_err = NULL;
const VMStateDescription *vmsd = field->vmsd;
/* size of a QTAILQ element */
size_t size = field->size;
@@ -638,80 +659,76 @@ static int get_qtailq(QEMUFile *f, void *pv, size_t unused_size,
int version_id = field->version_id;
void *elm;
- trace_get_qtailq(vmsd->name, version_id);
+ trace_load_qtailq(vmsd->name, version_id);
if (version_id > vmsd->version_id) {
- error_report("%s %s", vmsd->name, "too new");
- trace_get_qtailq_end(vmsd->name, "too new", -EINVAL);
-
- return -EINVAL;
+ error_setg(errp, "%s %s", vmsd->name, "too new");
+ return false;
}
if (version_id < vmsd->minimum_version_id) {
- error_report("%s %s", vmsd->name, "too old");
- trace_get_qtailq_end(vmsd->name, "too old", -EINVAL);
- return -EINVAL;
+ error_setg(errp, "%s %s", vmsd->name, "too old");
+ return false;
}
while (qemu_get_byte(f)) {
elm = g_malloc(size);
- ret = vmstate_load_state(f, vmsd, elm, version_id, &local_err);
- if (ret) {
- error_report_err(local_err);
- return ret;
+ if (!vmstate_load_vmsd(f, vmsd, elm, version_id, errp)) {
+ g_free(elm);
+ return false;
}
QTAILQ_RAW_INSERT_TAIL(pv, elm, entry_offset);
}
- trace_get_qtailq_end(vmsd->name, "end", ret);
- return ret;
+ trace_load_qtailq_end(vmsd->name);
+ return true;
}
-/* put for QTAILQ */
-static int put_qtailq(QEMUFile *f, void *pv, size_t unused_size,
- const VMStateField *field, JSONWriter *vmdesc)
+/* save for QTAILQ */
+static bool save_qtailq(QEMUFile *f, void *pv, size_t unused_size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
const VMStateDescription *vmsd = field->vmsd;
/* offset of the QTAILQ entry in a QTAILQ element*/
size_t entry_offset = field->start;
void *elm;
- int ret;
- Error *local_err = NULL;
- trace_put_qtailq(vmsd->name, vmsd->version_id);
+ trace_save_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, &local_err);
- if (ret) {
- error_report_err(local_err);
- return ret;
+ if (!vmstate_save_vmsd(f, vmsd, elm, vmdesc, errp)) {
+ return false;
}
}
qemu_put_byte(f, false);
- trace_put_qtailq_end(vmsd->name, "end");
+ trace_save_qtailq_end(vmsd->name);
- return 0;
+ return true;
}
const VMStateInfo vmstate_info_qtailq = {
.name = "qtailq",
- .get = get_qtailq,
- .put = put_qtailq,
+ .load = load_qtailq,
+ .save = save_qtailq,
};
-struct put_gtree_data {
+struct save_gtree_data {
QEMUFile *f;
const VMStateDescription *key_vmsd;
const VMStateDescription *val_vmsd;
JSONWriter *vmdesc;
- int ret;
+ Error **errp;
+ bool failed;
};
-static gboolean put_gtree_elem(gpointer key, gpointer value, gpointer data)
+/*
+ * save_gtree_elem - func for g_tree_foreach, return true to stop
+ * iteration.
+ */
+static gboolean save_gtree_elem(gpointer key, gpointer value, gpointer data)
{
- struct put_gtree_data *capsule = (struct put_gtree_data *)data;
+ struct save_gtree_data *capsule = (struct save_gtree_data *)data;
QEMUFile *f = capsule->f;
- int ret;
- Error *local_err = NULL;
qemu_put_byte(f, true);
@@ -719,58 +736,56 @@ 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,
- &local_err);
- if (ret) {
- error_report_err(local_err);
- capsule->ret = ret;
+ if (!vmstate_save_vmsd(f, capsule->key_vmsd, key, capsule->vmdesc,
+ capsule->errp)) {
+ capsule->failed = true;
return true;
}
}
/* put the data */
- ret = vmstate_save_state(f, capsule->val_vmsd, value, capsule->vmdesc,
- &local_err);
- if (ret) {
- error_report_err(local_err);
- capsule->ret = ret;
+ if (!vmstate_save_vmsd(f, capsule->val_vmsd, value, capsule->vmdesc,
+ capsule->errp)) {
+ capsule->failed = true;
return true;
}
return false;
}
-static int put_gtree(QEMUFile *f, void *pv, size_t unused_size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_gtree(QEMUFile *f, void *pv, size_t unused_size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
bool direct_key = (!field->start);
const VMStateDescription *key_vmsd = direct_key ? NULL : &field->vmsd[1];
const VMStateDescription *val_vmsd = &field->vmsd[0];
const char *key_vmsd_name = direct_key ? "direct" : key_vmsd->name;
- struct put_gtree_data capsule = {
+ struct save_gtree_data capsule = {
.f = f,
.key_vmsd = key_vmsd,
.val_vmsd = val_vmsd,
.vmdesc = vmdesc,
- .ret = 0};
+ .errp = errp,
+ .failed = false};
GTree **pval = pv;
GTree *tree = *pval;
uint32_t nnodes = g_tree_nnodes(tree);
- int ret;
- trace_put_gtree(field->name, key_vmsd_name, val_vmsd->name, nnodes);
+ trace_save_gtree(field->name, key_vmsd_name, val_vmsd->name, nnodes);
qemu_put_be32(f, nnodes);
- g_tree_foreach(tree, put_gtree_elem, (gpointer)&capsule);
+ g_tree_foreach(tree, save_gtree_elem, (gpointer)&capsule);
qemu_put_byte(f, false);
- ret = capsule.ret;
- if (ret) {
- error_report("%s : failed to save gtree (%d)", field->name, ret);
+ if (capsule.failed) {
+ trace_save_gtree_end(field->name, key_vmsd_name, val_vmsd->name);
+ return false;
}
- trace_put_gtree_end(field->name, key_vmsd_name, val_vmsd->name, ret);
- return ret;
+
+ trace_save_gtree_end(field->name, key_vmsd_name, val_vmsd->name);
+ return true;
}
-static int get_gtree(QEMUFile *f, void *pv, size_t unused_size,
- const VMStateField *field)
+static bool load_gtree(QEMUFile *f, void *pv, size_t unused_size,
+ const VMStateField *field, Error **errp)
{
bool direct_key = (!field->start);
const VMStateDescription *key_vmsd = direct_key ? NULL : &field->vmsd[1];
@@ -783,107 +798,97 @@ static int get_gtree(QEMUFile *f, void *pv, size_t unused_size,
GTree **pval = pv;
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) {
- error_report("%s %s", key_vmsd->name, "too new");
- return -EINVAL;
+ error_setg(errp, "%s %s", key_vmsd->name, "too new");
+ return false;
}
if (!direct_key && version_id < key_vmsd->minimum_version_id) {
- error_report("%s %s", key_vmsd->name, "too old");
- return -EINVAL;
+ error_setg(errp, "%s %s", key_vmsd->name, "too old");
+ return false;
}
if (version_id > val_vmsd->version_id) {
- error_report("%s %s", val_vmsd->name, "too new");
- return -EINVAL;
+ error_setg(errp, "%s %s", val_vmsd->name, "too new");
+ return false;
}
if (version_id < val_vmsd->minimum_version_id) {
- error_report("%s %s", val_vmsd->name, "too old");
- return -EINVAL;
+ error_setg(errp, "%s %s", val_vmsd->name, "too old");
+ return false;
}
nnodes = qemu_get_be32(f);
- trace_get_gtree(field->name, key_vmsd_name, val_vmsd->name, nnodes);
+ trace_load_gtree(field->name, key_vmsd_name, val_vmsd->name, nnodes);
while (qemu_get_byte(f)) {
if ((++count) > nnodes) {
- ret = -EINVAL;
break;
}
if (direct_key) {
key = (void *)(uintptr_t)qemu_get_be64(f);
} else {
key = g_malloc0(key_size);
- ret = vmstate_load_state(f, key_vmsd, key, version_id, &local_err);
- if (ret) {
- error_report_err(local_err);
+ if (!vmstate_load_vmsd(f, key_vmsd, key, version_id, errp)) {
goto key_error;
}
}
val = g_malloc0(val_size);
- ret = vmstate_load_state(f, val_vmsd, val, version_id, &local_err);
- if (ret) {
- error_report_err(local_err);
+ if (!vmstate_load_vmsd(f, val_vmsd, val, version_id, errp)) {
goto val_error;
}
g_tree_insert(tree, key, val);
}
if (count != nnodes) {
- error_report("%s inconsistent stream when loading the gtree",
- field->name);
- return -EINVAL;
+ error_setg(errp, "%s inconsistent stream when loading the gtree",
+ field->name);
+ return false;
}
- trace_get_gtree_end(field->name, key_vmsd_name, val_vmsd->name, ret);
- return ret;
+
+ trace_load_gtree_end(field->name, key_vmsd_name, val_vmsd->name);
+ return true;
+
val_error:
g_free(val);
+
key_error:
if (!direct_key) {
g_free(key);
}
- trace_get_gtree_end(field->name, key_vmsd_name, val_vmsd->name, ret);
- return ret;
+ return false;
}
const VMStateInfo vmstate_info_gtree = {
.name = "gtree",
- .get = get_gtree,
- .put = put_gtree,
+ .load = load_gtree,
+ .save = save_gtree,
};
-static int put_qlist(QEMUFile *f, void *pv, size_t unused_size,
- const VMStateField *field, JSONWriter *vmdesc)
+static bool save_qlist(QEMUFile *f, void *pv, size_t unused_size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
const VMStateDescription *vmsd = field->vmsd;
/* offset of the QTAILQ entry in a QTAILQ element*/
size_t entry_offset = field->start;
void *elm;
- int ret;
- Error *local_err = NULL;
- trace_put_qlist(field->name, vmsd->name, vmsd->version_id);
+ trace_save_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, &local_err);
- if (ret) {
- error_report_err(local_err);
- return ret;
+ if (!vmstate_save_vmsd(f, vmsd, elm, vmdesc, errp)) {
+ return false;
}
}
qemu_put_byte(f, false);
- trace_put_qlist_end(field->name, vmsd->name);
+ trace_save_qlist_end(field->name, vmsd->name);
- return 0;
+ return true;
}
-static int get_qlist(QEMUFile *f, void *pv, size_t unused_size,
- const VMStateField *field)
+static bool load_qlist(QEMUFile *f, void *pv, size_t unused_size,
+ const VMStateField *field, Error **errp)
{
- int ret = 0;
- Error *local_err = NULL;
const VMStateDescription *vmsd = field->vmsd;
/* size of a QLIST element */
size_t size = field->size;
@@ -892,23 +897,21 @@ static int get_qlist(QEMUFile *f, void *pv, size_t unused_size,
int version_id = field->version_id;
void *elm, *prev = NULL;
- trace_get_qlist(field->name, vmsd->name, vmsd->version_id);
+ trace_load_qlist(field->name, vmsd->name, vmsd->version_id);
if (version_id > vmsd->version_id) {
- error_report("%s %s", vmsd->name, "too new");
- return -EINVAL;
+ error_setg(errp, "%s %s", vmsd->name, "too new");
+ return false;
}
if (version_id < vmsd->minimum_version_id) {
- error_report("%s %s", vmsd->name, "too old");
- return -EINVAL;
+ error_setg(errp, "%s %s", vmsd->name, "too old");
+ return false;
}
while (qemu_get_byte(f)) {
elm = g_malloc(size);
- ret = vmstate_load_state(f, vmsd, elm, version_id, &local_err);
- if (ret) {
- error_report_err(local_err);
+ if (!vmstate_load_vmsd(f, vmsd, elm, version_id, errp)) {
g_free(elm);
- return ret;
+ return false;
}
if (!prev) {
QLIST_RAW_INSERT_HEAD(pv, elm, entry_offset);
@@ -917,13 +920,13 @@ static int get_qlist(QEMUFile *f, void *pv, size_t unused_size,
}
prev = elm;
}
- trace_get_qlist_end(field->name, vmsd->name);
+ trace_load_qlist_end(field->name, vmsd->name);
- return ret;
+ return true;
}
const VMStateInfo vmstate_info_qlist = {
.name = "qlist",
- .get = get_qlist,
- .put = put_qlist,
+ .load = load_qlist,
+ .save = save_qlist,
};
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 21/43] migration: Tweak description of migration property multifd-compression
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (19 preceding siblings ...)
2026-04-23 19:19 ` [PULL 20/43] migration/vmstate-types: move to new migration APIs Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 22/43] tests/qtest/migration: Add mapped-ram/postcopy validation test Fabiano Rosas
` (22 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Markus Armbruster
From: Markus Armbruster <armbru@redhat.com>
Help for the migration pseudo-device shows property
"multifd-compression" like this:
multifd-compression=<MultiFDCompression> - multifd_compression values (none/zlib/zstd/qpl/uadk/qatzip) (default: none)
Change it to
multifd-compression=<MultiFDCompression> - multifd compression method (none/zlib/zstd/qpl/uadk/qatzip) (default: none)
Signed-off-by: Markus Armbruster <armbru@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260326074247.188674-4-armbru@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
hw/core/qdev-properties-system.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hw/core/qdev-properties-system.c b/hw/core/qdev-properties-system.c
index a805ee2e1f..5bfdfe1ab9 100644
--- a/hw/core/qdev-properties-system.c
+++ b/hw/core/qdev-properties-system.c
@@ -681,7 +681,7 @@ const PropertyInfo qdev_prop_fdc_drive_type = {
const PropertyInfo qdev_prop_multifd_compression = {
.type = "MultiFDCompression",
- .description = "multifd_compression values"
+ .description = "multifd compression method"
" (none/zlib/zstd/qpl/uadk/qatzip)",
.enum_table = &MultiFDCompression_lookup,
.get = qdev_propinfo_get_enum,
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 22/43] tests/qtest/migration: Add mapped-ram/postcopy validation test
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (20 preceding siblings ...)
2026-04-23 19:19 ` [PULL 21/43] migration: Tweak description of migration property multifd-compression Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 23/43] migration: fix QIOChannelFile leak on error in file_connect_outgoing Fabiano Rosas
` (21 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Takeru Hayasaka
From: Takeru Hayasaka <hayatake396@gmail.com>
The migration capability checks reject enabling postcopy-ram together
with mapped-ram, but there is no qtest covering this incompatibility.
Add a validation test that verifies QMP rejects the combination in
both capability ordering cases and returns the expected error.
This keeps the existing capability boundary covered without changing
migration behavior.
Signed-off-by: Takeru Hayasaka <hayatake396@gmail.com>
Link: https://lore.kernel.org/qemu-devel/20260327164705.1990226-1-hayatake396@gmail.com
[unlink src_serial]
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
tests/qtest/migration/misc-tests.c | 52 ++++++++++++++++++++++++++++++
1 file changed, 52 insertions(+)
diff --git a/tests/qtest/migration/misc-tests.c b/tests/qtest/migration/misc-tests.c
index 196f1ca842..e114ac5972 100644
--- a/tests/qtest/migration/misc-tests.c
+++ b/tests/qtest/migration/misc-tests.c
@@ -215,6 +215,55 @@ static void do_test_validate_uri_channel(MigrateCommon *args)
migrate_end(from, to, false);
}
+static void validate_caps_pair(QTestState *from,
+ const char *first_capability,
+ const char *second_capability,
+ const char *expected_error)
+{
+ QDict *rsp;
+ const char *error_desc;
+
+ migrate_set_capability(from, first_capability, true);
+
+ rsp = qtest_qmp_assert_failure_ref(
+ from,
+ "{ 'execute': 'migrate-set-capabilities',"
+ " 'arguments': { 'capabilities': [ { "
+ " 'capability': %s, 'state': true } ] } }",
+ second_capability);
+
+ error_desc = qdict_get_str(rsp, "desc");
+ g_assert_cmpstr(error_desc, ==, expected_error);
+ qobject_unref(rsp);
+
+ migrate_set_capability(from, first_capability, false);
+}
+
+static void test_validate_caps_pair(char *test_path, MigrateCommon *args)
+{
+ g_autofree char *serial_path = g_strconcat(tmpfs, "/src_serial", NULL);
+ g_autofree char *cap_pair = g_path_get_basename(test_path);
+ QTestState *from, *to;
+
+ args->start.hide_stderr = true;
+ args->start.only_source = true;
+
+ if (migrate_start(&from, &to, "defer", &args->start)) {
+ return;
+ }
+
+ if (g_str_equal(cap_pair, "mapped_ram_postcopy")) {
+ const char *error =
+ "Mapped-ram migration is incompatible with postcopy";
+
+ validate_caps_pair(from, "mapped-ram", "postcopy-ram", error);
+ validate_caps_pair(from, "postcopy-ram", "mapped-ram", error);
+ }
+
+ qtest_quit(from);
+ unlink(serial_path);
+}
+
static void test_validate_uri_channels_both_set(char *name, MigrateCommon *args)
{
args->listen_uri = "defer",
@@ -276,4 +325,7 @@ void migration_test_add_misc(MigrationTestEnv *env)
test_validate_uri_channels_both_set);
migration_test_add("/migration/validate_uri/channels/none_set",
test_validate_uri_channels_none_set);
+ migration_test_add_suffix("/migration/validate_caps/",
+ "mapped_ram_postcopy",
+ test_validate_caps_pair);
}
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 23/43] migration: fix QIOChannelFile leak on error in file_connect_outgoing
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (21 preceding siblings ...)
2026-04-23 19:19 ` [PULL 22/43] tests/qtest/migration: Add mapped-ram/postcopy validation test Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 24/43] vmstate: Pass in struct itself for VMSTATE_ARRAY_OF_POINTER Fabiano Rosas
` (20 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Trieu Huynh
From: Trieu Huynh <vikingtc4@gmail.com>
Commit 03a680c978 changed g_autoptr(QIOChannelFile) to a plain pointer
but failed to restore the necessary object_unref() calls on error paths.
Previously, these were handled implicitly by the g_autoptr cleanup
mechanism.
Two error paths currently leak the QIOChannelFile object and its
underlying file descriptor:
1. When ftruncate() fails (e.g., on character or block devices).
2. When qio_channel_io_seek() fails after the channel is created.
In environments that retry migration automatically (e.g., libvirt),
these FDs accumulate until QEMU hits RLIMIT_NOFILE and fails with
EMFILE (Too many open files).
Add the missing object_unref() calls to both error paths to ensure
resources are properly released.
Signed-off-by: Trieu Huynh <vikingtc4@gmail.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260328121215.159532-1-vikingtc4@gmail.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/file.c | 2 ++
tests/qtest/migration/file-tests.c | 48 ++++++++++++++++++++++++++++++
2 files changed, 50 insertions(+)
diff --git a/migration/file.c b/migration/file.c
index 5618aced49..962b4b3a1b 100644
--- a/migration/file.c
+++ b/migration/file.c
@@ -112,6 +112,7 @@ QIOChannel *file_connect_outgoing(MigrationState *s,
error_setg_errno(errp, errno,
"failed to truncate migration file to offset %" PRIx64,
offset);
+ object_unref(OBJECT(fioc));
goto out;
}
@@ -119,6 +120,7 @@ QIOChannel *file_connect_outgoing(MigrationState *s,
ioc = QIO_CHANNEL(fioc);
if (offset && qio_channel_io_seek(ioc, offset, SEEK_SET, errp) < 0) {
+ object_unref(OBJECT(fioc));
ioc = NULL;
goto out;
}
diff --git a/tests/qtest/migration/file-tests.c b/tests/qtest/migration/file-tests.c
index 5d1b861cf6..fef172068f 100644
--- a/tests/qtest/migration/file-tests.c
+++ b/tests/qtest/migration/file-tests.c
@@ -20,6 +20,51 @@
static char *tmpfs;
+static int count_proc_fds(pid_t pid)
+{
+ g_autofree char *fddir = g_strdup_printf("/proc/%d/fd", (int)pid);
+ GDir *dir = g_dir_open(fddir, 0, NULL);
+ int count = 0;
+
+ if (!dir) {
+ return -1;
+ }
+ while (g_dir_read_name(dir)) {
+ count++;
+ }
+ g_dir_close(dir);
+ return count;
+}
+
+static void test_file_connect_outgoing_fd_leak(char *name, MigrateCommon *args)
+{
+ QTestState *from, *to;
+ int fd_before, fd_after;
+ const int retries = 5;
+ int i;
+ if (!g_file_test("/dev/full", G_FILE_TEST_EXISTS)) {
+ g_test_skip("/dev/full not available");
+ return;
+ }
+
+ args->listen_uri = "defer";
+ if (migrate_start(&from, &to, args->listen_uri, &args->start)) {
+ return;
+ }
+
+ fd_before = count_proc_fds(qtest_pid(from));
+ g_assert_cmpint(fd_before, >, 0);
+ for (i = 0; i < retries; i++) {
+ migrate_qmp_fail(from, "file:/dev/full", NULL, "{}");
+ migration_event_wait(from, "failed");
+ }
+
+ fd_after = count_proc_fds(qtest_pid(from));
+ g_assert_cmpint(fd_after, >, 0);
+ g_assert_cmpint(fd_after, ==, fd_before);
+ migrate_end(from, to, false);
+}
+
static void test_precopy_file(char *name, MigrateCommon *args)
{
g_autofree char *uri = g_strdup_printf("file:%s/%s", tmpfs,
@@ -314,6 +359,9 @@ void migration_test_add_file(MigrationTestEnv *env)
migration_test_add("/migration/precopy/file/offset/bad",
test_precopy_file_offset_bad);
+ migration_test_add("/migration/precopy/file/connect-outgoing-fd-leak",
+ test_file_connect_outgoing_fd_leak);
+
migration_test_add("/migration/precopy/file/mapped-ram",
test_precopy_file_mapped_ram);
migration_test_add("/migration/precopy/file/mapped-ram/live",
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 24/43] vmstate: Pass in struct itself for VMSTATE_ARRAY_OF_POINTER
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (22 preceding siblings ...)
2026-04-23 19:19 ` [PULL 23/43] migration: fix QIOChannelFile leak on error in file_connect_outgoing Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 25/43] vmstate: Pass in struct itself for VMSTATE_VARRAY_OF_POINTER_UINT32 Fabiano Rosas
` (19 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Xu, Alexander Mikhalitsyn, Philippe Mathieu-Daudé,
Juraj Marcin
From: Peter Xu <peterx@redhat.com>
Passing in a pointer almost never helps. Convert it to pass in struct for
further refactoring on VMS_ARRAY_OF_POINTER.
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-2-peterx@redhat.com
[delete spurious hunk touching roms/seabios]
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/migration/vmstate.h | 6 +++---
tests/unit/test-vmstate.c | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index d4a39aa794..d4880e75c2 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -547,9 +547,9 @@ extern const VMStateInfo vmstate_info_qlist;
.version_id = (_version), \
.num = (_num), \
.info = &(_info), \
- .size = sizeof(_type), \
+ .size = sizeof(_type *), \
.flags = VMS_ARRAY|VMS_ARRAY_OF_POINTER, \
- .offset = vmstate_offset_array(_state, _field, _type, _num), \
+ .offset = vmstate_offset_array(_state, _field, _type *, _num), \
}
#define VMSTATE_ARRAY_OF_POINTER_TO_STRUCT(_f, _s, _n, _v, _vmsd, _type) { \
@@ -1093,7 +1093,7 @@ extern const VMStateInfo vmstate_info_qlist;
VMSTATE_TIMER_PTR_V(_f, _s, 0)
#define VMSTATE_TIMER_PTR_ARRAY(_f, _s, _n) \
- VMSTATE_ARRAY_OF_POINTER(_f, _s, _n, 0, vmstate_info_timer, QEMUTimer *)
+ VMSTATE_ARRAY_OF_POINTER(_f, _s, _n, 0, vmstate_info_timer, QEMUTimer)
#define VMSTATE_TIMER_TEST(_f, _s, _test) \
VMSTATE_SINGLE_TEST(_f, _s, _test, 0, vmstate_info_timer, QEMUTimer)
diff --git a/tests/unit/test-vmstate.c b/tests/unit/test-vmstate.c
index cadbab3c5e..6a42cc1a4e 100644
--- a/tests/unit/test-vmstate.c
+++ b/tests/unit/test-vmstate.c
@@ -668,7 +668,7 @@ const VMStateDescription vmsd_arpp = {
.minimum_version_id = 1,
.fields = (const VMStateField[]) {
VMSTATE_ARRAY_OF_POINTER(ar, TestArrayOfPtrToInt,
- AR_SIZE, 0, vmstate_info_int32, int32_t*),
+ AR_SIZE, 0, vmstate_info_int32, int32_t),
VMSTATE_END_OF_LIST()
}
};
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 25/43] vmstate: Pass in struct itself for VMSTATE_VARRAY_OF_POINTER_UINT32
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (23 preceding siblings ...)
2026-04-23 19:19 ` [PULL 24/43] vmstate: Pass in struct itself for VMSTATE_ARRAY_OF_POINTER Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 26/43] vmstate: Do not set size for VMS_ARRAY_OF_POINTER Fabiano Rosas
` (18 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Xu, Alexander Mikhalitsyn, Philippe Mathieu-Daudé,
Juraj Marcin
From: Peter Xu <peterx@redhat.com>
Passing in a pointer almost never helps. Convert it to pass in struct for
further refactoring on VMS_ARRAY_OF_POINTER.
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-3-peterx@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/hw/intc/riscv_aclint.h | 6 +++---
include/migration/vmstate.h | 4 ++--
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/include/hw/intc/riscv_aclint.h b/include/hw/intc/riscv_aclint.h
index 5310615cbf..0e0b98acb0 100644
--- a/include/hw/intc/riscv_aclint.h
+++ b/include/hw/intc/riscv_aclint.h
@@ -80,8 +80,8 @@ enum {
RISCV_ACLINT_SWI_SIZE = 0x4000
};
-#define VMSTATE_TIMER_PTR_VARRAY(_f, _s, _f_n) \
-VMSTATE_VARRAY_OF_POINTER_UINT32(_f, _s, _f_n, 0, vmstate_info_timer, \
- QEMUTimer *)
+#define VMSTATE_TIMER_PTR_VARRAY(_f, _s, _f_n) \
+ VMSTATE_VARRAY_OF_POINTER_UINT32(_f, _s, _f_n, 0, vmstate_info_timer, \
+ QEMUTimer)
#endif
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index d4880e75c2..32bb8e8ebc 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -567,9 +567,9 @@ extern const VMStateInfo vmstate_info_qlist;
.version_id = (_version), \
.num_offset = vmstate_offset_value(_state, _field_num, uint32_t), \
.info = &(_info), \
- .size = sizeof(_type), \
+ .size = sizeof(_type *), \
.flags = VMS_VARRAY_UINT32 | VMS_ARRAY_OF_POINTER | VMS_POINTER, \
- .offset = vmstate_offset_pointer(_state, _field, _type), \
+ .offset = vmstate_offset_pointer(_state, _field, _type *), \
}
#define VMSTATE_STRUCT_SUB_ARRAY(_field, _state, _start, _num, _version, _vmsd, _type) { \
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 26/43] vmstate: Do not set size for VMS_ARRAY_OF_POINTER
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (24 preceding siblings ...)
2026-04-23 19:19 ` [PULL 25/43] vmstate: Pass in struct itself for VMSTATE_VARRAY_OF_POINTER_UINT32 Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 27/43] vmstate: Update max_elems early and check field compressable once Fabiano Rosas
` (17 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Xu, Alexander Mikhalitsyn, Philippe Mathieu-Daudé,
Juraj Marcin
From: Peter Xu <peterx@redhat.com>
When VMS_ARRAY_OF_POINTER is specified, it means the vmstate field is an
array of pointers.
The size of the element is not relevant to whatever it is stored inside: it
is always the host pointer size.
Let's reserve the "size" field in this case for future use, update
vmstate_size() so as to make it still work for array of pointers properly.
When at this, provide rich documentation on how size / size_offset works in
vmstate.
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-4-peterx@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/migration/vmstate.h | 20 ++++++++++++++++----
migration/savevm.c | 3 +++
migration/vmstate.c | 10 +++++++++-
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 32bb8e8ebc..350e62a55a 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -183,11 +183,26 @@ typedef enum {
struct VMStateField {
const char *name;
size_t offset;
+
+ /*
+ * @size or @size_offset specifies the size of the element embeded in
+ * the field. Only one of them should be present never both. When
+ * @size_offset is used together with VMS_VBUFFER, it means the size is
+ * dynamic calculated instead of a constant.
+ *
+ * When the field is an array of any type, this stores the size of one
+ * element of the array.
+ *
+ * NOTE: even if VMS_POINTER or VMS_ARRAY_OF_POINTER may be specified,
+ * this parameter always reflects the real size of the objects that a
+ * pointer point to.
+ */
size_t size;
+ size_t size_offset;
+
size_t start;
int num;
size_t num_offset;
- size_t size_offset;
const VMStateInfo *info;
enum VMStateFlags flags;
const VMStateDescription *vmsd;
@@ -547,7 +562,6 @@ extern const VMStateInfo vmstate_info_qlist;
.version_id = (_version), \
.num = (_num), \
.info = &(_info), \
- .size = sizeof(_type *), \
.flags = VMS_ARRAY|VMS_ARRAY_OF_POINTER, \
.offset = vmstate_offset_array(_state, _field, _type *, _num), \
}
@@ -557,7 +571,6 @@ extern const VMStateInfo vmstate_info_qlist;
.version_id = (_v), \
.num = (_n), \
.vmsd = &(_vmsd), \
- .size = sizeof(_type *), \
.flags = VMS_ARRAY|VMS_STRUCT|VMS_ARRAY_OF_POINTER, \
.offset = vmstate_offset_array(_s, _f, _type*, _n), \
}
@@ -567,7 +580,6 @@ extern const VMStateInfo vmstate_info_qlist;
.version_id = (_version), \
.num_offset = vmstate_offset_value(_state, _field_num, uint32_t), \
.info = &(_info), \
- .size = sizeof(_type *), \
.flags = VMS_VARRAY_UINT32 | VMS_ARRAY_OF_POINTER | VMS_POINTER, \
.offset = vmstate_offset_pointer(_state, _field, _type *), \
}
diff --git a/migration/savevm.c b/migration/savevm.c
index 8115203b51..f5a6fd0c66 100644
--- a/migration/savevm.c
+++ b/migration/savevm.c
@@ -868,6 +868,9 @@ static void vmstate_check(const VMStateDescription *vmsd)
if (field) {
while (field->name) {
+ if (field->flags & VMS_ARRAY_OF_POINTER) {
+ assert(field->size == 0);
+ }
if (field->flags & (VMS_STRUCT | VMS_VSTRUCT)) {
/* Recurse to sub structures */
vmstate_check(field->vmsd);
diff --git a/migration/vmstate.c b/migration/vmstate.c
index e98b5f5346..e29a8c3f49 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -110,13 +110,21 @@ static int vmstate_n_elems(void *opaque, const VMStateField *field)
static int vmstate_size(void *opaque, const VMStateField *field)
{
- int size = field->size;
+ int size;
if (field->flags & VMS_VBUFFER) {
size = *(int32_t *)(opaque + field->size_offset);
if (field->flags & VMS_MULTIPLY) {
size *= field->size;
}
+ } else if (field->flags & VMS_ARRAY_OF_POINTER) {
+ /*
+ * For an array of pointer, the each element is always size of a
+ * host pointer.
+ */
+ size = sizeof(void *);
+ } else {
+ size = field->size;
}
return size;
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 27/43] vmstate: Update max_elems early and check field compressable once
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (25 preceding siblings ...)
2026-04-23 19:19 ` [PULL 26/43] vmstate: Do not set size for VMS_ARRAY_OF_POINTER Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 28/43] vmstate: Rename VMS_NULLPTR_MARKER to VMS_MARKER_PTR_NULL Fabiano Rosas
` (16 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Alexander Mikhalitsyn, Juraj Marcin
From: Peter Xu <peterx@redhat.com>
QEMU has a trick in vmstate_save_vmsd_v(), where it will try to compress
multiple JSON entries into one with a count to avoid duplicated entries.
That only applies to the cases where vmsd_can_compress() should return
true. For example, vmsd_desc_field_start() later (who will take the
updated max_elems as the last parameter) will ignore the value passed in
when vmsd_can_compress() returns false.
Do that check once at the start of loop, and use it to update max_elems, so
that max_elems keeps 1 for uncompressable VMSD fields, which is more
straightforward.
This also paves way to make this counter work for ptr marker VMSD fields
too.
No functional change intended in this patch alone.
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-5-peterx@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate.c | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index e29a8c3f49..05badef42f 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -556,7 +556,8 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
void *curr_elem = first_elem + size * i;
const VMStateField *inner_field;
bool is_null;
- int max_elems = n_elems - i;
+ /* maximum number of elements to compress in the JSON blob */
+ int max_elems = vmsd_can_compress(field) ? (n_elems - i) : 1;
old_offset = qemu_file_transferred(f);
if (field->flags & VMS_ARRAY_OF_POINTER) {
@@ -587,7 +588,8 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
* vs. nullptr). Search ahead for the next null/non-null element
* and start a new compressed array if found.
*/
- if (vmdesc && (field->flags & VMS_ARRAY_OF_POINTER) &&
+ if (vmdesc && max_elems > 1 &&
+ (field->flags & VMS_ARRAY_OF_POINTER) &&
is_null != is_prev_null) {
is_prev_null = is_null;
@@ -626,7 +628,7 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
}
/* Compressed arrays only care about the first element */
- if (vmdesc_loop && vmsd_can_compress(field)) {
+ if (vmdesc_loop && max_elems > 1) {
vmdesc_loop = NULL;
}
}
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 28/43] vmstate: Rename VMS_NULLPTR_MARKER to VMS_MARKER_PTR_NULL
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (26 preceding siblings ...)
2026-04-23 19:19 ` [PULL 27/43] vmstate: Update max_elems early and check field compressable once Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 29/43] vmstate: Introduce vmstate_save_field_with_vmdesc() Fabiano Rosas
` (15 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel
Cc: Peter Xu, Alexander Mikhalitsyn, Philippe Mathieu-Daudé,
Juraj Marcin
From: Peter Xu <peterx@redhat.com>
Prepare for a new MARKER for non-NULL pointer.
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-6-peterx@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/migration/vmstate.h | 2 +-
migration/vmstate-types.c | 4 ++--
tests/unit/test-vmstate.c | 2 +-
3 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 350e62a55a..7396e38531 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -283,7 +283,7 @@ extern const VMStateInfo vmstate_info_uint64;
extern const VMStateInfo vmstate_info_fd;
/** Put this in the stream when migrating a null pointer.*/
-#define VMS_NULLPTR_MARKER (0x30U) /* '0' */
+#define VMS_MARKER_PTR_NULL (0x30U) /* '0' */
extern const VMStateInfo vmstate_info_nullptr;
extern const VMStateInfo vmstate_info_cpudouble;
diff --git a/migration/vmstate-types.c b/migration/vmstate-types.c
index 23f3433696..7622cf8f01 100644
--- a/migration/vmstate-types.c
+++ b/migration/vmstate-types.c
@@ -363,7 +363,7 @@ static bool load_nullptr(QEMUFile *f, void *pv, size_t size,
const VMStateField *field, Error **errp)
{
- if (qemu_get_byte(f) == VMS_NULLPTR_MARKER) {
+ if (qemu_get_byte(f) == VMS_MARKER_PTR_NULL) {
return true;
}
@@ -377,7 +377,7 @@ static bool save_nullptr(QEMUFile *f, void *pv, size_t size,
{
if (pv == NULL) {
- qemu_put_byte(f, VMS_NULLPTR_MARKER);
+ qemu_put_byte(f, VMS_MARKER_PTR_NULL);
return true;
}
diff --git a/tests/unit/test-vmstate.c b/tests/unit/test-vmstate.c
index 6a42cc1a4e..dae15786aa 100644
--- a/tests/unit/test-vmstate.c
+++ b/tests/unit/test-vmstate.c
@@ -620,7 +620,7 @@ static void test_arr_ptr_str_no0_load(void)
static uint8_t wire_arr_ptr_0[] = {
0x00, 0x00, 0x00, 0x00,
- VMS_NULLPTR_MARKER,
+ VMS_MARKER_PTR_NULL,
0x00, 0x00, 0x00, 0x02,
0x00, 0x00, 0x00, 0x03,
QEMU_VM_EOF
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 29/43] vmstate: Introduce vmstate_save_field_with_vmdesc()
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (27 preceding siblings ...)
2026-04-23 19:19 ` [PULL 28/43] vmstate: Rename VMS_NULLPTR_MARKER to VMS_MARKER_PTR_NULL Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 30/43] vmstate: Allow vmstate_info_nullptr to emit non-NULL markers Fabiano Rosas
` (14 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Alexander Mikhalitsyn, Juraj Marcin
From: Peter Xu <peterx@redhat.com>
Introduce a helper to do both the JSON blob generations and save vmstate.
This further shrinks the function a bit. More importantly, we'll need to
save two fields in one loop very soon in the future with the JSON blob.
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-7-peterx@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate.c | 45 ++++++++++++++++++++++++++++++++-------------
1 file changed, 32 insertions(+), 13 deletions(-)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 05badef42f..b274204e66 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -514,6 +514,35 @@ static bool vmstate_save_field(QEMUFile *f, void *pv, size_t size,
return true;
}
+/*
+ * Save a whole VMSD field, including its JSON blob separately when @vmdesc
+ * is specified.
+ */
+static inline bool
+vmstate_save_field_with_vmdesc(QEMUFile *f, void *pv, size_t size,
+ const VMStateDescription *vmsd,
+ const VMStateField *field, JSONWriter *vmdesc,
+ int i, int max, Error **errp)
+{
+ uint64_t old_offset, written_bytes;
+ bool ok;
+
+ vmsd_desc_field_start(vmsd, vmdesc, field, i, max);
+
+ old_offset = qemu_file_transferred(f);
+ ok = vmstate_save_field(f, pv, size, field, vmdesc, errp);
+ written_bytes = qemu_file_transferred(f) - old_offset;
+
+ vmsd_desc_field_end(vmsd, vmdesc, field, written_bytes);
+
+ if (!ok) {
+ error_prepend(errp, "Save of field %s/%s failed: ",
+ vmsd->name, field->name);
+ }
+
+ return ok;
+}
+
static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, JSONWriter *vmdesc,
int version_id, Error **errp)
@@ -542,7 +571,6 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
void *first_elem = opaque + field->offset;
int i, n_elems = vmstate_n_elems(opaque, field);
int size = vmstate_size(opaque, field);
- uint64_t old_offset, written_bytes;
JSONWriter *vmdesc_loop = vmdesc;
bool is_prev_null = false;
@@ -559,7 +587,6 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
/* maximum number of elements to compress in the JSON blob */
int max_elems = vmsd_can_compress(field) ? (n_elems - i) : 1;
- old_offset = qemu_file_transferred(f);
if (field->flags & VMS_ARRAY_OF_POINTER) {
assert(curr_elem);
curr_elem = *(void **)curr_elem;
@@ -606,15 +633,9 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
}
}
- vmsd_desc_field_start(vmsd, vmdesc_loop, inner_field,
- i, max_elems);
-
- ok = vmstate_save_field(f, curr_elem, size, inner_field,
- vmdesc_loop, errp);
-
- written_bytes = qemu_file_transferred(f) - old_offset;
- vmsd_desc_field_end(vmsd, vmdesc_loop, inner_field,
- written_bytes);
+ ok = vmstate_save_field_with_vmdesc(f, curr_elem, size, vmsd,
+ inner_field, vmdesc_loop,
+ i, max_elems, errp);
/* If we used a fake temp field.. free it now */
if (is_null) {
@@ -622,8 +643,6 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
}
if (!ok) {
- error_prepend(errp, "Save of field %s/%s failed: ",
- vmsd->name, field->name);
goto out;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 30/43] vmstate: Allow vmstate_info_nullptr to emit non-NULL markers
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (28 preceding siblings ...)
2026-04-23 19:19 ` [PULL 29/43] vmstate: Introduce vmstate_save_field_with_vmdesc() Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 31/43] vmstate: Implement load of ptr marker in vmstate core Fabiano Rosas
` (13 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Alexander Mikhalitsyn, Juraj Marcin
From: Peter Xu <peterx@redhat.com>
We used to have one vmstate called "nullptr" which is only used to generate
one-byte hint to say one pointer is NULL.
Let's extend its use so that it will generate another byte to say the
pointer is non-NULL.
With that, the name of the info struct (or functions) do not apply anymore.
Update correspondingly.
Update analyze-migration.py to work with the new layout.
No functional change intended yet.
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-8-peterx@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/migration/vmstate.h | 9 +++++++--
migration/vmstate-types.c | 34 ++++++++++++++++------------------
migration/vmstate.c | 25 +++++++++++++------------
scripts/analyze-migration.py | 22 ++++++++++++----------
4 files changed, 48 insertions(+), 42 deletions(-)
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 7396e38531..492069b8d2 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -282,9 +282,14 @@ extern const VMStateInfo vmstate_info_uint32;
extern const VMStateInfo vmstate_info_uint64;
extern const VMStateInfo vmstate_info_fd;
-/** Put this in the stream when migrating a null pointer.*/
+/*
+ * Put this in the stream when migrating a pointer to reflect either a NULL
+ * or valid pointer.
+ */
#define VMS_MARKER_PTR_NULL (0x30U) /* '0' */
-extern const VMStateInfo vmstate_info_nullptr;
+#define VMS_MARKER_PTR_VALID (0x31U) /* '1' */
+
+extern const VMStateInfo vmstate_info_ptr_marker;
extern const VMStateInfo vmstate_info_cpudouble;
diff --git a/migration/vmstate-types.c b/migration/vmstate-types.c
index 7622cf8f01..b31689fc3c 100644
--- a/migration/vmstate-types.c
+++ b/migration/vmstate-types.c
@@ -359,36 +359,34 @@ const VMStateInfo vmstate_info_fd = {
.save = save_fd,
};
-static bool load_nullptr(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, Error **errp)
+static bool load_ptr_marker(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, Error **errp)
{
- if (qemu_get_byte(f) == VMS_MARKER_PTR_NULL) {
+ int byte = qemu_get_byte(f);
+
+ if (byte == VMS_MARKER_PTR_NULL || byte == VMS_MARKER_PTR_VALID) {
+ /* TODO: process PTR_VALID case */
return true;
}
- error_setg(errp, "vmstate: load_nullptr expected VMS_NULLPTR_MARKER");
+ error_setg(errp, "%s: unexpected ptr marker: %d", __func__, byte);
return false;
}
-static bool save_nullptr(QEMUFile *f, void *pv, size_t size,
- const VMStateField *field, JSONWriter *vmdesc,
- Error **errp)
+static bool save_ptr_marker(QEMUFile *f, void *pv, size_t size,
+ const VMStateField *field, JSONWriter *vmdesc,
+ Error **errp)
{
- if (pv == NULL) {
- qemu_put_byte(f, VMS_MARKER_PTR_NULL);
- return true;
- }
-
- error_setg(errp, "vmstate: save_nullptr must be called with pv == NULL");
- return false;
+ qemu_put_byte(f, pv ? VMS_MARKER_PTR_VALID : VMS_MARKER_PTR_NULL);
+ return true;
}
-const VMStateInfo vmstate_info_nullptr = {
- .name = "nullptr",
- .load = load_nullptr,
- .save = save_nullptr,
+const VMStateInfo vmstate_info_ptr_marker = {
+ .name = "ptr-marker",
+ .load = load_ptr_marker,
+ .save = save_ptr_marker,
};
/* 64 bit unsigned int. See that the received value is the same than the one
diff --git a/migration/vmstate.c b/migration/vmstate.c
index b274204e66..b333aa1744 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -55,12 +55,12 @@ vmstate_field_exists(const VMStateDescription *vmsd, const VMStateField *field,
}
/*
- * Create a fake nullptr field when there's a NULL pointer detected in the
+ * Create a ptr marker field when there's a NULL pointer detected in the
* array of a VMS_ARRAY_OF_POINTER VMSD field. It's needed because we
* can't dereference the NULL pointer.
*/
static const VMStateField *
-vmsd_create_fake_nullptr_field(const VMStateField *field)
+vmsd_create_ptr_marker_field(const VMStateField *field)
{
VMStateField *fake = g_new0(VMStateField, 1);
@@ -71,12 +71,12 @@ vmsd_create_fake_nullptr_field(const VMStateField *field)
fake->name = field->name;
fake->version_id = field->version_id;
- /* Do not need "field_exists" check as it always exists (which is null) */
+ /* Do not need "field_exists" check as it always exists */
fake->field_exists = NULL;
- /* See vmstate_info_nullptr - use 1 byte to represent nullptr */
+ /* See vmstate_info_ptr_marker - use 1 byte to represent ptr status */
fake->size = 1;
- fake->info = &vmstate_info_nullptr;
+ fake->info = &vmstate_info_ptr_marker;
fake->flags = VMS_SINGLE;
/* All the rest fields shouldn't matter.. */
@@ -278,7 +278,7 @@ bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
* an array of pointers), use null placeholder and do
* not follow.
*/
- inner_field = vmsd_create_fake_nullptr_field(field);
+ inner_field = vmsd_create_ptr_marker_field(field);
} else {
inner_field = field;
}
@@ -583,26 +583,27 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
for (i = 0; i < n_elems; i++) {
void *curr_elem = first_elem + size * i;
const VMStateField *inner_field;
- bool is_null;
/* maximum number of elements to compress in the JSON blob */
int max_elems = vmsd_can_compress(field) ? (n_elems - i) : 1;
+ bool use_marker_field, is_null;
if (field->flags & VMS_ARRAY_OF_POINTER) {
assert(curr_elem);
curr_elem = *(void **)curr_elem;
}
- if (!curr_elem && size) {
+ is_null = !curr_elem && size;
+ use_marker_field = is_null;
+
+ if (use_marker_field) {
/*
* If null pointer found (which should only happen in
* an array of pointers), use null placeholder and do
* not follow.
*/
- inner_field = vmsd_create_fake_nullptr_field(field);
- is_null = true;
+ inner_field = vmsd_create_ptr_marker_field(field);
} else {
inner_field = field;
- is_null = false;
}
/*
@@ -638,7 +639,7 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
i, max_elems, errp);
/* If we used a fake temp field.. free it now */
- if (is_null) {
+ if (use_marker_field) {
g_clear_pointer((gpointer *)&inner_field, g_free);
}
diff --git a/scripts/analyze-migration.py b/scripts/analyze-migration.py
index e81deab8f9..1771ff781b 100755
--- a/scripts/analyze-migration.py
+++ b/scripts/analyze-migration.py
@@ -469,26 +469,26 @@ def __init__(self, desc, file):
super(VMSDFieldIntLE, self).__init__(desc, file)
self.dtype = '<i%d' % self.size
-class VMSDFieldNull(VMSDFieldGeneric):
+class VMSDFieldPtrMarker(VMSDFieldGeneric):
NULL_PTR_MARKER = b'0'
+ VALID_PTR_MARKER = b'1'
def __init__(self, desc, file):
- super(VMSDFieldNull, self).__init__(desc, file)
+ super(VMSDFieldPtrMarker, self).__init__(desc, file)
def __repr__(self):
- # A NULL pointer is encoded in the stream as a '0' to
- # disambiguate from a mere 0x0 value and avoid consumers
- # trying to follow the NULL pointer. Displaying '0', 0x30 or
- # 0x0 when analyzing the JSON debug stream could become
+ # A NULL / non-NULL pointer may be encoded in the stream as a
+ # '0'/'1' to represent the status of the pointer. Displaying '0',
+ # 0x30 or 0x0 when analyzing the JSON debug stream could become
# confusing, so use an explicit term instead.
- return "nullptr"
+ return "null-ptr" if self.data == self.NULL_PTR_MARKER else "valid-ptr"
def __str__(self):
return self.__repr__()
def read(self):
- super(VMSDFieldNull, self).read()
- assert(self.data == self.NULL_PTR_MARKER)
+ super(VMSDFieldPtrMarker, self).read()
+ assert(self.data in [self.NULL_PTR_MARKER, self.VALID_PTR_MARKER])
return self.data
class VMSDFieldBool(VMSDFieldGeneric):
@@ -642,7 +642,9 @@ def getDict(self):
"bitmap" : VMSDFieldGeneric,
"struct" : VMSDFieldStruct,
"capability": VMSDFieldCap,
- "nullptr": VMSDFieldNull,
+ # Keep the old nullptr for old binaries
+ "nullptr": VMSDFieldPtrMarker,
+ "ptr-marker": VMSDFieldPtrMarker,
"unknown" : VMSDFieldGeneric,
}
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 31/43] vmstate: Implement load of ptr marker in vmstate core
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (29 preceding siblings ...)
2026-04-23 19:19 ` [PULL 30/43] vmstate: Allow vmstate_info_nullptr to emit non-NULL markers Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 32/43] vmstate: Implement VMS_ARRAY_OF_POINTER_AUTO_ALLOC Fabiano Rosas
` (12 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Alexander Mikhalitsyn, Juraj Marcin
From: Peter Xu <peterx@redhat.com>
The loader side of ptr marker is pretty straightforward, instead of playing
the inner_field trick, just do the load manually assuming the marker layout
is a stable ABI (which it is true already).
This will remove some logic while loading VMSD, and hopefully it makes it
slightly easier to read. Unfortunately, we still need to keep the sender
side because of the JSON blob we're maintaining..
This paves way for future processing of non-NULL markers as well.
When at it, not check "size" anymore for existing NULL markers, and move it
under the same VMS_ARRAY_OF_POINTER section because that's the only place
that NULL marker can happen (which guarantess size==host ptr size, which is
non-zero).
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-9-peterx@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate-types.c | 12 ++++------
migration/vmstate.c | 46 ++++++++++++++++++++++++---------------
2 files changed, 32 insertions(+), 26 deletions(-)
diff --git a/migration/vmstate-types.c b/migration/vmstate-types.c
index b31689fc3c..ae465c5c2c 100644
--- a/migration/vmstate-types.c
+++ b/migration/vmstate-types.c
@@ -363,14 +363,10 @@ static bool load_ptr_marker(QEMUFile *f, void *pv, size_t size,
const VMStateField *field, Error **errp)
{
- int byte = qemu_get_byte(f);
-
- if (byte == VMS_MARKER_PTR_NULL || byte == VMS_MARKER_PTR_VALID) {
- /* TODO: process PTR_VALID case */
- return true;
- }
-
- error_setg(errp, "%s: unexpected ptr marker: %d", __func__, byte);
+ /*
+ * Load is done in vmstate core, see vmstate_ptr_marker_load().
+ */
+ g_assert_not_reached();
return false;
}
diff --git a/migration/vmstate.c b/migration/vmstate.c
index b333aa1744..47812eb882 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -142,6 +142,21 @@ static void vmstate_handle_alloc(void *ptr, const VMStateField *field,
}
}
+static bool vmstate_ptr_marker_load(QEMUFile *f, bool *load_field,
+ Error **errp)
+{
+ int byte = qemu_get_byte(f);
+
+ if (byte == VMS_MARKER_PTR_NULL) {
+ /* When it's a null ptr marker, do not continue the load */
+ *load_field = false;
+ return true;
+ }
+
+ error_setg(errp, "Unexpected ptr marker: %d", byte);
+ return false;
+}
+
static bool vmstate_pre_load(const VMStateDescription *vmsd, void *opaque,
Error **errp)
{
@@ -264,30 +279,25 @@ bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
}
for (i = 0; i < n_elems; i++) {
- bool ok;
+ /* If we will process the load of field? */
+ bool load_field = true;
+ bool ok = true;
void *curr_elem = first_elem + size * i;
- const VMStateField *inner_field;
if (field->flags & VMS_ARRAY_OF_POINTER) {
curr_elem = *(void **)curr_elem;
+ if (!curr_elem) {
+ /* Read the marker instead of VMSD itself */
+ if (!vmstate_ptr_marker_load(f, &load_field, errp)) {
+ trace_vmstate_load_field_error(field->name,
+ -EINVAL);
+ return false;
+ }
+ }
}
- if (!curr_elem && size) {
- /*
- * If null pointer found (which should only happen in
- * an array of pointers), use null placeholder and do
- * not follow.
- */
- inner_field = vmsd_create_ptr_marker_field(field);
- } else {
- inner_field = field;
- }
-
- ok = vmstate_load_field(f, curr_elem, size, inner_field, errp);
-
- /* If we used a fake temp field.. free it now */
- if (inner_field != field) {
- g_clear_pointer((gpointer *)&inner_field, g_free);
+ if (load_field) {
+ ok = vmstate_load_field(f, curr_elem, size, field, errp);
}
if (ok) {
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 32/43] vmstate: Implement VMS_ARRAY_OF_POINTER_AUTO_ALLOC
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (30 preceding siblings ...)
2026-04-23 19:19 ` [PULL 31/43] vmstate: Implement load of ptr marker in vmstate core Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 33/43] vmstate: Stop checking size for nullptr compression Fabiano Rosas
` (11 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Alexander Mikhalitsyn, Juraj Marcin
From: Peter Xu <peterx@redhat.com>
Introduce a new flag, VMS_ARRAY_OF_POINTER_AUTO_ALLOC, for VMSD field. It
must be used together with VMS_ARRAY_OF_POINTER.
It can be used to allow migration of an array of pointers where the
pointers may point to NULLs.
Note that we used to allow migration of a NULL pointer within an array that
is being migrated. That corresponds to the code around vmstate_info_nullptr
where we may get/put one byte showing that the element of an array is NULL.
That usage is fine but very limited, it's because even if it will migrate a
NULL pointer with a marker, it still works in a way that both src and dest
QEMUs must know exactly which elements of the array are non-NULL, so
instead of dynamically loading an array (which can have NULL pointers), it
actually only verifies the known NULL pointers are still NULL pointers
after migration.
Also, in that case since dest QEMU knows exactly which element is NULL,
which is not NULL, dest QEMU's device code will manage all allocations for
the elements before invoking vmstate_load_vmsd().
That's not enough per evolving needs of new device states that may want to
provide real dynamic array of pointers, like what Alexander proposed here
with the NVMe device migration:
https://lore.kernel.org/r/20260317102708.126725-1-alexander@mihalicyn.com
This patch is an alternative approach to address the problem.
Along with the flag, introduce two new macros:
VMSTATE_VARRAY_OF_POINTER_TO_STRUCT_UINT{8|32}_ALLOC()
Which will be used very soon in the NVMe series.
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Tested-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-10-peterx@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/migration/vmstate.h | 51 ++++++++++++-
migration/savevm.c | 27 ++++++-
migration/vmstate.c | 145 ++++++++++++++++++++++++++++++------
3 files changed, 199 insertions(+), 24 deletions(-)
diff --git a/include/migration/vmstate.h b/include/migration/vmstate.h
index 492069b8d2..28e3640e60 100644
--- a/include/migration/vmstate.h
+++ b/include/migration/vmstate.h
@@ -161,8 +161,21 @@ enum VMStateFlags {
* structure we are referencing to use. */
VMS_VSTRUCT = 0x8000,
+ /*
+ * This is a sub-flag for VMS_ARRAY_OF_POINTER. When this flag is set,
+ * VMS_ARRAY_OF_POINTER must also be set. When set, it means array
+ * elements can contain either valid or NULL pointers, vmstate core
+ * will be responsible for synchronizing the pointer status, providing
+ * proper memory allocations on the pointer when it is populated on the
+ * source QEMU. It also means the user of the field must make sure all
+ * the elements in the array are NULL pointers before loading. This
+ * should also work with VMS_ALLOC when the array itself also needs to
+ * be allocated.
+ */
+ VMS_ARRAY_OF_POINTER_AUTO_ALLOC = 0x10000,
+
/* Marker for end of list */
- VMS_END = 0x10000
+ VMS_END = 0x20000,
};
typedef enum {
@@ -580,6 +593,42 @@ extern const VMStateInfo vmstate_info_qlist;
.offset = vmstate_offset_array(_s, _f, _type*, _n), \
}
+/*
+ * For migrating a dynamically allocated uint{8,32}-indexed array of
+ * pointers to structures (with NULL entries and with auto memory
+ * allocation).
+ *
+ * _type: type of structure pointed to
+ * _vmsd: VMSD for structure _type (when VMS_STRUCT is set)
+ * _info: VMStateInfo for _type (when VMS_STRUCT is not set)
+ * start: size of (_type) pointed to (for auto memory allocation)
+ */
+#define VMSTATE_VARRAY_OF_POINTER_TO_STRUCT_UINT8_ALLOC(\
+ _field, _state, _field_num, _version, _vmsd, _type) { \
+ .name = (stringify(_field)), \
+ .version_id = (_version), \
+ .num_offset = vmstate_offset_value(_state, _field_num, uint8_t), \
+ .vmsd = &(_vmsd), \
+ .size = sizeof(_type), \
+ .flags = VMS_POINTER | VMS_VARRAY_UINT8 | \
+ VMS_ARRAY_OF_POINTER | VMS_STRUCT | \
+ VMS_ARRAY_OF_POINTER_AUTO_ALLOC, \
+ .offset = vmstate_offset_pointer(_state, _field, _type *), \
+}
+
+#define VMSTATE_VARRAY_OF_POINTER_TO_STRUCT_UINT32_ALLOC(\
+ _field, _state, _field_num, _version, _vmsd, _type) { \
+ .name = (stringify(_field)), \
+ .version_id = (_version), \
+ .num_offset = vmstate_offset_value(_state, _field_num, uint32_t), \
+ .vmsd = &(_vmsd), \
+ .size = sizeof(_type), \
+ .flags = VMS_POINTER | VMS_VARRAY_UINT32 | \
+ VMS_ARRAY_OF_POINTER | VMS_STRUCT | \
+ VMS_ARRAY_OF_POINTER_AUTO_ALLOC, \
+ .offset = vmstate_offset_pointer(_state, _field, _type *), \
+}
+
#define VMSTATE_VARRAY_OF_POINTER_UINT32(_field, _state, _field_num, _version, _info, _type) { \
.name = (stringify(_field)), \
.version_id = (_version), \
diff --git a/migration/savevm.c b/migration/savevm.c
index f5a6fd0c66..765df8ce2d 100644
--- a/migration/savevm.c
+++ b/migration/savevm.c
@@ -869,8 +869,33 @@ static void vmstate_check(const VMStateDescription *vmsd)
if (field) {
while (field->name) {
if (field->flags & VMS_ARRAY_OF_POINTER) {
- assert(field->size == 0);
+ if (field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC) {
+ /*
+ * Size must be provided because dest QEMU needs that
+ * info to know what to allocate
+ */
+ assert(field->size || field->size_offset);
+ } else {
+ /*
+ * Otherwise size info isn't useful (because it's
+ * always the size of host pointer), detect accidental
+ * setup of sizes in this case.
+ */
+ assert(field->size == 0 && field->size_offset == 0);
+ }
+ /*
+ * VMS_ARRAY_OF_POINTER must be used only together with one
+ * of VMS_(V)ARRAY* flags.
+ */
+ assert(field->flags & (VMS_ARRAY | VMS_VARRAY_INT32 |
+ VMS_VARRAY_UINT16 | VMS_VARRAY_UINT8 |
+ VMS_VARRAY_UINT32));
}
+
+ if (field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC) {
+ assert(field->flags & VMS_ARRAY_OF_POINTER);
+ }
+
if (field->flags & (VMS_STRUCT | VMS_VSTRUCT)) {
/* Recurse to sub structures */
vmstate_check(field->vmsd);
diff --git a/migration/vmstate.c b/migration/vmstate.c
index 47812eb882..de2ad822e8 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -153,6 +153,12 @@ static bool vmstate_ptr_marker_load(QEMUFile *f, bool *load_field,
return true;
}
+ if (byte == VMS_MARKER_PTR_VALID) {
+ /* We need to load the field right after the marker */
+ *load_field = true;
+ return true;
+ }
+
error_setg(errp, "Unexpected ptr marker: %d", byte);
return false;
}
@@ -234,6 +240,76 @@ static bool vmstate_post_load(const VMStateDescription *vmsd,
return true;
}
+/*
+ * Try to prepare loading the next element, the object pointer to be put
+ * into @next_elem. When @next_elem is NULL, it means we should skip
+ * loading this element.
+ *
+ * Returns false for errors, in which case *errp will be set, migration
+ * must be aborted.
+ */
+static bool vmstate_load_next(QEMUFile *f, const VMStateField *field,
+ void *first_elem, void **next_elem,
+ int size, int i, Error **errp)
+{
+ bool auto_alloc = field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;
+ void *ptr = first_elem + size * i, **pptr;
+ bool load_field;
+
+ if (!(field->flags & VMS_ARRAY_OF_POINTER)) {
+ /* Simplest case, no pointer involved */
+ *next_elem = ptr;
+ return true;
+ }
+
+ /*
+ * We're loading an array of pointers, switch to use pptr to make it
+ * easier to read later
+ */
+ pptr = (void **)ptr;
+
+ /*
+ * If auto_alloc is on, making sure the user provided an array of NULL
+ * pointers to start with
+ */
+ assert(!auto_alloc || *pptr == NULL);
+
+ /*
+ * When pointer is null, we must expect a ptr marker first. Use cases:
+ *
+ * (1) _AUTO_ALLOC implies a ptr marker will always exist, or,
+ *
+ * (2) the element on destination is NULL, which expects the src to send a
+ * NULL-only marker.
+ *
+ * Here, checking against a NULL pointer will work for both.
+ */
+ if (!*pptr) {
+ if (!vmstate_ptr_marker_load(f, &load_field, errp)) {
+ trace_vmstate_load_field_error(field->name, -EINVAL);
+ return false;
+ }
+
+ /*
+ * If loading is needed, do pre-allocation first (otherwise keeping
+ * *pptr==NULL to imply a skip below)
+ */
+ if (load_field) {
+ /* Only applies when auto_alloc=on on the field */
+ assert(auto_alloc);
+ /*
+ * NOTE: do not use vmstate_size() here, because we need the
+ * object size, not entry size of the array.
+ */
+ *pptr = g_malloc0(field->size);
+ }
+ }
+
+ /* Move the cursor to the next element for loading */
+ *next_elem = *pptr;
+ return true;
+}
+
bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
void *opaque, int version_id, Error **errp)
{
@@ -279,27 +355,22 @@ bool vmstate_load_vmsd(QEMUFile *f, const VMStateDescription *vmsd,
}
for (i = 0; i < n_elems; i++) {
- /* If we will process the load of field? */
- bool load_field = true;
- bool ok = true;
- void *curr_elem = first_elem + size * i;
+ void *curr_elem;
+ bool ok;
- if (field->flags & VMS_ARRAY_OF_POINTER) {
- curr_elem = *(void **)curr_elem;
- if (!curr_elem) {
- /* Read the marker instead of VMSD itself */
- if (!vmstate_ptr_marker_load(f, &load_field, errp)) {
- trace_vmstate_load_field_error(field->name,
- -EINVAL);
- return false;
- }
- }
+ ok = vmstate_load_next(f, field, first_elem, &curr_elem,
+ size, i, errp);
+ if (!ok) {
+ return false;
}
- if (load_field) {
- ok = vmstate_load_field(f, curr_elem, size, field, errp);
+ if (!curr_elem) {
+ /* Implies a skip */
+ continue;
}
+ ok = vmstate_load_field(f, curr_elem, size, field, errp);
+
if (ok) {
int ret = qemu_file_get_error(f);
if (ret < 0) {
@@ -397,6 +468,16 @@ static bool vmsd_can_compress(const VMStateField *field)
return false;
}
+ if (field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC) {
+ /*
+ * This may involve two VMSD fields to be saved, one for the
+ * marker to show if the pointer is NULL, followed by the real
+ * vmstate object. To make it simple at least for now, skip
+ * compression for this one.
+ */
+ return false;
+ }
+
if (field->flags & VMS_STRUCT) {
const VMStateField *sfield = field->vmsd->fields;
while (sfield->name) {
@@ -583,6 +664,12 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
int size = vmstate_size(opaque, field);
JSONWriter *vmdesc_loop = vmdesc;
bool is_prev_null = false;
+ /*
+ * When this is enabled, it means we will always push a ptr
+ * marker first for each element saying if it's populated.
+ */
+ bool use_dynamic_array =
+ field->flags & VMS_ARRAY_OF_POINTER_AUTO_ALLOC;
trace_vmstate_save_state_loop(vmsd->name, field->name, n_elems);
if (field->flags & VMS_POINTER) {
@@ -603,14 +690,9 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
}
is_null = !curr_elem && size;
- use_marker_field = is_null;
+ use_marker_field = use_dynamic_array || is_null;
if (use_marker_field) {
- /*
- * If null pointer found (which should only happen in
- * an array of pointers), use null placeholder and do
- * not follow.
- */
inner_field = vmsd_create_ptr_marker_field(field);
} else {
inner_field = field;
@@ -657,6 +739,25 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
goto out;
}
+ /*
+ * If we're using dynamic array and the element is
+ * populated, save the real object right after the marker.
+ */
+ if (use_dynamic_array && curr_elem) {
+ /*
+ * NOTE: do not use vmstate_size() here because we want
+ * to save the real VMSD object now.
+ */
+ ok = vmstate_save_field_with_vmdesc(f, curr_elem,
+ field->size, vmsd,
+ field, vmdesc_loop,
+ i, max_elems, errp);
+
+ if (!ok) {
+ goto out;
+ }
+ }
+
/* Compressed arrays only care about the first element */
if (vmdesc_loop && max_elems > 1) {
vmdesc_loop = NULL;
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 33/43] vmstate: Stop checking size for nullptr compression
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (31 preceding siblings ...)
2026-04-23 19:19 ` [PULL 32/43] vmstate: Implement VMS_ARRAY_OF_POINTER_AUTO_ALLOC Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 34/43] tests/unit/test-vmstate: add tests for VMS_ARRAY_OF_POINTER_AUTO_ALLOC Fabiano Rosas
` (10 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Alexander Mikhalitsyn, Juraj Marcin
The NULL pointer marker code applies only to VMS_ARRAY_OF_POINTER,
where the size is never NULL. Move the setting of is_null under
VMS_ARRAY_OF_POINTER, so we can stop checking the size.
Reviewed-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-11-peterx@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/vmstate.c | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/migration/vmstate.c b/migration/vmstate.c
index de2ad822e8..2f13b48a37 100644
--- a/migration/vmstate.c
+++ b/migration/vmstate.c
@@ -682,14 +682,14 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
const VMStateField *inner_field;
/* maximum number of elements to compress in the JSON blob */
int max_elems = vmsd_can_compress(field) ? (n_elems - i) : 1;
- bool use_marker_field, is_null;
+ bool use_marker_field, is_null = false;
if (field->flags & VMS_ARRAY_OF_POINTER) {
assert(curr_elem);
curr_elem = *(void **)curr_elem;
+ is_null = !curr_elem;
}
- is_null = !curr_elem && size;
use_marker_field = use_dynamic_array || is_null;
if (use_marker_field) {
@@ -717,7 +717,7 @@ static bool vmstate_save_vmsd_v(QEMUFile *f, const VMStateDescription *vmsd,
for (int j = i + 1; j < n_elems; j++) {
void *elem = *(void **)(first_elem + size * j);
- bool elem_is_null = !elem && size;
+ bool elem_is_null = !elem;
if (is_null != elem_is_null) {
max_elems = j - i;
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 34/43] tests/unit/test-vmstate: add tests for VMS_ARRAY_OF_POINTER_AUTO_ALLOC
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (32 preceding siblings ...)
2026-04-23 19:19 ` [PULL 33/43] vmstate: Stop checking size for nullptr compression Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 35/43] migration: validate page_size in mapped-ram header before use Fabiano Rosas
` (9 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Alexander Mikhalitsyn, Juraj Marcin
From: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
Add tests for VMSTATE_VARRAY_OF_POINTER_TO_STRUCT_UINT32_ALLOC.
Signed-off-by: Alexander Mikhalitsyn <aleksandr.mikhalitsyn@futurfusion.io>
[peterx: Removed two tests due to macro not used, rebase, fix warning]
Signed-off-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Juraj Marcin <jmarcin@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260401202844.673494-12-peterx@redhat.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
tests/unit/test-vmstate.c | 86 +++++++++++++++++++++++++++++++++++++++
1 file changed, 86 insertions(+)
diff --git a/tests/unit/test-vmstate.c b/tests/unit/test-vmstate.c
index dae15786aa..df1fb4c778 100644
--- a/tests/unit/test-vmstate.c
+++ b/tests/unit/test-vmstate.c
@@ -702,6 +702,88 @@ static void test_arr_ptr_prim_0_load(void)
}
}
+static uint8_t wire_arr_ptr_with_nulls[] = {
+ VMS_MARKER_PTR_VALID,
+ 0x00, 0x00, 0x00, 0x00,
+ VMS_MARKER_PTR_NULL,
+ VMS_MARKER_PTR_VALID,
+ 0x00, 0x00, 0x00, 0x02,
+ VMS_MARKER_PTR_VALID,
+ 0x00, 0x00, 0x00, 0x03,
+ QEMU_VM_EOF
+};
+
+typedef struct {
+ uint32_t ar_items_num;
+ TestStructTriv **ar;
+} TestVArrayOfPtrToStuctWithNULLs;
+
+const VMStateDescription vmsd_arps_with_nulls = {
+ .name = "test/arps_with_nulls",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_VARRAY_OF_POINTER_TO_STRUCT_UINT32_ALLOC(
+ ar, TestVArrayOfPtrToStuctWithNULLs, ar_items_num,
+ 0, vmsd_tst, TestStructTriv),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static void test_arr_ptr_nulls_str_save(void)
+{
+ TestStructTriv ar[AR_SIZE] = { {.i = 0}, {.i = 1}, {.i = 2}, {.i = 3} };
+ TestVArrayOfPtrToStuctWithNULLs sample = {};
+ int idx;
+
+ sample.ar_items_num = AR_SIZE;
+ sample.ar = g_new0(TestStructTriv*, sample.ar_items_num);
+ sample.ar[0] = g_new0(TestStructTriv, 1);
+ *sample.ar[0] = ar[0];
+ /* note, sample.ar[1] remains NULL */
+ sample.ar[2] = g_new0(TestStructTriv, 1);
+ *sample.ar[2] = ar[2];
+ sample.ar[3] = g_new0(TestStructTriv, 1);
+ *sample.ar[3] = ar[3];
+
+ save_vmstate(&vmsd_arps_with_nulls, &sample);
+ compare_vmstate(wire_arr_ptr_with_nulls, sizeof(wire_arr_ptr_with_nulls));
+
+ for (idx = 0; idx < AR_SIZE; ++idx) {
+ g_free(sample.ar[idx]);
+ }
+ g_free(sample.ar);
+}
+
+static void test_arr_ptr_nulls_str_load(void)
+{
+ TestStructTriv ar_gt[AR_SIZE] = {{.i = 0}, {.i = 0}, {.i = 2}, {.i = 3} };
+ TestVArrayOfPtrToStuctWithNULLs obj = {};
+ int idx;
+
+ obj.ar_items_num = AR_SIZE;
+ obj.ar = g_new0(TestStructTriv*, obj.ar_items_num);
+
+ save_buffer(wire_arr_ptr_with_nulls, sizeof(wire_arr_ptr_with_nulls));
+ SUCCESS(load_vmstate_one(
+ &vmsd_arps_with_nulls, &obj, 1,
+ wire_arr_ptr_with_nulls, sizeof(wire_arr_ptr_with_nulls)));
+
+ for (idx = 0; idx < AR_SIZE; ++idx) {
+ if (idx == 1) {
+ g_assert_cmpint((uintptr_t)(obj.ar[idx]), ==, 0);
+ } else {
+ /* compare the target array ar with the ground truth array ar_gt */
+ g_assert_cmpint(ar_gt[idx].i, ==, obj.ar[idx]->i);
+ }
+ }
+
+ for (idx = 0; idx < AR_SIZE; ++idx) {
+ g_free(obj.ar[idx]);
+ }
+ g_free(obj.ar);
+}
+
/* test QTAILQ migration */
typedef struct TestQtailqElement TestQtailqElement;
@@ -1568,6 +1650,10 @@ int main(int argc, char **argv)
test_arr_ptr_prim_0_save);
g_test_add_func("/vmstate/array/ptr/prim/0/load",
test_arr_ptr_prim_0_load);
+ g_test_add_func("/vmstate/array/ptr-nulls/str/save",
+ test_arr_ptr_nulls_str_save);
+ g_test_add_func("/vmstate/array/ptr-nulls/str/load",
+ test_arr_ptr_nulls_str_load);
g_test_add_func("/vmstate/qtailq/save/saveq", test_save_q);
g_test_add_func("/vmstate/qtailq/load/loadq", test_load_q);
g_test_add_func("/vmstate/gtree/save/savedomain", test_gtree_save_domain);
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 35/43] migration: validate page_size in mapped-ram header before use
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (33 preceding siblings ...)
2026-04-23 19:19 ` [PULL 34/43] tests/unit/test-vmstate: add tests for VMS_ARRAY_OF_POINTER_AUTO_ALLOC Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 36/43] io/channel: introduce qio_channel_pread{v, }_all{, _eof}() Fabiano Rosas
` (8 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Trieu Huynh
From: Trieu Huynh <vikingtc4@gmail.com>
mapped_ram_read_header() reads page_size from the migration stream and
stores it in MappedRamHeader, but does not validate that the value is
non-zero before it is later used in parse_ramblock_mapped_ram():
num_pages = length / header.page_size;
If a corrupted or malformed migration stream provides invalid, guest
resumes either with corrupted memory or crashes unexpectedly (eg.
page_size = 0)
Add validation in mapped_ram_read_header() to reject invalid page_size
values early and return an error instead of continuing with an invalid
header.
Steps to reproduce:
Create a migration snapshot with mapped-ram enabled:
(qemu) migrate_set_capability mapped-ram on
(qemu) migrate file:/tmp/qemu-snapshots/snapshot.bin
Modify the snapshot so that MappedRamHeader.page_size becomes diff with
target psize. (0/512/8192/1GB).
Restore the snapshot:
(qemu) migrate_set_capability mapped-ram on
(qemu) migrate_incoming file:/tmp/qemu-snapshots/snapshot.bin
As-is:
* [0]: Floating point exception (core dumped)
* [512/8192]: Silent corruption
* [1GB]: "post load hook failed for: kvm-tpr-opt" (EPERM)
To-be:
* All: qemu-system-x86_64: Migration mapped-ram header has invalid
page_size [val] (expected 4096)
Signed-off-by: Trieu Huynh <vikingtc4@gmail.com>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260405094447.11347-1-viking4@gmail.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/ram.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/migration/ram.c b/migration/ram.c
index 979751f61b..2046f16caa 100644
--- a/migration/ram.c
+++ b/migration/ram.c
@@ -3088,6 +3088,12 @@ static bool mapped_ram_read_header(QEMUFile *file, MappedRamHeader *header,
}
header->page_size = be64_to_cpu(header->page_size);
+ if (header->page_size != TARGET_PAGE_SIZE) {
+ error_setg(errp, "Migration mapped-ram header has invalid "
+ "page_size %" PRIu64 " (expected %d)",
+ header->page_size, TARGET_PAGE_SIZE);
+ return false;
+ }
header->bitmap_offset = be64_to_cpu(header->bitmap_offset);
header->pages_offset = be64_to_cpu(header->pages_offset);
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 36/43] io/channel: introduce qio_channel_pread{v, }_all{, _eof}()
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (34 preceding siblings ...)
2026-04-23 19:19 ` [PULL 35/43] migration: validate page_size in mapped-ram header before use Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 37/43] io/channel: introduce qio_channel_pwrite{v,}_all() Fabiano Rosas
` (7 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Junjie Cao, Daniel P. Berrangé
From: Junjie Cao <junjie.cao@intel.com>
qio_channel_pread() and qio_channel_preadv() perform a single
positioned read and may return a short result. Callers that need all
bytes currently have to open-code a retry loop or simply treat a short
read as an error.
Introduce four new helpers following the existing read_all / readv_all
pattern:
qio_channel_preadv_all_eof() -- retry loop; returns 1 on success,
0 on clean EOF, -1 on error.
qio_channel_preadv_all() -- wraps _eof; treats early EOF as
error; returns 0 / -1.
qio_channel_pread_all_eof() -- single-buffer convenience wrapper
around preadv_all_eof().
qio_channel_pread_all() -- single-buffer convenience wrapper
around preadv_all().
These advance the file offset internally after each partial read.
All four are marked coroutine_mixed_fn, consistent with the existing
_all helpers.
Suggested-by: Peter Xu <peterx@redhat.com>
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Acked-by: Daniel P. Berrangé <berrange@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260413214549.926435-2-junjie.cao@intel.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/io/channel.h | 92 ++++++++++++++++++++++++++++++++++++++++++++
io/channel.c | 91 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 183 insertions(+)
diff --git a/include/io/channel.h b/include/io/channel.h
index 1b02350437..47af409ede 100644
--- a/include/io/channel.h
+++ b/include/io/channel.h
@@ -634,6 +634,98 @@ ssize_t qio_channel_preadv(QIOChannel *ioc, const struct iovec *iov,
ssize_t qio_channel_pread(QIOChannel *ioc, void *buf, size_t buflen,
off_t offset, Error **errp);
+/**
+ * qio_channel_preadv_all_eof:
+ * @ioc: the channel object
+ * @iov: the array of memory regions to read data into
+ * @niov: the length of the @iov array
+ * @offset: the starting offset in the channel to read from
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Reads @iov, possibly blocking or (if the channel is non-blocking)
+ * yielding from the current coroutine multiple times until the entire
+ * content is read. If end-of-file occurs immediately it is not an
+ * error, but if it occurs after data has been read it will return
+ * an error rather than a short-read. Otherwise behaves as
+ * qio_channel_preadv().
+ *
+ * Returns: 1 if all bytes were read, 0 if end-of-file occurs
+ * without data, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_preadv_all_eof(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp);
+
+/**
+ * qio_channel_preadv_all:
+ * @ioc: the channel object
+ * @iov: the array of memory regions to read data into
+ * @niov: the length of the @iov array
+ * @offset: the starting offset in the channel to read from
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Reads @iov, possibly blocking or (if the channel is non-blocking)
+ * yielding from the current coroutine multiple times until the entire
+ * content is read. If end-of-file occurs before all requested data
+ * has been read, an error will be reported. Otherwise behaves as
+ * qio_channel_preadv().
+ *
+ * Returns: 0 if all bytes were read, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_preadv_all(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp);
+
+/**
+ * qio_channel_pread_all_eof:
+ * @ioc: the channel object
+ * @buf: the memory region to read data into
+ * @buflen: the number of bytes to read into @buf
+ * @offset: the starting offset in the channel to read from
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Reads @buflen bytes, possibly blocking or (if the channel is
+ * non-blocking) yielding from the current coroutine multiple times
+ * until the entire content is read. If end-of-file occurs
+ * immediately it is not an error, but if it occurs after data has
+ * been read it will return an error rather than a short-read.
+ * Otherwise behaves as qio_channel_pread().
+ *
+ * Returns: 1 if all bytes were read, 0 if end-of-file occurs
+ * without data, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_pread_all_eof(QIOChannel *ioc,
+ void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp);
+
+/**
+ * qio_channel_pread_all:
+ * @ioc: the channel object
+ * @buf: the memory region to read data into
+ * @buflen: the number of bytes to read into @buf
+ * @offset: the starting offset in the channel to read from
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Reads @buflen bytes, possibly blocking or (if the channel is
+ * non-blocking) yielding from the current coroutine multiple times
+ * until the entire content is read. If end-of-file occurs before
+ * all requested data has been read, an error will be reported.
+ * Otherwise behaves as qio_channel_pread().
+ *
+ * Returns: 0 if all bytes were read, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_pread_all(QIOChannel *ioc,
+ void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp);
+
/**
* qio_channel_shutdown:
* @ioc: the channel object
diff --git a/io/channel.c b/io/channel.c
index cc02d997a4..52c1abfcbc 100644
--- a/io/channel.c
+++ b/io/channel.c
@@ -507,6 +507,97 @@ ssize_t qio_channel_pread(QIOChannel *ioc, void *buf, size_t buflen,
return qio_channel_preadv(ioc, &iov, 1, offset, errp);
}
+int coroutine_mixed_fn qio_channel_preadv_all_eof(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp)
+{
+ int ret = -1;
+ struct iovec *local_iov = g_new(struct iovec, niov);
+ struct iovec *local_iov_head = local_iov;
+ unsigned int nlocal_iov = niov;
+ bool partial = false;
+
+ nlocal_iov = iov_copy(local_iov, nlocal_iov,
+ iov, niov,
+ 0, iov_size(iov, niov));
+
+ while (nlocal_iov > 0) {
+ ssize_t len;
+ len = qio_channel_preadv(ioc, local_iov, nlocal_iov, offset, errp);
+
+ if (len == QIO_CHANNEL_ERR_BLOCK) {
+ qio_channel_wait_cond(ioc, G_IO_IN);
+ continue;
+ }
+
+ if (len == 0) {
+ if (!partial) {
+ ret = 0;
+ goto cleanup;
+ }
+ error_setg(errp,
+ "Unexpected end-of-file before all data were read");
+ goto cleanup;
+ }
+
+ if (len < 0) {
+ goto cleanup;
+ }
+
+ partial = true;
+ offset += len;
+ iov_discard_front(&local_iov, &nlocal_iov, len);
+ }
+
+ ret = 1;
+
+ cleanup:
+ g_free(local_iov_head);
+ return ret;
+}
+
+int coroutine_mixed_fn qio_channel_preadv_all(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp)
+{
+ int ret = qio_channel_preadv_all_eof(ioc, iov, niov, offset, errp);
+
+ if (ret == 0) {
+ error_setg(errp,
+ "Unexpected end-of-file before all data were read");
+ return -1;
+ }
+ if (ret == 1) {
+ return 0;
+ }
+
+ return ret;
+}
+
+int coroutine_mixed_fn qio_channel_pread_all_eof(QIOChannel *ioc,
+ void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp)
+{
+ struct iovec iov = { .iov_base = buf, .iov_len = buflen };
+ return qio_channel_preadv_all_eof(ioc, &iov, 1, offset, errp);
+}
+
+int coroutine_mixed_fn qio_channel_pread_all(QIOChannel *ioc,
+ void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp)
+{
+ struct iovec iov = { .iov_base = buf, .iov_len = buflen };
+ return qio_channel_preadv_all(ioc, &iov, 1, offset, errp);
+}
+
int qio_channel_shutdown(QIOChannel *ioc,
QIOChannelShutdown how,
Error **errp)
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 37/43] io/channel: introduce qio_channel_pwrite{v,}_all()
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (35 preceding siblings ...)
2026-04-23 19:19 ` [PULL 36/43] io/channel: introduce qio_channel_pread{v, }_all{, _eof}() Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 38/43] migration/file: fix type mismatch and NULL deref in multifd_file_recv_data Fabiano Rosas
` (6 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Junjie Cao
From: Junjie Cao <junjie.cao@intel.com>
Add positioned write helpers that retry on short writes, matching
the pread_all family from the previous patch.
qio_channel_pwritev_all() -- retry loop; returns 0 on success,
-1 on error.
qio_channel_pwrite_all() -- single-buffer convenience wrapper.
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Link: https://lore.kernel.org/qemu-devel/20260413214549.926435-3-junjie.cao@intel.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
include/io/channel.h | 41 +++++++++++++++++++++++++++++++++++++
io/channel.c | 48 ++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 89 insertions(+)
diff --git a/include/io/channel.h b/include/io/channel.h
index 47af409ede..287d10cd6f 100644
--- a/include/io/channel.h
+++ b/include/io/channel.h
@@ -598,6 +598,47 @@ ssize_t qio_channel_pwritev(QIOChannel *ioc, const struct iovec *iov,
ssize_t qio_channel_pwrite(QIOChannel *ioc, void *buf, size_t buflen,
off_t offset, Error **errp);
+/**
+ * qio_channel_pwritev_all:
+ * @ioc: the channel object
+ * @iov: the array of memory regions to write data from
+ * @niov: the length of the @iov array
+ * @offset: the starting offset in the channel to write to
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Writes @iov, possibly blocking or (if the channel is non-blocking)
+ * yielding from the current coroutine multiple times until the entire
+ * content is written. Otherwise behaves as qio_channel_pwritev().
+ *
+ * Returns: 0 if all bytes were written, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_pwritev_all(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp);
+
+/**
+ * qio_channel_pwrite_all:
+ * @ioc: the channel object
+ * @buf: the memory region to write data from
+ * @buflen: the number of bytes to write from @buf
+ * @offset: the starting offset in the channel to write to
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Writes @buflen bytes from @buf, possibly blocking or (if the
+ * channel is non-blocking) yielding from the current coroutine
+ * multiple times until the entire content is written. Otherwise
+ * behaves as qio_channel_pwrite().
+ *
+ * Returns: 0 if all bytes were written, or -1 on error
+ */
+int coroutine_mixed_fn qio_channel_pwrite_all(QIOChannel *ioc,
+ const void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp);
+
/**
* qio_channel_preadv
* @ioc: the channel object
diff --git a/io/channel.c b/io/channel.c
index 52c1abfcbc..2853dadb68 100644
--- a/io/channel.c
+++ b/io/channel.c
@@ -478,6 +478,54 @@ ssize_t qio_channel_pwrite(QIOChannel *ioc, void *buf, size_t buflen,
return qio_channel_pwritev(ioc, &iov, 1, offset, errp);
}
+int coroutine_mixed_fn qio_channel_pwritev_all(QIOChannel *ioc,
+ const struct iovec *iov,
+ size_t niov,
+ off_t offset,
+ Error **errp)
+{
+ int ret = -1;
+ struct iovec *local_iov = g_new(struct iovec, niov);
+ struct iovec *local_iov_head = local_iov;
+ unsigned int nlocal_iov = niov;
+
+ nlocal_iov = iov_copy(local_iov, nlocal_iov,
+ iov, niov,
+ 0, iov_size(iov, niov));
+
+ while (nlocal_iov > 0) {
+ ssize_t len;
+
+ len = qio_channel_pwritev(ioc, local_iov, nlocal_iov, offset, errp);
+
+ if (len == QIO_CHANNEL_ERR_BLOCK) {
+ qio_channel_wait_cond(ioc, G_IO_OUT);
+ continue;
+ }
+ if (len < 0) {
+ goto cleanup;
+ }
+
+ offset += len;
+ iov_discard_front(&local_iov, &nlocal_iov, len);
+ }
+
+ ret = 0;
+ cleanup:
+ g_free(local_iov_head);
+ return ret;
+}
+
+int coroutine_mixed_fn qio_channel_pwrite_all(QIOChannel *ioc,
+ const void *buf,
+ size_t buflen,
+ off_t offset,
+ Error **errp)
+{
+ struct iovec iov = { .iov_base = (char *)buf, .iov_len = buflen };
+ return qio_channel_pwritev_all(ioc, &iov, 1, offset, errp);
+}
+
ssize_t qio_channel_preadv(QIOChannel *ioc, const struct iovec *iov,
size_t niov, off_t offset, Error **errp)
{
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 38/43] migration/file: fix type mismatch and NULL deref in multifd_file_recv_data
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (36 preceding siblings ...)
2026-04-23 19:19 ` [PULL 37/43] io/channel: introduce qio_channel_pwrite{v,}_all() Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 39/43] tests/unit: add pread/pwrite _all tests for io channel file Fabiano Rosas
` (5 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Junjie Cao, Daniel P. Berrangé
From: Junjie Cao <junjie.cao@intel.com>
multifd_file_recv_data() stores the return value of qio_channel_pread()
(ssize_t) in a size_t variable. On I/O error the -1 return value wraps
to SIZE_MAX, producing a nonsensical read size in the error message.
More critically, a short read (0 <= ret < data->size) is possible when
the migration file is truncated. In that case qio_channel_pread()
returns a non-negative value without setting *errp. The function then
calls error_prepend(errp, ...) which dereferences *errp -- a NULL
pointer -- crashing QEMU.
Fix both issues by switching to qio_channel_pread_all() introduced in
a previous patch, which retries on short reads and treats end-of-file
as an error, so the caller no longer needs to check the byte count
manually. Add ERRP_GUARD() so that error_prepend() works correctly
even when errp is &error_fatal or NULL.
Fixes: a49d15a38d3d ("migration/multifd: Support incoming mapped-ram stream format")
Suggested-by: Peter Xu <peterx@redhat.com>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Link: https://lore.kernel.org/qemu-devel/20260413214549.926435-4-junjie.cao@intel.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/file.c | 13 +++++++------
1 file changed, 7 insertions(+), 6 deletions(-)
diff --git a/migration/file.c b/migration/file.c
index 962b4b3a1b..4e2524e548 100644
--- a/migration/file.c
+++ b/migration/file.c
@@ -256,15 +256,16 @@ int file_write_ramblock_iov(QIOChannel *ioc, const struct iovec *iov,
int multifd_file_recv_data(MultiFDRecvParams *p, Error **errp)
{
+ ERRP_GUARD();
MultiFDRecvData *data = p->data;
- size_t ret;
+ int ret;
- ret = qio_channel_pread(p->c, (char *) data->opaque,
- data->size, data->file_offset, errp);
- if (ret != data->size) {
+ ret = qio_channel_pread_all(p->c, (char *) data->opaque,
+ data->size, data->file_offset, errp);
+ if (ret != 0) {
error_prepend(errp,
- "multifd recv (%u): read 0x%zx, expected 0x%zx",
- p->id, ret, data->size);
+ "multifd recv (%u): ",
+ p->id);
return -1;
}
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 39/43] tests/unit: add pread/pwrite _all tests for io channel file
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (37 preceding siblings ...)
2026-04-23 19:19 ` [PULL 38/43] migration/file: fix type mismatch and NULL deref in multifd_file_recv_data Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 40/43] tests/qtest/migration: fix fd leak in ufd_version_check Fabiano Rosas
` (4 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Junjie Cao, Daniel P. Berrangé
From: Junjie Cao <junjie.cao@intel.com>
Add unit tests for the new qio_channel_pread{v,}_all{,_eof}() and
qio_channel_pwrite{v,}_all() APIs.
The basic tests write data to a file channel, then read it back at
various offsets using both the single-buffer and iovec variants to
make sure the round-trip produces identical content. The _eof tests
verify all three return cases -- full read (1), clean EOF (0), and
partial-then-EOF (-1 with error set) -- and check that the strict
wrappers (preadv_all / pread_all) treat a clean EOF as an error.
All tests are guarded by CONFIG_PREADV since the underlying channel
methods require preadv(2).
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
Reviewed-by: Fabiano Rosas <farosas@suse.de>
Reviewed-by: Daniel P. Berrangé <berrange@redhat.com>
Acked-by: Daniel P. Berrangé <berrange@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260413214549.926435-5-junjie.cao@intel.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
tests/unit/test-io-channel-file.c | 207 ++++++++++++++++++++++++++++++
1 file changed, 207 insertions(+)
diff --git a/tests/unit/test-io-channel-file.c b/tests/unit/test-io-channel-file.c
index 1977006ce9..b597350dca 100644
--- a/tests/unit/test-io-channel-file.c
+++ b/tests/unit/test-io-channel-file.c
@@ -102,6 +102,203 @@ static void test_io_channel_fd(void)
}
+#ifdef CONFIG_PREADV
+static void test_io_channel_pread_all(void)
+{
+ QIOChannel *ioc;
+ char write_buf[] = "Hello World, pread_all";
+ char read_buf[sizeof(write_buf)] = {0};
+ int ret;
+
+ unlink(TEST_FILE);
+ ioc = QIO_CHANNEL(qio_channel_file_new_path(
+ TEST_FILE,
+ O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
+ TEST_MASK,
+ &error_abort));
+
+ ret = qio_channel_pwrite_all(ioc, write_buf, sizeof(write_buf),
+ 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* Read back at offset 0 */
+ ret = qio_channel_pread_all(ioc, read_buf, sizeof(read_buf),
+ 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpmem(write_buf, sizeof(write_buf),
+ read_buf, sizeof(read_buf));
+
+ /* Read at a non-zero offset */
+ memset(read_buf, 0, sizeof(read_buf));
+ ret = qio_channel_pread_all(ioc, read_buf, sizeof(write_buf) - 7,
+ 7, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_cmpmem(write_buf + 7, sizeof(write_buf) - 7,
+ read_buf, sizeof(write_buf) - 7);
+
+ unlink(TEST_FILE);
+ object_unref(OBJECT(ioc));
+}
+
+static void test_io_channel_preadv_all(void)
+{
+ QIOChannel *ioc;
+ char write_buf[256];
+ char read_buf[256] = {0};
+ struct iovec write_iov[2];
+ struct iovec read_iov[2];
+ int ret;
+ size_t i;
+
+ for (i = 0; i < sizeof(write_buf); i++) {
+ write_buf[i] = i & 0xff;
+ }
+
+ unlink(TEST_FILE);
+ ioc = QIO_CHANNEL(qio_channel_file_new_path(
+ TEST_FILE,
+ O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
+ TEST_MASK,
+ &error_abort));
+
+ /* Write using pwritev_all with 2 iovecs */
+ write_iov[0].iov_base = write_buf;
+ write_iov[0].iov_len = 128;
+ write_iov[1].iov_base = write_buf + 128;
+ write_iov[1].iov_len = 128;
+ ret = qio_channel_pwritev_all(ioc, write_iov, 2, 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* Read back using preadv_all with 2 iovecs */
+ read_iov[0].iov_base = read_buf;
+ read_iov[0].iov_len = 128;
+ read_iov[1].iov_base = read_buf + 128;
+ read_iov[1].iov_len = 128;
+ ret = qio_channel_preadv_all(ioc, read_iov, 2, 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ g_assert_cmpmem(write_buf, sizeof(write_buf),
+ read_buf, sizeof(read_buf));
+
+ /* Read at non-zero offset with preadv_all */
+ memset(read_buf, 0, sizeof(read_buf));
+ read_iov[0].iov_base = read_buf;
+ read_iov[0].iov_len = 64;
+ read_iov[1].iov_base = read_buf + 64;
+ read_iov[1].iov_len = 64;
+ ret = qio_channel_preadv_all(ioc, read_iov, 2, 128, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ g_assert_cmpmem(write_buf + 128, 128,
+ read_buf, 128);
+
+ unlink(TEST_FILE);
+ object_unref(OBJECT(ioc));
+}
+
+static void test_io_channel_preadv_all_eof(void)
+{
+ QIOChannel *ioc;
+ char write_buf[] = "Hello World, preadv_all_eof";
+ char read_buf[sizeof(write_buf)] = {0};
+ struct iovec iov;
+ int ret;
+ Error *err = NULL;
+
+ unlink(TEST_FILE);
+ ioc = QIO_CHANNEL(qio_channel_file_new_path(
+ TEST_FILE,
+ O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
+ TEST_MASK,
+ &error_abort));
+
+ ret = qio_channel_pwrite_all(ioc, write_buf, sizeof(write_buf),
+ 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* Full read succeeds: should return 1 */
+ iov.iov_base = read_buf;
+ iov.iov_len = sizeof(read_buf);
+ ret = qio_channel_preadv_all_eof(ioc, &iov, 1, 0, &error_abort);
+ g_assert_cmpint(ret, ==, 1);
+ g_assert_cmpmem(write_buf, sizeof(write_buf),
+ read_buf, sizeof(read_buf));
+
+ /* Clean EOF: offset at file end, should return 0 */
+ iov.iov_base = read_buf;
+ iov.iov_len = 1;
+ ret = qio_channel_preadv_all_eof(ioc, &iov, 1,
+ sizeof(write_buf), &err);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_null(err);
+
+ /* Partial EOF: start before end, request extends past */
+ iov.iov_base = read_buf;
+ iov.iov_len = 8;
+ ret = qio_channel_preadv_all_eof(ioc, &iov, 1,
+ sizeof(write_buf) - 4, &err);
+ g_assert_cmpint(ret, ==, -1);
+ g_assert_nonnull(err);
+ error_free(err);
+ err = NULL;
+
+ /* Strict wrapper (preadv_all) treats clean EOF as error */
+ iov.iov_base = read_buf;
+ iov.iov_len = 1;
+ ret = qio_channel_preadv_all(ioc, &iov, 1,
+ sizeof(write_buf), &err);
+ g_assert_cmpint(ret, ==, -1);
+ g_assert_nonnull(err);
+ error_free(err);
+
+ unlink(TEST_FILE);
+ object_unref(OBJECT(ioc));
+}
+
+static void test_io_channel_pread_all_eof(void)
+{
+ QIOChannel *ioc;
+ char write_buf[] = "Hello World, pread_all_eof";
+ char read_buf[sizeof(write_buf)] = {0};
+ int ret;
+ Error *err = NULL;
+
+ unlink(TEST_FILE);
+ ioc = QIO_CHANNEL(qio_channel_file_new_path(
+ TEST_FILE,
+ O_RDWR | O_CREAT | O_TRUNC | O_BINARY,
+ TEST_MASK,
+ &error_abort));
+
+ ret = qio_channel_pwrite_all(ioc, write_buf, sizeof(write_buf),
+ 0, &error_abort);
+ g_assert_cmpint(ret, ==, 0);
+
+ /* Full read succeeds: should return 1 */
+ ret = qio_channel_pread_all_eof(ioc, read_buf, sizeof(read_buf),
+ 0, &error_abort);
+ g_assert_cmpint(ret, ==, 1);
+ g_assert_cmpmem(write_buf, sizeof(write_buf),
+ read_buf, sizeof(read_buf));
+
+ /* Clean EOF: should return 0 */
+ ret = qio_channel_pread_all_eof(ioc, read_buf, 1,
+ sizeof(write_buf), &err);
+ g_assert_cmpint(ret, ==, 0);
+ g_assert_null(err);
+
+ /* Partial EOF: should return -1 */
+ ret = qio_channel_pread_all_eof(ioc, read_buf, 8,
+ sizeof(write_buf) - 4, &err);
+ g_assert_cmpint(ret, ==, -1);
+ g_assert_nonnull(err);
+ error_free(err);
+
+ unlink(TEST_FILE);
+ object_unref(OBJECT(ioc));
+}
+#endif /* CONFIG_PREADV */
+
#ifndef _WIN32
static void test_io_channel_pipe(bool async)
{
@@ -147,6 +344,16 @@ int main(int argc, char **argv)
g_test_add_func("/io/channel/file", test_io_channel_file);
g_test_add_func("/io/channel/file/rdwr", test_io_channel_file_rdwr);
g_test_add_func("/io/channel/file/fd", test_io_channel_fd);
+#ifdef CONFIG_PREADV
+ g_test_add_func("/io/channel/file/pread-all",
+ test_io_channel_pread_all);
+ g_test_add_func("/io/channel/file/preadv-all",
+ test_io_channel_preadv_all);
+ g_test_add_func("/io/channel/file/preadv-all-eof",
+ test_io_channel_preadv_all_eof);
+ g_test_add_func("/io/channel/file/pread-all-eof",
+ test_io_channel_pread_all_eof);
+#endif
#ifndef _WIN32
g_test_add_func("/io/channel/pipe/sync", test_io_channel_pipe_sync);
g_test_add_func("/io/channel/pipe/async", test_io_channel_pipe_async);
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 40/43] tests/qtest/migration: fix fd leak in ufd_version_check
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (38 preceding siblings ...)
2026-04-23 19:19 ` [PULL 39/43] tests/unit: add pread/pwrite _all tests for io channel file Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 41/43] migration/qemu-file: switch buffer_at functions to positioned I/O _all helpers Fabiano Rosas
` (3 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Trieu Huynh
From: Trieu Huynh <vikingtc4@gmail.com>
ufd_version_check() opens a userfaultfd via uffd_open() but never closes
it on any path where the open succeeded: the UFFDIO_API failure path,
the missing-ioctls path, and the success path all returned without
calling close(ufd).
Convert to a goto-out pattern consistent with uffd_open() used in
util/userfaultfd.c and migration/postcopy-ram.c, ensuring the fd is
always closed before returning.
Signed-off-by: Trieu Huynh <vikingtc4@gmail.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260419110304.8661-1-viking4@gmail.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
tests/qtest/migration/migration-util.c | 10 +++++++---
1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/tests/qtest/migration/migration-util.c b/tests/qtest/migration/migration-util.c
index 2648ad7f61..db66d8b24f 100644
--- a/tests/qtest/migration/migration-util.c
+++ b/tests/qtest/migration/migration-util.c
@@ -343,6 +343,7 @@ bool ufd_version_check(bool *uffd_feature_thread_id)
{
struct uffdio_api api_struct;
uint64_t ioctl_mask;
+ bool ret = false;
int ufd = uffd_open(O_CLOEXEC);
@@ -355,7 +356,7 @@ bool ufd_version_check(bool *uffd_feature_thread_id)
api_struct.features = 0;
if (ioctl(ufd, UFFDIO_API, &api_struct)) {
g_test_message("Skipping test: UFFDIO_API failed");
- return false;
+ goto release_ufd;
}
if (uffd_feature_thread_id) {
@@ -366,10 +367,13 @@ bool ufd_version_check(bool *uffd_feature_thread_id)
1ULL << _UFFDIO_UNREGISTER);
if ((api_struct.ioctls & ioctl_mask) != ioctl_mask) {
g_test_message("Skipping test: Missing userfault feature");
- return false;
+ goto release_ufd;
}
- return true;
+ ret = true;
+release_ufd:
+ close(ufd);
+ return ret;
}
#else
bool ufd_version_check(bool *uffd_feature_thread_id)
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 41/43] migration/qemu-file: switch buffer_at functions to positioned I/O _all helpers
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (39 preceding siblings ...)
2026-04-23 19:19 ` [PULL 40/43] tests/qtest/migration: fix fd leak in ufd_version_check Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 42/43] migration/file: switch file_write_ramblock_iov to pwritev_all Fabiano Rosas
` (2 subsequent siblings)
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Junjie Cao
From: Junjie Cao <junjie.cao@intel.com>
qemu_put_buffer_at() and qemu_get_buffer_at() have the same pattern as
the bug fixed in multifd_file_recv_data(): the ssize_t return value from
the channel layer is stored in a size_t variable, and a short transfer
would be mishandled rather than retried.
Switch to qio_channel_pwrite_all() / qio_channel_pread_all() which
handle short transfers internally and make the code more robust and
consistent with the rest of the positioned I/O call sites.
Fixes: 7f5b50a401 ("migration/qemu-file: add utility methods for working with seekable channels")
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260420201317.30199-2-junjie.cao@intel.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/qemu-file.c | 35 +++--------------------------------
1 file changed, 3 insertions(+), 32 deletions(-)
diff --git a/migration/qemu-file.c b/migration/qemu-file.c
index 9cf7dc3bd5..9dfb202203 100644
--- a/migration/qemu-file.c
+++ b/migration/qemu-file.c
@@ -535,28 +535,13 @@ void qemu_put_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
off_t pos)
{
Error *err = NULL;
- size_t ret;
if (f->last_error) {
return;
}
qemu_fflush(f);
- ret = qio_channel_pwrite(f->ioc, (char *)buf, buflen, pos, &err);
-
- if (err) {
- qemu_file_set_error_obj(f, -EIO, err);
- return;
- }
-
- if ((ssize_t)ret == QIO_CHANNEL_ERR_BLOCK) {
- qemu_file_set_error_obj(f, -EAGAIN, NULL);
- return;
- }
-
- if (ret != buflen) {
- error_setg(&err, "Partial write of size %zu, expected %zu", ret,
- buflen);
+ if (qio_channel_pwrite_all(f->ioc, buf, buflen, pos, &err) < 0) {
qemu_file_set_error_obj(f, -EIO, err);
return;
}
@@ -569,31 +554,17 @@ size_t qemu_get_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
off_t pos)
{
Error *err = NULL;
- size_t ret;
if (f->last_error) {
return 0;
}
- ret = qio_channel_pread(f->ioc, (char *)buf, buflen, pos, &err);
-
- if ((ssize_t)ret == -1 || err) {
- qemu_file_set_error_obj(f, -EIO, err);
- return 0;
- }
-
- if ((ssize_t)ret == QIO_CHANNEL_ERR_BLOCK) {
- qemu_file_set_error_obj(f, -EAGAIN, NULL);
- return 0;
- }
-
- if (ret != buflen) {
- error_setg(&err, "Partial read of size %zu, expected %zu", ret, buflen);
+ if (qio_channel_pread_all(f->ioc, (char *)buf, buflen, pos, &err) < 0) {
qemu_file_set_error_obj(f, -EIO, err);
return 0;
}
- return ret;
+ return buflen;
}
void qemu_set_offset(QEMUFile *f, off_t off, int whence)
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 42/43] migration/file: switch file_write_ramblock_iov to pwritev_all
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (40 preceding siblings ...)
2026-04-23 19:19 ` [PULL 41/43] migration/qemu-file: switch buffer_at functions to positioned I/O _all helpers Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-23 19:19 ` [PULL 43/43] migration/qemu-file: drop incorrect const from qemu_get_buffer_at buf Fabiano Rosas
2026-04-25 16:58 ` [PULL 00/43] Migration patches for 2026-04-23 Stefan Hajnoczi
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Junjie Cao
From: Junjie Cao <junjie.cao@intel.com>
file_write_ramblock_iov() uses single-shot qio_channel_pwritev() and
only checks for ret < 0. A short write (0 <= ret < requested) would be
treated as success.
Switch to qio_channel_pwritev_all() which retries until all bytes are
written or an error occurs.
Fixes: f427d90b98 ("migration/multifd: Support outgoing mapped-ram stream format")
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260420201317.30199-3-junjie.cao@intel.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/file.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/migration/file.c b/migration/file.c
index 4e2524e548..8ba1de2006 100644
--- a/migration/file.c
+++ b/migration/file.c
@@ -202,7 +202,7 @@ void file_connect_incoming(FileMigrationArgs *file_args, Error **errp)
int file_write_ramblock_iov(QIOChannel *ioc, const struct iovec *iov,
int niov, MultiFDPages_t *pages, Error **errp)
{
- ssize_t ret = 0;
+ int ret = 0;
int i, slice_idx, slice_num;
uintptr_t base, next, offset;
size_t len;
@@ -241,8 +241,8 @@ int file_write_ramblock_iov(QIOChannel *ioc, const struct iovec *iov,
break;
}
- ret = qio_channel_pwritev(ioc, &iov[slice_idx], slice_num,
- block->pages_offset + offset, errp);
+ ret = qio_channel_pwritev_all(ioc, &iov[slice_idx], slice_num,
+ block->pages_offset + offset, errp);
if (ret < 0) {
break;
}
@@ -251,7 +251,7 @@ int file_write_ramblock_iov(QIOChannel *ioc, const struct iovec *iov,
slice_num = 0;
}
- return (ret < 0) ? ret : 0;
+ return ret;
}
int multifd_file_recv_data(MultiFDRecvParams *p, Error **errp)
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* [PULL 43/43] migration/qemu-file: drop incorrect const from qemu_get_buffer_at buf
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (41 preceding siblings ...)
2026-04-23 19:19 ` [PULL 42/43] migration/file: switch file_write_ramblock_iov to pwritev_all Fabiano Rosas
@ 2026-04-23 19:19 ` Fabiano Rosas
2026-04-25 16:58 ` [PULL 00/43] Migration patches for 2026-04-23 Stefan Hajnoczi
43 siblings, 0 replies; 45+ messages in thread
From: Fabiano Rosas @ 2026-04-23 19:19 UTC (permalink / raw)
To: qemu-devel; +Cc: Peter Xu, Junjie Cao, Philippe Mathieu-Daudé
From: Junjie Cao <junjie.cao@intel.com>
qemu_get_buffer_at() reads data *into* buf -- it should not be const.
Drop the qualifier and remove the now-unnecessary cast.
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Reviewed-by: Peter Xu <peterx@redhat.com>
Link: https://lore.kernel.org/qemu-devel/20260420201317.30199-4-junjie.cao@intel.com
Signed-off-by: Fabiano Rosas <farosas@suse.de>
---
migration/qemu-file.c | 4 ++--
migration/qemu-file.h | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/migration/qemu-file.c b/migration/qemu-file.c
index 9dfb202203..d5a48115bd 100644
--- a/migration/qemu-file.c
+++ b/migration/qemu-file.c
@@ -550,7 +550,7 @@ void qemu_put_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
}
-size_t qemu_get_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
+size_t qemu_get_buffer_at(QEMUFile *f, uint8_t *buf, size_t buflen,
off_t pos)
{
Error *err = NULL;
@@ -559,7 +559,7 @@ size_t qemu_get_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
return 0;
}
- if (qio_channel_pread_all(f->ioc, (char *)buf, buflen, pos, &err) < 0) {
+ if (qio_channel_pread_all(f->ioc, buf, buflen, pos, &err) < 0) {
qemu_file_set_error_obj(f, -EIO, err);
return 0;
}
diff --git a/migration/qemu-file.h b/migration/qemu-file.h
index a390554208..8f824c124d 100644
--- a/migration/qemu-file.h
+++ b/migration/qemu-file.h
@@ -76,7 +76,7 @@ void qemu_set_offset(QEMUFile *f, off_t off, int whence);
off_t qemu_get_offset(QEMUFile *f);
void qemu_put_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
off_t pos);
-size_t qemu_get_buffer_at(QEMUFile *f, const uint8_t *buf, size_t buflen,
+size_t qemu_get_buffer_at(QEMUFile *f, uint8_t *buf, size_t buflen,
off_t pos);
QIOChannel *qemu_file_get_ioc(QEMUFile *file);
--
2.51.0
^ permalink raw reply related [flat|nested] 45+ messages in thread* Re: [PULL 00/43] Migration patches for 2026-04-23
2026-04-23 19:19 [PULL 00/43] Migration patches for 2026-04-23 Fabiano Rosas
` (42 preceding siblings ...)
2026-04-23 19:19 ` [PULL 43/43] migration/qemu-file: drop incorrect const from qemu_get_buffer_at buf Fabiano Rosas
@ 2026-04-25 16:58 ` Stefan Hajnoczi
43 siblings, 0 replies; 45+ messages in thread
From: Stefan Hajnoczi @ 2026-04-25 16:58 UTC (permalink / raw)
To: Fabiano Rosas; +Cc: qemu-devel, Peter Xu
[-- Attachment #1: Type: text/plain, Size: 116 bytes --]
Applied, thanks.
Please update the changelog at https://wiki.qemu.org/ChangeLog/11.1 for any user-visible changes.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 45+ messages in thread