1 /*
2 * Validate -readconfig
3 *
4 * Copyright (c) 2022 Red Hat, Inc.
5 *
6 * This work is licensed under the terms of the GNU GPL, version 2 or later.
7 * See the COPYING file in the top-level directory.
8 */
9
10 #include "qemu/osdep.h"
11 #include "libqtest.h"
12 #include "qapi/error.h"
13 #include "qapi/qapi-visit-machine.h"
14 #include "qapi/qapi-visit-qom.h"
15 #include "qapi/qapi-visit-ui.h"
16 #include "qapi/qmp/qdict.h"
17 #include "qapi/qmp/qlist.h"
18 #include "qapi/qobject-input-visitor.h"
19 #include "qapi/qmp/qstring.h"
20 #include "qemu/units.h"
21
qtest_init_with_config(const char * cfgdata)22 static QTestState *qtest_init_with_config(const char *cfgdata)
23 {
24 GError *error = NULL;
25 g_autofree char *args = NULL;
26 int cfgfd = -1;
27 g_autofree char *cfgpath = NULL;
28 QTestState *qts;
29 ssize_t ret;
30
31 cfgfd = g_file_open_tmp("readconfig-test-XXXXXX", &cfgpath, &error);
32 g_assert_no_error(error);
33 g_assert_cmpint(cfgfd, >=, 0);
34
35 ret = qemu_write_full(cfgfd, cfgdata, strlen(cfgdata));
36 close(cfgfd);
37 if (ret < 0) {
38 unlink(cfgpath);
39 }
40 g_assert_cmpint(ret, ==, strlen(cfgdata));
41
42 args = g_strdup_printf("-nodefaults -machine none -readconfig %s", cfgpath);
43
44 qts = qtest_init(args);
45
46 unlink(cfgpath);
47
48 return qts;
49 }
50
test_x86_memdev_resp(QObject * res,const char * mem_id,int size)51 static void test_x86_memdev_resp(QObject *res, const char *mem_id, int size)
52 {
53 Visitor *v;
54 g_autoptr(MemdevList) memdevs = NULL;
55 Memdev *memdev;
56
57 g_assert(res);
58 v = qobject_input_visitor_new(res);
59 visit_type_MemdevList(v, NULL, &memdevs, &error_abort);
60
61 g_assert(memdevs);
62 g_assert(memdevs->value);
63 g_assert(!memdevs->next);
64
65 memdev = memdevs->value;
66 g_assert_cmpstr(memdev->id, ==, mem_id);
67 g_assert_cmpint(memdev->size, ==, size * MiB);
68
69 visit_free(v);
70 }
71
test_x86_memdev(void)72 static void test_x86_memdev(void)
73 {
74 QDict *resp;
75 QTestState *qts;
76 const char *cfgdata =
77 "[memory]\n"
78 "size = \"200\"";
79
80 qts = qtest_init_with_config(cfgdata);
81 /* Test valid command */
82 resp = qtest_qmp(qts, "{ 'execute': 'query-memdev' }");
83 test_x86_memdev_resp(qdict_get(resp, "return"), "ram", 200);
84 qobject_unref(resp);
85
86 qtest_quit(qts);
87 }
88
89 /* FIXME: The test is currently broken on FreeBSD */
90 #if defined(CONFIG_SPICE) && !defined(__FreeBSD__)
test_spice_resp(QObject * res)91 static void test_spice_resp(QObject *res)
92 {
93 Visitor *v;
94 g_autoptr(SpiceInfo) spice = NULL;
95
96 g_assert(res);
97 v = qobject_input_visitor_new(res);
98 visit_type_SpiceInfo(v, "spice", &spice, &error_abort);
99
100 g_assert(spice);
101 g_assert(spice->enabled);
102
103 visit_free(v);
104 }
105
test_spice(void)106 static void test_spice(void)
107 {
108 QDict *resp;
109 QTestState *qts;
110 const char *cfgdata =
111 "[spice]\n"
112 #ifndef WIN32
113 "unix = \"on\"\n"
114 #endif
115 "disable-ticketing = \"on\"\n";
116
117 qts = qtest_init_with_config(cfgdata);
118 /* Test valid command */
119 resp = qtest_qmp(qts, "{ 'execute': 'query-spice' }");
120 test_spice_resp(qdict_get(resp, "return"));
121 qobject_unref(resp);
122
123 qtest_quit(qts);
124 }
125 #endif
126
test_object_available(QObject * res,const char * name,const char * type)127 static void test_object_available(QObject *res, const char *name,
128 const char *type)
129 {
130 Visitor *v;
131 g_autoptr(ObjectPropertyInfoList) objs = NULL;
132 ObjectPropertyInfoList *tmp;
133 ObjectPropertyInfo *obj;
134 bool object_available = false;
135 g_autofree char *childtype = g_strdup_printf("child<%s>", type);
136
137 g_assert(res);
138 v = qobject_input_visitor_new(res);
139 visit_type_ObjectPropertyInfoList(v, NULL, &objs, &error_abort);
140
141 g_assert(objs);
142 tmp = objs;
143 while (tmp) {
144 g_assert(tmp->value);
145
146 obj = tmp->value;
147 if (g_str_equal(obj->name, name) && g_str_equal(obj->type, childtype)) {
148 object_available = true;
149 break;
150 }
151
152 tmp = tmp->next;
153 }
154
155 g_assert(object_available);
156
157 visit_free(v);
158 }
159
test_object_rng(void)160 static void test_object_rng(void)
161 {
162 QDict *resp;
163 QTestState *qts;
164 const char *cfgdata =
165 "[object]\n"
166 "qom-type = \"rng-builtin\"\n"
167 "id = \"rng0\"\n";
168
169 qts = qtest_init_with_config(cfgdata);
170 /* Test valid command */
171 resp = qtest_qmp(qts,
172 "{ 'execute': 'qom-list',"
173 " 'arguments': {'path': '/objects' }}");
174 test_object_available(qdict_get(resp, "return"), "rng0", "rng-builtin");
175 qobject_unref(resp);
176
177 qtest_quit(qts);
178 }
179
test_docs_config_ich9(void)180 static void test_docs_config_ich9(void)
181 {
182 QTestState *qts;
183 QDict *resp;
184 QObject *qobj;
185
186 qts = qtest_initf("-nodefaults -readconfig docs/config/ich9-ehci-uhci.cfg");
187
188 resp = qtest_qmp(qts, "{ 'execute': 'qom-list',"
189 " 'arguments': {'path': '/machine/peripheral' }}");
190 qobj = qdict_get(resp, "return");
191 test_object_available(qobj, "ehci", "ich9-usb-ehci1");
192 test_object_available(qobj, "uhci-1", "ich9-usb-uhci1");
193 test_object_available(qobj, "uhci-2", "ich9-usb-uhci2");
194 test_object_available(qobj, "uhci-3", "ich9-usb-uhci3");
195 qobject_unref(resp);
196
197 qtest_quit(qts);
198 }
199
200 #if defined(CONFIG_POSIX) && defined(CONFIG_SLIRP)
201
make_temp_img(const char * template,const char * format,int size)202 static char *make_temp_img(const char *template, const char *format, int size)
203 {
204 GError *error = NULL;
205 char *temp_name;
206 int fd;
207
208 /* Create a temporary image names */
209 fd = g_file_open_tmp(template, &temp_name, &error);
210 if (fd == -1) {
211 fprintf(stderr, "unable to create file: %s\n", error->message);
212 g_error_free(error);
213 return NULL;
214 }
215 close(fd);
216
217 if (!mkimg(temp_name, format, size)) {
218 fprintf(stderr, "qemu-img failed to create %s\n", temp_name);
219 g_free(temp_name);
220 return NULL;
221 }
222
223 return temp_name;
224 }
225
226 struct device {
227 const char *name;
228 const char *type;
229 };
230
test_docs_q35(const char * input_file,struct device * devices)231 static void test_docs_q35(const char *input_file, struct device *devices)
232 {
233 QTestState *qts;
234 QDict *resp;
235 QObject *qobj;
236 int ret, i;
237 g_autofree char *cfg_file = NULL, *sedcmd = NULL;
238 g_autofree char *hd_file = NULL, *cd_file = NULL;
239
240 /* Check that all the devices are available in the QEMU binary */
241 for (i = 0; devices[i].name; i++) {
242 if (!qtest_has_device(devices[i].type)) {
243 g_test_skip("one of the required devices is not available");
244 return;
245 }
246 }
247
248 hd_file = make_temp_img("qtest_disk_XXXXXX.qcow2", "qcow2", 1);
249 cd_file = make_temp_img("qtest_cdrom_XXXXXX.iso", "raw", 1);
250 if (!hd_file || !cd_file) {
251 g_test_skip("could not create disk images");
252 goto cleanup;
253 }
254
255 /* Create a temporary config file where we replace the disk image names */
256 ret = g_file_open_tmp("q35-emulated-XXXXXX.cfg", &cfg_file, NULL);
257 if (ret == -1) {
258 g_test_skip("could not create temporary config file");
259 goto cleanup;
260 }
261 close(ret);
262
263 sedcmd = g_strdup_printf("sed -e 's,guest.qcow2,%s,' -e 's,install.iso,%s,'"
264 " %s %s > '%s'",
265 hd_file, cd_file,
266 !qtest_has_accel("kvm") ? "-e '/accel/d'" : "",
267 input_file, cfg_file);
268 ret = system(sedcmd);
269 if (ret) {
270 g_test_skip("could not modify temporary config file");
271 goto cleanup;
272 }
273
274 qts = qtest_initf("-machine none -nodefaults -readconfig %s", cfg_file);
275
276 /* Check memory size */
277 resp = qtest_qmp(qts, "{ 'execute': 'query-memdev' }");
278 test_x86_memdev_resp(qdict_get(resp, "return"), "pc.ram", 1024);
279 qobject_unref(resp);
280
281 resp = qtest_qmp(qts, "{ 'execute': 'qom-list',"
282 " 'arguments': {'path': '/machine/peripheral' }}");
283 qobj = qdict_get(resp, "return");
284
285 /* Check that all the devices have been created */
286 for (i = 0; devices[i].name; i++) {
287 test_object_available(qobj, devices[i].name, devices[i].type);
288 }
289
290 qobject_unref(resp);
291
292 qtest_quit(qts);
293
294 cleanup:
295 if (hd_file) {
296 unlink(hd_file);
297 }
298 if (cd_file) {
299 unlink(cd_file);
300 }
301 if (cfg_file) {
302 unlink(cfg_file);
303 }
304 }
305
test_docs_q35_emulated(void)306 static void test_docs_q35_emulated(void)
307 {
308 struct device devices[] = {
309 { "ich9-pcie-port-1", "ioh3420" },
310 { "ich9-pcie-port-2", "ioh3420" },
311 { "ich9-pcie-port-3", "ioh3420" },
312 { "ich9-pcie-port-4", "ioh3420" },
313 { "ich9-pci-bridge", "i82801b11-bridge" },
314 { "ich9-ehci-1", "ich9-usb-ehci1" },
315 { "ich9-ehci-2", "ich9-usb-ehci2" },
316 { "ich9-uhci-1", "ich9-usb-uhci1" },
317 { "ich9-uhci-2", "ich9-usb-uhci2" },
318 { "ich9-uhci-3", "ich9-usb-uhci3" },
319 { "ich9-uhci-4", "ich9-usb-uhci4" },
320 { "ich9-uhci-5", "ich9-usb-uhci5" },
321 { "ich9-uhci-6", "ich9-usb-uhci6" },
322 { "sata-disk", "ide-hd" },
323 { "sata-optical-disk", "ide-cd" },
324 { "net", "e1000" },
325 { "video", "VGA" },
326 { "ich9-hda-audio", "ich9-intel-hda" },
327 { "ich9-hda-duplex", "hda-duplex" },
328 { NULL, NULL }
329 };
330
331 test_docs_q35("docs/config/q35-emulated.cfg", devices);
332 }
333
test_docs_q35_virtio_graphical(void)334 static void test_docs_q35_virtio_graphical(void)
335 {
336 struct device devices[] = {
337 { "pcie.1", "pcie-root-port" },
338 { "pcie.2", "pcie-root-port" },
339 { "pcie.3", "pcie-root-port" },
340 { "pcie.4", "pcie-root-port" },
341 { "pcie.5", "pcie-root-port" },
342 { "pcie.6", "pcie-root-port" },
343 { "pcie.7", "pcie-root-port" },
344 { "pcie.8", "pcie-root-port" },
345 { "scsi", "virtio-scsi-pci" },
346 { "scsi-disk", "scsi-hd" },
347 { "scsi-optical-disk", "scsi-cd" },
348 { "net", "virtio-net-pci" },
349 { "usb", "nec-usb-xhci" },
350 { "tablet", "usb-tablet" },
351 { "video", "qxl-vga" },
352 { "sound", "ich9-intel-hda" },
353 { "duplex", "hda-duplex" },
354 { NULL, NULL }
355 };
356
357 test_docs_q35("docs/config/q35-virtio-graphical.cfg", devices);
358 }
359
test_docs_q35_virtio_serial(void)360 static void test_docs_q35_virtio_serial(void)
361 {
362 struct device devices[] = {
363 { "pcie.1", "pcie-root-port" },
364 { "pcie.2", "pcie-root-port" },
365 { "pcie.3", "pcie-root-port" },
366 { "pcie.4", "pcie-root-port" },
367 { "pcie.5", "pcie-root-port" },
368 { "pcie.6", "pcie-root-port" },
369 { "pcie.7", "pcie-root-port" },
370 { "pcie.8", "pcie-root-port" },
371 { "scsi", "virtio-scsi-pci" },
372 { "scsi-disk", "scsi-hd" },
373 { "scsi-optical-disk", "scsi-cd" },
374 { "net", "virtio-net-pci" },
375 { NULL, NULL }
376 };
377
378 test_docs_q35("docs/config/q35-virtio-serial.cfg", devices);
379 }
380
381 #endif /* CONFIG_LINUX */
382
main(int argc,char * argv[])383 int main(int argc, char *argv[])
384 {
385 const char *arch;
386 g_test_init(&argc, &argv, NULL);
387
388 arch = qtest_get_arch();
389
390 if (g_str_equal(arch, "i386") ||
391 g_str_equal(arch, "x86_64")) {
392 qtest_add_func("readconfig/x86/memdev", test_x86_memdev);
393 if (qtest_has_device("ich9-usb-ehci1") &&
394 qtest_has_device("ich9-usb-uhci1")) {
395 qtest_add_func("readconfig/x86/ich9-ehci-uhci", test_docs_config_ich9);
396 }
397 #if defined(CONFIG_POSIX) && defined(CONFIG_SLIRP)
398 qtest_add_func("readconfig/x86/q35-emulated", test_docs_q35_emulated);
399 qtest_add_func("readconfig/x86/q35-virtio-graphical",
400 test_docs_q35_virtio_graphical);
401 if (g_test_slow()) {
402 /*
403 * q35-virtio-serial.cfg is a subset of q35-virtio-graphical.cfg,
404 * so we can skip the test in quick mode
405 */
406 qtest_add_func("readconfig/x86/q35-virtio-serial",
407 test_docs_q35_virtio_serial);
408 }
409 #endif
410 }
411 #if defined(CONFIG_SPICE) && !defined(__FreeBSD__)
412 qtest_add_func("readconfig/spice", test_spice);
413 #endif
414
415 qtest_add_func("readconfig/object-rng", test_object_rng);
416
417 return g_test_run();
418 }
419