1 | /* |
2 | * VMState interpreter |
3 | * |
4 | * Copyright (c) 2009-2017 Red Hat Inc |
5 | * |
6 | * Authors: |
7 | * Juan Quintela <quintela@redhat.com> |
8 | * |
9 | * This work is licensed under the terms of the GNU GPL, version 2 or later. |
10 | * See the COPYING file in the top-level directory. |
11 | */ |
12 | |
13 | #include "qemu/osdep.h" |
14 | #include "migration.h" |
15 | #include "migration/vmstate.h" |
16 | #include "savevm.h" |
17 | #include "qemu-file.h" |
18 | #include "qemu/bitops.h" |
19 | #include "qemu/error-report.h" |
20 | #include "trace.h" |
21 | #include "qjson.h" |
22 | |
23 | static int vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd, |
24 | void *opaque, QJSON *vmdesc); |
25 | static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd, |
26 | void *opaque); |
27 | |
28 | static int vmstate_n_elems(void *opaque, const VMStateField *field) |
29 | { |
30 | int n_elems = 1; |
31 | |
32 | if (field->flags & VMS_ARRAY) { |
33 | n_elems = field->num; |
34 | } else if (field->flags & VMS_VARRAY_INT32) { |
35 | n_elems = *(int32_t *)(opaque+field->num_offset); |
36 | } else if (field->flags & VMS_VARRAY_UINT32) { |
37 | n_elems = *(uint32_t *)(opaque+field->num_offset); |
38 | } else if (field->flags & VMS_VARRAY_UINT16) { |
39 | n_elems = *(uint16_t *)(opaque+field->num_offset); |
40 | } else if (field->flags & VMS_VARRAY_UINT8) { |
41 | n_elems = *(uint8_t *)(opaque+field->num_offset); |
42 | } |
43 | |
44 | if (field->flags & VMS_MULTIPLY_ELEMENTS) { |
45 | n_elems *= field->num; |
46 | } |
47 | |
48 | trace_vmstate_n_elems(field->name, n_elems); |
49 | return n_elems; |
50 | } |
51 | |
52 | static int vmstate_size(void *opaque, const VMStateField *field) |
53 | { |
54 | int size = field->size; |
55 | |
56 | if (field->flags & VMS_VBUFFER) { |
57 | size = *(int32_t *)(opaque+field->size_offset); |
58 | if (field->flags & VMS_MULTIPLY) { |
59 | size *= field->size; |
60 | } |
61 | } |
62 | |
63 | return size; |
64 | } |
65 | |
66 | static void vmstate_handle_alloc(void *ptr, const VMStateField *field, |
67 | void *opaque) |
68 | { |
69 | if (field->flags & VMS_POINTER && field->flags & VMS_ALLOC) { |
70 | gsize size = vmstate_size(opaque, field); |
71 | size *= vmstate_n_elems(opaque, field); |
72 | if (size) { |
73 | *(void **)ptr = g_malloc(size); |
74 | } |
75 | } |
76 | } |
77 | |
78 | int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd, |
79 | void *opaque, int version_id) |
80 | { |
81 | const VMStateField *field = vmsd->fields; |
82 | int ret = 0; |
83 | |
84 | trace_vmstate_load_state(vmsd->name, version_id); |
85 | if (version_id > vmsd->version_id) { |
86 | error_report("%s: incoming version_id %d is too new " |
87 | "for local version_id %d" , |
88 | vmsd->name, version_id, vmsd->version_id); |
89 | trace_vmstate_load_state_end(vmsd->name, "too new" , -EINVAL); |
90 | return -EINVAL; |
91 | } |
92 | if (version_id < vmsd->minimum_version_id) { |
93 | if (vmsd->load_state_old && |
94 | version_id >= vmsd->minimum_version_id_old) { |
95 | ret = vmsd->load_state_old(f, opaque, version_id); |
96 | trace_vmstate_load_state_end(vmsd->name, "old path" , ret); |
97 | return ret; |
98 | } |
99 | error_report("%s: incoming version_id %d is too old " |
100 | "for local minimum version_id %d" , |
101 | vmsd->name, version_id, vmsd->minimum_version_id); |
102 | trace_vmstate_load_state_end(vmsd->name, "too old" , -EINVAL); |
103 | return -EINVAL; |
104 | } |
105 | if (vmsd->pre_load) { |
106 | int ret = vmsd->pre_load(opaque); |
107 | if (ret) { |
108 | return ret; |
109 | } |
110 | } |
111 | while (field->name) { |
112 | trace_vmstate_load_state_field(vmsd->name, field->name); |
113 | if ((field->field_exists && |
114 | field->field_exists(opaque, version_id)) || |
115 | (!field->field_exists && |
116 | field->version_id <= version_id)) { |
117 | void *first_elem = opaque + field->offset; |
118 | int i, n_elems = vmstate_n_elems(opaque, field); |
119 | int size = vmstate_size(opaque, field); |
120 | |
121 | vmstate_handle_alloc(first_elem, field, opaque); |
122 | if (field->flags & VMS_POINTER) { |
123 | first_elem = *(void **)first_elem; |
124 | assert(first_elem || !n_elems || !size); |
125 | } |
126 | for (i = 0; i < n_elems; i++) { |
127 | void *curr_elem = first_elem + size * i; |
128 | |
129 | if (field->flags & VMS_ARRAY_OF_POINTER) { |
130 | curr_elem = *(void **)curr_elem; |
131 | } |
132 | if (!curr_elem && size) { |
133 | /* if null pointer check placeholder and do not follow */ |
134 | assert(field->flags & VMS_ARRAY_OF_POINTER); |
135 | ret = vmstate_info_nullptr.get(f, curr_elem, size, NULL); |
136 | } else if (field->flags & VMS_STRUCT) { |
137 | ret = vmstate_load_state(f, field->vmsd, curr_elem, |
138 | field->vmsd->version_id); |
139 | } else if (field->flags & VMS_VSTRUCT) { |
140 | ret = vmstate_load_state(f, field->vmsd, curr_elem, |
141 | field->struct_version_id); |
142 | } else { |
143 | ret = field->info->get(f, curr_elem, size, field); |
144 | } |
145 | if (ret >= 0) { |
146 | ret = qemu_file_get_error(f); |
147 | } |
148 | if (ret < 0) { |
149 | qemu_file_set_error(f, ret); |
150 | error_report("Failed to load %s:%s" , vmsd->name, |
151 | field->name); |
152 | trace_vmstate_load_field_error(field->name, ret); |
153 | return ret; |
154 | } |
155 | } |
156 | } else if (field->flags & VMS_MUST_EXIST) { |
157 | error_report("Input validation failed: %s/%s" , |
158 | vmsd->name, field->name); |
159 | return -1; |
160 | } |
161 | field++; |
162 | } |
163 | ret = vmstate_subsection_load(f, vmsd, opaque); |
164 | if (ret != 0) { |
165 | return ret; |
166 | } |
167 | if (vmsd->post_load) { |
168 | ret = vmsd->post_load(opaque, version_id); |
169 | } |
170 | trace_vmstate_load_state_end(vmsd->name, "end" , ret); |
171 | return ret; |
172 | } |
173 | |
174 | static int vmfield_name_num(const VMStateField *start, |
175 | const VMStateField *search) |
176 | { |
177 | const VMStateField *field; |
178 | int found = 0; |
179 | |
180 | for (field = start; field->name; field++) { |
181 | if (!strcmp(field->name, search->name)) { |
182 | if (field == search) { |
183 | return found; |
184 | } |
185 | found++; |
186 | } |
187 | } |
188 | |
189 | return -1; |
190 | } |
191 | |
192 | static bool vmfield_name_is_unique(const VMStateField *start, |
193 | const VMStateField *search) |
194 | { |
195 | const VMStateField *field; |
196 | int found = 0; |
197 | |
198 | for (field = start; field->name; field++) { |
199 | if (!strcmp(field->name, search->name)) { |
200 | found++; |
201 | /* name found more than once, so it's not unique */ |
202 | if (found > 1) { |
203 | return false; |
204 | } |
205 | } |
206 | } |
207 | |
208 | return true; |
209 | } |
210 | |
211 | static const char *vmfield_get_type_name(const VMStateField *field) |
212 | { |
213 | const char *type = "unknown" ; |
214 | |
215 | if (field->flags & VMS_STRUCT) { |
216 | type = "struct" ; |
217 | } else if (field->flags & VMS_VSTRUCT) { |
218 | type = "vstruct" ; |
219 | } else if (field->info->name) { |
220 | type = field->info->name; |
221 | } |
222 | |
223 | return type; |
224 | } |
225 | |
226 | static bool vmsd_can_compress(const VMStateField *field) |
227 | { |
228 | if (field->field_exists) { |
229 | /* Dynamically existing fields mess up compression */ |
230 | return false; |
231 | } |
232 | |
233 | if (field->flags & VMS_STRUCT) { |
234 | const VMStateField *sfield = field->vmsd->fields; |
235 | while (sfield->name) { |
236 | if (!vmsd_can_compress(sfield)) { |
237 | /* Child elements can't compress, so can't we */ |
238 | return false; |
239 | } |
240 | sfield++; |
241 | } |
242 | |
243 | if (field->vmsd->subsections) { |
244 | /* Subsections may come and go, better don't compress */ |
245 | return false; |
246 | } |
247 | } |
248 | |
249 | return true; |
250 | } |
251 | |
252 | static void vmsd_desc_field_start(const VMStateDescription *vmsd, QJSON *vmdesc, |
253 | const VMStateField *field, int i, int max) |
254 | { |
255 | char *name, *old_name; |
256 | bool is_array = max > 1; |
257 | bool can_compress = vmsd_can_compress(field); |
258 | |
259 | if (!vmdesc) { |
260 | return; |
261 | } |
262 | |
263 | name = g_strdup(field->name); |
264 | |
265 | /* Field name is not unique, need to make it unique */ |
266 | if (!vmfield_name_is_unique(vmsd->fields, field)) { |
267 | int num = vmfield_name_num(vmsd->fields, field); |
268 | old_name = name; |
269 | name = g_strdup_printf("%s[%d]" , name, num); |
270 | g_free(old_name); |
271 | } |
272 | |
273 | json_start_object(vmdesc, NULL); |
274 | json_prop_str(vmdesc, "name" , name); |
275 | if (is_array) { |
276 | if (can_compress) { |
277 | json_prop_int(vmdesc, "array_len" , max); |
278 | } else { |
279 | json_prop_int(vmdesc, "index" , i); |
280 | } |
281 | } |
282 | json_prop_str(vmdesc, "type" , vmfield_get_type_name(field)); |
283 | |
284 | if (field->flags & VMS_STRUCT) { |
285 | json_start_object(vmdesc, "struct" ); |
286 | } |
287 | |
288 | g_free(name); |
289 | } |
290 | |
291 | static void vmsd_desc_field_end(const VMStateDescription *vmsd, QJSON *vmdesc, |
292 | const VMStateField *field, size_t size, int i) |
293 | { |
294 | if (!vmdesc) { |
295 | return; |
296 | } |
297 | |
298 | if (field->flags & VMS_STRUCT) { |
299 | /* We printed a struct in between, close its child object */ |
300 | json_end_object(vmdesc); |
301 | } |
302 | |
303 | json_prop_int(vmdesc, "size" , size); |
304 | json_end_object(vmdesc); |
305 | } |
306 | |
307 | |
308 | bool vmstate_save_needed(const VMStateDescription *vmsd, void *opaque) |
309 | { |
310 | if (vmsd->needed && !vmsd->needed(opaque)) { |
311 | /* optional section not needed */ |
312 | return false; |
313 | } |
314 | return true; |
315 | } |
316 | |
317 | |
318 | int vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd, |
319 | void *opaque, QJSON *vmdesc_id) |
320 | { |
321 | return vmstate_save_state_v(f, vmsd, opaque, vmdesc_id, vmsd->version_id); |
322 | } |
323 | |
324 | int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd, |
325 | void *opaque, QJSON *vmdesc, int version_id) |
326 | { |
327 | int ret = 0; |
328 | const VMStateField *field = vmsd->fields; |
329 | |
330 | trace_vmstate_save_state_top(vmsd->name); |
331 | |
332 | if (vmsd->pre_save) { |
333 | ret = vmsd->pre_save(opaque); |
334 | trace_vmstate_save_state_pre_save_res(vmsd->name, ret); |
335 | if (ret) { |
336 | error_report("pre-save failed: %s" , vmsd->name); |
337 | return ret; |
338 | } |
339 | } |
340 | |
341 | if (vmdesc) { |
342 | json_prop_str(vmdesc, "vmsd_name" , vmsd->name); |
343 | json_prop_int(vmdesc, "version" , version_id); |
344 | json_start_array(vmdesc, "fields" ); |
345 | } |
346 | |
347 | while (field->name) { |
348 | if ((field->field_exists && |
349 | field->field_exists(opaque, version_id)) || |
350 | (!field->field_exists && |
351 | field->version_id <= version_id)) { |
352 | void *first_elem = opaque + field->offset; |
353 | int i, n_elems = vmstate_n_elems(opaque, field); |
354 | int size = vmstate_size(opaque, field); |
355 | int64_t old_offset, written_bytes; |
356 | QJSON *vmdesc_loop = vmdesc; |
357 | |
358 | trace_vmstate_save_state_loop(vmsd->name, field->name, n_elems); |
359 | if (field->flags & VMS_POINTER) { |
360 | first_elem = *(void **)first_elem; |
361 | assert(first_elem || !n_elems || !size); |
362 | } |
363 | for (i = 0; i < n_elems; i++) { |
364 | void *curr_elem = first_elem + size * i; |
365 | ret = 0; |
366 | |
367 | vmsd_desc_field_start(vmsd, vmdesc_loop, field, i, n_elems); |
368 | old_offset = qemu_ftell_fast(f); |
369 | if (field->flags & VMS_ARRAY_OF_POINTER) { |
370 | assert(curr_elem); |
371 | curr_elem = *(void **)curr_elem; |
372 | } |
373 | if (!curr_elem && size) { |
374 | /* if null pointer write placeholder and do not follow */ |
375 | assert(field->flags & VMS_ARRAY_OF_POINTER); |
376 | ret = vmstate_info_nullptr.put(f, curr_elem, size, NULL, |
377 | NULL); |
378 | } else if (field->flags & VMS_STRUCT) { |
379 | ret = vmstate_save_state(f, field->vmsd, curr_elem, |
380 | vmdesc_loop); |
381 | } else if (field->flags & VMS_VSTRUCT) { |
382 | ret = vmstate_save_state_v(f, field->vmsd, curr_elem, |
383 | vmdesc_loop, |
384 | field->struct_version_id); |
385 | } else { |
386 | ret = field->info->put(f, curr_elem, size, field, |
387 | vmdesc_loop); |
388 | } |
389 | if (ret) { |
390 | error_report("Save of field %s/%s failed" , |
391 | vmsd->name, field->name); |
392 | if (vmsd->post_save) { |
393 | vmsd->post_save(opaque); |
394 | } |
395 | return ret; |
396 | } |
397 | |
398 | written_bytes = qemu_ftell_fast(f) - old_offset; |
399 | vmsd_desc_field_end(vmsd, vmdesc_loop, field, written_bytes, i); |
400 | |
401 | /* Compressed arrays only care about the first element */ |
402 | if (vmdesc_loop && vmsd_can_compress(field)) { |
403 | vmdesc_loop = NULL; |
404 | } |
405 | } |
406 | } else { |
407 | if (field->flags & VMS_MUST_EXIST) { |
408 | error_report("Output state validation failed: %s/%s" , |
409 | vmsd->name, field->name); |
410 | assert(!(field->flags & VMS_MUST_EXIST)); |
411 | } |
412 | } |
413 | field++; |
414 | } |
415 | |
416 | if (vmdesc) { |
417 | json_end_array(vmdesc); |
418 | } |
419 | |
420 | ret = vmstate_subsection_save(f, vmsd, opaque, vmdesc); |
421 | |
422 | if (vmsd->post_save) { |
423 | int ps_ret = vmsd->post_save(opaque); |
424 | if (!ret) { |
425 | ret = ps_ret; |
426 | } |
427 | } |
428 | return ret; |
429 | } |
430 | |
431 | static const VMStateDescription * |
432 | vmstate_get_subsection(const VMStateDescription **sub, char *idstr) |
433 | { |
434 | while (sub && *sub) { |
435 | if (strcmp(idstr, (*sub)->name) == 0) { |
436 | return *sub; |
437 | } |
438 | sub++; |
439 | } |
440 | return NULL; |
441 | } |
442 | |
443 | static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd, |
444 | void *opaque) |
445 | { |
446 | trace_vmstate_subsection_load(vmsd->name); |
447 | |
448 | while (qemu_peek_byte(f, 0) == QEMU_VM_SUBSECTION) { |
449 | char idstr[256], *idstr_ret; |
450 | int ret; |
451 | uint8_t version_id, len, size; |
452 | const VMStateDescription *sub_vmsd; |
453 | |
454 | len = qemu_peek_byte(f, 1); |
455 | if (len < strlen(vmsd->name) + 1) { |
456 | /* subsection name has be be "section_name/a" */ |
457 | trace_vmstate_subsection_load_bad(vmsd->name, "(short)" , "" ); |
458 | return 0; |
459 | } |
460 | size = qemu_peek_buffer(f, (uint8_t **)&idstr_ret, len, 2); |
461 | if (size != len) { |
462 | trace_vmstate_subsection_load_bad(vmsd->name, "(peek fail)" , "" ); |
463 | return 0; |
464 | } |
465 | memcpy(idstr, idstr_ret, size); |
466 | idstr[size] = 0; |
467 | |
468 | if (strncmp(vmsd->name, idstr, strlen(vmsd->name)) != 0) { |
469 | trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(prefix)" ); |
470 | /* it doesn't have a valid subsection name */ |
471 | return 0; |
472 | } |
473 | sub_vmsd = vmstate_get_subsection(vmsd->subsections, idstr); |
474 | if (sub_vmsd == NULL) { |
475 | trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(lookup)" ); |
476 | return -ENOENT; |
477 | } |
478 | qemu_file_skip(f, 1); /* subsection */ |
479 | qemu_file_skip(f, 1); /* len */ |
480 | qemu_file_skip(f, len); /* idstr */ |
481 | version_id = qemu_get_be32(f); |
482 | |
483 | ret = vmstate_load_state(f, sub_vmsd, opaque, version_id); |
484 | if (ret) { |
485 | trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(child)" ); |
486 | return ret; |
487 | } |
488 | } |
489 | |
490 | trace_vmstate_subsection_load_good(vmsd->name); |
491 | return 0; |
492 | } |
493 | |
494 | static int vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd, |
495 | void *opaque, QJSON *vmdesc) |
496 | { |
497 | const VMStateDescription **sub = vmsd->subsections; |
498 | bool vmdesc_has_subsections = false; |
499 | int ret = 0; |
500 | |
501 | trace_vmstate_subsection_save_top(vmsd->name); |
502 | while (sub && *sub) { |
503 | if (vmstate_save_needed(*sub, opaque)) { |
504 | const VMStateDescription *vmsdsub = *sub; |
505 | uint8_t len; |
506 | |
507 | trace_vmstate_subsection_save_loop(vmsd->name, vmsdsub->name); |
508 | if (vmdesc) { |
509 | /* Only create subsection array when we have any */ |
510 | if (!vmdesc_has_subsections) { |
511 | json_start_array(vmdesc, "subsections" ); |
512 | vmdesc_has_subsections = true; |
513 | } |
514 | |
515 | json_start_object(vmdesc, NULL); |
516 | } |
517 | |
518 | qemu_put_byte(f, QEMU_VM_SUBSECTION); |
519 | len = strlen(vmsdsub->name); |
520 | qemu_put_byte(f, len); |
521 | qemu_put_buffer(f, (uint8_t *)vmsdsub->name, len); |
522 | qemu_put_be32(f, vmsdsub->version_id); |
523 | ret = vmstate_save_state(f, vmsdsub, opaque, vmdesc); |
524 | if (ret) { |
525 | return ret; |
526 | } |
527 | |
528 | if (vmdesc) { |
529 | json_end_object(vmdesc); |
530 | } |
531 | } |
532 | sub++; |
533 | } |
534 | |
535 | if (vmdesc_has_subsections) { |
536 | json_end_array(vmdesc); |
537 | } |
538 | |
539 | return ret; |
540 | } |
541 | |