xref: /openbmc/qemu/tests/unit/test-xs-node.c (revision 8d7f2e76)
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 typedef 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 
33 static 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 */
58 static 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 
89 struct compare_walk {
90     char path[XENSTORE_ABS_PATH_MAX + 1];
91     XsNode *parent_2;
92     bool compare_ok;
93 };
94 
95 
96 static 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 
108 static 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 
129 static void compare_child(gpointer, gpointer, gpointer);
130 
131 static 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 
161 static 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 
182 static 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 
198 static 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 
220 static 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 
233 static 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 
241 static 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 
275 static 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 
314 static 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 absolute 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 
494 static 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 
603 static void test_xs_node_tx_fail(void)
604 {
605     do_test_xs_node_tx(true, true);
606 }
607 
608 static 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 }
613 static void test_xs_node_tx_succeed(void)
614 {
615     do_test_xs_node_tx(false, true);
616 }
617 
618 static 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 
686 static 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 
771 static 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 
857 int 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