qemu/tests/unit/test-xs-node.c
<<
>>
Prefs
   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
  14static int nr_xs_nodes;
  15static 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__
  26typedef unsigned long xen_pfn_t;
  27
  28#include "hw/i386/kvm/xenstore_impl.c"
  29
  30#define DOMID_QEMU 0
  31#define DOMID_GUEST 1
  32
  33static void dump_ref(const char *name, XsNode *n, int indent)
  34{
  35    int i;
  36
  37    if (!indent && name) {
  38        printf("%s:\n", name);
  39    }
  40
  41    for (i = 0; i < indent; i++) {
  42        printf(" ");
  43    }
  44
  45    printf("->%p(%d, '%s'): '%.*s'%s%s\n", n, n->ref, n->name,
  46           (int)(n->content ? n->content->len : strlen("<empty>")),
  47           n->content ? (char *)n->content->data : "<empty>",
  48           n->modified_in_tx ? " MODIFIED" : "",
  49           n->deleted_in_tx ? " DELETED" : "");
  50
  51    if (n->children) {
  52        g_hash_table_foreach(n->children, (void *)dump_ref,
  53                             GINT_TO_POINTER(indent + 2));
  54    }
  55}
  56
  57/* This doesn't happen in qemu but we want to make valgrind happy */
  58static void xs_impl_delete(XenstoreImplState *s, bool last)
  59{
  60    int err;
  61
  62    xs_impl_reset_watches(s, DOMID_GUEST);
  63    g_assert(!s->nr_domu_watches);
  64
  65    err = xs_impl_rm(s, DOMID_QEMU, XBT_NULL, "/local");
  66    g_assert(!err);
  67    g_assert(s->nr_nodes == 1);
  68
  69    g_hash_table_unref(s->watches);
  70    g_hash_table_unref(s->transactions);
  71    xs_node_unref(s->root);
  72    g_free(s);
  73
  74    if (!last) {
  75        return;
  76    }
  77
  78    if (xs_node_list) {
  79        GList *l;
  80        for (l = xs_node_list; l; l = l->next) {
  81            XsNode *n = l->data;
  82            printf("Remaining node at %p name %s ref %u\n", n, n->name,
  83                   n->ref);
  84        }
  85    }
  86    g_assert(!nr_xs_nodes);
  87}
  88
  89struct compare_walk {
  90    char path[XENSTORE_ABS_PATH_MAX + 1];
  91    XsNode *parent_2;
  92    bool compare_ok;
  93};
  94
  95
  96static bool compare_perms(GList *p1, GList *p2)
  97{
  98    while (p1) {
  99        if (!p2 || g_strcmp0(p1->data, p2->data)) {
 100            return false;
 101        }
 102        p1 = p1->next;
 103        p2 = p2->next;
 104    }
 105    return (p2 == NULL);
 106}
 107
 108static bool compare_content(GByteArray *c1, GByteArray *c2)
 109{
 110    size_t len1 = 0, len2 = 0;
 111
 112    if (c1) {
 113        len1 = c1->len;
 114    }
 115    if (c2) {
 116        len2 = c2->len;
 117    }
 118    if (len1 != len2) {
 119        return false;
 120    }
 121
 122    if (!len1) {
 123        return true;
 124    }
 125
 126    return !memcmp(c1->data, c2->data, len1);
 127}
 128
 129static void compare_child(gpointer, gpointer, gpointer);
 130
 131static void compare_nodes(struct compare_walk *cw, XsNode *n1, XsNode *n2)
 132{
 133    int nr_children1 = 0, nr_children2 = 0;
 134
 135    if (n1->children) {
 136        nr_children1 = g_hash_table_size(n1->children);
 137    }
 138    if (n2->children) {
 139        nr_children2 = g_hash_table_size(n2->children);
 140    }
 141
 142    if (n1->ref != n2->ref ||
 143        n1->deleted_in_tx != n2->deleted_in_tx ||
 144        n1->modified_in_tx != n2->modified_in_tx ||
 145        !compare_perms(n1->perms, n2->perms) ||
 146        !compare_content(n1->content, n2->content) ||
 147        nr_children1 != nr_children2) {
 148        cw->compare_ok = false;
 149        printf("Compare failure on '%s'\n", cw->path);
 150    }
 151
 152    if (nr_children1) {
 153        XsNode *oldparent = cw->parent_2;
 154        cw->parent_2 = n2;
 155        g_hash_table_foreach(n1->children, compare_child, cw);
 156
 157        cw->parent_2 = oldparent;
 158    }
 159}
 160
 161static void compare_child(gpointer key, gpointer val, gpointer opaque)
 162{
 163    struct compare_walk *cw = opaque;
 164    char *childname = key;
 165    XsNode *child1 = val;
 166    XsNode *child2 = g_hash_table_lookup(cw->parent_2->children, childname);
 167    int pathlen = strlen(cw->path);
 168
 169    if (!child2) {
 170        cw->compare_ok = false;
 171        printf("Child '%s' does not exist under '%s'\n", childname, cw->path);
 172        return;
 173    }
 174
 175    strncat(cw->path, "/", sizeof(cw->path) - 1);
 176    strncat(cw->path, childname, sizeof(cw->path) - 1);
 177
 178    compare_nodes(cw, child1, child2);
 179    cw->path[pathlen] = '\0';
 180}
 181
 182static bool compare_trees(XsNode *n1, XsNode *n2)
 183{
 184    struct compare_walk cw;
 185
 186    cw.path[0] = '\0';
 187    cw.parent_2 = n2;
 188    cw.compare_ok = true;
 189
 190    if (!n1 || !n2) {
 191        return false;
 192    }
 193
 194    compare_nodes(&cw, n1, n2);
 195    return cw.compare_ok;
 196}
 197
 198static void compare_tx(gpointer key, gpointer val, gpointer opaque)
 199{
 200    XenstoreImplState *s2 = opaque;
 201    XsTransaction *t1 = val, *t2;
 202    unsigned int tx_id = GPOINTER_TO_INT(key);
 203
 204    t2 = g_hash_table_lookup(s2->transactions, key);
 205    g_assert(t2);
 206
 207    g_assert(t1->tx_id == tx_id);
 208    g_assert(t2->tx_id == tx_id);
 209    g_assert(t1->base_tx == t2->base_tx);
 210    g_assert(t1->dom_id == t2->dom_id);
 211    if (!compare_trees(t1->root, t2->root)) {
 212        printf("Comparison failure in TX %u after serdes:\n", tx_id);
 213        dump_ref("Original", t1->root, 0);
 214        dump_ref("Deserialised", t2->root, 0);
 215        g_assert(0);
 216    }
 217    g_assert(t1->nr_nodes == t2->nr_nodes);
 218}
 219
 220static int write_str(XenstoreImplState *s, unsigned int dom_id,
 221                          unsigned int tx_id, const char *path,
 222                          const char *content)
 223{
 224    GByteArray *d = g_byte_array_new();
 225    int err;
 226
 227    g_byte_array_append(d, (void *)content, strlen(content));
 228    err = xs_impl_write(s, dom_id, tx_id, path, d);
 229    g_byte_array_unref(d);
 230    return err;
 231}
 232
 233static void watch_cb(void *_str, const char *path, const char *token)
 234{
 235    GString *str = _str;
 236
 237    g_string_append(str, path);
 238    g_string_append(str, token);
 239}
 240
 241static void check_serdes(XenstoreImplState *s)
 242{
 243    XenstoreImplState *s2 = xs_impl_create(DOMID_GUEST);
 244    GByteArray *bytes = xs_impl_serialize(s);
 245    int nr_transactions1, nr_transactions2;
 246    int ret;
 247
 248    ret = xs_impl_deserialize(s2, bytes, DOMID_GUEST, watch_cb, NULL);
 249    g_assert(!ret);
 250
 251    g_byte_array_unref(bytes);
 252
 253    g_assert(s->last_tx == s2->last_tx);
 254    g_assert(s->root_tx == s2->root_tx);
 255
 256    if (!compare_trees(s->root, s2->root)) {
 257        printf("Comparison failure in main tree after serdes:\n");
 258        dump_ref("Original", s->root, 0);
 259        dump_ref("Deserialised", s2->root, 0);
 260        g_assert(0);
 261    }
 262
 263    nr_transactions1 = g_hash_table_size(s->transactions);
 264    nr_transactions2 = g_hash_table_size(s2->transactions);
 265    g_assert(nr_transactions1 == nr_transactions2);
 266
 267    g_hash_table_foreach(s->transactions, compare_tx, s2);
 268
 269    g_assert(s->nr_domu_watches == s2->nr_domu_watches);
 270    g_assert(s->nr_domu_transactions == s2->nr_domu_transactions);
 271    g_assert(s->nr_nodes == s2->nr_nodes);
 272    xs_impl_delete(s2, false);
 273}
 274
 275static XenstoreImplState *setup(void)
 276{
 277   XenstoreImplState *s = xs_impl_create(DOMID_GUEST);
 278   char *abspath;
 279   GList *perms;
 280   int err;
 281
 282   abspath = g_strdup_printf("/local/domain/%u", DOMID_GUEST);
 283
 284   err = write_str(s, DOMID_QEMU, XBT_NULL, abspath, "");
 285   g_assert(!err);
 286   g_assert(s->nr_nodes == 4);
 287
 288   perms = g_list_append(NULL, g_strdup_printf("n%u", DOMID_QEMU));
 289   perms = g_list_append(perms, g_strdup_printf("r%u", DOMID_GUEST));
 290
 291   err = xs_impl_set_perms(s, DOMID_QEMU, XBT_NULL, abspath, perms);
 292   g_assert(!err);
 293
 294   g_list_free_full(perms, g_free);
 295   g_free(abspath);
 296
 297   abspath = g_strdup_printf("/local/domain/%u/some", DOMID_GUEST);
 298
 299   err = write_str(s, DOMID_QEMU, XBT_NULL, abspath, "");
 300   g_assert(!err);
 301   g_assert(s->nr_nodes == 5);
 302
 303   perms = g_list_append(NULL, g_strdup_printf("n%u", DOMID_GUEST));
 304
 305   err = xs_impl_set_perms(s, DOMID_QEMU, XBT_NULL, abspath, perms);
 306   g_assert(!err);
 307
 308   g_list_free_full(perms, g_free);
 309   g_free(abspath);
 310
 311   return s;
 312}
 313
 314static void test_xs_node_simple(void)
 315{
 316    GByteArray *data = g_byte_array_new();
 317    XenstoreImplState *s = setup();
 318    GString *guest_watches = g_string_new(NULL);
 319    GString *qemu_watches = g_string_new(NULL);
 320    GList *items = NULL;
 321    XsNode *old_root;
 322    uint64_t gencnt;
 323    int err;
 324
 325    g_assert(s);
 326
 327    err = xs_impl_watch(s, DOMID_GUEST, "some", "guestwatch",
 328                        watch_cb, guest_watches);
 329    g_assert(!err);
 330    g_assert(guest_watches->len == strlen("someguestwatch"));
 331    g_assert(!strcmp(guest_watches->str, "someguestwatch"));
 332    g_string_truncate(guest_watches, 0);
 333
 334    err = xs_impl_watch(s, 0, "/local/domain/1/some", "qemuwatch",
 335                        watch_cb, qemu_watches);
 336    g_assert(!err);
 337    g_assert(qemu_watches->len == strlen("/local/domain/1/someqemuwatch"));
 338    g_assert(!strcmp(qemu_watches->str, "/local/domain/1/someqemuwatch"));
 339    g_string_truncate(qemu_watches, 0);
 340
 341    /* Read gives ENOENT when it should */
 342    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "foo", data);
 343    g_assert(err == ENOENT);
 344
 345    /* Write works */
 346    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path",
 347                    "something");
 348    g_assert(s->nr_nodes == 7);
 349    g_assert(!err);
 350    g_assert(!strcmp(guest_watches->str,
 351                     "some/relative/pathguestwatch"));
 352    g_assert(!strcmp(qemu_watches->str,
 353                     "/local/domain/1/some/relative/pathqemuwatch"));
 354
 355    g_string_truncate(qemu_watches, 0);
 356    g_string_truncate(guest_watches, 0);
 357    xs_impl_reset_watches(s, 0);
 358
 359    /* Read gives back what we wrote */
 360    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative/path", data);
 361    g_assert(!err);
 362    g_assert(data->len == strlen("something"));
 363    g_assert(!memcmp(data->data, "something", data->len));
 364
 365    /* Even if we use an abolute path */
 366    g_byte_array_set_size(data, 0);
 367    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL,
 368                       "/local/domain/1/some/relative/path", data);
 369    g_assert(!err);
 370    g_assert(data->len == strlen("something"));
 371
 372    g_assert(!qemu_watches->len);
 373    g_assert(!guest_watches->len);
 374    /* Keep a copy, to force COW mode */
 375    old_root = xs_node_ref(s->root);
 376
 377    /* Write somewhere we aren't allowed, in COW mode */
 378    err = write_str(s, DOMID_GUEST, XBT_NULL, "/local/domain/badplace",
 379                    "moredata");
 380    g_assert(err == EACCES);
 381    g_assert(s->nr_nodes == 7);
 382
 383    /* Write works again */
 384    err = write_str(s, DOMID_GUEST, XBT_NULL,
 385                    "/local/domain/1/some/relative/path2",
 386                    "something else");
 387    g_assert(!err);
 388    g_assert(s->nr_nodes == 8);
 389    g_assert(!qemu_watches->len);
 390    g_assert(!strcmp(guest_watches->str, "some/relative/path2guestwatch"));
 391    g_string_truncate(guest_watches, 0);
 392
 393    /* Overwrite an existing node */
 394    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path",
 395                    "another thing");
 396    g_assert(!err);
 397    g_assert(s->nr_nodes == 8);
 398    g_assert(!qemu_watches->len);
 399    g_assert(!strcmp(guest_watches->str, "some/relative/pathguestwatch"));
 400    g_string_truncate(guest_watches, 0);
 401
 402    /* We can list the two files we wrote */
 403    err = xs_impl_directory(s, DOMID_GUEST, XBT_NULL, "some/relative", &gencnt,
 404                            &items);
 405    g_assert(!err);
 406    g_assert(items);
 407    g_assert(gencnt == 2);
 408    g_assert(!strcmp(items->data, "path"));
 409    g_assert(items->next);
 410    g_assert(!strcmp(items->next->data, "path2"));
 411    g_assert(!items->next->next);
 412    g_list_free_full(items, g_free);
 413
 414    err = xs_impl_unwatch(s, DOMID_GUEST, "some", "guestwatch",
 415                          watch_cb, guest_watches);
 416    g_assert(!err);
 417
 418    err = xs_impl_unwatch(s, DOMID_GUEST, "some", "guestwatch",
 419                          watch_cb, guest_watches);
 420    g_assert(err == ENOENT);
 421
 422    err = xs_impl_watch(s, DOMID_GUEST, "some/relative/path2", "watchp2",
 423                        watch_cb, guest_watches);
 424    g_assert(!err);
 425    g_assert(guest_watches->len == strlen("some/relative/path2watchp2"));
 426    g_assert(!strcmp(guest_watches->str, "some/relative/path2watchp2"));
 427    g_string_truncate(guest_watches, 0);
 428
 429    err = xs_impl_watch(s, DOMID_GUEST, "/local/domain/1/some/relative",
 430                        "watchrel", watch_cb, guest_watches);
 431    g_assert(!err);
 432    g_assert(guest_watches->len ==
 433             strlen("/local/domain/1/some/relativewatchrel"));
 434    g_assert(!strcmp(guest_watches->str,
 435                     "/local/domain/1/some/relativewatchrel"));
 436    g_string_truncate(guest_watches, 0);
 437
 438    /* Write somewhere else which already existed */
 439    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative", "moredata");
 440    g_assert(!err);
 441    g_assert(s->nr_nodes == 8);
 442
 443    /* Write somewhere we aren't allowed */
 444    err = write_str(s, DOMID_GUEST, XBT_NULL, "/local/domain/badplace",
 445                    "moredata");
 446    g_assert(err == EACCES);
 447
 448    g_assert(!strcmp(guest_watches->str,
 449                     "/local/domain/1/some/relativewatchrel"));
 450    g_string_truncate(guest_watches, 0);
 451
 452    g_byte_array_set_size(data, 0);
 453    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data);
 454    g_assert(!err);
 455    g_assert(data->len == strlen("moredata"));
 456    g_assert(!memcmp(data->data, "moredata", data->len));
 457
 458    /* Overwrite existing data */
 459    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative", "otherdata");
 460    g_assert(!err);
 461    g_string_truncate(guest_watches, 0);
 462
 463    g_byte_array_set_size(data, 0);
 464    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data);
 465    g_assert(!err);
 466    g_assert(data->len == strlen("otherdata"));
 467    g_assert(!memcmp(data->data, "otherdata", data->len));
 468
 469    /* Remove the subtree */
 470    err = xs_impl_rm(s, DOMID_GUEST, XBT_NULL, "some/relative");
 471    g_assert(!err);
 472    g_assert(s->nr_nodes == 5);
 473
 474    /* Each watch fires with the least specific relevant path */
 475    g_assert(strstr(guest_watches->str,
 476                    "some/relative/path2watchp2"));
 477    g_assert(strstr(guest_watches->str,
 478                    "/local/domain/1/some/relativewatchrel"));
 479    g_string_truncate(guest_watches, 0);
 480
 481    g_byte_array_set_size(data, 0);
 482    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data);
 483    g_assert(err == ENOENT);
 484    g_byte_array_unref(data);
 485
 486    xs_impl_reset_watches(s, DOMID_GUEST);
 487    g_string_free(qemu_watches, true);
 488    g_string_free(guest_watches, true);
 489    xs_node_unref(old_root);
 490    xs_impl_delete(s, true);
 491}
 492
 493
 494static void do_test_xs_node_tx(bool fail, bool commit)
 495{
 496    XenstoreImplState *s = setup();
 497    GString *watches = g_string_new(NULL);
 498    GByteArray *data = g_byte_array_new();
 499    unsigned int tx_id = XBT_NULL;
 500    int err;
 501
 502    g_assert(s);
 503
 504    /* Set a watch */
 505    err = xs_impl_watch(s, DOMID_GUEST, "some", "watch",
 506                        watch_cb, watches);
 507    g_assert(!err);
 508    g_assert(watches->len == strlen("somewatch"));
 509    g_assert(!strcmp(watches->str, "somewatch"));
 510    g_string_truncate(watches, 0);
 511
 512    /* Write something */
 513    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path",
 514                    "something");
 515    g_assert(s->nr_nodes == 7);
 516    g_assert(!err);
 517    g_assert(!strcmp(watches->str,
 518                     "some/relative/pathwatch"));
 519    g_string_truncate(watches, 0);
 520
 521    /* Create a transaction */
 522    err = xs_impl_transaction_start(s, DOMID_GUEST, &tx_id);
 523    g_assert(!err);
 524
 525    if (fail) {
 526        /* Write something else in the root */
 527        err = write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path",
 528                        "another thing");
 529        g_assert(!err);
 530        g_assert(s->nr_nodes == 7);
 531        g_assert(!strcmp(watches->str,
 532                         "some/relative/pathwatch"));
 533        g_string_truncate(watches, 0);
 534    }
 535
 536    g_assert(!watches->len);
 537
 538    /* Perform a write in the transaction */
 539    err = write_str(s, DOMID_GUEST, tx_id, "some/relative/path",
 540                    "something else");
 541    g_assert(!err);
 542    g_assert(s->nr_nodes == 7);
 543    g_assert(!watches->len);
 544
 545    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative/path", data);
 546    g_assert(!err);
 547    if (fail) {
 548        g_assert(data->len == strlen("another thing"));
 549        g_assert(!memcmp(data->data, "another thing", data->len));
 550    } else {
 551        g_assert(data->len == strlen("something"));
 552        g_assert(!memcmp(data->data, "something", data->len));
 553    }
 554    g_byte_array_set_size(data, 0);
 555
 556    err = xs_impl_read(s, DOMID_GUEST, tx_id, "some/relative/path", data);
 557    g_assert(!err);
 558    g_assert(data->len == strlen("something else"));
 559    g_assert(!memcmp(data->data, "something else", data->len));
 560    g_byte_array_set_size(data, 0);
 561
 562    check_serdes(s);
 563
 564    /* Attempt to commit the transaction */
 565    err = xs_impl_transaction_end(s, DOMID_GUEST, tx_id, commit);
 566    if (commit && fail) {
 567        g_assert(err == EAGAIN);
 568    } else {
 569        g_assert(!err);
 570    }
 571    if (commit && !fail) {
 572        g_assert(!strcmp(watches->str,
 573                         "some/relative/pathwatch"));
 574        g_string_truncate(watches, 0);
 575    } else {
 576       g_assert(!watches->len);
 577    }
 578    g_assert(s->nr_nodes == 7);
 579
 580    check_serdes(s);
 581
 582    err = xs_impl_unwatch(s, DOMID_GUEST, "some", "watch",
 583                        watch_cb, watches);
 584    g_assert(!err);
 585
 586    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative/path", data);
 587    g_assert(!err);
 588    if (fail) {
 589        g_assert(data->len == strlen("another thing"));
 590        g_assert(!memcmp(data->data, "another thing", data->len));
 591    } else if (commit) {
 592        g_assert(data->len == strlen("something else"));
 593        g_assert(!memcmp(data->data, "something else", data->len));
 594    } else {
 595        g_assert(data->len == strlen("something"));
 596        g_assert(!memcmp(data->data, "something", data->len));
 597    }
 598    g_byte_array_unref(data);
 599    g_string_free(watches, true);
 600    xs_impl_delete(s, true);
 601}
 602
 603static void test_xs_node_tx_fail(void)
 604{
 605    do_test_xs_node_tx(true, true);
 606}
 607
 608static void test_xs_node_tx_abort(void)
 609{
 610    do_test_xs_node_tx(false, false);
 611    do_test_xs_node_tx(true, false);
 612}
 613static void test_xs_node_tx_succeed(void)
 614{
 615    do_test_xs_node_tx(false, true);
 616}
 617
 618static void test_xs_node_tx_rm(void)
 619{
 620    XenstoreImplState *s = setup();
 621    GString *watches = g_string_new(NULL);
 622    GByteArray *data = g_byte_array_new();
 623    unsigned int tx_id = XBT_NULL;
 624    int err;
 625
 626    g_assert(s);
 627
 628    /* Set a watch */
 629    err = xs_impl_watch(s, DOMID_GUEST, "some", "watch",
 630                        watch_cb, watches);
 631    g_assert(!err);
 632    g_assert(watches->len == strlen("somewatch"));
 633    g_assert(!strcmp(watches->str, "somewatch"));
 634    g_string_truncate(watches, 0);
 635
 636    /* Write something */
 637    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path",
 638                    "something");
 639    g_assert(!err);
 640    g_assert(s->nr_nodes == 9);
 641    g_assert(!strcmp(watches->str,
 642                     "some/deep/dark/relative/pathwatch"));
 643    g_string_truncate(watches, 0);
 644
 645    /* Create a transaction */
 646    err = xs_impl_transaction_start(s, DOMID_GUEST, &tx_id);
 647    g_assert(!err);
 648
 649    /* Delete the tree in the transaction */
 650    err = xs_impl_rm(s, DOMID_GUEST, tx_id, "some/deep/dark");
 651    g_assert(!err);
 652    g_assert(s->nr_nodes == 9);
 653    g_assert(!watches->len);
 654
 655    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path",
 656                       data);
 657    g_assert(!err);
 658    g_assert(data->len == strlen("something"));
 659    g_assert(!memcmp(data->data, "something", data->len));
 660    g_byte_array_set_size(data, 0);
 661
 662    check_serdes(s);
 663
 664    /* Commit the transaction */
 665    err = xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true);
 666    g_assert(!err);
 667    g_assert(s->nr_nodes == 6);
 668
 669    g_assert(!strcmp(watches->str, "some/deep/darkwatch"));
 670    g_string_truncate(watches, 0);
 671
 672    /* Now the node is gone */
 673    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path",
 674                       data);
 675    g_assert(err == ENOENT);
 676    g_byte_array_unref(data);
 677
 678    err = xs_impl_unwatch(s, DOMID_GUEST, "some", "watch",
 679                        watch_cb, watches);
 680    g_assert(!err);
 681
 682    g_string_free(watches, true);
 683    xs_impl_delete(s, true);
 684}
 685
 686static void test_xs_node_tx_resurrect(void)
 687{
 688    XenstoreImplState *s = setup();
 689    GString *watches = g_string_new(NULL);
 690    GByteArray *data = g_byte_array_new();
 691    unsigned int tx_id = XBT_NULL;
 692    int err;
 693
 694    g_assert(s);
 695
 696    /* Write something */
 697    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path",
 698                    "something");
 699    g_assert(!err);
 700    g_assert(s->nr_nodes == 9);
 701
 702    /* Another node to remain shared */
 703    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/place/safe", "keepme");
 704    g_assert(!err);
 705    g_assert(s->nr_nodes == 11);
 706
 707    /* This node will be wiped and resurrected */
 708    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark",
 709                    "foo");
 710    g_assert(!err);
 711    g_assert(s->nr_nodes == 11);
 712
 713    /* Set a watch */
 714    err = xs_impl_watch(s, DOMID_GUEST, "some", "watch",
 715                        watch_cb, watches);
 716    g_assert(!err);
 717    g_assert(watches->len == strlen("somewatch"));
 718    g_assert(!strcmp(watches->str, "somewatch"));
 719    g_string_truncate(watches, 0);
 720
 721    /* Create a transaction */
 722    err = xs_impl_transaction_start(s, DOMID_GUEST, &tx_id);
 723    g_assert(!err);
 724
 725    /* Delete the tree in the transaction */
 726    err = xs_impl_rm(s, DOMID_GUEST, tx_id, "some/deep");
 727    g_assert(!err);
 728    g_assert(s->nr_nodes == 11);
 729    g_assert(!watches->len);
 730
 731    /* Resurrect part of it */
 732    err = write_str(s, DOMID_GUEST, tx_id, "some/deep/dark/different/path",
 733                    "something");
 734    g_assert(!err);
 735    g_assert(s->nr_nodes == 11);
 736
 737    check_serdes(s);
 738
 739    /* Commit the transaction */
 740    err = xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true);
 741    g_assert(!err);
 742    g_assert(s->nr_nodes == 11);
 743
 744    check_serdes(s);
 745
 746    /* lost data */
 747    g_assert(strstr(watches->str, "some/deep/dark/different/pathwatch"));
 748    /* topmost deleted */
 749    g_assert(strstr(watches->str, "some/deep/dark/relativewatch"));
 750    /* lost data */
 751    g_assert(strstr(watches->str, "some/deep/darkwatch"));
 752
 753    g_string_truncate(watches, 0);
 754
 755    /* Now the node is gone */
 756    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path",
 757                       data);
 758    g_assert(err == ENOENT);
 759    g_byte_array_unref(data);
 760
 761    check_serdes(s);
 762
 763    err = xs_impl_unwatch(s, DOMID_GUEST, "some", "watch",
 764                        watch_cb, watches);
 765    g_assert(!err);
 766
 767    g_string_free(watches, true);
 768    xs_impl_delete(s, true);
 769}
 770
 771static void test_xs_node_tx_resurrect2(void)
 772{
 773    XenstoreImplState *s = setup();
 774    GString *watches = g_string_new(NULL);
 775    GByteArray *data = g_byte_array_new();
 776    unsigned int tx_id = XBT_NULL;
 777    int err;
 778
 779    g_assert(s);
 780
 781    /* Write something */
 782    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path",
 783                    "something");
 784    g_assert(!err);
 785    g_assert(s->nr_nodes == 9);
 786
 787    /* Another node to remain shared */
 788    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/place/safe", "keepme");
 789    g_assert(!err);
 790    g_assert(s->nr_nodes == 11);
 791
 792    /* This node will be wiped and resurrected */
 793    err = write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark",
 794                    "foo");
 795    g_assert(!err);
 796    g_assert(s->nr_nodes == 11);
 797
 798    /* Set a watch */
 799    err = xs_impl_watch(s, DOMID_GUEST, "some", "watch",
 800                        watch_cb, watches);
 801    g_assert(!err);
 802    g_assert(watches->len == strlen("somewatch"));
 803    g_assert(!strcmp(watches->str, "somewatch"));
 804    g_string_truncate(watches, 0);
 805
 806    /* Create a transaction */
 807    err = xs_impl_transaction_start(s, DOMID_GUEST, &tx_id);
 808    g_assert(!err);
 809
 810    /* Delete the tree in the transaction */
 811    err = xs_impl_rm(s, DOMID_GUEST, tx_id, "some/deep");
 812    g_assert(!err);
 813    g_assert(s->nr_nodes == 11);
 814    g_assert(!watches->len);
 815
 816    /* Resurrect part of it */
 817    err = write_str(s, DOMID_GUEST, tx_id, "some/deep/dark/relative/path",
 818                    "something");
 819    g_assert(!err);
 820    g_assert(s->nr_nodes == 11);
 821
 822    check_serdes(s);
 823
 824    /* Commit the transaction */
 825    err = xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true);
 826    g_assert(!err);
 827    g_assert(s->nr_nodes == 11);
 828
 829    check_serdes(s);
 830
 831    /* lost data */
 832    g_assert(strstr(watches->str, "some/deep/dark/relative/pathwatch"));
 833    /* lost data */
 834    g_assert(strstr(watches->str, "some/deep/darkwatch"));
 835
 836    g_string_truncate(watches, 0);
 837
 838    /* Now the node is gone */
 839    err = xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/path",
 840                       data);
 841    g_assert(!err);
 842    g_assert(data->len == strlen("something"));
 843    g_assert(!memcmp(data->data, "something", data->len));
 844
 845    g_byte_array_unref(data);
 846
 847    check_serdes(s);
 848
 849    err = xs_impl_unwatch(s, DOMID_GUEST, "some", "watch",
 850                        watch_cb, watches);
 851    g_assert(!err);
 852
 853    g_string_free(watches, true);
 854    xs_impl_delete(s, true);
 855}
 856
 857int main(int argc, char **argv)
 858{
 859    g_test_init(&argc, &argv, NULL);
 860    module_call_init(MODULE_INIT_QOM);
 861
 862    g_test_add_func("/xs_node/simple", test_xs_node_simple);
 863    g_test_add_func("/xs_node/tx_abort", test_xs_node_tx_abort);
 864    g_test_add_func("/xs_node/tx_fail", test_xs_node_tx_fail);
 865    g_test_add_func("/xs_node/tx_succeed", test_xs_node_tx_succeed);
 866    g_test_add_func("/xs_node/tx_rm", test_xs_node_tx_rm);
 867    g_test_add_func("/xs_node/tx_resurrect", test_xs_node_tx_resurrect);
 868    g_test_add_func("/xs_node/tx_resurrect2", test_xs_node_tx_resurrect2);
 869
 870    return g_test_run();
 871}
 872