From nobody Tue Feb 10 07:41:59 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=fail; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1677771376829520.4199082893314; Thu, 2 Mar 2023 07:36:16 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pXkxh-00046Z-6S; Thu, 02 Mar 2023 10:35:17 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pXkxQ-0003tx-Jt for qemu-devel@nongnu.org; Thu, 02 Mar 2023 10:35:05 -0500 Received: from desiato.infradead.org ([2001:8b0:10b:1:d65d:64ff:fe57:4e05]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pXkxM-0001fD-F3 for qemu-devel@nongnu.org; Thu, 02 Mar 2023 10:35:00 -0500 Received: from i7.infradead.org ([2001:8b0:10b:1:21e:67ff:fecb:7a92]) by desiato.infradead.org with esmtpsa (Exim 4.96 #2 (Red Hat Linux)) id 1pXkx4-00FL3d-0o; Thu, 02 Mar 2023 15:34:39 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.96 #2 (Red Hat Linux)) id 1pXkx4-004uyj-0S; Thu, 02 Mar 2023 15:34:38 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=desiato.20200630; h=Sender:Content-Transfer-Encoding: MIME-Version:References:In-Reply-To:Message-Id:Date:Subject:Cc:To:From: Reply-To:Content-Type:Content-ID:Content-Description; bh=Ca2714vgfm18G+KeFQHG/S9JS+BdaUec2bE3wcS910w=; b=qyL9Kow45NCZ9QXvTmhTUdR7fP UZgbx0jLXWCGC2o642X5kABEQ3uxiER8SvY8ieySF2nq5ZwLsuV0bwwEM2jDfXqjtz4SY+mUfRD/A VIJEQGIuAxFEILYbPrtQLaSGOvmndf82DwEfZgt5aWNUcAAMqM/ptFjUINSB9UJXavstslaR3tu9C e16Wtac8SiRFl5/ev5mGu1N1SnUwWLW7hkiHTYS05SCNqBe5BFbQYMefdailPDkVBGuhd1nxZuZEb m71IrNCzneaP8uhfZ6LgR4Qol0eiMtv1yang1PnVab3YAvcQZfABxcnyBCo7GHEjxuyLkYydBChQ8 2i8AIudg==; From: David Woodhouse To: qemu-devel@nongnu.org Cc: Paolo Bonzini , Paul Durrant , Joao Martins , Ankur Arora , Stefano Stabellini , vikram.garhwal@amd.com, Anthony Perard , xen-devel@lists.xenproject.org Subject: [RFC PATCH v1 07/25] hw/xen: Implement core serialize/deserialize methods for xenstore_impl Date: Thu, 2 Mar 2023 15:34:17 +0000 Message-Id: <20230302153435.1170111-8-dwmw2@infradead.org> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230302153435.1170111-1-dwmw2@infradead.org> References: <20230302153435.1170111-1-dwmw2@infradead.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-SRS-Rewrite: SMTP reverse-path rewritten from by desiato.infradead.org. See http://www.infradead.org/rpr.html Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: none client-ip=2001:8b0:10b:1:d65d:64ff:fe57:4e05; envelope-from=BATV+9a0490e5ac528e462c30+7130+infradead.org+dwmw2@desiato.srs.infradead.org; helo=desiato.infradead.org X-Spam_score_int: -43 X-Spam_score: -4.4 X-Spam_bar: ---- X-Spam_report: (-4.4 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_NONE=0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: fail (Header signature does not verify) X-ZM-MESSAGEID: 1677771377827100003 Content-Type: text/plain; charset="utf-8" From: David Woodhouse In fact I think we want to only serialize the contents of the domain's path in /local/domain/${domid} and leave the rest to be recreated? Will defer to Paul for that. Signed-off-by: David Woodhouse Reviewed-by: Paul Durrant --- hw/i386/kvm/xen_xenstore.c | 25 +- hw/i386/kvm/xenstore_impl.c | 574 +++++++++++++++++++++++++++++++++++- hw/i386/kvm/xenstore_impl.h | 5 + tests/unit/test-xs-node.c | 236 ++++++++++++++- 4 files changed, 824 insertions(+), 16 deletions(-) diff --git a/hw/i386/kvm/xen_xenstore.c b/hw/i386/kvm/xen_xenstore.c index 3b409e3817..1b1358ad4c 100644 --- a/hw/i386/kvm/xen_xenstore.c +++ b/hw/i386/kvm/xen_xenstore.c @@ -66,6 +66,9 @@ struct XenXenstoreState { evtchn_port_t guest_port; evtchn_port_t be_port; struct xenevtchn_handle *eh; + + uint8_t *impl_state; + uint32_t impl_state_size; }; =20 struct XenXenstoreState *xen_xenstore_singleton; @@ -109,16 +112,26 @@ static bool xen_xenstore_is_needed(void *opaque) static int xen_xenstore_pre_save(void *opaque) { XenXenstoreState *s =3D opaque; + GByteArray *save; =20 if (s->eh) { s->guest_port =3D xen_be_evtchn_get_guest_port(s->eh); } + + g_free(s->impl_state); + save =3D xs_impl_serialize(s->impl); + s->impl_state =3D save->data; + s->impl_state_size =3D save->len; + g_byte_array_free(save, false); + return 0; } =20 static int xen_xenstore_post_load(void *opaque, int ver) { XenXenstoreState *s =3D opaque; + GByteArray *save; + int ret; =20 /* * As qemu/dom0, rebind to the guest's port. The Windows drivers may @@ -135,7 +148,13 @@ static int xen_xenstore_post_load(void *opaque, int ve= r) } s->be_port =3D be_port; } - return 0; + + save =3D g_byte_array_new_take(s->impl_state, s->impl_state_size); + s->impl_state =3D NULL; + s->impl_state_size =3D 0; + + ret =3D xs_impl_deserialize(s->impl, save, xen_domid, fire_watch_cb, s= ); + return ret; } =20 static const VMStateDescription xen_xenstore_vmstate =3D { @@ -155,6 +174,10 @@ static const VMStateDescription xen_xenstore_vmstate = =3D { VMSTATE_BOOL(rsp_pending, XenXenstoreState), VMSTATE_UINT32(guest_port, XenXenstoreState), VMSTATE_BOOL(fatal_error, XenXenstoreState), + VMSTATE_UINT32(impl_state_size, XenXenstoreState), + VMSTATE_VARRAY_UINT32_ALLOC(impl_state, XenXenstoreState, + impl_state_size, 0, + vmstate_info_uint8, uint8_t), VMSTATE_END_OF_LIST() } }; diff --git a/hw/i386/kvm/xenstore_impl.c b/hw/i386/kvm/xenstore_impl.c index 7988bde88f..82e7ae06f5 100644 --- a/hw/i386/kvm/xenstore_impl.c +++ b/hw/i386/kvm/xenstore_impl.c @@ -37,6 +37,7 @@ typedef struct XsNode { uint64_t gencnt; bool deleted_in_tx; bool modified_in_tx; + unsigned int serialized_tx; #ifdef XS_NODE_UNIT_TEST gchar *name; /* debug only */ #endif @@ -68,6 +69,7 @@ struct XenstoreImplState { unsigned int nr_domu_transactions; unsigned int root_tx; unsigned int last_tx; + bool serialized; }; =20 =20 @@ -1156,8 +1158,10 @@ int xs_impl_set_perms(XenstoreImplState *s, unsigned= int dom_id, return xs_node_walk(n, &op); } =20 -int xs_impl_watch(XenstoreImplState *s, unsigned int dom_id, const char *p= ath, - const char *token, xs_impl_watch_fn fn, void *opaque) +static int do_xs_impl_watch(XenstoreImplState *s, unsigned int dom_id, + const char *path, const char *token, + xs_impl_watch_fn fn, void *opaque) + { char abspath[XENSTORE_ABS_PATH_MAX + 1]; XsWatch *w, *l; @@ -1200,12 +1204,22 @@ int xs_impl_watch(XenstoreImplState *s, unsigned in= t dom_id, const char *path, s->nr_domu_watches++; } =20 - /* A new watch should fire immediately */ - fn(opaque, path, token); - return 0; } =20 +int xs_impl_watch(XenstoreImplState *s, unsigned int dom_id, const char *p= ath, + const char *token, xs_impl_watch_fn fn, void *opaque) +{ + int ret =3D do_xs_impl_watch(s, dom_id, path, token, fn, opaque); + + if (!ret) { + /* A new watch should fire immediately */ + fn(opaque, path, token); + } + + return ret; +} + static XsWatch *free_watch(XenstoreImplState *s, XsWatch *w) { XsWatch *next =3D w->next; @@ -1361,3 +1375,553 @@ XenstoreImplState *xs_impl_create(unsigned int dom_= id) s->root_tx =3D s->last_tx =3D 1; return s; } + + +static void clear_serialized_tx(gpointer key, gpointer value, gpointer opa= que) +{ + XsNode *n =3D value; + + n->serialized_tx =3D XBT_NULL; + if (n->children) { + g_hash_table_foreach(n->children, clear_serialized_tx, NULL); + } +} + +static void clear_tx_serialized_tx(gpointer key, gpointer value, + gpointer opaque) +{ + XsTransaction *t =3D value; + + clear_serialized_tx(NULL, t->root, NULL); +} + +static void write_be32(GByteArray *save, uint32_t val) +{ + uint32_t be =3D htonl(val); + g_byte_array_append(save, (void *)&be, sizeof(be)); +} + + +struct save_state { + GByteArray *bytes; + unsigned int tx_id; +}; + +#define MODIFIED_IN_TX (1U << 0) +#define DELETED_IN_TX (1U << 1) +#define NODE_REF (1U << 2) + +static void save_node(gpointer key, gpointer value, gpointer opaque) +{ + struct save_state *ss =3D opaque; + XsNode *n =3D value; + char *name =3D key; + uint8_t flag =3D 0; + + /* Child nodes (i.e. anything but the root) have a name */ + if (name) { + g_byte_array_append(ss->bytes, key, strlen(key) + 1); + } + + /* + * If we already wrote this node, refer to the previous copy. + * There's no rename/move in XenStore, so all we need to find + * it is the tx_id of the transation in which it exists. Which + * may be the root tx. + */ + if (n->serialized_tx !=3D XBT_NULL) { + flag =3D NODE_REF; + g_byte_array_append(ss->bytes, &flag, 1); + write_be32(ss->bytes, n->serialized_tx); + } else { + GList *l; + n->serialized_tx =3D ss->tx_id; + + if (n->modified_in_tx) { + flag |=3D MODIFIED_IN_TX; + } + if (n->deleted_in_tx) { + flag |=3D DELETED_IN_TX; + } + g_byte_array_append(ss->bytes, &flag, 1); + + if (n->content) { + write_be32(ss->bytes, n->content->len); + g_byte_array_append(ss->bytes, n->content->data, n->content->l= en); + } else { + write_be32(ss->bytes, 0); + } + + for (l =3D n->perms; l; l =3D l->next) { + g_byte_array_append(ss->bytes, l->data, strlen(l->data) + 1); + } + /* NUL termination after perms */ + g_byte_array_append(ss->bytes, (void *)"", 1); + + if (n->children) { + g_hash_table_foreach(n->children, save_node, ss); + } + /* NUL termination after children (child name is NUL) */ + g_byte_array_append(ss->bytes, (void *)"", 1); + } +} + +static void save_tree(struct save_state *ss, uint32_t tx_id, XsNode *root) +{ + write_be32(ss->bytes, tx_id); + ss->tx_id =3D tx_id; + save_node(NULL, root, ss); +} + +static void save_tx(gpointer key, gpointer value, gpointer opaque) +{ + uint32_t tx_id =3D GPOINTER_TO_INT(key); + struct save_state *ss =3D opaque; + XsTransaction *n =3D value; + + write_be32(ss->bytes, n->base_tx); + write_be32(ss->bytes, n->dom_id); + + save_tree(ss, tx_id, n->root); +} + +static void save_watch(gpointer key, gpointer value, gpointer opaque) +{ + struct save_state *ss =3D opaque; + XsWatch *w =3D value; + + /* We only save the *guest* watches. */ + if (w->dom_id) { + gpointer relpath =3D key + w->rel_prefix; + g_byte_array_append(ss->bytes, relpath, strlen(relpath) + 1); + g_byte_array_append(ss->bytes, (void *)w->token, strlen(w->token) = + 1); + } +} + +GByteArray *xs_impl_serialize(XenstoreImplState *s) +{ + struct save_state ss; + + ss.bytes =3D g_byte_array_new(); + + /* + * node =3D flags [ real_node / node_ref ] + * flags =3D uint8_t (MODIFIED_IN_TX | DELETED_IN_TX | NODE_REF) + * node_ref =3D tx_id (in which the original version of this node ex= ists) + * real_node =3D content perms child* NUL + * content =3D len data + * len =3D uint32_t + * data =3D uint8_t{len} + * perms =3D perm* NUL + * perm =3D asciiz + * child =3D name node + * name =3D asciiz + * + * tree =3D tx_id node + * tx_id =3D uint32_t + * + * transaction =3D base_tx_id dom_id tree + * base_tx_id =3D uint32_t + * dom_id =3D uint32_t + * + * tx_list =3D tree transaction* XBT_NULL + * + * watch =3D path token + * path =3D asciiz + * token =3D asciiz + * + * watch_list =3D watch* NUL + * + * xs_serialize_stream =3D last_tx tx_list watch_list + * last_tx =3D uint32_t + */ + + /* Clear serialized_tx in every node. */ + if (s->serialized) { + clear_serialized_tx(NULL, s->root, NULL); + g_hash_table_foreach(s->transactions, clear_tx_serialized_tx, NULL= ); + } + + s->serialized =3D true; + + write_be32(ss.bytes, s->last_tx); + save_tree(&ss, s->root_tx, s->root); + g_hash_table_foreach(s->transactions, save_tx, &ss); + + write_be32(ss.bytes, XBT_NULL); + + g_hash_table_foreach(s->watches, save_watch, &ss); + g_byte_array_append(ss.bytes, (void *)"", 1); + + return ss.bytes; +} + +struct unsave_state { + char path[XENSTORE_ABS_PATH_MAX + 1]; + XenstoreImplState *s; + GByteArray *bytes; + uint8_t *d; + size_t l; + bool root_walk; +}; + +static int consume_be32(struct unsave_state *us, unsigned int *val) +{ + uint32_t d; + + if (us->l < sizeof(d)) { + return -EINVAL; + } + memcpy(&d, us->d, sizeof(d)); + *val =3D ntohl(d); + us->d +=3D sizeof(d); + us->l -=3D sizeof(d); + return 0; +} + +static int consume_string(struct unsave_state *us, char **str, size_t *len) +{ + size_t l; + + if (!us->l) { + return -EINVAL; + } + + l =3D strnlen((void *)us->d, us->l); + if (l =3D=3D us->l) { + return -EINVAL; + } + + if (str) { + *str =3D (void *)us->d; + } + if (len) { + *len =3D l; + } + + us->d +=3D l + 1; + us->l -=3D l + 1; + return 0; +} + +static XsNode *lookup_node(XsNode *n, char *path) +{ + char *slash =3D strchr(path, '/'); + XsNode *child; + + if (path[0] =3D=3D '\0') { + return n; + } + + if (slash) { + *slash =3D '\0'; + } + + if (!n->children) { + return NULL; + } + child =3D g_hash_table_lookup(n->children, path); + if (!slash) { + return child; + } + + *slash =3D '/'; + if (!child) { + return NULL; + } + return lookup_node(child, slash + 1); +} + +static XsNode *lookup_tx_node(struct unsave_state *us, unsigned int tx_id) +{ + XsTransaction *t; + if (tx_id =3D=3D us->s->root_tx) { + return lookup_node(us->s->root, us->path + 1); + } + + t =3D g_hash_table_lookup(us->s->transactions, GINT_TO_POINTER(tx_id)); + if (!t) { + return NULL; + } + g_assert(t->root); + return lookup_node(t->root, us->path + 1); +} + +static void count_child_nodes(gpointer key, gpointer value, gpointer user_= data) +{ + unsigned int *nr_nodes =3D user_data; + XsNode *n =3D value; + + (*nr_nodes)++; + + if (n->children) { + g_hash_table_foreach(n->children, count_child_nodes, nr_nodes); + } +} + +static int consume_node(struct unsave_state *us, XsNode **nodep, + unsigned int *nr_nodes) +{ + XsNode *n =3D NULL; + uint8_t flags; + int ret; + + if (us->l < 1) { + return -EINVAL; + } + flags =3D us->d[0]; + us->d++; + us->l--; + + if (flags =3D=3D NODE_REF) { + unsigned int tx; + + ret =3D consume_be32(us, &tx); + if (ret) { + return ret; + } + + n =3D lookup_tx_node(us, tx); + if (!n) { + return -EINVAL; + } + n->ref++; + if (n->children) { + g_hash_table_foreach(n->children, count_child_nodes, nr_nodes); + } + } else { + uint32_t datalen; + + if (flags & ~(DELETED_IN_TX | MODIFIED_IN_TX)) { + return -EINVAL; + } + n =3D xs_node_new(); + + if (flags & DELETED_IN_TX) { + n->deleted_in_tx =3D true; + } + if (flags & MODIFIED_IN_TX) { + n->modified_in_tx =3D true; + } + ret =3D consume_be32(us, &datalen); + if (ret) { + xs_node_unref(n); + return -EINVAL; + } + if (datalen) { + if (datalen > us->l) { + xs_node_unref(n); + return -EINVAL; + } + + GByteArray *node_data =3D g_byte_array_new(); + g_byte_array_append(node_data, us->d, datalen); + us->d +=3D datalen; + us->l -=3D datalen; + n->content =3D node_data; + + if (us->root_walk) { + n->modified_in_tx =3D true; + } + } + while (1) { + char *perm =3D NULL; + size_t permlen =3D 0; + + ret =3D consume_string(us, &perm, &permlen); + if (ret) { + xs_node_unref(n); + return ret; + } + + if (!permlen) { + break; + } + + n->perms =3D g_list_append(n->perms, g_strdup(perm)); + } + + /* Now children */ + while (1) { + size_t childlen; + char *childname; + char *pathend; + XsNode *child =3D NULL; + + ret =3D consume_string(us, &childname, &childlen); + if (ret) { + xs_node_unref(n); + return ret; + } + + if (!childlen) { + break; + } + + pathend =3D us->path + strlen(us->path); + strncat(us->path, "/", sizeof(us->path) - 1); + strncat(us->path, childname, sizeof(us->path) - 1); + + ret =3D consume_node(us, &child, nr_nodes); + *pathend =3D '\0'; + if (ret) { + xs_node_unref(n); + return ret; + } + g_assert(child); + xs_node_add_child(n, childname, child); + } + + /* + * If the node has no data and no children we still want to fire + * a watch on it. + */ + if (us->root_walk && !n->children) { + n->modified_in_tx =3D true; + } + } + + if (!n->deleted_in_tx) { + (*nr_nodes)++; + } + + *nodep =3D n; + return 0; +} + +static int consume_tree(struct unsave_state *us, XsTransaction *t) +{ + int ret; + + ret =3D consume_be32(us, &t->tx_id); + if (ret) { + return ret; + } + + if (t->tx_id > us->s->last_tx) { + return -EINVAL; + } + + us->path[0] =3D '\0'; + + return consume_node(us, &t->root, &t->nr_nodes); +} + +int xs_impl_deserialize(XenstoreImplState *s, GByteArray *bytes, + unsigned int dom_id, xs_impl_watch_fn watch_fn, + void *watch_opaque) +{ + struct unsave_state us; + XsTransaction base_t =3D { 0 }; + int ret; + + us.s =3D s; + us.bytes =3D bytes; + us.d =3D bytes->data; + us.l =3D bytes->len; + + xs_impl_reset_watches(s, dom_id); + g_hash_table_remove_all(s->transactions); + + xs_node_unref(s->root); + s->root =3D NULL; + s->root_tx =3D s->last_tx =3D XBT_NULL; + + ret =3D consume_be32(&us, &s->last_tx); + if (ret) { + return ret; + } + + /* + * Consume the base tree into a transaction so that watches can be + * fired as we commit it. By setting us.root_walk we cause the nodes + * to be marked as 'modified_in_tx' as they are created, so that the + * watches are triggered on them. + */ + base_t.dom_id =3D dom_id; + base_t.base_tx =3D XBT_NULL; + us.root_walk =3D true; + ret =3D consume_tree(&us, &base_t); + if (ret) { + return ret; + } + us.root_walk =3D false; + + /* + * Commit the transaction now while the refcount on all nodes is 1. + * Note that we haven't yet reinstated the *guest* watches but that's + * OK because we don't want the guest to see any changes. Even any + * backend nodes which get recreated should be *precisely* as they + * were before the migration. Back ends may have been instantiated + * already, and will see the frontend magically blink into existence + * now (well, from the aio_bh which fires the watches). It's their + * responsibility to rebuild everything precisely as it was before. + */ + ret =3D transaction_commit(s, &base_t); + if (ret) { + return ret; + } + + while (1) { + unsigned int base_tx; + XsTransaction *t; + + ret =3D consume_be32(&us, &base_tx); + if (ret) { + return ret; + } + if (base_tx =3D=3D XBT_NULL) { + break; + } + + t =3D g_new0(XsTransaction, 1); + t->base_tx =3D base_tx; + + ret =3D consume_be32(&us, &t->dom_id); + if (!ret) { + ret =3D consume_tree(&us, t); + } + if (ret) { + g_free(t); + return ret; + } + g_assert(t->root); + if (t->dom_id) { + s->nr_domu_transactions++; + } + g_hash_table_insert(s->transactions, GINT_TO_POINTER(t->tx_id), t); + } + + while (1) { + char *path, *token; + size_t pathlen, toklen; + + ret =3D consume_string(&us, &path, &pathlen); + if (ret) { + return ret; + } + if (!pathlen) { + break; + } + + ret =3D consume_string(&us, &token, &toklen); + if (ret) { + return ret; + } + + if (!watch_fn) { + continue; + } + + ret =3D do_xs_impl_watch(s, dom_id, path, token, watch_fn, watch_o= paque); + if (ret) { + return ret; + } + } + + if (us.l) { + return -EINVAL; + } + + return 0; +} diff --git a/hw/i386/kvm/xenstore_impl.h b/hw/i386/kvm/xenstore_impl.h index 2f81251b5e..bbe2391e2e 100644 --- a/hw/i386/kvm/xenstore_impl.h +++ b/hw/i386/kvm/xenstore_impl.h @@ -61,4 +61,9 @@ int xs_impl_unwatch(XenstoreImplState *s, unsigned int do= m_id, void *opaque); int xs_impl_reset_watches(XenstoreImplState *s, unsigned int dom_id); =20 +GByteArray *xs_impl_serialize(XenstoreImplState *s); +int xs_impl_deserialize(XenstoreImplState *s, GByteArray *bytes, + unsigned int dom_id, xs_impl_watch_fn watch_fn, + void *watch_opaque); + #endif /* QEMU_XENSTORE_IMPL_H */ diff --git a/tests/unit/test-xs-node.c b/tests/unit/test-xs-node.c index 2c0f89c694..fda6a047d0 100644 --- a/tests/unit/test-xs-node.c +++ b/tests/unit/test-xs-node.c @@ -29,8 +29,32 @@ static GList *xs_node_list; #define DOMID_QEMU 0 #define DOMID_GUEST 1 =20 +static void dump_ref(const char *name, XsNode *n, int indent) +{ + int i; + + if (!indent && name) { + printf("%s:\n", name); + } + + for (i =3D 0; i < indent; i++) { + printf(" "); + } + + printf("->%p(%d, '%s'): '%.*s'%s%s\n", n, n->ref, n->name, + (int)(n->content ? n->content->len : strlen("")), + n->content ? (char *)n->content->data : "", + n->modified_in_tx ? " MODIFIED" : "", + n->deleted_in_tx ? " DELETED" : ""); + + if (n->children) { + g_hash_table_foreach(n->children, (void *)dump_ref, + GINT_TO_POINTER(indent + 2)); + } +} + /* This doesn't happen in qemu but we want to make valgrind happy */ -static void xs_impl_delete(XenstoreImplState *s) +static void xs_impl_delete(XenstoreImplState *s, bool last) { int err; =20 @@ -46,6 +70,10 @@ static void xs_impl_delete(XenstoreImplState *s) xs_node_unref(s->root); g_free(s); =20 + if (!last) { + return; + } + if (xs_node_list) { GList *l; for (l =3D xs_node_list; l; l =3D l->next) { @@ -57,6 +85,137 @@ static void xs_impl_delete(XenstoreImplState *s) g_assert(!nr_xs_nodes); } =20 +struct compare_walk { + char path[XENSTORE_ABS_PATH_MAX + 1]; + XsNode *parent_2; + bool compare_ok; +}; + + +static bool compare_perms(GList *p1, GList *p2) +{ + while (p1) { + if (!p2 || g_strcmp0(p1->data, p2->data)) { + return false; + } + p1 =3D p1->next; + p2 =3D p2->next; + } + return (p2 =3D=3D NULL); +} + +static bool compare_content(GByteArray *c1, GByteArray *c2) +{ + size_t len1 =3D 0, len2 =3D 0; + + if (c1) { + len1 =3D c1->len; + } + if (c2) { + len2 =3D c2->len; + } + if (len1 !=3D len2) { + return false; + } + + if (!len1) { + return true; + } + + return !memcmp(c1->data, c2->data, len1); +} + +static void compare_child(gpointer, gpointer, gpointer); + +static void compare_nodes(struct compare_walk *cw, XsNode *n1, XsNode *n2) +{ + int nr_children1 =3D 0, nr_children2 =3D 0; + + if (n1->children) { + nr_children1 =3D g_hash_table_size(n1->children); + } + if (n2->children) { + nr_children2 =3D g_hash_table_size(n2->children); + } + + if (n1->ref !=3D n2->ref || + n1->deleted_in_tx !=3D n2->deleted_in_tx || + n1->modified_in_tx !=3D n2->modified_in_tx || + !compare_perms(n1->perms, n2->perms) || + !compare_content(n1->content, n2->content) || + nr_children1 !=3D nr_children2) { + cw->compare_ok =3D false; + printf("Compare failure on '%s'\n", cw->path); + } + + if (nr_children1) { + XsNode *oldparent =3D cw->parent_2; + cw->parent_2 =3D n2; + g_hash_table_foreach(n1->children, compare_child, cw); + + cw->parent_2 =3D oldparent; + } +} + +static void compare_child(gpointer key, gpointer val, gpointer opaque) +{ + struct compare_walk *cw =3D opaque; + char *childname =3D key; + XsNode *child1 =3D val; + XsNode *child2 =3D g_hash_table_lookup(cw->parent_2->children, childna= me); + int pathlen =3D strlen(cw->path); + + if (!child2) { + cw->compare_ok =3D false; + printf("Child '%s' does not exist under '%s'\n", childname, cw->pa= th); + return; + } + + strncat(cw->path, "/", sizeof(cw->path) - 1); + strncat(cw->path, childname, sizeof(cw->path) - 1); + + compare_nodes(cw, child1, child2); + cw->path[pathlen] =3D '\0'; +} + +static bool compare_trees(XsNode *n1, XsNode *n2) +{ + struct compare_walk cw; + + cw.path[0] =3D '\0'; + cw.parent_2 =3D n2; + cw.compare_ok =3D true; + + if (!n1 || !n2) { + return false; + } + + compare_nodes(&cw, n1, n2); + return cw.compare_ok; +} + +static void compare_tx(gpointer key, gpointer val, gpointer opaque) +{ + XenstoreImplState *s2 =3D opaque; + XsTransaction *t1 =3D val, *t2; + unsigned int tx_id =3D GPOINTER_TO_INT(key); + + t2 =3D g_hash_table_lookup(s2->transactions, key); + g_assert(t2); + + g_assert(t1->tx_id =3D=3D tx_id); + g_assert(t2->tx_id =3D=3D tx_id); + g_assert(t1->base_tx =3D=3D t2->base_tx); + g_assert(t1->dom_id =3D=3D t2->dom_id); + if (!compare_trees(t1->root, t2->root)) { + printf("Comparison failure in TX %u after serdes:\n", tx_id); + dump_ref("Original", t1->root, 0); + dump_ref("Deserialised", t2->root, 0); + g_assert(0); + } + g_assert(t1->nr_nodes =3D=3D t2->nr_nodes); +} + static int write_str(XenstoreImplState *s, unsigned int dom_id, unsigned int tx_id, const char *path, const char *content) @@ -78,6 +237,40 @@ static void watch_cb(void *_str, const char *path, cons= t char *token) g_string_append(str, token); } =20 +static void check_serdes(XenstoreImplState *s) +{ + XenstoreImplState *s2 =3D xs_impl_create(DOMID_GUEST); + GByteArray *bytes =3D xs_impl_serialize(s); + int nr_transactions1, nr_transactions2; + int ret; + + ret =3D xs_impl_deserialize(s2, bytes, DOMID_GUEST, watch_cb, NULL); + g_assert(!ret); + + g_byte_array_unref(bytes); + + g_assert(s->last_tx =3D=3D s2->last_tx); + g_assert(s->root_tx =3D=3D s2->root_tx); + + if (!compare_trees(s->root, s2->root)) { + printf("Comparison failure in main tree after serdes:\n"); + dump_ref("Original", s->root, 0); + dump_ref("Deserialised", s2->root, 0); + g_assert(0); + } + + nr_transactions1 =3D g_hash_table_size(s->transactions); + nr_transactions2 =3D g_hash_table_size(s2->transactions); + g_assert(nr_transactions1 =3D=3D nr_transactions2); + + g_hash_table_foreach(s->transactions, compare_tx, s2); + + g_assert(s->nr_domu_watches =3D=3D s2->nr_domu_watches); + g_assert(s->nr_domu_transactions =3D=3D s2->nr_domu_transactions); + g_assert(s->nr_nodes =3D=3D s2->nr_nodes); + xs_impl_delete(s2, false); +} + static XenstoreImplState *setup(void) { XenstoreImplState *s =3D xs_impl_create(DOMID_GUEST); @@ -293,7 +486,7 @@ static void test_xs_node_simple(void) g_string_free(qemu_watches, true); g_string_free(guest_watches, true); xs_node_unref(old_root); - xs_impl_delete(s); + xs_impl_delete(s, true); } =20 =20 @@ -365,6 +558,8 @@ static void do_test_xs_node_tx(bool fail, bool commit) g_assert(!memcmp(data->data, "something else", data->len)); g_byte_array_set_size(data, 0); =20 + check_serdes(s); + /* Attempt to commit the transaction */ err =3D xs_impl_transaction_end(s, DOMID_GUEST, tx_id, commit); if (commit && fail) { @@ -381,6 +576,8 @@ static void do_test_xs_node_tx(bool fail, bool commit) } g_assert(s->nr_nodes =3D=3D 7); =20 + check_serdes(s); + err =3D xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", watch_cb, watches); g_assert(!err); @@ -399,7 +596,7 @@ static void do_test_xs_node_tx(bool fail, bool commit) } g_byte_array_unref(data); g_string_free(watches, true); - xs_impl_delete(s); + xs_impl_delete(s, true); } =20 static void test_xs_node_tx_fail(void) @@ -461,6 +658,8 @@ static void test_xs_node_tx_rm(void) g_assert(!memcmp(data->data, "something", data->len)); g_byte_array_set_size(data, 0); =20 + check_serdes(s); + /* Commit the transaction */ err =3D xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true); g_assert(!err); @@ -480,7 +679,7 @@ static void test_xs_node_tx_rm(void) g_assert(!err); =20 g_string_free(watches, true); - xs_impl_delete(s); + xs_impl_delete(s, true); } =20 static void test_xs_node_tx_resurrect(void) @@ -499,11 +698,16 @@ static void test_xs_node_tx_resurrect(void) g_assert(!err); g_assert(s->nr_nodes =3D=3D 9); =20 + /* Another node to remain shared */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/place/safe", "keepme= "); + g_assert(!err); + g_assert(s->nr_nodes =3D=3D 11); + /* This node will be wiped and resurrected */ err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark", "foo"); g_assert(!err); - g_assert(s->nr_nodes =3D=3D 9); + g_assert(s->nr_nodes =3D=3D 11); =20 /* Set a watch */ err =3D xs_impl_watch(s, DOMID_GUEST, "some", "watch", @@ -520,19 +724,23 @@ static void test_xs_node_tx_resurrect(void) /* Delete the tree in the transaction */ err =3D xs_impl_rm(s, DOMID_GUEST, tx_id, "some/deep"); g_assert(!err); - g_assert(s->nr_nodes =3D=3D 9); + g_assert(s->nr_nodes =3D=3D 11); g_assert(!watches->len); =20 /* Resurrect part of it */ err =3D write_str(s, DOMID_GUEST, tx_id, "some/deep/dark/different/pat= h", "something"); g_assert(!err); - g_assert(s->nr_nodes =3D=3D 9); + g_assert(s->nr_nodes =3D=3D 11); + + check_serdes(s); =20 /* Commit the transaction */ err =3D xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true); g_assert(!err); - g_assert(s->nr_nodes =3D=3D 9); + g_assert(s->nr_nodes =3D=3D 11); + + check_serdes(s); =20 /* lost data */ g_assert(strstr(watches->str, "some/deep/dark/different/pathwatch")); @@ -549,12 +757,14 @@ static void test_xs_node_tx_resurrect(void) g_assert(err =3D=3D ENOENT); g_byte_array_unref(data); =20 + check_serdes(s); + err =3D xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", watch_cb, watches); g_assert(!err); =20 g_string_free(watches, true); - xs_impl_delete(s); + xs_impl_delete(s, true); } =20 static void test_xs_node_tx_resurrect2(void) @@ -608,11 +818,15 @@ static void test_xs_node_tx_resurrect2(void) g_assert(!err); g_assert(s->nr_nodes =3D=3D 11); =20 + check_serdes(s); + /* Commit the transaction */ err =3D xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true); g_assert(!err); g_assert(s->nr_nodes =3D=3D 11); =20 + check_serdes(s); + /* lost data */ g_assert(strstr(watches->str, "some/deep/dark/relative/pathwatch")); /* lost data */ @@ -629,12 +843,14 @@ static void test_xs_node_tx_resurrect2(void) =20 g_byte_array_unref(data); =20 + check_serdes(s); + err =3D xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", watch_cb, watches); g_assert(!err); =20 g_string_free(watches, true); - xs_impl_delete(s); + xs_impl_delete(s, true); } =20 int main(int argc, char **argv) --=20 2.39.0