1 /* 2 * QEMU XenStore XsNode testing 3 * 4 * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved. 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 "qapi/error.h" 12 #include "qemu/module.h" 13 14 static int nr_xs_nodes; 15 static GList *xs_node_list; 16 17 #define XS_NODE_UNIT_TEST 18 19 /* 20 * We don't need the core Xen definitions. And we *do* want to be able 21 * to run the unit tests even on architectures that Xen doesn't support 22 * (because life's too short to bother doing otherwise, and test coverage 23 * doesn't hurt). 24 */ 25 #define __XEN_PUBLIC_XEN_H__ 26 27 #include "hw/i386/kvm/xenstore_impl.c" 28 29 #define DOMID_QEMU 0 30 #define DOMID_GUEST 1 31 32 /* This doesn't happen in qemu but we want to make valgrind happy */ 33 static void xs_impl_delete(XenstoreImplState *s) 34 { 35 int err; 36 37 xs_impl_reset_watches(s, DOMID_GUEST); 38 g_assert(!s->nr_domu_watches); 39 40 err = xs_impl_rm(s, DOMID_QEMU, XBT_NULL, "/local"); 41 g_assert(!err); 42 g_assert(s->nr_nodes == 1); 43 44 g_hash_table_unref(s->watches); 45 g_hash_table_unref(s->transactions); 46 xs_node_unref(s->root); 47 g_free(s); 48 49 if (xs_node_list) { 50 GList *l; 51 for (l = xs_node_list; l; l = l->next) { 52 XsNode *n = l->data; 53 printf("Remaining node at %p name %s ref %u\n", n, n->name, 54 n->ref); 55 } 56 } 57 g_assert(!nr_xs_nodes); 58 } 59 60 static int write_str(XenstoreImplState *s, unsigned int dom_id, 61 unsigned int tx_id, const char *path, 62 const char *content) 63 { 64 GByteArray *d = g_byte_array_new(); 65 int err; 66 67 g_byte_array_append(d, (void *)content, strlen(content)); 68 err = xs_impl_write(s, dom_id, tx_id, path, d); 69 g_byte_array_unref(d); 70 return err; 71 } 72 73 static void watch_cb(void *_str, const char *path, const char *token) 74 { 75 GString *str = _str; 76 77 g_string_append(str, path); 78 g_string_append(str, token); 79 } 80 81 static XenstoreImplState *setup(void) 82 { 83 XenstoreImplState *s = xs_impl_create(); 84 char *abspath; 85 int err; 86 87 abspath = g_strdup_printf("/local/domain/%u", DOMID_GUEST); 88 89 err = write_str(s, DOMID_QEMU, XBT_NULL, abspath, ""); 90 g_assert(!err); 91 g_assert(s->nr_nodes == 4); 92 93 g_free(abspath); 94 95 abspath = g_strdup_printf("/local/domain/%u/some", DOMID_GUEST); 96 97 err = write_str(s, DOMID_QEMU, XBT_NULL, abspath, ""); 98 g_assert(!err); 99 g_assert(s->nr_nodes == 5); 100 101 g_free(abspath); 102 103 return s; 104 } 105 106 static void test_xs_node_simple(void) 107 { 108 GByteArray *data = g_byte_array_new(); 109 XenstoreImplState *s = setup(); 110 GString *guest_watches = g_string_new(NULL); 111 GString *qemu_watches = g_string_new(NULL); 112 GList *items = NULL; 113 XsNode *old_root; 114 uint64_t gencnt; 115 int err; 116 117 g_assert(s); 118 119 err = xs_impl_watch(s, DOMID_GUEST, "some", "guestwatch", 120 watch_cb, guest_watches); 121 g_assert(!err); 122 g_assert(guest_watches->len == strlen("someguestwatch")); 123 g_assert(!strcmp(guest_watches->str, "someguestwatch")); 124 g_string_truncate(guest_watches, 0); 125 126 err = xs_impl_watch(s, 0, "/local/domain/1/some", "qemuwatch", 127 watch_cb, qemu_watches); 128 g_assert(!err); 129 g_assert(qemu_watches->len == strlen("/local/domain/1/someqemuwatch")); 130 g_assert(!strcmp(qemu_watches->str, "/local/domain/1/someqemuwatch")); 131 g_string_truncate(qemu_watches, 0); 132 133 /* Read gives ENOENT when it should */ 134 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "foo", data); 135 g_assert(err == ENOENT); 136 137 /* Write works */ 138 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path", 139 "something"); 140 g_assert(s->nr_nodes == 7); 141 g_assert(!err); 142 g_assert(!strcmp(guest_watches->str, 143 "some/relative/pathguestwatch")); 144 g_assert(!strcmp(qemu_watches->str, 145 "/local/domain/1/some/relative/pathqemuwatch")); 146 147 g_string_truncate(qemu_watches, 0); 148 g_string_truncate(guest_watches, 0); 149 xs_impl_reset_watches(s, 0); 150 151 /* Read gives back what we wrote */ 152 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative/path", data); 153 g_assert(!err); 154 g_assert(data->len == strlen("something")); 155 g_assert(!memcmp(data->data, "something", data->len)); 156 157 /* Even if we use an abolute path */ 158 g_byte_array_set_size(data, 0); 159 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, 160 "/local/domain/1/some/relative/path", data); 161 g_assert(!err); 162 g_assert(data->len == strlen("something")); 163 164 g_assert(!qemu_watches->len); 165 g_assert(!guest_watches->len); 166 /* Keep a copy, to force COW mode */ 167 old_root = xs_node_ref(s->root); 168 169 /* Write works again */ 170 err = write_str(s, DOMID_GUEST, XBT_NULL, 171 "/local/domain/1/some/relative/path2", 172 "something else"); 173 g_assert(!err); 174 g_assert(s->nr_nodes == 8); 175 g_assert(!qemu_watches->len); 176 g_assert(!strcmp(guest_watches->str, "some/relative/path2guestwatch")); 177 g_string_truncate(guest_watches, 0); 178 179 /* Overwrite an existing node */ 180 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path", 181 "another thing"); 182 g_assert(!err); 183 g_assert(s->nr_nodes == 8); 184 g_assert(!qemu_watches->len); 185 g_assert(!strcmp(guest_watches->str, "some/relative/pathguestwatch")); 186 g_string_truncate(guest_watches, 0); 187 188 /* We can list the two files we wrote */ 189 err = xs_impl_directory(s, DOMID_GUEST, XBT_NULL, "some/relative", &gencnt, 190 &items); 191 g_assert(!err); 192 g_assert(items); 193 g_assert(gencnt == 2); 194 g_assert(!strcmp(items->data, "path")); 195 g_assert(items->next); 196 g_assert(!strcmp(items->next->data, "path2")); 197 g_assert(!items->next->next); 198 g_list_free_full(items, g_free); 199 200 err = xs_impl_unwatch(s, DOMID_GUEST, "some", "guestwatch", 201 watch_cb, guest_watches); 202 g_assert(!err); 203 204 err = xs_impl_unwatch(s, DOMID_GUEST, "some", "guestwatch", 205 watch_cb, guest_watches); 206 g_assert(err == ENOENT); 207 208 err = xs_impl_watch(s, DOMID_GUEST, "some/relative/path2", "watchp2", 209 watch_cb, guest_watches); 210 g_assert(!err); 211 g_assert(guest_watches->len == strlen("some/relative/path2watchp2")); 212 g_assert(!strcmp(guest_watches->str, "some/relative/path2watchp2")); 213 g_string_truncate(guest_watches, 0); 214 215 err = xs_impl_watch(s, DOMID_GUEST, "/local/domain/1/some/relative", 216 "watchrel", watch_cb, guest_watches); 217 g_assert(!err); 218 g_assert(guest_watches->len == 219 strlen("/local/domain/1/some/relativewatchrel")); 220 g_assert(!strcmp(guest_watches->str, 221 "/local/domain/1/some/relativewatchrel")); 222 g_string_truncate(guest_watches, 0); 223 224 /* Write somewhere else which already existed */ 225 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative", "moredata"); 226 g_assert(!err); 227 g_assert(s->nr_nodes == 8); 228 229 g_assert(!strcmp(guest_watches->str, 230 "/local/domain/1/some/relativewatchrel")); 231 g_string_truncate(guest_watches, 0); 232 233 g_byte_array_set_size(data, 0); 234 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data); 235 g_assert(!err); 236 g_assert(data->len == strlen("moredata")); 237 g_assert(!memcmp(data->data, "moredata", data->len)); 238 239 /* Overwrite existing data */ 240 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative", "otherdata"); 241 g_assert(!err); 242 g_string_truncate(guest_watches, 0); 243 244 g_byte_array_set_size(data, 0); 245 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data); 246 g_assert(!err); 247 g_assert(data->len == strlen("otherdata")); 248 g_assert(!memcmp(data->data, "otherdata", data->len)); 249 250 /* Remove the subtree */ 251 err = xs_impl_rm(s, DOMID_GUEST, XBT_NULL, "some/relative"); 252 g_assert(!err); 253 g_assert(s->nr_nodes == 5); 254 255 /* Each watch fires with the least specific relevant path */ 256 g_assert(strstr(guest_watches->str, 257 "some/relative/path2watchp2")); 258 g_assert(strstr(guest_watches->str, 259 "/local/domain/1/some/relativewatchrel")); 260 g_string_truncate(guest_watches, 0); 261 262 g_byte_array_set_size(data, 0); 263 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data); 264 g_assert(err == ENOENT); 265 g_byte_array_unref(data); 266 267 xs_impl_reset_watches(s, DOMID_GUEST); 268 g_string_free(qemu_watches, true); 269 g_string_free(guest_watches, true); 270 xs_node_unref(old_root); 271 xs_impl_delete(s); 272 } 273 274 275 static void do_test_xs_node_tx(bool fail, bool commit) 276 { 277 XenstoreImplState *s = setup(); 278 GString *watches = g_string_new(NULL); 279 GByteArray *data = g_byte_array_new(); 280 unsigned int tx_id = XBT_NULL; 281 int err; 282 283 g_assert(s); 284 285 /* Set a watch */ 286 err = xs_impl_watch(s, DOMID_GUEST, "some", "watch", 287 watch_cb, watches); 288 g_assert(!err); 289 g_assert(watches->len == strlen("somewatch")); 290 g_assert(!strcmp(watches->str, "somewatch")); 291 g_string_truncate(watches, 0); 292 293 /* Write something */ 294 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path", 295 "something"); 296 g_assert(s->nr_nodes == 7); 297 g_assert(!err); 298 g_assert(!strcmp(watches->str, 299 "some/relative/pathwatch")); 300 g_string_truncate(watches, 0); 301 302 /* Create a transaction */ 303 err = xs_impl_transaction_start(s, DOMID_GUEST, &tx_id); 304 g_assert(!err); 305 306 if (fail) { 307 /* Write something else in the root */ 308 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path", 309 "another thing"); 310 g_assert(!err); 311 g_assert(s->nr_nodes == 7); 312 g_assert(!strcmp(watches->str, 313 "some/relative/pathwatch")); 314 g_string_truncate(watches, 0); 315 } 316 317 g_assert(!watches->len); 318 319 /* Perform a write in the transaction */ 320 err = write_str(s, DOMID_GUEST, tx_id, "some/relative/path", 321 "something else"); 322 g_assert(!err); 323 g_assert(s->nr_nodes == 7); 324 g_assert(!watches->len); 325 326 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative/path", data); 327 g_assert(!err); 328 if (fail) { 329 g_assert(data->len == strlen("another thing")); 330 g_assert(!memcmp(data->data, "another thing", data->len)); 331 } else { 332 g_assert(data->len == strlen("something")); 333 g_assert(!memcmp(data->data, "something", data->len)); 334 } 335 g_byte_array_set_size(data, 0); 336 337 err = xs_impl_read(s, DOMID_GUEST, tx_id, "some/relative/path", data); 338 g_assert(!err); 339 g_assert(data->len == strlen("something else")); 340 g_assert(!memcmp(data->data, "something else", data->len)); 341 g_byte_array_set_size(data, 0); 342 343 /* Attempt to commit the transaction */ 344 err = xs_impl_transaction_end(s, DOMID_GUEST, tx_id, commit); 345 if (commit && fail) { 346 g_assert(err == EAGAIN); 347 } else { 348 g_assert(!err); 349 } 350 if (commit && !fail) { 351 g_assert(!strcmp(watches->str, 352 "some/relative/pathwatch")); 353 g_string_truncate(watches, 0); 354 } else { 355 g_assert(!watches->len); 356 } 357 g_assert(s->nr_nodes == 7); 358 359 err = xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", 360 watch_cb, watches); 361 g_assert(!err); 362 363 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative/path", data); 364 g_assert(!err); 365 if (fail) { 366 g_assert(data->len == strlen("another thing")); 367 g_assert(!memcmp(data->data, "another thing", data->len)); 368 } else if (commit) { 369 g_assert(data->len == strlen("something else")); 370 g_assert(!memcmp(data->data, "something else", data->len)); 371 } else { 372 g_assert(data->len == strlen("something")); 373 g_assert(!memcmp(data->data, "something", data->len)); 374 } 375 g_byte_array_unref(data); 376 g_string_free(watches, true); 377 xs_impl_delete(s); 378 } 379 380 static void test_xs_node_tx_fail(void) 381 { 382 do_test_xs_node_tx(true, true); 383 } 384 385 static void test_xs_node_tx_abort(void) 386 { 387 do_test_xs_node_tx(false, false); 388 do_test_xs_node_tx(true, false); 389 } 390 static void test_xs_node_tx_succeed(void) 391 { 392 do_test_xs_node_tx(false, true); 393 } 394 395 static void test_xs_node_tx_rm(void) 396 { 397 XenstoreImplState *s = setup(); 398 GString *watches = g_string_new(NULL); 399 GByteArray *data = g_byte_array_new(); 400 unsigned int tx_id = XBT_NULL; 401 int err; 402 403 g_assert(s); 404 405 /* Set a watch */ 406 err = xs_impl_watch(s, DOMID_GUEST, "some", "watch", 407 watch_cb, watches); 408 g_assert(!err); 409 g_assert(watches->len == strlen("somewatch")); 410 g_assert(!strcmp(watches->str, "somewatch")); 411 g_string_truncate(watches, 0); 412 413 /* Write something */ 414 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path", 415 "something"); 416 g_assert(!err); 417 g_assert(s->nr_nodes == 9); 418 g_assert(!strcmp(watches->str, 419 "some/deep/dark/relative/pathwatch")); 420 g_string_truncate(watches, 0); 421 422 /* Create a transaction */ 423 err = xs_impl_transaction_start(s, DOMID_GUEST, &tx_id); 424 g_assert(!err); 425 426 /* Delete the tree in the transaction */ 427 err = xs_impl_rm(s, DOMID_GUEST, tx_id, "some/deep/dark"); 428 g_assert(!err); 429 g_assert(s->nr_nodes == 9); 430 g_assert(!watches->len); 431 432 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path", 433 data); 434 g_assert(!err); 435 g_assert(data->len == strlen("something")); 436 g_assert(!memcmp(data->data, "something", data->len)); 437 g_byte_array_set_size(data, 0); 438 439 /* Commit the transaction */ 440 err = xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true); 441 g_assert(!err); 442 g_assert(s->nr_nodes == 6); 443 444 g_assert(!strcmp(watches->str, "some/deep/darkwatch")); 445 g_string_truncate(watches, 0); 446 447 /* Now the node is gone */ 448 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path", 449 data); 450 g_assert(err == ENOENT); 451 g_byte_array_unref(data); 452 453 err = xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", 454 watch_cb, watches); 455 g_assert(!err); 456 457 g_string_free(watches, true); 458 xs_impl_delete(s); 459 } 460 461 static void test_xs_node_tx_resurrect(void) 462 { 463 XenstoreImplState *s = setup(); 464 GString *watches = g_string_new(NULL); 465 GByteArray *data = g_byte_array_new(); 466 unsigned int tx_id = XBT_NULL; 467 int err; 468 469 g_assert(s); 470 471 /* Write something */ 472 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path", 473 "something"); 474 g_assert(!err); 475 g_assert(s->nr_nodes == 9); 476 477 /* This node will be wiped and resurrected */ 478 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark", 479 "foo"); 480 g_assert(!err); 481 g_assert(s->nr_nodes == 9); 482 483 /* Set a watch */ 484 err = xs_impl_watch(s, DOMID_GUEST, "some", "watch", 485 watch_cb, watches); 486 g_assert(!err); 487 g_assert(watches->len == strlen("somewatch")); 488 g_assert(!strcmp(watches->str, "somewatch")); 489 g_string_truncate(watches, 0); 490 491 /* Create a transaction */ 492 err = xs_impl_transaction_start(s, DOMID_GUEST, &tx_id); 493 g_assert(!err); 494 495 /* Delete the tree in the transaction */ 496 err = xs_impl_rm(s, DOMID_GUEST, tx_id, "some/deep"); 497 g_assert(!err); 498 g_assert(s->nr_nodes == 9); 499 g_assert(!watches->len); 500 501 /* Resurrect part of it */ 502 err = write_str(s, DOMID_GUEST, tx_id, "some/deep/dark/different/path", 503 "something"); 504 g_assert(!err); 505 g_assert(s->nr_nodes == 9); 506 507 /* Commit the transaction */ 508 err = xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true); 509 g_assert(!err); 510 g_assert(s->nr_nodes == 9); 511 512 /* lost data */ 513 g_assert(strstr(watches->str, "some/deep/dark/different/pathwatch")); 514 /* topmost deleted */ 515 g_assert(strstr(watches->str, "some/deep/dark/relativewatch")); 516 /* lost data */ 517 g_assert(strstr(watches->str, "some/deep/darkwatch")); 518 519 g_string_truncate(watches, 0); 520 521 /* Now the node is gone */ 522 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path", 523 data); 524 g_assert(err == ENOENT); 525 g_byte_array_unref(data); 526 527 err = xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", 528 watch_cb, watches); 529 g_assert(!err); 530 531 g_string_free(watches, true); 532 xs_impl_delete(s); 533 } 534 535 static void test_xs_node_tx_resurrect2(void) 536 { 537 XenstoreImplState *s = setup(); 538 GString *watches = g_string_new(NULL); 539 GByteArray *data = g_byte_array_new(); 540 unsigned int tx_id = XBT_NULL; 541 int err; 542 543 g_assert(s); 544 545 /* Write something */ 546 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path", 547 "something"); 548 g_assert(!err); 549 g_assert(s->nr_nodes == 9); 550 551 /* Another node to remain shared */ 552 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/place/safe", "keepme"); 553 g_assert(!err); 554 g_assert(s->nr_nodes == 11); 555 556 /* This node will be wiped and resurrected */ 557 err = write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark", 558 "foo"); 559 g_assert(!err); 560 g_assert(s->nr_nodes == 11); 561 562 /* Set a watch */ 563 err = xs_impl_watch(s, DOMID_GUEST, "some", "watch", 564 watch_cb, watches); 565 g_assert(!err); 566 g_assert(watches->len == strlen("somewatch")); 567 g_assert(!strcmp(watches->str, "somewatch")); 568 g_string_truncate(watches, 0); 569 570 /* Create a transaction */ 571 err = xs_impl_transaction_start(s, DOMID_GUEST, &tx_id); 572 g_assert(!err); 573 574 /* Delete the tree in the transaction */ 575 err = xs_impl_rm(s, DOMID_GUEST, tx_id, "some/deep"); 576 g_assert(!err); 577 g_assert(s->nr_nodes == 11); 578 g_assert(!watches->len); 579 580 /* Resurrect part of it */ 581 err = write_str(s, DOMID_GUEST, tx_id, "some/deep/dark/relative/path", 582 "something"); 583 g_assert(!err); 584 g_assert(s->nr_nodes == 11); 585 586 /* Commit the transaction */ 587 err = xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true); 588 g_assert(!err); 589 g_assert(s->nr_nodes == 11); 590 591 /* lost data */ 592 g_assert(strstr(watches->str, "some/deep/dark/relative/pathwatch")); 593 /* lost data */ 594 g_assert(strstr(watches->str, "some/deep/darkwatch")); 595 596 g_string_truncate(watches, 0); 597 598 /* Now the node is gone */ 599 err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path", 600 data); 601 g_assert(!err); 602 g_assert(data->len == strlen("something")); 603 g_assert(!memcmp(data->data, "something", data->len)); 604 605 g_byte_array_unref(data); 606 607 err = xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", 608 watch_cb, watches); 609 g_assert(!err); 610 611 g_string_free(watches, true); 612 xs_impl_delete(s); 613 } 614 615 int main(int argc, char **argv) 616 { 617 g_test_init(&argc, &argv, NULL); 618 module_call_init(MODULE_INIT_QOM); 619 620 g_test_add_func("/xs_node/simple", test_xs_node_simple); 621 g_test_add_func("/xs_node/tx_abort", test_xs_node_tx_abort); 622 g_test_add_func("/xs_node/tx_fail", test_xs_node_tx_fail); 623 g_test_add_func("/xs_node/tx_succeed", test_xs_node_tx_succeed); 624 g_test_add_func("/xs_node/tx_rm", test_xs_node_tx_rm); 625 g_test_add_func("/xs_node/tx_resurrect", test_xs_node_tx_resurrect); 626 g_test_add_func("/xs_node/tx_resurrect2", test_xs_node_tx_resurrect2); 627 628 return g_test_run(); 629 } 630