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;