vmstate.c 16.6 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
/*
 * VMState interpreter
 *
 * Copyright (c) 2009-2017 Red Hat Inc
 *
 * Authors:
 *  Juan Quintela <quintela@redhat.com>
 *
 * This work is licensed under the terms of the GNU GPL, version 2 or later.
 * See the COPYING file in the top-level directory.
 */

P
Peter Maydell 已提交
13
#include "qemu/osdep.h"
14
#include "qemu-common.h"
15
#include "migration.h"
16
#include "migration/vmstate.h"
17
#include "savevm.h"
J
Juan Quintela 已提交
18
#include "qemu-file.h"
19
#include "qemu/bitops.h"
20
#include "qemu/error-report.h"
21
#include "trace.h"
22
#include "qjson.h"
23

24 25
static int vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
                                   void *opaque, QJSON *vmdesc);
26 27 28
static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
                                   void *opaque);

29
static int vmstate_n_elems(void *opaque, const VMStateField *field)
30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
{
    int n_elems = 1;

    if (field->flags & VMS_ARRAY) {
        n_elems = field->num;
    } else if (field->flags & VMS_VARRAY_INT32) {
        n_elems = *(int32_t *)(opaque+field->num_offset);
    } else if (field->flags & VMS_VARRAY_UINT32) {
        n_elems = *(uint32_t *)(opaque+field->num_offset);
    } else if (field->flags & VMS_VARRAY_UINT16) {
        n_elems = *(uint16_t *)(opaque+field->num_offset);
    } else if (field->flags & VMS_VARRAY_UINT8) {
        n_elems = *(uint8_t *)(opaque+field->num_offset);
    }

45 46 47 48
    if (field->flags & VMS_MULTIPLY_ELEMENTS) {
        n_elems *= field->num;
    }

49
    trace_vmstate_n_elems(field->name, n_elems);
50 51 52
    return n_elems;
}

53
static int vmstate_size(void *opaque, const VMStateField *field)
54 55 56 57 58 59 60 61 62 63 64 65 66
{
    int size = field->size;

    if (field->flags & VMS_VBUFFER) {
        size = *(int32_t *)(opaque+field->size_offset);
        if (field->flags & VMS_MULTIPLY) {
            size *= field->size;
        }
    }

    return size;
}

67 68
static void vmstate_handle_alloc(void *ptr, const VMStateField *field,
                                 void *opaque)
69
{
70 71 72 73 74
    if (field->flags & VMS_POINTER && field->flags & VMS_ALLOC) {
        gsize size = vmstate_size(opaque, field);
        size *= vmstate_n_elems(opaque, field);
        if (size) {
            *(void **)ptr = g_malloc(size);
75
        }
76 77 78
    }
}

79 80 81
int vmstate_load_state(QEMUFile *f, const VMStateDescription *vmsd,
                       void *opaque, int version_id)
{
82
    const VMStateField *field = vmsd->fields;
83
    int ret = 0;
84

85
    trace_vmstate_load_state(vmsd->name, version_id);
86
    if (version_id > vmsd->version_id) {
J
Jianjun Duan 已提交
87 88 89
        error_report("%s: incoming version_id %d is too new "
                     "for local version_id %d",
                     vmsd->name, version_id, vmsd->version_id);
90
        trace_vmstate_load_state_end(vmsd->name, "too new", -EINVAL);
91 92 93
        return -EINVAL;
    }
    if  (version_id < vmsd->minimum_version_id) {
94 95
        if (vmsd->load_state_old &&
            version_id >= vmsd->minimum_version_id_old) {
96 97 98
            ret = vmsd->load_state_old(f, opaque, version_id);
            trace_vmstate_load_state_end(vmsd->name, "old path", ret);
            return ret;
99
        }
J
Jianjun Duan 已提交
100 101 102
        error_report("%s: incoming version_id %d is too old "
                     "for local minimum version_id  %d",
                     vmsd->name, version_id, vmsd->minimum_version_id);
103
        trace_vmstate_load_state_end(vmsd->name, "too old", -EINVAL);
104
        return -EINVAL;
105 106 107 108 109 110 111 112
    }
    if (vmsd->pre_load) {
        int ret = vmsd->pre_load(opaque);
        if (ret) {
            return ret;
        }
    }
    while (field->name) {
113
        trace_vmstate_load_state_field(vmsd->name, field->name);
114 115 116 117
        if ((field->field_exists &&
             field->field_exists(opaque, version_id)) ||
            (!field->field_exists &&
             field->version_id <= version_id)) {
118
            void *first_elem = opaque + field->offset;
119 120 121
            int i, n_elems = vmstate_n_elems(opaque, field);
            int size = vmstate_size(opaque, field);

122 123 124
            vmstate_handle_alloc(first_elem, field, opaque);
            if (field->flags & VMS_POINTER) {
                first_elem = *(void **)first_elem;
125
                assert(first_elem || !n_elems || !size);
126
            }
127
            for (i = 0; i < n_elems; i++) {
128
                void *curr_elem = first_elem + size * i;
129 130

                if (field->flags & VMS_ARRAY_OF_POINTER) {
131
                    curr_elem = *(void **)curr_elem;
132
                }
133
                if (!curr_elem && size) {
134 135 136 137
                    /* if null pointer check placeholder and do not follow */
                    assert(field->flags & VMS_ARRAY_OF_POINTER);
                    ret = vmstate_info_nullptr.get(f, curr_elem, size, NULL);
                } else if (field->flags & VMS_STRUCT) {
138
                    ret = vmstate_load_state(f, field->vmsd, curr_elem,
139
                                             field->vmsd->version_id);
C
Corey Minyard 已提交
140 141 142
                } else if (field->flags & VMS_VSTRUCT) {
                    ret = vmstate_load_state(f, field->vmsd, curr_elem,
                                             field->struct_version_id);
143
                } else {
144
                    ret = field->info->get(f, curr_elem, size, field);
145
                }
146 147 148
                if (ret >= 0) {
                    ret = qemu_file_get_error(f);
                }
149
                if (ret < 0) {
150
                    qemu_file_set_error(f, ret);
151 152
                    error_report("Failed to load %s:%s", vmsd->name,
                                 field->name);
153
                    trace_vmstate_load_field_error(field->name, ret);
154 155 156
                    return ret;
                }
            }
M
Michael S. Tsirkin 已提交
157
        } else if (field->flags & VMS_MUST_EXIST) {
158 159
            error_report("Input validation failed: %s/%s",
                         vmsd->name, field->name);
M
Michael S. Tsirkin 已提交
160
            return -1;
161 162 163 164 165 166 167 168
        }
        field++;
    }
    ret = vmstate_subsection_load(f, vmsd, opaque);
    if (ret != 0) {
        return ret;
    }
    if (vmsd->post_load) {
169
        ret = vmsd->post_load(opaque, version_id);
170
    }
171 172
    trace_vmstate_load_state_end(vmsd->name, "end", ret);
    return ret;
173 174
}

175 176
static int vmfield_name_num(const VMStateField *start,
                            const VMStateField *search)
177
{
178
    const VMStateField *field;
179 180 181 182 183 184 185 186 187 188 189 190 191 192
    int found = 0;

    for (field = start; field->name; field++) {
        if (!strcmp(field->name, search->name)) {
            if (field == search) {
                return found;
            }
            found++;
        }
    }

    return -1;
}

193 194
static bool vmfield_name_is_unique(const VMStateField *start,
                                   const VMStateField *search)
195
{
196
    const VMStateField *field;
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
    int found = 0;

    for (field = start; field->name; field++) {
        if (!strcmp(field->name, search->name)) {
            found++;
            /* name found more than once, so it's not unique */
            if (found > 1) {
                return false;
            }
        }
    }

    return true;
}

212
static const char *vmfield_get_type_name(const VMStateField *field)
213 214 215 216 217
{
    const char *type = "unknown";

    if (field->flags & VMS_STRUCT) {
        type = "struct";
C
Corey Minyard 已提交
218 219
    } else if (field->flags & VMS_VSTRUCT) {
        type = "vstruct";
220 221 222 223 224 225 226
    } else if (field->info->name) {
        type = field->info->name;
    }

    return type;
}

227
static bool vmsd_can_compress(const VMStateField *field)
228 229 230 231 232 233 234
{
    if (field->field_exists) {
        /* Dynamically existing fields mess up compression */
        return false;
    }

    if (field->flags & VMS_STRUCT) {
235
        const VMStateField *sfield = field->vmsd->fields;
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253
        while (sfield->name) {
            if (!vmsd_can_compress(sfield)) {
                /* Child elements can't compress, so can't we */
                return false;
            }
            sfield++;
        }

        if (field->vmsd->subsections) {
            /* Subsections may come and go, better don't compress */
            return false;
        }
    }

    return true;
}

static void vmsd_desc_field_start(const VMStateDescription *vmsd, QJSON *vmdesc,
254
                                  const VMStateField *field, int i, int max)
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
{
    char *name, *old_name;
    bool is_array = max > 1;
    bool can_compress = vmsd_can_compress(field);

    if (!vmdesc) {
        return;
    }

    name = g_strdup(field->name);

    /* Field name is not unique, need to make it unique */
    if (!vmfield_name_is_unique(vmsd->fields, field)) {
        int num = vmfield_name_num(vmsd->fields, field);
        old_name = name;
        name = g_strdup_printf("%s[%d]", name, num);
        g_free(old_name);
    }

    json_start_object(vmdesc, NULL);
    json_prop_str(vmdesc, "name", name);
    if (is_array) {
        if (can_compress) {
            json_prop_int(vmdesc, "array_len", max);
        } else {
            json_prop_int(vmdesc, "index", i);
        }
    }
    json_prop_str(vmdesc, "type", vmfield_get_type_name(field));

    if (field->flags & VMS_STRUCT) {
        json_start_object(vmdesc, "struct");
    }

    g_free(name);
}

static void vmsd_desc_field_end(const VMStateDescription *vmsd, QJSON *vmdesc,
293
                                const VMStateField *field, size_t size, int i)
294 295 296 297 298 299 300 301 302 303 304 305 306 307
{
    if (!vmdesc) {
        return;
    }

    if (field->flags & VMS_STRUCT) {
        /* We printed a struct in between, close its child object */
        json_end_object(vmdesc);
    }

    json_prop_int(vmdesc, "size", size);
    json_end_object(vmdesc);
}

308 309 310 311 312 313 314 315 316 317 318

bool vmstate_save_needed(const VMStateDescription *vmsd, void *opaque)
{
    if (vmsd->needed && !vmsd->needed(opaque)) {
        /* optional section not needed */
        return false;
    }
    return true;
}


319
int vmstate_save_state(QEMUFile *f, const VMStateDescription *vmsd,
C
Corey Minyard 已提交
320 321 322 323 324 325 326
                       void *opaque, QJSON *vmdesc_id)
{
    return vmstate_save_state_v(f, vmsd, opaque, vmdesc_id, vmsd->version_id);
}

int vmstate_save_state_v(QEMUFile *f, const VMStateDescription *vmsd,
                         void *opaque, QJSON *vmdesc, int version_id)
327
{
328
    int ret = 0;
329
    const VMStateField *field = vmsd->fields;
330

331 332
    trace_vmstate_save_state_top(vmsd->name);

333
    if (vmsd->pre_save) {
334 335 336 337 338 339
        ret = vmsd->pre_save(opaque);
        trace_vmstate_save_state_pre_save_res(vmsd->name, ret);
        if (ret) {
            error_report("pre-save failed: %s", vmsd->name);
            return ret;
        }
340
    }
341 342 343

    if (vmdesc) {
        json_prop_str(vmdesc, "vmsd_name", vmsd->name);
C
Corey Minyard 已提交
344
        json_prop_int(vmdesc, "version", version_id);
345 346 347
        json_start_array(vmdesc, "fields");
    }

348
    while (field->name) {
C
Corey Minyard 已提交
349 350 351 352
        if ((field->field_exists &&
             field->field_exists(opaque, version_id)) ||
            (!field->field_exists &&
             field->version_id <= version_id)) {
353
            void *first_elem = opaque + field->offset;
354 355
            int i, n_elems = vmstate_n_elems(opaque, field);
            int size = vmstate_size(opaque, field);
356 357
            int64_t old_offset, written_bytes;
            QJSON *vmdesc_loop = vmdesc;
358

359
            trace_vmstate_save_state_loop(vmsd->name, field->name, n_elems);
360 361
            if (field->flags & VMS_POINTER) {
                first_elem = *(void **)first_elem;
362
                assert(first_elem || !n_elems || !size);
363
            }
364
            for (i = 0; i < n_elems; i++) {
365
                void *curr_elem = first_elem + size * i;
366
                ret = 0;
367

368 369
                vmsd_desc_field_start(vmsd, vmdesc_loop, field, i, n_elems);
                old_offset = qemu_ftell_fast(f);
370
                if (field->flags & VMS_ARRAY_OF_POINTER) {
371 372
                    assert(curr_elem);
                    curr_elem = *(void **)curr_elem;
373
                }
374
                if (!curr_elem && size) {
375 376
                    /* if null pointer write placeholder and do not follow */
                    assert(field->flags & VMS_ARRAY_OF_POINTER);
377 378
                    ret = vmstate_info_nullptr.put(f, curr_elem, size, NULL,
                                                   NULL);
379
                } else if (field->flags & VMS_STRUCT) {
380 381
                    ret = vmstate_save_state(f, field->vmsd, curr_elem,
                                             vmdesc_loop);
C
Corey Minyard 已提交
382 383 384 385
                } else if (field->flags & VMS_VSTRUCT) {
                    ret = vmstate_save_state_v(f, field->vmsd, curr_elem,
                                               vmdesc_loop,
                                               field->struct_version_id);
386
                } else {
387 388 389 390 391 392 393
                    ret = field->info->put(f, curr_elem, size, field,
                                     vmdesc_loop);
                }
                if (ret) {
                    error_report("Save of field %s/%s failed",
                                 vmsd->name, field->name);
                    return ret;
394
                }
395 396 397 398 399 400 401 402

                written_bytes = qemu_ftell_fast(f) - old_offset;
                vmsd_desc_field_end(vmsd, vmdesc_loop, field, written_bytes, i);

                /* Compressed arrays only care about the first element */
                if (vmdesc_loop && vmsd_can_compress(field)) {
                    vmdesc_loop = NULL;
                }
403
            }
M
Michael S. Tsirkin 已提交
404 405
        } else {
            if (field->flags & VMS_MUST_EXIST) {
406
                error_report("Output state validation failed: %s/%s",
M
Michael S. Tsirkin 已提交
407 408 409
                        vmsd->name, field->name);
                assert(!(field->flags & VMS_MUST_EXIST));
            }
410 411 412
        }
        field++;
    }
413 414 415 416 417

    if (vmdesc) {
        json_end_array(vmdesc);
    }

418
    return vmstate_subsection_save(f, vmsd, opaque, vmdesc);
419 420 421
}

static const VMStateDescription *
422
vmstate_get_subsection(const VMStateDescription **sub, char *idstr)
423
{
424
    while (sub && *sub) {
425 426
        if (strcmp(idstr, (*sub)->name) == 0) {
            return *sub;
427 428 429 430 431 432 433 434 435
        }
        sub++;
    }
    return NULL;
}

static int vmstate_subsection_load(QEMUFile *f, const VMStateDescription *vmsd,
                                   void *opaque)
{
436 437
    trace_vmstate_subsection_load(vmsd->name);

438
    while (qemu_peek_byte(f, 0) == QEMU_VM_SUBSECTION) {
439
        char idstr[256], *idstr_ret;
440 441 442 443 444 445 446
        int ret;
        uint8_t version_id, len, size;
        const VMStateDescription *sub_vmsd;

        len = qemu_peek_byte(f, 1);
        if (len < strlen(vmsd->name) + 1) {
            /* subsection name has be be "section_name/a" */
447
            trace_vmstate_subsection_load_bad(vmsd->name, "(short)", "");
448 449
            return 0;
        }
450
        size = qemu_peek_buffer(f, (uint8_t **)&idstr_ret, len, 2);
451
        if (size != len) {
452
            trace_vmstate_subsection_load_bad(vmsd->name, "(peek fail)", "");
453 454
            return 0;
        }
455
        memcpy(idstr, idstr_ret, size);
456 457 458
        idstr[size] = 0;

        if (strncmp(vmsd->name, idstr, strlen(vmsd->name)) != 0) {
459 460
            trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(prefix)");
            /* it doesn't have a valid subsection name */
461 462 463 464
            return 0;
        }
        sub_vmsd = vmstate_get_subsection(vmsd->subsections, idstr);
        if (sub_vmsd == NULL) {
465
            trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(lookup)");
466 467 468 469 470 471 472 473 474
            return -ENOENT;
        }
        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);
        if (ret) {
475
            trace_vmstate_subsection_load_bad(vmsd->name, idstr, "(child)");
476 477 478
            return ret;
        }
    }
479 480

    trace_vmstate_subsection_load_good(vmsd->name);
481 482 483
    return 0;
}

484 485
static int vmstate_subsection_save(QEMUFile *f, const VMStateDescription *vmsd,
                                   void *opaque, QJSON *vmdesc)
486
{
487
    const VMStateDescription **sub = vmsd->subsections;
488
    bool subsection_found = false;
489
    int ret = 0;
490

491
    trace_vmstate_subsection_save_top(vmsd->name);
492 493
    while (sub && *sub) {
        if (vmstate_save_needed(*sub, opaque)) {
494
            const VMStateDescription *vmsdsub = *sub;
495 496
            uint8_t len;

497
            trace_vmstate_subsection_save_loop(vmsd->name, vmsdsub->name);
498 499 500 501 502 503 504 505 506 507
            if (vmdesc) {
                /* Only create subsection array when we have any */
                if (!subsection_found) {
                    json_start_array(vmdesc, "subsections");
                    subsection_found = true;
                }

                json_start_object(vmdesc, NULL);
            }

508
            qemu_put_byte(f, QEMU_VM_SUBSECTION);
509
            len = strlen(vmsdsub->name);
510
            qemu_put_byte(f, len);
511 512
            qemu_put_buffer(f, (uint8_t *)vmsdsub->name, len);
            qemu_put_be32(f, vmsdsub->version_id);
513 514 515 516
            ret = vmstate_save_state(f, vmsdsub, opaque, vmdesc);
            if (ret) {
                return ret;
            }
517 518 519 520

            if (vmdesc) {
                json_end_object(vmdesc);
            }
521 522 523
        }
        sub++;
    }
524 525 526 527

    if (vmdesc && subsection_found) {
        json_end_array(vmdesc);
    }
528 529

    return ret;
530
}