qemu-config.c 17.1 KB
Newer Older
Gerd Hoffmann's avatar
Gerd Hoffmann committed
1
#include "qemu-common.h"
2
#include "qemu-error.h"
Gerd Hoffmann's avatar
Gerd Hoffmann committed
3
4
#include "qemu-option.h"
#include "qemu-config.h"
5
#include "hw/qdev.h"
Gerd Hoffmann's avatar
Gerd Hoffmann committed
6

7
static QemuOptsList qemu_drive_opts = {
Gerd Hoffmann's avatar
Gerd Hoffmann committed
8
    .name = "drive",
Blue Swirl's avatar
Blue Swirl committed
9
    .head = QTAILQ_HEAD_INITIALIZER(qemu_drive_opts.head),
Gerd Hoffmann's avatar
Gerd Hoffmann committed
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
    .desc = {
        {
            .name = "bus",
            .type = QEMU_OPT_NUMBER,
            .help = "bus number",
        },{
            .name = "unit",
            .type = QEMU_OPT_NUMBER,
            .help = "unit number (i.e. lun for scsi)",
        },{
            .name = "if",
            .type = QEMU_OPT_STRING,
            .help = "interface (ide, scsi, sd, mtd, floppy, pflash, virtio)",
        },{
            .name = "index",
            .type = QEMU_OPT_NUMBER,
        },{
            .name = "cyls",
            .type = QEMU_OPT_NUMBER,
            .help = "number of cylinders (ide disk geometry)",
        },{
            .name = "heads",
            .type = QEMU_OPT_NUMBER,
            .help = "number of heads (ide disk geometry)",
        },{
            .name = "secs",
            .type = QEMU_OPT_NUMBER,
            .help = "number of sectors (ide disk geometry)",
        },{
            .name = "trans",
            .type = QEMU_OPT_STRING,
            .help = "chs translation (auto, lba. none)",
        },{
            .name = "media",
            .type = QEMU_OPT_STRING,
            .help = "media type (disk, cdrom)",
        },{
            .name = "snapshot",
            .type = QEMU_OPT_BOOL,
        },{
            .name = "file",
            .type = QEMU_OPT_STRING,
            .help = "disk image",
        },{
            .name = "cache",
            .type = QEMU_OPT_STRING,
56
            .help = "host cache usage (none, writeback, writethrough, unsafe)",
57
58
59
60
        },{
            .name = "aio",
            .type = QEMU_OPT_STRING,
            .help = "host AIO implementation (threads, native)",
Gerd Hoffmann's avatar
Gerd Hoffmann committed
61
62
63
64
65
66
67
        },{
            .name = "format",
            .type = QEMU_OPT_STRING,
            .help = "disk format (raw, qcow2, ...)",
        },{
            .name = "serial",
            .type = QEMU_OPT_STRING,
68
69
70
        },{
            .name = "rerror",
            .type = QEMU_OPT_STRING,
Gerd Hoffmann's avatar
Gerd Hoffmann committed
71
72
73
74
75
76
77
        },{
            .name = "werror",
            .type = QEMU_OPT_STRING,
        },{
            .name = "addr",
            .type = QEMU_OPT_STRING,
            .help = "pci address (virtio only)",
78
79
80
        },{
            .name = "readonly",
            .type = QEMU_OPT_BOOL,
Gerd Hoffmann's avatar
Gerd Hoffmann committed
81
        },
82
        { /* end of list */ }
Gerd Hoffmann's avatar
Gerd Hoffmann committed
83
84
85
    },
};

86
static QemuOptsList qemu_chardev_opts = {
87
    .name = "chardev",
88
    .implied_opt_name = "backend",
Blue Swirl's avatar
Blue Swirl committed
89
    .head = QTAILQ_HEAD_INITIALIZER(qemu_chardev_opts.head),
90
    .desc = {
91
92
93
94
95
96
        {
            .name = "backend",
            .type = QEMU_OPT_STRING,
        },{
            .name = "path",
            .type = QEMU_OPT_STRING,
97
98
99
100
101
102
        },{
            .name = "host",
            .type = QEMU_OPT_STRING,
        },{
            .name = "port",
            .type = QEMU_OPT_STRING,
103
104
105
106
107
108
        },{
            .name = "localaddr",
            .type = QEMU_OPT_STRING,
        },{
            .name = "localport",
            .type = QEMU_OPT_STRING,
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
        },{
            .name = "to",
            .type = QEMU_OPT_NUMBER,
        },{
            .name = "ipv4",
            .type = QEMU_OPT_BOOL,
        },{
            .name = "ipv6",
            .type = QEMU_OPT_BOOL,
        },{
            .name = "wait",
            .type = QEMU_OPT_BOOL,
        },{
            .name = "server",
            .type = QEMU_OPT_BOOL,
        },{
            .name = "delay",
            .type = QEMU_OPT_BOOL,
        },{
            .name = "telnet",
            .type = QEMU_OPT_BOOL,
130
131
132
133
134
135
136
137
138
139
140
141
        },{
            .name = "width",
            .type = QEMU_OPT_NUMBER,
        },{
            .name = "height",
            .type = QEMU_OPT_NUMBER,
        },{
            .name = "cols",
            .type = QEMU_OPT_NUMBER,
        },{
            .name = "rows",
            .type = QEMU_OPT_NUMBER,
Gerd Hoffmann's avatar
Gerd Hoffmann committed
142
143
144
        },{
            .name = "mux",
            .type = QEMU_OPT_BOOL,
145
146
147
        },{
            .name = "signal",
            .type = QEMU_OPT_BOOL,
Alon Levy's avatar
Alon Levy committed
148
149
150
151
152
153
        },{
            .name = "name",
            .type = QEMU_OPT_STRING,
        },{
            .name = "debug",
            .type = QEMU_OPT_NUMBER,
154
        },
155
        { /* end of list */ }
156
157
158
    },
};

159
160
161
162
163
164
165
166
167
168
169
QemuOptsList qemu_fsdev_opts = {
    .name = "fsdev",
    .implied_opt_name = "fstype",
    .head = QTAILQ_HEAD_INITIALIZER(qemu_fsdev_opts.head),
    .desc = {
        {
            .name = "fstype",
            .type = QEMU_OPT_STRING,
        }, {
            .name = "path",
            .type = QEMU_OPT_STRING,
170
171
172
        }, {
            .name = "security_model",
            .type = QEMU_OPT_STRING,
173
174
175
176
177
        },
        { /*End of list */ }
    },
};

178
179
180
181
182
183
184
185
186
187
188
189
190
191
QemuOptsList qemu_virtfs_opts = {
    .name = "virtfs",
    .implied_opt_name = "fstype",
    .head = QTAILQ_HEAD_INITIALIZER(qemu_virtfs_opts.head),
    .desc = {
        {
            .name = "fstype",
            .type = QEMU_OPT_STRING,
        }, {
            .name = "path",
            .type = QEMU_OPT_STRING,
        }, {
            .name = "mount_tag",
            .type = QEMU_OPT_STRING,
192
193
194
        }, {
            .name = "security_model",
            .type = QEMU_OPT_STRING,
195
196
197
198
199
200
        },

        { /*End of list */ }
    },
};

201
static QemuOptsList qemu_device_opts = {
Gerd Hoffmann's avatar
Gerd Hoffmann committed
202
    .name = "device",
203
    .implied_opt_name = "driver",
Blue Swirl's avatar
Blue Swirl committed
204
    .head = QTAILQ_HEAD_INITIALIZER(qemu_device_opts.head),
Gerd Hoffmann's avatar
Gerd Hoffmann committed
205
206
207
208
209
210
    .desc = {
        /*
         * no elements => accept any
         * sanity checking will happen later
         * when setting device properties
         */
211
        { /* end of list */ }
Gerd Hoffmann's avatar
Gerd Hoffmann committed
212
213
214
    },
};

215
static QemuOptsList qemu_netdev_opts = {
Mark McLoughlin's avatar
Mark McLoughlin committed
216
    .name = "netdev",
217
    .implied_opt_name = "type",
Mark McLoughlin's avatar
Mark McLoughlin committed
218
219
220
221
222
223
224
225
226
227
    .head = QTAILQ_HEAD_INITIALIZER(qemu_netdev_opts.head),
    .desc = {
        /*
         * no elements => accept any params
         * validation will happen later
         */
        { /* end of list */ }
    },
};

228
static QemuOptsList qemu_net_opts = {
Mark McLoughlin's avatar
Mark McLoughlin committed
229
    .name = "net",
230
    .implied_opt_name = "type",
Mark McLoughlin's avatar
Mark McLoughlin committed
231
232
233
234
235
236
237
238
239
240
    .head = QTAILQ_HEAD_INITIALIZER(qemu_net_opts.head),
    .desc = {
        /*
         * no elements => accept any params
         * validation will happen later
         */
        { /* end of list */ }
    },
};

241
static QemuOptsList qemu_rtc_opts = {
242
243
244
245
246
247
    .name = "rtc",
    .head = QTAILQ_HEAD_INITIALIZER(qemu_rtc_opts.head),
    .desc = {
        {
            .name = "base",
            .type = QEMU_OPT_STRING,
Jan Kiszka's avatar
Jan Kiszka committed
248
249
250
        },{
            .name = "clock",
            .type = QEMU_OPT_STRING,
251
252
253
254
        },{
            .name = "driftfix",
            .type = QEMU_OPT_STRING,
        },
255
        { /* end of list */ }
256
257
258
    },
};

259
static QemuOptsList qemu_global_opts = {
260
261
262
263
264
265
266
267
268
269
270
271
272
    .name = "global",
    .head = QTAILQ_HEAD_INITIALIZER(qemu_global_opts.head),
    .desc = {
        {
            .name = "driver",
            .type = QEMU_OPT_STRING,
        },{
            .name = "property",
            .type = QEMU_OPT_STRING,
        },{
            .name = "value",
            .type = QEMU_OPT_STRING,
        },
273
        { /* end of list */ }
274
275
276
    },
};

277
static QemuOptsList qemu_mon_opts = {
278
    .name = "mon",
279
    .implied_opt_name = "chardev",
280
281
282
283
284
285
286
287
288
289
290
    .head = QTAILQ_HEAD_INITIALIZER(qemu_mon_opts.head),
    .desc = {
        {
            .name = "mode",
            .type = QEMU_OPT_STRING,
        },{
            .name = "chardev",
            .type = QEMU_OPT_STRING,
        },{
            .name = "default",
            .type = QEMU_OPT_BOOL,
291
292
293
        },{
            .name = "pretty",
            .type = QEMU_OPT_BOOL,
294
        },
295
        { /* end of list */ }
296
297
298
    },
};

299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
#ifdef CONFIG_SIMPLE_TRACE
static QemuOptsList qemu_trace_opts = {
    .name = "trace",
    .implied_opt_name = "trace",
    .head = QTAILQ_HEAD_INITIALIZER(qemu_trace_opts.head),
    .desc = {
        {
            .name = "file",
            .type = QEMU_OPT_STRING,
        },
        { /* end if list */ }
    },
};
#endif

314
static QemuOptsList qemu_cpudef_opts = {
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
    .name = "cpudef",
    .head = QTAILQ_HEAD_INITIALIZER(qemu_cpudef_opts.head),
    .desc = {
        {
            .name = "name",
            .type = QEMU_OPT_STRING,
        },{
            .name = "level",
            .type = QEMU_OPT_NUMBER,
        },{
            .name = "vendor",
            .type = QEMU_OPT_STRING,
        },{
            .name = "family",
            .type = QEMU_OPT_NUMBER,
        },{
            .name = "model",
            .type = QEMU_OPT_NUMBER,
        },{
            .name = "stepping",
            .type = QEMU_OPT_NUMBER,
        },{
            .name = "feature_edx",      /* cpuid 0000_0001.edx */
            .type = QEMU_OPT_STRING,
        },{
            .name = "feature_ecx",      /* cpuid 0000_0001.ecx */
            .type = QEMU_OPT_STRING,
        },{
            .name = "extfeature_edx",   /* cpuid 8000_0001.edx */
            .type = QEMU_OPT_STRING,
        },{
            .name = "extfeature_ecx",   /* cpuid 8000_0001.ecx */
            .type = QEMU_OPT_STRING,
        },{
            .name = "xlevel",
            .type = QEMU_OPT_NUMBER,
        },{
            .name = "model_id",
            .type = QEMU_OPT_STRING,
        },{
            .name = "vendor_override",
            .type = QEMU_OPT_NUMBER,
        },
        { /* end of list */ }
    },
};

Gerd Hoffmann's avatar
Gerd Hoffmann committed
362
363
364
365
366
367
368
QemuOptsList qemu_spice_opts = {
    .name = "spice",
    .head = QTAILQ_HEAD_INITIALIZER(qemu_spice_opts.head),
    .desc = {
        {
            .name = "port",
            .type = QEMU_OPT_NUMBER,
Gerd Hoffmann's avatar
Gerd Hoffmann committed
369
370
371
        },{
            .name = "tls-port",
            .type = QEMU_OPT_NUMBER,
372
373
374
375
376
377
378
379
380
        },{
            .name = "addr",
            .type = QEMU_OPT_STRING,
        },{
            .name = "ipv4",
            .type = QEMU_OPT_BOOL,
        },{
            .name = "ipv6",
            .type = QEMU_OPT_BOOL,
Gerd Hoffmann's avatar
Gerd Hoffmann committed
381
382
383
384
385
386
        },{
            .name = "password",
            .type = QEMU_OPT_STRING,
        },{
            .name = "disable-ticketing",
            .type = QEMU_OPT_BOOL,
Gerd Hoffmann's avatar
Gerd Hoffmann committed
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
        },{
            .name = "x509-dir",
            .type = QEMU_OPT_STRING,
        },{
            .name = "x509-key-file",
            .type = QEMU_OPT_STRING,
        },{
            .name = "x509-key-password",
            .type = QEMU_OPT_STRING,
        },{
            .name = "x509-cert-file",
            .type = QEMU_OPT_STRING,
        },{
            .name = "x509-cacert-file",
            .type = QEMU_OPT_STRING,
        },{
            .name = "x509-dh-key-file",
            .type = QEMU_OPT_STRING,
        },{
            .name = "tls-ciphers",
            .type = QEMU_OPT_STRING,
408
409
410
411
412
413
        },{
            .name = "tls-channel",
            .type = QEMU_OPT_STRING,
        },{
            .name = "plaintext-channel",
            .type = QEMU_OPT_STRING,
414
415
416
417
418
419
420
421
422
        },{
            .name = "image-compression",
            .type = QEMU_OPT_STRING,
        },{
            .name = "jpeg-wan-compression",
            .type = QEMU_OPT_STRING,
        },{
            .name = "zlib-glz-wan-compression",
            .type = QEMU_OPT_STRING,
Gerd Hoffmann's avatar
Gerd Hoffmann committed
423
424
425
426
427
428
429
430
431
        },{
            .name = "streaming-video",
            .type = QEMU_OPT_STRING,
        },{
            .name = "agent-mouse",
            .type = QEMU_OPT_BOOL,
        },{
            .name = "playback-compression",
            .type = QEMU_OPT_BOOL,
Gerd Hoffmann's avatar
Gerd Hoffmann committed
432
433
434
435
436
        },
        { /* end if list */ }
    },
};

Gleb Natapov's avatar
Gleb Natapov committed
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
QemuOptsList qemu_option_rom_opts = {
    .name = "option-rom",
    .implied_opt_name = "romfile",
    .head = QTAILQ_HEAD_INITIALIZER(qemu_option_rom_opts.head),
    .desc = {
        {
            .name = "bootindex",
            .type = QEMU_OPT_NUMBER,
        }, {
            .name = "romfile",
            .type = QEMU_OPT_STRING,
        },
        { /* end if list */ }
    },
};

453
454
455
456
457
458
459
460
461
462
463
464
465
static QemuOptsList qemu_machine_opts = {
    .name = "machine",
    .head = QTAILQ_HEAD_INITIALIZER(qemu_machine_opts.head),
    .desc = {
        {
            .name = "accel",
            .type = QEMU_OPT_STRING,
            .help = "accelerator list",
        },
        { /* End of list */ }
    },
};

466
static QemuOptsList *vm_config_groups[32] = {
Gerd Hoffmann's avatar
Gerd Hoffmann committed
467
    &qemu_drive_opts,
468
    &qemu_chardev_opts,
Gerd Hoffmann's avatar
Gerd Hoffmann committed
469
    &qemu_device_opts,
Mark McLoughlin's avatar
Mark McLoughlin committed
470
    &qemu_netdev_opts,
Mark McLoughlin's avatar
Mark McLoughlin committed
471
    &qemu_net_opts,
Jan Kiszka's avatar
Jan Kiszka committed
472
    &qemu_rtc_opts,
473
    &qemu_global_opts,
474
    &qemu_mon_opts,
475
    &qemu_cpudef_opts,
476
477
478
#ifdef CONFIG_SIMPLE_TRACE
    &qemu_trace_opts,
#endif
Gleb Natapov's avatar
Gleb Natapov committed
479
    &qemu_option_rom_opts,
480
    &qemu_machine_opts,
Gerd Hoffmann's avatar
Gerd Hoffmann committed
481
482
483
    NULL,
};

484
static QemuOptsList *find_list(QemuOptsList **lists, const char *group)
Gerd Hoffmann's avatar
Gerd Hoffmann committed
485
486
487
488
489
490
491
492
{
    int i;

    for (i = 0; lists[i] != NULL; i++) {
        if (strcmp(lists[i]->name, group) == 0)
            break;
    }
    if (lists[i] == NULL) {
493
        error_report("there is no option group \"%s\"", group);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
494
495
496
497
    }
    return lists[i];
}

498
499
500
501
502
QemuOptsList *qemu_find_opts(const char *group)
{
    return find_list(vm_config_groups, group);
}

503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
void qemu_add_opts(QemuOptsList *list)
{
    int entries, i;

    entries = ARRAY_SIZE(vm_config_groups);
    entries--; /* keep list NULL terminated */
    for (i = 0; i < entries; i++) {
        if (vm_config_groups[i] == NULL) {
            vm_config_groups[i] = list;
            return;
        }
    }
    fprintf(stderr, "ran out of space in vm_config_groups");
    abort();
}

Gerd Hoffmann's avatar
Gerd Hoffmann committed
519
520
521
int qemu_set_option(const char *str)
{
    char group[64], id[64], arg[64];
Gerd Hoffmann's avatar
Gerd Hoffmann committed
522
    QemuOptsList *list;
Gerd Hoffmann's avatar
Gerd Hoffmann committed
523
    QemuOpts *opts;
Gerd Hoffmann's avatar
Gerd Hoffmann committed
524
    int rc, offset;
Gerd Hoffmann's avatar
Gerd Hoffmann committed
525
526
527

    rc = sscanf(str, "%63[^.].%63[^.].%63[^=]%n", group, id, arg, &offset);
    if (rc < 3 || str[offset] != '=') {
528
        error_report("can't parse: \"%s\"", str);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
529
530
531
        return -1;
    }

532
    list = qemu_find_opts(group);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
533
    if (list == NULL) {
Gerd Hoffmann's avatar
Gerd Hoffmann committed
534
535
536
        return -1;
    }

Gerd Hoffmann's avatar
Gerd Hoffmann committed
537
    opts = qemu_opts_find(list, id);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
538
    if (!opts) {
539
540
        error_report("there is no %s \"%s\" defined",
                     list->name, id);
Gerd Hoffmann's avatar
Gerd Hoffmann committed
541
542
543
        return -1;
    }

Mark McLoughlin's avatar
Mark McLoughlin committed
544
    if (qemu_opt_set(opts, arg, str+offset+1) == -1) {
Gerd Hoffmann's avatar
Gerd Hoffmann committed
545
546
547
548
549
        return -1;
    }
    return 0;
}

550
551
552
553
554
555
556
557
int qemu_global_option(const char *str)
{
    char driver[64], property[64];
    QemuOpts *opts;
    int rc, offset;

    rc = sscanf(str, "%63[^.].%63[^=]%n", driver, property, &offset);
    if (rc < 2 || str[offset] != '=') {
558
        error_report("can't parse: \"%s\"", str);
559
560
561
562
563
564
565
566
567
568
        return -1;
    }

    opts = qemu_opts_create(&qemu_global_opts, NULL, 0);
    qemu_opt_set(opts, "driver", driver);
    qemu_opt_set(opts, "property", property);
    qemu_opt_set(opts, "value", str+offset+1);
    return 0;
}

Gerd Hoffmann's avatar
Gerd Hoffmann committed
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
struct ConfigWriteData {
    QemuOptsList *list;
    FILE *fp;
};

static int config_write_opt(const char *name, const char *value, void *opaque)
{
    struct ConfigWriteData *data = opaque;

    fprintf(data->fp, "  %s = \"%s\"\n", name, value);
    return 0;
}

static int config_write_opts(QemuOpts *opts, void *opaque)
{
    struct ConfigWriteData *data = opaque;
    const char *id = qemu_opts_id(opts);

    if (id) {
        fprintf(data->fp, "[%s \"%s\"]\n", data->list->name, id);
    } else {
        fprintf(data->fp, "[%s]\n", data->list->name);
    }
    qemu_opt_foreach(opts, config_write_opt, data, 0);
    fprintf(data->fp, "\n");
    return 0;
}

void qemu_config_write(FILE *fp)
{
    struct ConfigWriteData data = { .fp = fp };
600
    QemuOptsList **lists = vm_config_groups;
Gerd Hoffmann's avatar
Gerd Hoffmann committed
601
602
603
604
605
606
607
608
    int i;

    fprintf(fp, "# qemu config file\n\n");
    for (i = 0; lists[i] != NULL; i++) {
        data.list = lists[i];
        qemu_opts_foreach(data.list, config_write_opts, &data, 0);
    }
}
609

610
int qemu_config_parse(FILE *fp, QemuOptsList **lists, const char *fname)
611
612
{
    char line[1024], group[64], id[64], arg[64], value[1024];
613
    Location loc;
614
615
    QemuOptsList *list = NULL;
    QemuOpts *opts = NULL;
616
    int res = -1, lno = 0;
617

618
    loc_push_none(&loc);
619
    while (fgets(line, sizeof(line), fp) != NULL) {
620
        loc_set_file(fname, ++lno);
621
622
623
624
625
626
627
628
629
630
        if (line[0] == '\n') {
            /* skip empty lines */
            continue;
        }
        if (line[0] == '#') {
            /* comment */
            continue;
        }
        if (sscanf(line, "[%63s \"%63[^\"]\"]", group, id) == 2) {
            /* group with id */
631
            list = find_list(lists, group);
632
            if (list == NULL)
633
                goto out;
634
635
636
637
638
            opts = qemu_opts_create(list, id, 1);
            continue;
        }
        if (sscanf(line, "[%63[^]]]", group) == 1) {
            /* group without id */
639
            list = find_list(lists, group);
640
            if (list == NULL)
641
                goto out;
642
643
644
645
646
647
            opts = qemu_opts_create(list, NULL, 0);
            continue;
        }
        if (sscanf(line, " %63s = \"%1023[^\"]\"", arg, value) == 2) {
            /* arg = value */
            if (opts == NULL) {
648
649
                error_report("no group defined");
                goto out;
650
651
            }
            if (qemu_opt_set(opts, arg, value) != 0) {
652
                goto out;
653
654
655
            }
            continue;
        }
656
657
        error_report("parse error");
        goto out;
658
    }
659
660
661
662
    if (ferror(fp)) {
        error_report("error reading file");
        goto out;
    }
663
664
665
666
    res = 0;
out:
    loc_pop(&loc);
    return res;
667
}
668
669
670
671

int qemu_read_config_file(const char *filename)
{
    FILE *f = fopen(filename, "r");
672
673
    int ret;

674
675
676
677
    if (f == NULL) {
        return -errno;
    }

678
    ret = qemu_config_parse(f, vm_config_groups, filename);
679
680
    fclose(f);

681
682
683
684
685
    if (ret == 0) {
        return 0;
    } else {
        return -EINVAL;
    }
686
}