@@ -224,6 +224,7 @@ extern const VMStateInfo vmstate_info_unused_buffer;
extern const VMStateInfo vmstate_info_tmp;
extern const VMStateInfo vmstate_info_bitmap;
extern const VMStateInfo vmstate_info_qtailq;
+extern const VMStateInfo vmstate_info_qlist;
#define type_check_2darray(t1,t2,n,m) ((t1(*)[n][m])0 - (t2*)0)
/*
@@ -754,6 +755,26 @@ extern const VMStateInfo vmstate_info_qtailq;
.start = offsetof(_type, _next), \
}
+/*
+ * For migrating a QLIST
+ * Target QLIST needs be properly initialized.
+ * _type: type of QLIST element
+ * _next: name of QLIST_ENTRY entry field in QLIST element
+ * _vmsd: VMSD for QLIST element
+ * size: size of QLIST element
+ * start: offset of QLIST_ENTRY in QTAILQ element
+ */
+#define VMSTATE_QLIST_V(_field, _state, _version, _vmsd, _type, _next) \
+{ \
+ .name = (stringify(_field)), \
+ .version_id = (_version), \
+ .vmsd = &(_vmsd), \
+ .size = sizeof(_type), \
+ .info = &vmstate_info_qlist, \
+ .offset = offsetof(_state, _field), \
+ .start = offsetof(_type, _next), \
+}
+
/* _f : field name
_f_n : num of elements field_name
_n : num of elements
@@ -491,4 +491,34 @@ union { \
QTAILQ_RAW_TQH_CIRC(head)->tql_prev = QTAILQ_RAW_TQE_CIRC(elm, entry); \
} while (/*CONSTCOND*/0)
+#define QLIST_RAW_FIRST(head) \
+ field_at_offset(head, 0, void *)
+
+#define QLIST_RAW_NEXT(elm, entry) \
+ field_at_offset(elm, entry, void *)
+
+#define QLIST_RAW_PREVIOUS(elm, entry) \
+ field_at_offset(elm, entry + sizeof(void *), void *)
+
+#define QLIST_RAW_FOREACH(elm, head, entry) \
+ for ((elm) = *QLIST_RAW_FIRST(head); \
+ (elm); \
+ (elm) = *QLIST_RAW_NEXT(elm, entry))
+
+#define QLIST_RAW_INSERT_TAIL(head, elm, entry) do { \
+ void *iter, *last = NULL; \
+ *QLIST_RAW_NEXT(elm, entry) = NULL; \
+ if (!*QLIST_RAW_FIRST(head)) { \
+ *QLIST_RAW_FIRST(head) = elm; \
+ *QLIST_RAW_PREVIOUS(elm, entry) = head; \
+ break; \
+ } \
+ for (iter = *QLIST_RAW_FIRST(head); \
+ iter; last = iter, iter = *QLIST_RAW_NEXT(iter, entry)) \
+ { } \
+ *QLIST_RAW_NEXT(last, entry) = elm; \
+ *QLIST_RAW_PREVIOUS(elm, entry) = last; \
+} while (0)
+
+
#endif /* QEMU_SYS_QUEUE_H */
@@ -691,3 +691,63 @@ const VMStateInfo vmstate_info_qtailq = {
.get = get_qtailq,
.put = put_qtailq,
};
+
+static int put_qlist(QEMUFile *f, void *pv, size_t unused_size,
+ const VMStateField *field, QJSON *vmdesc)
+{
+ const VMStateDescription *vmsd = field->vmsd;
+ /* offset of the QTAILQ entry in a QTAILQ element*/
+ size_t entry_offset = field->start;
+ void *elm;
+ int ret;
+
+ QLIST_RAW_FOREACH(elm, pv, entry_offset) {
+ qemu_put_byte(f, true);
+ ret = vmstate_save_state(f, vmsd, elm, vmdesc);
+ if (ret) {
+ return ret;
+ }
+ }
+ qemu_put_byte(f, false);
+
+ return 0;
+}
+
+static int get_qlist(QEMUFile *f, void *pv, size_t unused_size,
+ const VMStateField *field)
+{
+ int ret = 0;
+ const VMStateDescription *vmsd = field->vmsd;
+ /* size of a QLIST element */
+ size_t size = field->size;
+ /* offset of the QLIST entry in a QLIST element */
+ size_t entry_offset = field->start;
+ int version_id = field->version_id;
+ void *elm;
+
+ if (version_id > vmsd->version_id) {
+ error_report("%s %s", vmsd->name, "too new");
+ return -EINVAL;
+ }
+ if (version_id < vmsd->minimum_version_id) {
+ error_report("%s %s", vmsd->name, "too old");
+ return -EINVAL;
+ }
+
+ while (qemu_get_byte(f)) {
+ elm = g_malloc(size);
+ ret = vmstate_load_state(f, vmsd, elm, version_id);
+ if (ret) {
+ return ret;
+ }
+ QLIST_RAW_INSERT_TAIL(pv, elm, entry_offset);
+ }
+
+ return ret;
+}
+
+const VMStateInfo vmstate_info_qlist = {
+ .name = "qlist",
+ .get = get_qlist,
+ .put = put_qlist,
+};
@@ -812,6 +812,137 @@ static void test_load_q(void)
qemu_fclose(fload);
}
+/* test QLIST Migration */
+
+typedef struct TestQListElement {
+ uint32_t id;
+ QLIST_ENTRY(TestQListElement) next;
+} TestQListElement;
+
+typedef struct TestQListContainer {
+ uint32_t id;
+ QLIST_HEAD(, TestQListElement) list;
+} TestQListContainer;
+
+static const VMStateDescription vmstate_qlist_element = {
+ .name = "test/queue list",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(id, TestQListElement),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static const VMStateDescription vmstate_container = {
+ .name = "test/container/qlist",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (VMStateField[]) {
+ VMSTATE_UINT32(id, TestQListContainer),
+ VMSTATE_QLIST_V(list, TestQListContainer, 1, vmstate_qlist_element,
+ TestQListElement, next),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
+static uint8_t qlist_dump[] = {
+ 0x00, 0x00, 0x00, 0x01, /* container id */
+ 0x1, /* start of a */
+ 0x00, 0x00, 0x00, 0x0a,
+ 0x1, /* start of b */
+ 0x00, 0x00, 0x0b, 0x00,
+ 0x1, /* start of c */
+ 0x00, 0x0c, 0x00, 0x00,
+ 0x1, /* start of d */
+ 0x0d, 0x00, 0x00, 0x00,
+ 0x0, /* end of list */
+ QEMU_VM_EOF, /* just to ensure we won't get EOF reported prematurely */
+};
+
+static TestQListContainer *alloc_container(void)
+{
+ TestQListElement *a = g_malloc(sizeof(TestQListElement));
+ TestQListElement *b = g_malloc(sizeof(TestQListElement));
+ TestQListElement *c = g_malloc(sizeof(TestQListElement));
+ TestQListElement *d = g_malloc(sizeof(TestQListElement));
+ TestQListContainer *container = g_malloc(sizeof(TestQListContainer));
+
+ a->id = 0x0a;
+ b->id = 0x0b00;
+ c->id = 0xc0000;
+ d->id = 0xd000000;
+ container->id = 1;
+
+ QLIST_INIT(&container->list);
+ QLIST_INSERT_HEAD(&container->list, d, next);
+ QLIST_INSERT_HEAD(&container->list, c, next);
+ QLIST_INSERT_HEAD(&container->list, b, next);
+ QLIST_INSERT_HEAD(&container->list, a, next);
+ return container;
+}
+
+static void free_container(TestQListContainer *container)
+{
+ TestQListElement *iter, *tmp;
+
+ QLIST_FOREACH_SAFE(iter, &container->list, next, tmp) {
+ QLIST_REMOVE(iter, next);
+ g_free(iter);
+ }
+ g_free(container);
+}
+
+static void compare_containers(TestQListContainer *c1, TestQListContainer *c2)
+{
+ TestQListElement *first_item_c1, *first_item_c2;
+
+ while (!QLIST_EMPTY(&c1->list)) {
+ first_item_c1 = QLIST_FIRST(&c1->list);
+ first_item_c2 = QLIST_FIRST(&c2->list);
+ assert(first_item_c2);
+ assert(first_item_c1->id == first_item_c2->id);
+ QLIST_REMOVE(first_item_c1, next);
+ QLIST_REMOVE(first_item_c2, next);
+ g_free(first_item_c1);
+ g_free(first_item_c2);
+ }
+ assert(QLIST_EMPTY(&c2->list));
+}
+
+static void test_save_qlist(void)
+{
+ TestQListContainer *container = alloc_container();
+
+ save_vmstate(&vmstate_container, container);
+ compare_vmstate(qlist_dump, sizeof(qlist_dump));
+ free_container(container);
+}
+
+static void test_load_qlist(void)
+{
+ QEMUFile *fsave, *fload;
+ TestQListContainer *orig_container = alloc_container();
+ TestQListContainer *dest_container = g_malloc0(sizeof(TestQListContainer));
+ char eof;
+
+ QLIST_INIT(&dest_container->list);
+
+ fsave = open_test_file(true);
+ qemu_put_buffer(fsave, qlist_dump, sizeof(qlist_dump));
+ g_assert(!qemu_file_get_error(fsave));
+ qemu_fclose(fsave);
+
+ fload = open_test_file(false);
+ vmstate_load_state(fload, &vmstate_container, dest_container, 1);
+ eof = qemu_get_byte(fload);
+ g_assert(!qemu_file_get_error(fload));
+ g_assert_cmpint(eof, ==, QEMU_VM_EOF);
+ compare_containers(orig_container, dest_container);
+ free_container(orig_container);
+ free_container(dest_container);
+}
+
typedef struct TmpTestStruct {
TestStruct *parent;
int64_t diff;
@@ -932,6 +1063,8 @@ int main(int argc, char **argv)
test_arr_ptr_prim_0_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/qlist/save/saveqlist", test_save_qlist);
+ g_test_add_func("/vmstate/qlist/load/loadqlist", test_load_qlist);
g_test_add_func("/vmstate/tmp_struct", test_tmp_struct);
g_test_run();
94869d5c52 ("migration: migrate QTAILQ") introduced support for QTAILQ migration. Let's do the same for QLIST. Signed-off-by: Eric Auger <eric.auger@redhat.com> --- include/migration/vmstate.h | 21 ++++++ include/qemu/queue.h | 30 ++++++++ migration/vmstate-types.c | 60 ++++++++++++++++ tests/test-vmstate.c | 133 ++++++++++++++++++++++++++++++++++++ 4 files changed, 244 insertions(+)