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