From nobody Tue May 21 02:38:50 2024 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 1675263774559728.8766620423069; Wed, 1 Feb 2023 07:02:54 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pNENY-0004Eq-Kz; Wed, 01 Feb 2023 09:46:29 -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 1pNELN-0001X1-Qd for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:19 -0500 Received: from casper.infradead.org ([2001:8b0:10b:1236::1]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pNELG-0005a8-4E for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:12 -0500 Received: from i7.infradead.org ([2001:8b0:10b:1:21e:67ff:fecb:7a92]) by casper.infradead.org with esmtpsa (Exim 4.94.2 #2 (Red Hat Linux)) id 1pNELA-00CNSG-9t; Wed, 01 Feb 2023 14:44:00 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.96 #2 (Red Hat Linux)) id 1pNELA-007Jw7-0z; Wed, 01 Feb 2023 14:44:00 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=LJUzhC7C/kPMen1bYSCp06Y4c8gk1Q8tUyrjl0PFZok=; b=cR/QEir+tCvWEi75HIfxEGjbYt 8LBukL591WP1/P++LRNO28xPYK0Tj36p4ZuYwqrfPIT5BbDs/2Zr9mF6HdCucD4kRsPYolq9vFk01 fPzFeOJTWUrD30g3epwWm6z+BAY/bnQ/XAnfHT9zmPIUXcC+t+yLCNfQVERyiNKDst1oUYejJrDLE QRudrRoP7ajHb1wXXgcfiXQtX8rCNDu+irermg67vNCsFs85RHtc8teJD2Ou+0gs1kknhvo27DgDj APCWZs1J+jccM1IPumomiafXHglcMQcG9PMKhdKTB1aJgWfOt8Z0s03PN9D6qocr+xYDkWTX5mxlb YewMspBw==; From: David Woodhouse To: Peter Maydell , qemu-devel@nongnu.org Cc: Paolo Bonzini , Paul Durrant , Joao Martins , Ankur Arora , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Thomas Huth , =?UTF-8?q?Alex=20Benn=C3=A9e?= , Juan Quintela , "Dr . David Alan Gilbert" , Claudio Fontana , Julien Grall , "Michael S. Tsirkin" , Marcel Apfelbaum , armbru@redhat.com Subject: [RFC PATCH v1 1/8] hw/xen: Add xenstore wire implementation and implementation stubs Date: Wed, 1 Feb 2023 14:43:51 +0000 Message-Id: <20230201144358.1744876-2-dwmw2@infradead.org> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230201144358.1744876-1-dwmw2@infradead.org> References: <20230201144358.1744876-1-dwmw2@infradead.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-SRS-Rewrite: SMTP reverse-path rewritten from by casper.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:1236::1; envelope-from=BATV+8c5eeea0684575598b25+7101+infradead.org+dwmw2@casper.srs.infradead.org; helo=casper.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: 1675263776770100001 From: David Woodhouse This implements the basic wire protocol for the XenStore commands, punting all the actual implementation to xs_impl_* functions which all just return errors for now. Signed-off-by: David Woodhouse --- hw/i386/kvm/meson.build | 1 + hw/i386/kvm/trace-events | 14 + hw/i386/kvm/xen_xenstore.c | 746 +++++++++++++++++++++++++++++++++++- hw/i386/kvm/xenstore_impl.c | 116 ++++++ hw/i386/kvm/xenstore_impl.h | 53 +++ 5 files changed, 921 insertions(+), 9 deletions(-) create mode 100644 hw/i386/kvm/xenstore_impl.c create mode 100644 hw/i386/kvm/xenstore_impl.h diff --git a/hw/i386/kvm/meson.build b/hw/i386/kvm/meson.build index 82dd6ae7c6..6621ba5cd7 100644 --- a/hw/i386/kvm/meson.build +++ b/hw/i386/kvm/meson.build @@ -9,6 +9,7 @@ i386_kvm_ss.add(when: 'CONFIG_XEN_EMU', if_true: files( 'xen_evtchn.c', 'xen_gnttab.c', 'xen_xenstore.c', + 'xenstore_impl.c', )) =20 i386_ss.add_all(when: 'CONFIG_KVM', if_true: i386_kvm_ss) diff --git a/hw/i386/kvm/trace-events b/hw/i386/kvm/trace-events index b83c3eb965..69b129a66c 100644 --- a/hw/i386/kvm/trace-events +++ b/hw/i386/kvm/trace-events @@ -3,3 +3,17 @@ kvm_xen_unmap_pirq(int pirq, int gsi) "pirq %d gsi %d" kvm_xen_get_free_pirq(int pirq, int type) "pirq %d type %d" kvm_xen_bind_pirq(int pirq, int port) "pirq %d port %d" kvm_xen_unmask_pirq(int pirq, char *dev, int vector) "pirq %d dev %s vecto= r %d" +xenstore_error(unsigned int id, unsigned int tx_id, const char *err) "req = %u tx %u err %s" +xenstore_read(unsigned int tx_id, const char *path) "tx %u path %s" +xenstore_write(unsigned int tx_id, const char *path) "tx %u path %s" +xenstore_mkdir(unsigned int tx_id, const char *path) "tx %u path %s" +xenstore_directory(unsigned int tx_id, const char *path) "tx %u path %s" +xenstore_transaction_start(unsigned int new_tx) "new_tx %u" +xenstore_transaction_end(unsigned int tx_id, bool commit) "tx %u commit %d" +xenstore_rm(unsigned int tx_id, const char *path) "tx %u path %s" +xenstore_get_perms(unsigned int tx_id, const char *path) "tx %u path %s" +xenstore_set_perms(unsigned int tx_id, const char *path) "tx %u path %s" +xenstore_watch(const char *path, const char *token) "path %s token %s" +xenstore_unwatch(const char *path, const char *token) "path %s token %s" +xenstore_reset_watches(void) "" +xenstore_watch_event(const char *path, const char *token) "path %s token %= s" diff --git a/hw/i386/kvm/xen_xenstore.c b/hw/i386/kvm/xen_xenstore.c index 2388842d15..eb810c371e 100644 --- a/hw/i386/kvm/xen_xenstore.c +++ b/hw/i386/kvm/xen_xenstore.c @@ -28,6 +28,10 @@ #include "sysemu/kvm.h" #include "sysemu/kvm_xen.h" =20 +#include "trace.h" + +#include "xenstore_impl.h" + #include "hw/xen/interface/io/xs_wire.h" #include "hw/xen/interface/event_channel.h" =20 @@ -47,6 +51,9 @@ struct XenXenstoreState { SysBusDevice busdev; /*< public >*/ =20 + XenstoreImplState *impl; + GList *watch_events; + MemoryRegion xenstore_page; struct xenstore_domain_interface *xs; uint8_t req_data[XENSTORE_HEADER_SIZE + XENSTORE_PAYLOAD_MAX]; @@ -64,6 +71,7 @@ struct XenXenstoreState { struct XenXenstoreState *xen_xenstore_singleton; =20 static void xen_xenstore_event(void *opaque); +static void fire_watch_cb(void *opaque, const char *path, const char *toke= n); =20 static void xen_xenstore_realize(DeviceState *dev, Error **errp) { @@ -89,6 +97,8 @@ static void xen_xenstore_realize(DeviceState *dev, Error = **errp) } aio_set_fd_handler(qemu_get_aio_context(), xen_be_evtchn_fd(s->eh), tr= ue, xen_xenstore_event, NULL, NULL, NULL, s); + + s->impl =3D xs_impl_create(); } =20 static bool xen_xenstore_is_needed(void *opaque) @@ -209,20 +219,635 @@ static void reset_rsp(XenXenstoreState *s) s->rsp_offset =3D 0; } =20 +static void xs_error(XenXenstoreState *s, unsigned int id, unsigned int tx= _id, + int errnum) +{ + struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; + const char *errstr =3D NULL; + + for (unsigned int i =3D 0; i < ARRAY_SIZE(xsd_errors); i++) { + struct xsd_errors *xsd_error =3D &xsd_errors[i]; + + if (xsd_error->errnum =3D=3D errnum) { + errstr =3D xsd_error->errstring; + break; + } + } + assert(errstr); + + trace_xenstore_error(id, tx_id, errstr); + + rsp->type =3D XS_ERROR; + rsp->req_id =3D id; + rsp->tx_id =3D tx_id; + rsp->len =3D (uint32_t)strlen(errstr) + 1; + + memcpy(&rsp[1], errstr, rsp->len); +} + +static void xs_ok(XenXenstoreState *s, unsigned int type, unsigned int req= _id, + unsigned int tx_id) +{ + struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; + const char *okstr =3D "OK"; + + rsp->type =3D type; + rsp->req_id =3D req_id; + rsp->tx_id =3D tx_id; + rsp->len =3D (uint32_t)strlen(okstr) + 1; + + memcpy(&rsp[1], okstr, rsp->len); +} + +/* + * The correct request and response formats are documented in xen.git: + * docs/misc/xenstore.txt. A summary is given below for convenience. + * The '|' symbol represents a NUL character. + * + * TRANSACTION_START | | + * is an opaque uint32_t allocated by xenstored + * represented as unsigned decimal. After this, transaction may + * be referenced by using (as 32-bit binary) in the + * tx_id request header field. When transaction is started whole + * db is copied; reads and writes happen on the copy. + * It is not legal to send non-0 tx_id in TRANSACTION_START. + * + * TRANSACTION_END T| OK| + * TRANSACTION_END F| OK| + * tx_id must refer to existing transaction. After this + * request the tx_id is no longer valid and may be reused by + * xenstore. If F, the transaction is discarded. If T, + * it is committed: if there were any other intervening writes + * then our END gets get EAGAIN. + * + * The plan is that in the future only intervening `conflicting' + * writes cause EAGAIN, meaning only writes or other commits + * which changed paths which were read or written in the + * transaction at hand. + * + * READ | + * WRITE | OK| + * Store and read the octet string at . + * WRITE creates any missing parent paths, with empty values. + * + * MKDIR | OK| + * Ensures that the exists, by necessary by creating + * it and any missing parents with empty values. If + * or any parent already exists, its value is left unchanged. + * + * RM | + * Ensures that the does not exist, by deleting + * it and all of its children. It is not an error if does + * not exist, but it _is_ an error if 's immediate parent + * does not exist either. + * + * DIRECTORY | |* + * Gives a list of the immediate children of , as only the + * leafnames. The resulting children are each named + * /. + * + * GET_PERMS | |+ + * SET_PERMS ||+? + * is one of the following: + * w write only + * r read only + * b both read and write + * n no access + * See the comment block at the top of xs.cpp for more information on + * XenStore permissions. + * + * WATCH ||? + * Adds a watch. + * + * When a is modified (including path creation, removal, + * contents change or permissions change) this generates an event + * on the changed . Changes made in transactions cause an + * event only if and when committed. Each occurring event is + * matched against all the watches currently set up, and each + * matching watch results in a WATCH_EVENT message (see below). + * + * The event's path matches the watch's if it is an child + * of . + * + * When a watch is first set up it is triggered once straight + * away, with equal to . Watches may be triggered + * spuriously. The tx_id in a WATCH request is ignored. + * + * Watches are supposed to be restricted by the permissions + * system but in practice the implementation is imperfect. + * Applications should not rely on being sent a notification for + * paths that they cannot read; however, an application may rely + * on being sent a watch when a path which it _is_ able to read + * is deleted even if that leaves only a nonexistent unreadable + * parent. A notification may omitted if a node's permissions + * are changed so as to make it unreadable, in which case future + * notifications may be suppressed (and if the node is later made + * readable, some notifications may have been lost). + * + * WATCH_EVENT || + * Unsolicited `reply' generated for matching modification events + * as described above. req_id and tx_id are both 0. + * + * is the event's path, ie the actual path that was + * modified; however if the event was the recursive removal of an + * parent of , is just + * (rather than the actual path which was removed). So + * is a child of , regardless. + * + * Iff for the watch was specified as a relative pathname, + * the path will also be relative (with the same base, + * obviously). + * + * UNWATCH ||? + * + * RESET_WATCHES | + * Reset all watches and transactions of the caller. + */ + +static void xs_read(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *req_data, unsigned int le= n) +{ + const char *path =3D (const char *)req_data; + struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; + uint8_t *rsp_data =3D (uint8_t *)&rsp[1]; + g_autoptr(GByteArray) data =3D g_byte_array_new(); + int err; + + if (len =3D=3D 0 || req_data[len - 1] !=3D '\0') { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + trace_xenstore_read(tx_id, path); + err =3D xs_impl_read(s->impl, xen_domid, tx_id, path, data); + if (err) { + xs_error(s, req_id, tx_id, err); + return; + } + + rsp->type =3D XS_READ; + rsp->req_id =3D req_id; + rsp->tx_id =3D tx_id; + rsp->len =3D 0; + + len =3D data->len; + if (len > XENSTORE_PAYLOAD_MAX) { + xs_error(s, req_id, tx_id, E2BIG); + return; + } + + memcpy(&rsp_data[rsp->len], data->data, len); + rsp->len +=3D len; +} + +static void xs_write(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *req_data, + unsigned int len) +{ + g_autoptr(GByteArray) data =3D g_byte_array_new(); + const char *path; + int err; + + if (len =3D=3D 0) { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + path =3D (const char *)req_data; + + while (len--) { + if (*req_data++ =3D=3D '\0') + break; + if (len =3D=3D 0) { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + } + + g_byte_array_append(data, req_data, len); + + trace_xenstore_write(tx_id, path); + err =3D xs_impl_write(s->impl, xen_domid, tx_id, path, data); + if (err) { + xs_error(s, req_id, tx_id, err); + return; + } + + xs_ok(s, XS_WRITE, req_id, tx_id); +} + +static void xs_mkdir(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *req_data, unsigned int l= en) +{ + g_autoptr(GByteArray) data =3D g_byte_array_new(); + const char *path; + int err; + + if (len =3D=3D 0 || req_data[len - 1] !=3D '\0') { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + path =3D (const char *)req_data; + + trace_xenstore_mkdir(tx_id, path); + err =3D xs_impl_read(s->impl, xen_domid, tx_id, path, data); + if (err =3D=3D ENOENT) + err =3D xs_impl_write(s->impl, xen_domid, tx_id, path, data); + + if (!err) { + xs_error(s, req_id, tx_id, err); + return; + } + + xs_ok(s, XS_MKDIR, req_id, tx_id); +} + +static void xs_append_strings(XenXenstoreState *s, struct xsd_sockmsg *rsp, + GList *strings) +{ + uint8_t *rsp_data =3D (uint8_t *)&rsp[1]; + GList *l; + + for (l =3D strings; l; l =3D l->next) { + size_t len =3D strlen(l->data); + + if (rsp->len + len >=3D XENSTORE_PAYLOAD_MAX) { + xs_error(s, rsp->req_id, rsp->tx_id, E2BIG); + return; + } + + memcpy(&rsp_data[rsp->len], l->data, len); + rsp->len +=3D len; + + rsp_data[rsp->len] =3D '\0'; + rsp->len++; + } +} + +static void xs_directory(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *req_data, + unsigned int len) +{ + struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; + GList *items =3D NULL; + const char *path; + int err; + + if (len =3D=3D 0 || req_data[len - 1] !=3D '\0') { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + path =3D (const char *)req_data; + + trace_xenstore_directory(tx_id, path); + err =3D xs_impl_directory(s->impl, xen_domid, tx_id, path, &items); + if (err !=3D 0) { + xs_error(s, req_id, tx_id, err); + return; + } + + rsp->type =3D XS_DIRECTORY; + rsp->req_id =3D req_id; + rsp->tx_id =3D tx_id; + rsp->len =3D 0; + + xs_append_strings(s, rsp, items); + + g_list_free_full(items, g_free); +} + +static void xs_transaction_start(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *req_data, + unsigned int len) +{ + struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; + char *rsp_data =3D (char *)&rsp[1]; + int err; + + if (len !=3D 1 || req_data[0] !=3D '\0') { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + rsp->type =3D XS_TRANSACTION_START; + rsp->req_id =3D req_id; + rsp->tx_id =3D tx_id; + rsp->len =3D 0; + + err =3D xs_impl_transaction_start(s->impl, xen_domid, &tx_id); + if (err) { + xs_error(s, req_id, tx_id, err); + return; + } + + trace_xenstore_transaction_start(tx_id); + + rsp->len =3D snprintf(rsp_data, XENSTORE_PAYLOAD_MAX, "%u", tx_id); + assert(rsp->len < XENSTORE_PAYLOAD_MAX); + rsp->len++; +} + +static void xs_transaction_end(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *req_data, + unsigned int len) +{ + bool commit; + int err; + + if (len !=3D 2 || req_data[1] !=3D '\0') { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + switch (req_data[0]) { + case 'T': + commit =3D true; + break; + case 'F': + commit =3D false; + break; + default: + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + trace_xenstore_transaction_end(tx_id, commit); + err =3D xs_impl_transaction_end(s->impl, xen_domid, tx_id, commit); + if (err) { + xs_error(s, req_id, tx_id, err); + return; + } + + xs_ok(s, XS_TRANSACTION_END, req_id, tx_id); +} + +static void xs_rm(XenXenstoreState *s, unsigned int req_id, unsigned int t= x_id, + uint8_t *req_data, unsigned int len) +{ + const char *path =3D (const char *)req_data; + int err; + + if (len =3D=3D 0 || req_data[len - 1] !=3D '\0') { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + trace_xenstore_rm(tx_id, path); + err =3D xs_impl_rm(s->impl, xen_domid, tx_id, path); + if (err) { + xs_error(s, req_id, tx_id, err); + return; + } + + xs_ok(s, XS_RM, req_id, tx_id); +} + +static void xs_get_perms(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *req_data, + unsigned int len) +{ + const char *path =3D (const char *)req_data; + struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; + GList *perms =3D NULL; + int err; + + if (len =3D=3D 0 || req_data[len - 1] !=3D '\0') { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + trace_xenstore_get_perms(tx_id, path); + err =3D xs_impl_get_perms(s->impl, xen_domid, tx_id, path, &perms); + if (err) { + xs_error(s, req_id, tx_id, err); + return; + } + + rsp->type =3D XS_GET_PERMS; + rsp->req_id =3D req_id; + rsp->tx_id =3D tx_id; + rsp->len =3D 0; + + xs_append_strings(s, rsp, perms); + + g_list_free_full(perms, g_free); +} + +static void xs_set_perms(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *req_data, + unsigned int len) +{ + const char *path =3D (const char *)req_data; + uint8_t *perm; + GList *perms =3D NULL; + int err; + + if (len =3D=3D 0) { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + while (len--) { + if (*req_data++ =3D=3D '\0') + break; + if (len =3D=3D 0) { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + } + + perm =3D req_data; + while (len--) { + if (*req_data++ =3D=3D '\0') { + perms =3D g_list_append(perms, perm); + perm =3D req_data; + } + } + + /* + * Note that there may be trailing garbage at the end of the buffer. + * This is explicitly permitted by the '?' at the end of the definitio= n: + * + * SET_PERMS ||+? + */ + + trace_xenstore_set_perms(tx_id, path); + err =3D xs_impl_set_perms(s->impl, xen_domid, tx_id, path, perms); + g_list_free(perms); + if (err) { + xs_error(s, req_id, tx_id, err); + return; + } + + xs_ok(s, XS_SET_PERMS, req_id, tx_id); +} + +static void xs_watch(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *req_data, unsigned int l= en) +{ + const char *token, *path =3D (const char *)req_data; + int err; + + if (len =3D=3D 0) { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + while (len--) { + if (*req_data++ =3D=3D '\0') + break; + if (len =3D=3D 0) { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + } + + token =3D (const char *)req_data; + while (len--) { + if (*req_data++ =3D=3D '\0') + break; + if (len =3D=3D 0) { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + } + + /* + * Note that there may be trailing garbage at the end of the buffer. + * This is explicitly permitted by the '?' at the end of the definitio= n: + * + * WATCH ||? + */ + + trace_xenstore_watch(path, token); + err =3D xs_impl_watch(s->impl, xen_domid, path, token, fire_watch_cb, = s); + if (err) { + xs_error(s, req_id, tx_id, err); + return; + } + + xs_ok(s, XS_WATCH, req_id, tx_id); +} + +static void xs_unwatch(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *req_data, unsigned int= len) +{ + const char *token, *path =3D (const char *)req_data; + int err; + + if (len =3D=3D 0) { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + while (len--) { + if (*req_data++ =3D=3D '\0') + break; + if (len =3D=3D 0) { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + } + + token =3D (const char *)req_data; + while (len--) { + if (*req_data++ =3D=3D '\0') + break; + if (len =3D=3D 0) { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + } + + trace_xenstore_unwatch(path, token); + err =3D xs_impl_unwatch(s->impl, xen_domid, path, token, fire_watch_cb= , s); + if (err) { + xs_error(s, req_id, tx_id, err); + return; + } + + xs_ok(s, XS_UNWATCH, req_id, tx_id); +} + +static void xs_reset_watches(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *req_data, + unsigned int len) +{ + if (len =3D=3D 0 || req_data[len - 1] !=3D '\0') { + xs_error(s, req_id, tx_id, EINVAL); + return; + } + + trace_xenstore_reset_watches(); + xs_impl_reset_watches(s->impl, xen_domid); + + xs_ok(s, XS_RESET_WATCHES, req_id, tx_id); +} + +static void xs_priv(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *data, + unsigned int len) +{ + xs_error(s, req_id, tx_id, EACCES); +} + +static void xs_unimpl(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *data, + unsigned int len) +{ + xs_error(s, req_id, tx_id, ENOSYS); +} + +typedef void (*xs_impl)(XenXenstoreState *s, unsigned int req_id, + unsigned int tx_id, uint8_t *data, + unsigned int len); + +struct xsd_req { + const char *name; + xs_impl fn; +}; +#define XSD_REQ(_type, _fn) \ + [_type] =3D { .name =3D #_type, .fn =3D _fn } + +struct xsd_req xsd_reqs[] =3D { + XSD_REQ(XS_READ, xs_read), + XSD_REQ(XS_WRITE, xs_write), + XSD_REQ(XS_MKDIR, xs_mkdir), + XSD_REQ(XS_DIRECTORY, xs_directory), + XSD_REQ(XS_TRANSACTION_START, xs_transaction_start), + XSD_REQ(XS_TRANSACTION_END, xs_transaction_end), + XSD_REQ(XS_RM, xs_rm), + XSD_REQ(XS_GET_PERMS, xs_get_perms), + XSD_REQ(XS_SET_PERMS, xs_set_perms), + XSD_REQ(XS_WATCH, xs_watch), + XSD_REQ(XS_UNWATCH, xs_unwatch), + XSD_REQ(XS_CONTROL, xs_priv), + XSD_REQ(XS_INTRODUCE, xs_priv), + XSD_REQ(XS_RELEASE, xs_priv), + XSD_REQ(XS_IS_DOMAIN_INTRODUCED, xs_priv), + XSD_REQ(XS_RESUME, xs_priv), + XSD_REQ(XS_SET_TARGET, xs_priv), + XSD_REQ(XS_RESET_WATCHES, xs_reset_watches), +}; + static void process_req(XenXenstoreState *s) { struct xsd_sockmsg *req =3D (struct xsd_sockmsg *)s->req_data; - struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; - const char enosys[] =3D "ENOSYS"; + xs_impl handler =3D NULL; =20 assert(req_pending(s)); - assert(!s->rsp_pending); + assert(!s->rsp_pending); =20 - rsp->type =3D XS_ERROR; - rsp->req_id =3D req->req_id; - rsp->tx_id =3D req->tx_id; - rsp->len =3D sizeof(enosys); - memcpy((void *)&rsp[1], enosys, sizeof(enosys)); + if (req->type < ARRAY_SIZE(xsd_reqs)) { + handler =3D xsd_reqs[req->type].fn; + } + if (!handler) { + handler =3D &xs_unimpl; + } + + handler(s, req->req_id, req->tx_id, (uint8_t *)&req[1], req->len); =20 s->rsp_pending =3D true; reset_req(s); @@ -382,6 +1007,105 @@ static unsigned int put_rsp(XenXenstoreState *s) return copylen; } =20 +static void deliver_watch(XenXenstoreState *s, const char *path, + const char *token) +{ + struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; + uint8_t *rsp_data =3D (uint8_t *)&rsp[1]; + unsigned int len; + + assert(!s->rsp_pending); + + trace_xenstore_watch_event(path, token); + + rsp->type =3D XS_WATCH_EVENT; + rsp->req_id =3D 0; + rsp->tx_id =3D 0; + rsp->len =3D 0; + + len =3D strlen(path); + + /* XENSTORE_ABS/REL_PATH_MAX should ensure there can be no overflow */ + assert(rsp->len + len < XENSTORE_PAYLOAD_MAX); + + memcpy(&rsp_data[rsp->len], path, len); + rsp->len +=3D len; + rsp_data[rsp->len] =3D '\0'; + rsp->len++; + + len =3D strlen(token); + /* + * It is possible for the guest to have chosen a token that will + * not fit (along with the patch) into a watch event. We have no + * choice but to drop the event if this is the case. + */ + if (rsp->len + len >=3D XENSTORE_PAYLOAD_MAX) + return; + + memcpy(&rsp_data[rsp->len], token, len); + rsp->len +=3D len; + rsp_data[rsp->len] =3D '\0'; + rsp->len++; + + s->rsp_pending =3D true; +} + +struct watch_event { + char *path; + char *token; +}; + +static void queue_watch(XenXenstoreState *s, const char *path, + const char *token) +{ + struct watch_event *ev =3D g_new0(struct watch_event, 1); + + ev->path =3D g_strdup(path); + ev->token =3D g_strdup(token); + + s->watch_events =3D g_list_append(s->watch_events, ev); +} + +static void fire_watch_cb(void *opaque, const char *path, const char *toke= n) +{ + XenXenstoreState *s =3D opaque; + + assert(qemu_mutex_iothread_locked()); + + /* + * If there's a response pending, we obviously can't scribble over + * it. But if there's a request pending, it has dibs on the buffer + * too. + * + * In the common case of a watch firing due to backend activity + * when the ring was otherwise idle, we should be able to copy the + * strings directly into the rsp_data and thence the actual ring, + * without needing to perform any allocations and queue them. + */ + if (s->rsp_pending || req_pending(s)) { + queue_watch(s, path, token); + } else { + deliver_watch(s, path, token); + /* + * If the message was queued because there was already ring activi= ty, + * no need to wake the guest. But if not, we need to send the evtc= hn. + */ + xen_be_evtchn_notify(s->eh, s->be_port); + } +} + +static void process_watch_events(XenXenstoreState *s) +{ + struct watch_event *ev =3D s->watch_events->data; + + deliver_watch(s, ev->path, ev->token); + + s->watch_events =3D g_list_remove(s->watch_events, ev); + g_free(ev->path); + g_free(ev->token); + g_free(ev); +} + static void xen_xenstore_event(void *opaque) { XenXenstoreState *s =3D opaque; @@ -400,13 +1124,17 @@ static void xen_xenstore_event(void *opaque) copied_to =3D copied_from =3D 0; processed =3D false; =20 + if (!s->rsp_pending && s->watch_events) { + process_watch_events(s); + } + if (s->rsp_pending) copied_to =3D put_rsp(s); =20 if (!req_pending(s)) copied_from =3D get_req(s); =20 - if (req_pending(s) && !s->rsp_pending) { + if (req_pending(s) && !s->rsp_pending && !s->watch_events) { process_req(s); processed =3D true; } diff --git a/hw/i386/kvm/xenstore_impl.c b/hw/i386/kvm/xenstore_impl.c new file mode 100644 index 0000000000..d44c403426 --- /dev/null +++ b/hw/i386/kvm/xenstore_impl.c @@ -0,0 +1,116 @@ +/* + * QEMU Xen emulation: The actual implementation of XenStore + * + * Copyright =C2=A9 2023 Amazon.com, Inc. or its affiliates. All Rights Re= served. + * + * Authors: David Woodhouse , Paul Durrant + * + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" + +#include "xen_xenstore.h" +#include "xenstore_impl.h" + +struct XenstoreImplState { +}; + +int xs_impl_read(XenstoreImplState *s, unsigned int dom_id, unsigned int t= x_id, + const char *path, GByteArray *data) +{ + /* + * The data GByteArray shall exist, and will be freed by caller. + * Just g_byte_array_append() to it. + */ + return ENOENT; +} + +int xs_impl_write(XenstoreImplState *s, unsigned int dom_id, + unsigned int tx_id, const char *path, GByteArray *data) +{ + /* + * The data GByteArray shall exist, will be freed by caller. You are + * free to use g_byte_array_steal() and keep the data. + */ + return ENOSYS; +} + +int xs_impl_directory(XenstoreImplState *s, unsigned int dom_id, + unsigned int tx_id, const char *path, GList **items) +{ + /* + * The items are (char *) to be freed by caller. Although it's consumed + * immediately so if you want to change it to (const char *) and keep + * them, go ahead and change the caller. + */ + return ENOENT; +} + +int xs_impl_transaction_start(XenstoreImplState *s, unsigned int dom_id, + unsigned int *tx_id) +{ + return ENOSYS; +} + +int xs_impl_transaction_end(XenstoreImplState *s, unsigned int dom_id, + unsigned int tx_id, bool commit) +{ + return ENOSYS; +} + +int xs_impl_rm(XenstoreImplState *s, unsigned int dom_id, unsigned int tx_= id, + const char *path) +{ + return ENOSYS; +} + +int xs_impl_get_perms(XenstoreImplState *s, unsigned int dom_id, + unsigned int tx_id, const char *path, GList **perms) +{ + /* + * The perms are (char *) in the wire format to be + * freed by the caller. + */ + return ENOSYS; +} + +int xs_impl_set_perms(XenstoreImplState *s, unsigned int dom_id, + unsigned int tx_id, const char *path, GList *perms) +{ + /* + * The perms are (const char *) in the wire format. + */ + return ENOSYS; +} + +int xs_impl_watch(XenstoreImplState *s, unsigned int dom_id, const char *p= ath, + const char *token, xs_impl_watch_fn fn, void *opaque) +{ + /* + * When calling the callback @fn, note that the path should + * precisely match the relative path that the guest provided, even + * if it was a relative path which needed to be prefixed with + * /local/domain/${domid}/ + */ + return ENOSYS; +} + +int xs_impl_unwatch(XenstoreImplState *s, unsigned int dom_id, + const char *path, const char *token, + xs_impl_watch_fn fn, void *opaque) +{ + /* Remove the watch that matches all four criteria */ + return ENOSYS; +} + +int xs_impl_reset_watches(XenstoreImplState *s, unsigned int dom_id) +{ + return ENOSYS; +} + +XenstoreImplState *xs_impl_create(void) +{ + return g_new0(XenstoreImplState, 1); +} diff --git a/hw/i386/kvm/xenstore_impl.h b/hw/i386/kvm/xenstore_impl.h new file mode 100644 index 0000000000..deefc4c412 --- /dev/null +++ b/hw/i386/kvm/xenstore_impl.h @@ -0,0 +1,53 @@ +/* + * QEMU Xen emulation: The actual implementation of XenStore + * + * Copyright =C2=A9 2023 Amazon.com, Inc. or its affiliates. All Rights Re= served. + * + * Authors: David Woodhouse + * + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * See the COPYING file in the top-level directory. + */ + +#ifndef __QEMU_XENSTORE_IMPL_H__ +#define __QEMU_XENSTORE_IMPL_H__ + +typedef struct XenstoreImplState XenstoreImplState; + +XenstoreImplState *xs_impl_create(void); + +/* + * These functions return *positive* error numbers. This is a little + * unconventional but it helps to keep us honest because there is + * also a very limited set of error numbers that they are permitted + * to return (those in xsd_errors). + */ + +int xs_impl_read(XenstoreImplState *s, unsigned int dom_id, unsigned int t= x_id, + const char *path, GByteArray *data); +int xs_impl_write(XenstoreImplState *s, unsigned int dom_id, + unsigned int tx_id, const char *path, GByteArray *data); +int xs_impl_directory(XenstoreImplState *s, unsigned int dom_id, + unsigned int tx_id, const char *path, GList **items); +int xs_impl_transaction_start(XenstoreImplState *s, unsigned int dom_id, + unsigned int *tx_id); +int xs_impl_transaction_end(XenstoreImplState *s, unsigned int dom_id, + unsigned int tx_id, bool commit); +int xs_impl_rm(XenstoreImplState *s, unsigned int dom_id, unsigned int tx_= id, + const char *path); +int xs_impl_get_perms(XenstoreImplState *s, unsigned int dom_id, + unsigned int tx_id, const char *path, GList **perms); +int xs_impl_set_perms(XenstoreImplState *s, unsigned int dom_id, + unsigned int tx_id, const char *path, GList *perms); + +/* This differs from xs_watch_fn because it has the token */ +typedef void(xs_impl_watch_fn)(void *opaque, const char *path, + const char *token); +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 xs_impl_unwatch(XenstoreImplState *s, unsigned int dom_id, + const char *path, const char *token, xs_impl_watch_fn = fn, + void *opaque); +int xs_impl_reset_watches(XenstoreImplState *s, unsigned int dom_id); + +#endif /* __QEMU_XENSTORE_IMPL_H__ */ --=20 2.39.0 From nobody Tue May 21 02:38:50 2024 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 1675267832344313.1106982887429; Wed, 1 Feb 2023 08:10:32 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pNENE-0002uw-PP; Wed, 01 Feb 2023 09:46:11 -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 1pNELN-0001Wx-Py for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:19 -0500 Received: from casper.infradead.org ([2001:8b0:10b:1236::1]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pNELG-0005aB-4C for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:11 -0500 Received: from i7.infradead.org ([2001:8b0:10b:1:21e:67ff:fecb:7a92]) by casper.infradead.org with esmtpsa (Exim 4.94.2 #2 (Red Hat Linux)) id 1pNELA-00CNSH-Bs; Wed, 01 Feb 2023 14:44:01 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.96 #2 (Red Hat Linux)) id 1pNELA-007JwB-1C; Wed, 01 Feb 2023 14:44:00 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; h=Sender:Content-Transfer-Encoding: Content-Type:MIME-Version:References:In-Reply-To:Message-Id:Date:Subject:Cc: To:From:Reply-To:Content-ID:Content-Description; bh=2/cJG5IhRTLJ5MYDDxTfadZ//rO1BuiO4gJyXSimNyg=; b=Og4quAatv+qpSrG0bOdl0Ie9l0 yn3JqbN0+GbW2hjITKLobgwaLOrLnjYKGk8+KxagaDKlbNn1b3CQoma0/cvLKR80HxqK7SX0zPNZK SZ1Q1sn49OU4l2iD2+HQLsxQO0JPy0StqLGqfTg9QuUnW+ubOwDbRZbCrGAlfAkSD19f+JNUCKHUH A2zn4OsA6VRL/ZqxMRbefTJvxTs6++1z2Dl1LwB38k4D5FFN/n7XV5of+sg+xde/V2rIFkvaqrdfN Gv/tI/Kedv80rrFJ7PUMoH8C+Lm6JPf9gm9v9O/JjDEksk8kVcal2/d6a3lKTvWNr+Mv+XrKgYzLW JCZfbKcA==; From: David Woodhouse To: Peter Maydell , qemu-devel@nongnu.org Cc: Paolo Bonzini , Paul Durrant , Joao Martins , Ankur Arora , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Thomas Huth , =?UTF-8?q?Alex=20Benn=C3=A9e?= , Juan Quintela , "Dr . David Alan Gilbert" , Claudio Fontana , Julien Grall , "Michael S. Tsirkin" , Marcel Apfelbaum , armbru@redhat.com Subject: [RFC PATCH v1 2/8] hw/xen: Add basic XenStore tree walk and write/read/directory support Date: Wed, 1 Feb 2023 14:43:52 +0000 Message-Id: <20230201144358.1744876-3-dwmw2@infradead.org> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230201144358.1744876-1-dwmw2@infradead.org> References: <20230201144358.1744876-1-dwmw2@infradead.org> MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable X-SRS-Rewrite: SMTP reverse-path rewritten from by casper.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:1236::1; envelope-from=BATV+8c5eeea0684575598b25+7101+infradead.org+dwmw2@casper.srs.infradead.org; helo=casper.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: 1675267834083100005 From: David Woodhouse This is a fairly simple implementation of a copy-on-write tree. The node walk function starts off at the root, with 'inplace =3D=3D true'. If it ever encounters a node with a refcount greater than one (including the root node), then that node is shared with other trees, and cannot be modified in place, so the inplace flag is cleared and we copy on write from there on down. Xenstore write has 'mkdir -p' semantics and will create the intermediate nodes if they don't already exist, so in that case we flip the inplace flag back to true as as populated the newly-created nodes. We put a copy of the absolute path into the buffer in the struct walk_op, with *two* NUL terminators at the end. As xs_node_walk() goes down the tree, it replaces the next '/' separator with a NUL so that it can use the 'child name' in place. The next recursion down then puts the '/' back and repeats the exercise for the next path element... if it doesn't hit that *second* NUL termination which indicates the true end of the path. Signed-off-by: David Woodhouse --- hw/i386/kvm/xen_xenstore.c | 41 ++-- hw/i386/kvm/xenstore_impl.c | 438 ++++++++++++++++++++++++++++++++++-- hw/i386/kvm/xenstore_impl.h | 22 +- tests/unit/meson.build | 1 + tests/unit/test-xs-node.c | 174 ++++++++++++++ 5 files changed, 630 insertions(+), 46 deletions(-) create mode 100644 tests/unit/test-xs-node.c diff --git a/hw/i386/kvm/xen_xenstore.c b/hw/i386/kvm/xen_xenstore.c index eb810c371e..705e937519 100644 --- a/hw/i386/kvm/xen_xenstore.c +++ b/hw/i386/kvm/xen_xenstore.c @@ -219,8 +219,8 @@ static void reset_rsp(XenXenstoreState *s) s->rsp_offset =3D 0; } =20 -static void xs_error(XenXenstoreState *s, unsigned int id, unsigned int tx= _id, - int errnum) +static void xs_error(XenXenstoreState *s, unsigned int id, + xs_transaction_t tx_id, int errnum) { struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; const char *errstr =3D NULL; @@ -246,7 +246,7 @@ static void xs_error(XenXenstoreState *s, unsigned int = id, unsigned int tx_id, } =20 static void xs_ok(XenXenstoreState *s, unsigned int type, unsigned int req= _id, - unsigned int tx_id) + xs_transaction_t tx_id) { struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; const char *okstr =3D "OK"; @@ -365,7 +365,7 @@ static void xs_ok(XenXenstoreState *s, unsigned int typ= e, unsigned int req_id, */ =20 static void xs_read(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *req_data, unsigned int le= n) + xs_transaction_t tx_id, uint8_t *req_data, unsigned in= t len) { const char *path =3D (const char *)req_data; struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; @@ -401,7 +401,7 @@ static void xs_read(XenXenstoreState *s, unsigned int r= eq_id, } =20 static void xs_write(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *req_data, + xs_transaction_t tx_id, uint8_t *req_data, unsigned int len) { g_autoptr(GByteArray) data =3D g_byte_array_new(); @@ -437,7 +437,8 @@ static void xs_write(XenXenstoreState *s, unsigned int = req_id, } =20 static void xs_mkdir(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *req_data, unsigned int l= en) + xs_transaction_t tx_id, uint8_t *req_data, + unsigned int len) { g_autoptr(GByteArray) data =3D g_byte_array_new(); const char *path; @@ -486,7 +487,7 @@ static void xs_append_strings(XenXenstoreState *s, stru= ct xsd_sockmsg *rsp, } =20 static void xs_directory(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *req_data, + xs_transaction_t tx_id, uint8_t *req_data, unsigned int len) { struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; @@ -519,7 +520,7 @@ static void xs_directory(XenXenstoreState *s, unsigned = int req_id, } =20 static void xs_transaction_start(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *req_data, + xs_transaction_t tx_id, uint8_t *req_data, unsigned int len) { struct xsd_sockmsg *rsp =3D (struct xsd_sockmsg *)s->rsp_data; @@ -550,7 +551,7 @@ static void xs_transaction_start(XenXenstoreState *s, u= nsigned int req_id, } =20 static void xs_transaction_end(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *req_data, + xs_transaction_t tx_id, uint8_t *req_data, unsigned int len) { bool commit; @@ -583,8 +584,8 @@ static void xs_transaction_end(XenXenstoreState *s, uns= igned int req_id, xs_ok(s, XS_TRANSACTION_END, req_id, tx_id); } =20 -static void xs_rm(XenXenstoreState *s, unsigned int req_id, unsigned int t= x_id, - uint8_t *req_data, unsigned int len) +static void xs_rm(XenXenstoreState *s, unsigned int req_id, + xs_transaction_t tx_id, uint8_t *req_data, unsigned int = len) { const char *path =3D (const char *)req_data; int err; @@ -605,7 +606,7 @@ static void xs_rm(XenXenstoreState *s, unsigned int req= _id, unsigned int tx_id, } =20 static void xs_get_perms(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *req_data, + xs_transaction_t tx_id, uint8_t *req_data, unsigned int len) { const char *path =3D (const char *)req_data; @@ -636,7 +637,7 @@ static void xs_get_perms(XenXenstoreState *s, unsigned = int req_id, } =20 static void xs_set_perms(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *req_data, + xs_transaction_t tx_id, uint8_t *req_data, unsigned int len) { const char *path =3D (const char *)req_data; @@ -685,7 +686,8 @@ static void xs_set_perms(XenXenstoreState *s, unsigned = int req_id, } =20 static void xs_watch(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *req_data, unsigned int l= en) + xs_transaction_t tx_id, uint8_t *req_data, + unsigned int len) { const char *token, *path =3D (const char *)req_data; int err; @@ -732,7 +734,8 @@ static void xs_watch(XenXenstoreState *s, unsigned int = req_id, } =20 static void xs_unwatch(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *req_data, unsigned int= len) + xs_transaction_t tx_id, uint8_t *req_data, + unsigned int len) { const char *token, *path =3D (const char *)req_data; int err; @@ -772,7 +775,7 @@ static void xs_unwatch(XenXenstoreState *s, unsigned in= t req_id, } =20 static void xs_reset_watches(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *req_data, + xs_transaction_t tx_id, uint8_t *req_data, unsigned int len) { if (len =3D=3D 0 || req_data[len - 1] !=3D '\0') { @@ -787,21 +790,21 @@ static void xs_reset_watches(XenXenstoreState *s, uns= igned int req_id, } =20 static void xs_priv(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *data, + xs_transaction_t tx_id, uint8_t *data, unsigned int len) { xs_error(s, req_id, tx_id, EACCES); } =20 static void xs_unimpl(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *data, + xs_transaction_t tx_id, uint8_t *data, unsigned int len) { xs_error(s, req_id, tx_id, ENOSYS); } =20 typedef void (*xs_impl)(XenXenstoreState *s, unsigned int req_id, - unsigned int tx_id, uint8_t *data, + xs_transaction_t tx_id, uint8_t *data, unsigned int len); =20 struct xsd_req { diff --git a/hw/i386/kvm/xenstore_impl.c b/hw/i386/kvm/xenstore_impl.c index d44c403426..c4e47f97ba 100644 --- a/hw/i386/kvm/xenstore_impl.c +++ b/hw/i386/kvm/xenstore_impl.c @@ -10,42 +10,422 @@ */ =20 #include "qemu/osdep.h" +#include "qom/object.h" =20 #include "xen_xenstore.h" #include "xenstore_impl.h" =20 +#include "hw/xen/interface/io/xs_wire.h" + +#define TYPE_XS_NODE "xs-node" + +OBJECT_DECLARE_TYPE(XsNode, XsNodeClass, XS_NODE) + +typedef struct XsNode { + Object obj; + GByteArray *content; + GHashTable *children; +#ifdef XS_NODE_UNIT_TEST + gchar *name; /* debug only */ +#endif +} XsNode; + struct XenstoreImplState { + XsNode *root; +}; + +static void xs_node_init(Object *obj) +{ +#ifdef XS_NODE_UNIT_TEST + XsNode *n =3D XS_NODE(obj); + nr_xs_nodes++; + xs_node_list =3D g_list_prepend(xs_node_list, n); +#endif +} + +static void xs_node_finalize(Object *obj) +{ + XsNode *n =3D XS_NODE(obj); + if (n->content) { + g_byte_array_unref(n->content); + } + if (n->children) { + g_hash_table_unref(n->children); + } +#ifdef XS_NODE_UNIT_TEST + g_free(n->name); + nr_xs_nodes--; + xs_node_list =3D g_list_remove(xs_node_list, n); +#endif +} + +static const TypeInfo xs_node_info =3D { + .name =3D TYPE_XS_NODE, + .parent =3D TYPE_OBJECT, + .instance_size =3D sizeof(XsNode), + .instance_init =3D xs_node_init, + .instance_finalize =3D xs_node_finalize, +}; + +static void xs_impl_register_types(void) +{ + type_register_static(&xs_node_info); +} + +type_init(xs_impl_register_types) + +static inline XsNode *xs_node_new(void) +{ + return XS_NODE(object_new(TYPE_XS_NODE)); +} + +static inline XsNode *xs_node_ref(XsNode *n) +{ + return XS_NODE(object_ref(OBJECT(n))); +} + +static inline void xs_node_unref(XsNode *n) +{ + object_unref(OBJECT(n)); +} + +/* For copying from one hash table to another using g_hash_table_foreach()= */ +static void do_insert(gpointer key, gpointer value, gpointer user_data) +{ + g_hash_table_insert(user_data, g_strdup(key), object_ref(value)); +} + +static XsNode *xs_node_copy(XsNode *old) +{ + XsNode *n =3D xs_node_new(); + + if (old->children) { + n->children =3D g_hash_table_new_full(g_str_hash, g_str_equal, g_f= ree, + object_unref); + g_hash_table_foreach(old->children, do_insert, n->children); + } + if (old && old->content) { + n->content =3D g_byte_array_ref(old->content); + } + return n; +} + +static void xs_node_add_child(XsNode *n, const char *path_elem, XsNode *ch= ild) +{ + assert(!strchr(path_elem, '/')); + + if (!child) { + assert(n->children); + g_hash_table_remove(n->children, path_elem); + return; + } + +#ifdef XS_NODE_UNIT_TEST + g_free(child->name); + child->name =3D g_strdup(path_elem); +#endif + if (!n->children) { + n->children =3D g_hash_table_new_full(g_str_hash, g_str_equal, g_f= ree, + object_unref); + } + g_hash_table_insert(n->children, g_strdup(path_elem), child); +} + +struct walk_op { + struct XenstoreImplState *s; + char path[XENSTORE_ABS_PATH_MAX + 2]; /* Two NUL terminators */ + int (*op_fn)(XsNode **n, struct walk_op *op); + void *op_opaque; + + unsigned int dom_id; + + /* + * This is maintained on the way *down* the walk to indicate + * whether nodes can be modified in place or whether COW is + * required. It starts off being true, as we're always going to + * replace the root node. If we walk into a shared subtree it + * becomes false. If we start *creating* new nodes for a write, + * it becomes true again. + * + * Do not use it on the way back up. + */ + bool inplace; + bool mutating; + bool create_dirs; }; =20 -int xs_impl_read(XenstoreImplState *s, unsigned int dom_id, unsigned int t= x_id, - const char *path, GByteArray *data) +static int xs_node_add_content(XsNode **n, struct walk_op *op) +{ + /* We *are* the node to be written. Either this or a copy. */ + if (!op->inplace) { + XsNode *old =3D *n; + *n =3D xs_node_copy(old); + xs_node_unref(old); + } + + if ((*n)->content) { + g_byte_array_unref((*n)->content); + } + (*n)->content =3D g_byte_array_ref(op->op_opaque); + return 0; +} + +static int xs_node_get_content(XsNode **n, struct walk_op *op) +{ + GByteArray *data =3D op->op_opaque; + GByteArray *node_data; + + assert(op->inplace); + assert(*n); + + node_data =3D (*n)->content; + if (node_data) { + g_byte_array_append(data, node_data->data, node_data->len); + } + + return 0; +} + +static int xs_node_rm(XsNode **n, struct walk_op *op) +{ + bool this_inplace =3D op->inplace; + + if (this_inplace) { + xs_node_unref(*n); + } + *n =3D NULL; + return 0; +} + +/* + * Passed a full reference in *n which it may free if it needs to COW. + * + * When changing the tree, the op->inplace flag indicates whether this + * node may be modified in place (i.e. it and all its parents had a + * refcount of one). If walking down the tree we find a node whose + * refcount is higher, we must clear op->inplace and COW from there + * down. Unless we are creating new nodes as scaffolding for a write + * (which works like 'mkdir -p' does). In which case those newly + * created nodes can (and must) be modified in place again. + */ +static int xs_node_walk(XsNode **n, struct walk_op *op) +{ + char *child_name =3D NULL; + size_t namelen; + XsNode *old =3D *n, *child =3D NULL; + bool stole_child =3D false; + bool this_inplace; + int err; + + namelen =3D strlen(op->path); + + /* Is there a child, or do we hit the double-NUL termination? */ + if (op->path[namelen + 1]) { + char *slash; + child_name =3D op->path + namelen + 1; + slash =3D strchr(child_name, '/'); + if (slash) { + *slash =3D '\0'; + } + op->path[namelen] =3D '/'; + } + + /* If we walk into a subtree which is shared, we must COW */ + if (op->mutating && old->obj.ref !=3D 1) { + op->inplace =3D false; + } + + if (!child_name) { + /* This is the actual node on which the operation shall be perform= ed */ + return op->op_fn(n, op); + } + + /* op->inplace will be further modified during the recursion */ + this_inplace =3D op->inplace; + + if (old && old->children) { + child =3D g_hash_table_lookup(old->children, child_name); + /* This is a *weak* reference to 'child', owned by the hash table = */ + } + + if (child) { + xs_node_ref(child); + /* + * Now we own it too. But if we can modify inplace, that's going to + * foil the check and force it to COW. We want to be the *only* ow= ner + * so that it can be modified in place, so remove it from the hash + * table in that case. We'll add it (or its replacement) back late= r. + */ + if (op->mutating && this_inplace) { + g_hash_table_remove(old->children, child_name); + stole_child =3D true; + } + } else if (op->create_dirs) { + child =3D xs_node_new(); + /* + * If we're creating a new child, we can clearly modify it (and its + * children) in place from here on down. + */ + op->inplace =3D true; + } else { + op->path[namelen] =3D '\0'; + return ENOENT; + } + + /* + * Except for the temporary child-stealing as noted, our node has not + * changed yet. We don't yet know the overall operation will complete. + */ + err =3D xs_node_walk(&child, op); + if (err || !op->mutating) { + if (stole_child) { + /* Put it back as it was. */ + g_hash_table_replace(old->children, g_strdup(child_name), chil= d); + } else { + xs_node_unref(child); + } + op->path[namelen] =3D '\0'; + return err; + } + + /* + * Now we know the operation has completed successfully and we're on + * the way back up. Make the change, substituting 'child' in the + * node at our level. + */ + if (!this_inplace) { + *n =3D xs_node_copy(old); + xs_node_unref(old); + } + + xs_node_add_child(*n, child_name, child); + + op->path[namelen] =3D '\0'; + return 0; +} + +static void append_directory_item(gpointer key, gpointer value, + gpointer user_data) +{ + GList **items =3D user_data; + + *items =3D g_list_insert_sorted(*items, g_strdup(key), g_str_equal); +} + +/* Populates items with char * names which caller must free. */ +static int xs_node_directory(XsNode **n, struct walk_op *op) +{ + GList **items =3D op->op_opaque; + + assert(op->inplace); + assert(*n); + + if ((*n)->children) { + g_hash_table_foreach((*n)->children, append_directory_item, items); + } + + return 0; +} + +static int init_walk_op(XenstoreImplState *s, struct walk_op *op, + xs_transaction_t tx_id, unsigned int dom_id, + const char *path, XsNode ***rootp) +{ + if (path[0] =3D=3D '/') { + if (strlen(path) > XENSTORE_ABS_PATH_MAX) { + return E2BIG; + } + snprintf(op->path, sizeof(op->path), "%s", path); + } else { + if (strlen(path) > XENSTORE_REL_PATH_MAX) { + return E2BIG; + } + snprintf(op->path, sizeof(op->path), "/local/domain/%u/%s", dom_id, + path); + } + /* + * We use *two* NUL terminators at the end of the path, as during the = walk + * we will temporarily turn each '/' into a NUL to allow us to use that + * path element for the lookup. + */ + op->path[strlen(op->path) + 1] =3D '\0'; + op->path[0] =3D '\0'; + op->inplace =3D true; + op->mutating =3D false; + op->create_dirs =3D false; + op->dom_id =3D dom_id; + op->s =3D s; + + if (tx_id =3D=3D XBT_NULL) { + *rootp =3D &s->root; + } else { + return ENOENT; + } + + return 0; +} + +int xs_impl_read(XenstoreImplState *s, unsigned int dom_id, + xs_transaction_t tx_id, const char *path, GByteArray *dat= a) { /* * The data GByteArray shall exist, and will be freed by caller. * Just g_byte_array_append() to it. */ - return ENOENT; + struct walk_op op; + XsNode **n; + int ret; + + ret =3D init_walk_op(s, &op, tx_id, dom_id, path, &n); + if (ret) { + return ret; + } + op.op_fn =3D xs_node_get_content; + op.op_opaque =3D data; + return xs_node_walk(n, &op); } =20 int xs_impl_write(XenstoreImplState *s, unsigned int dom_id, - unsigned int tx_id, const char *path, GByteArray *data) + xs_transaction_t tx_id, const char *path, GByteArray *da= ta) { /* * The data GByteArray shall exist, will be freed by caller. You are - * free to use g_byte_array_steal() and keep the data. + * free to use g_byte_array_steal() and keep the data. Or just ref it. */ - return ENOSYS; + struct walk_op op; + XsNode **n; + int ret; + + ret =3D init_walk_op(s, &op, tx_id, dom_id, path, &n); + if (ret) { + return ret; + } + op.op_fn =3D xs_node_add_content; + op.op_opaque =3D data; + op.mutating =3D true; + op.create_dirs =3D true; + return xs_node_walk(n, &op); } =20 int xs_impl_directory(XenstoreImplState *s, unsigned int dom_id, - unsigned int tx_id, const char *path, GList **items) + xs_transaction_t tx_id, const char *path, GList **it= ems) { /* * The items are (char *) to be freed by caller. Although it's consumed * immediately so if you want to change it to (const char *) and keep * them, go ahead and change the caller. */ - return ENOENT; + struct walk_op op; + XsNode **n; + int ret; + + ret =3D init_walk_op(s, &op, tx_id, dom_id, path, &n); + if (ret) { + return ret; + } + op.op_fn =3D xs_node_directory; + op.op_opaque =3D items; + return xs_node_walk(n, &op); } =20 int xs_impl_transaction_start(XenstoreImplState *s, unsigned int dom_id, @@ -55,19 +435,29 @@ int xs_impl_transaction_start(XenstoreImplState *s, un= signed int dom_id, } =20 int xs_impl_transaction_end(XenstoreImplState *s, unsigned int dom_id, - unsigned int tx_id, bool commit) + xs_transaction_t tx_id, bool commit) { return ENOSYS; } =20 -int xs_impl_rm(XenstoreImplState *s, unsigned int dom_id, unsigned int tx_= id, - const char *path) +int xs_impl_rm(XenstoreImplState *s, unsigned int dom_id, + xs_transaction_t tx_id, const char *path) { - return ENOSYS; + struct walk_op op; + XsNode **n; + int ret; + + ret =3D init_walk_op(s, &op, tx_id, dom_id, path, &n); + if (ret) { + return ret; + } + op.op_fn =3D xs_node_rm; + op.mutating =3D true; + return xs_node_walk(n, &op); } =20 int xs_impl_get_perms(XenstoreImplState *s, unsigned int dom_id, - unsigned int tx_id, const char *path, GList **perms) + xs_transaction_t tx_id, const char *path, GList **pe= rms) { /* * The perms are (char *) in the wire format to be @@ -77,7 +467,7 @@ int xs_impl_get_perms(XenstoreImplState *s, unsigned int= dom_id, } =20 int xs_impl_set_perms(XenstoreImplState *s, unsigned int dom_id, - unsigned int tx_id, const char *path, GList *perms) + xs_transaction_t tx_id, const char *path, GList *per= ms) { /* * The perms are (const char *) in the wire format. @@ -94,23 +484,35 @@ int xs_impl_watch(XenstoreImplState *s, unsigned int d= om_id, const char *path, * if it was a relative path which needed to be prefixed with * /local/domain/${domid}/ */ - return ENOSYS; + return ENOSYS; } =20 int xs_impl_unwatch(XenstoreImplState *s, unsigned int dom_id, const char *path, const char *token, xs_impl_watch_fn fn, void *opaque) { - /* Remove the watch that matches all four criteria */ - return ENOSYS; + /* + * When calling the callback @fn, note that the path should + * precisely match the relative path that the guest provided, even + * if it was a relative path which needed to be prefixed with + * /local/domain/${domid}/ + */ + return ENOSYS; } =20 int xs_impl_reset_watches(XenstoreImplState *s, unsigned int dom_id) { + /* Remove the watch that matches all four criteria */ return ENOSYS; } =20 XenstoreImplState *xs_impl_create(void) { - return g_new0(XenstoreImplState, 1); + XenstoreImplState *s =3D g_new0(XenstoreImplState, 1); + + s->root =3D xs_node_new(); +#ifdef XS_NODE_UNIT_TEST + s->root->name =3D g_strdup("/"); +#endif + return s; } diff --git a/hw/i386/kvm/xenstore_impl.h b/hw/i386/kvm/xenstore_impl.h index deefc4c412..d0b98a94db 100644 --- a/hw/i386/kvm/xenstore_impl.h +++ b/hw/i386/kvm/xenstore_impl.h @@ -12,6 +12,10 @@ #ifndef __QEMU_XENSTORE_IMPL_H__ #define __QEMU_XENSTORE_IMPL_H__ =20 +typedef uint32_t xs_transaction_t; + +#define XBT_NULL 0 + typedef struct XenstoreImplState XenstoreImplState; =20 XenstoreImplState *xs_impl_create(void); @@ -23,22 +27,22 @@ XenstoreImplState *xs_impl_create(void); * to return (those in xsd_errors). */ =20 -int xs_impl_read(XenstoreImplState *s, unsigned int dom_id, unsigned int t= x_id, - const char *path, GByteArray *data); +int xs_impl_read(XenstoreImplState *s, unsigned int dom_id, + xs_transaction_t tx_id, const char *path, GByteArray *dat= a); int xs_impl_write(XenstoreImplState *s, unsigned int dom_id, - unsigned int tx_id, const char *path, GByteArray *data); + xs_transaction_t tx_id, const char *path, GByteArray *da= ta); int xs_impl_directory(XenstoreImplState *s, unsigned int dom_id, - unsigned int tx_id, const char *path, GList **items); + xs_transaction_t tx_id, const char *path, GList **it= ems); int xs_impl_transaction_start(XenstoreImplState *s, unsigned int dom_id, unsigned int *tx_id); int xs_impl_transaction_end(XenstoreImplState *s, unsigned int dom_id, - unsigned int tx_id, bool commit); -int xs_impl_rm(XenstoreImplState *s, unsigned int dom_id, unsigned int tx_= id, - const char *path); + xs_transaction_t tx_id, bool commit); +int xs_impl_rm(XenstoreImplState *s, unsigned int dom_id, + xs_transaction_t tx_id, const char *path); int xs_impl_get_perms(XenstoreImplState *s, unsigned int dom_id, - unsigned int tx_id, const char *path, GList **perms); + xs_transaction_t tx_id, const char *path, GList **pe= rms); int xs_impl_set_perms(XenstoreImplState *s, unsigned int dom_id, - unsigned int tx_id, const char *path, GList *perms); + xs_transaction_t tx_id, const char *path, GList *per= ms); =20 /* This differs from xs_watch_fn because it has the token */ typedef void(xs_impl_watch_fn)(void *opaque, const char *path, diff --git a/tests/unit/meson.build b/tests/unit/meson.build index ffa444f432..ee54b3c3eb 100644 --- a/tests/unit/meson.build +++ b/tests/unit/meson.build @@ -48,6 +48,7 @@ tests =3D { 'test-qapi-util': [], 'test-smp-parse': [qom, meson.project_source_root() / 'hw/core/machine-s= mp.c'], 'test-interval-tree': [], + 'test-xs-node': [qom], } =20 if have_system or have_tools diff --git a/tests/unit/test-xs-node.c b/tests/unit/test-xs-node.c new file mode 100644 index 0000000000..ab47936a9a --- /dev/null +++ b/tests/unit/test-xs-node.c @@ -0,0 +1,174 @@ +/* + * QEMU XenStore XsNode testing + * + * Copyright =C2=A9 2023 Amazon.com, Inc. or its affiliates. All Rights Re= served. + + * This work is licensed under the terms of the GNU GPL, version 2 or late= r. + * See the COPYING file in the top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/module.h" + +static int nr_xs_nodes; +static GList *xs_node_list; + +#define XS_NODE_UNIT_TEST +#include "hw/i386/kvm/xenstore_impl.c" + +#define DOMID_QEMU 0 +#define DOMID_GUEST 1 + +/* This doesn't happen in qemu but we want to make valgrind happy */ +static void xs_impl_delete(XenstoreImplState *s) +{ + xs_node_unref(s->root); + g_free(s); + + if (xs_node_list) { + GList *l; + for (l =3D xs_node_list; l; l =3D l->next) { + XsNode *n =3D l->data; + printf("Remaining node at %p name %s ref %d\n", n, n->name, + n->obj.ref); + } + } + g_assert(!nr_xs_nodes); +} + +static int write_str(XenstoreImplState *s, unsigned int dom_id, + unsigned int tx_id, const char *path, + const char *content) +{ + GByteArray *d =3D g_byte_array_new(); + int err; + + g_byte_array_append(d, (void *)content, strlen(content)); + err =3D xs_impl_write(s, dom_id, tx_id, path, d); + g_byte_array_unref(d); + return err; +} + +static XenstoreImplState *setup(void) +{ + XenstoreImplState *s =3D xs_impl_create(); + char *abspath; + int err; + + abspath =3D g_strdup_printf("/local/domain/%u", DOMID_GUEST); + + err =3D write_str(s, DOMID_QEMU, XBT_NULL, abspath, ""); + g_assert(!err); + + g_free(abspath); + + abspath =3D g_strdup_printf("/local/domain/%u/some", DOMID_GUEST); + + err =3D write_str(s, DOMID_QEMU, XBT_NULL, abspath, ""); + g_assert(!err); + + g_free(abspath); + + return s; +} + +static void test_xs_node_simple(void) +{ + XenstoreImplState *s =3D setup(); + GList *items =3D NULL; + GByteArray *data =3D g_byte_array_new(); + XsNode *old_root; + int err; + + g_assert(s); + + /* Read gives ENOENT when it should */ + err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "foo", data); + g_assert(err =3D=3D ENOENT); + + /* Write works */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path", + "something"); + g_assert(!err); + + /* Read gives back what we wrote */ + err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative/path", d= ata); + g_assert(!err); + g_assert(data->len =3D=3D strlen("something")); + g_assert(!memcmp(data->data, "something", data->len)); + + /* Even if we use an abolute path */ + g_byte_array_set_size(data, 0); + err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, + "/local/domain/1/some/relative/path", data); + g_assert(!err); + g_assert(data->len =3D=3D strlen("something")); + + /* Keep a copy, to force COW mode */ + old_root =3D xs_node_ref(s->root); + + /* Write works again */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, + "/local/domain/1/some/relative/path2", + "something else"); + g_assert(!err); + + /* Overwrite an existing node */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path", + "another thing"); + g_assert(!err); + + /* We can list the two files we wrote */ + err =3D xs_impl_directory(s, DOMID_GUEST, XBT_NULL, "some/relative", &= items); + g_assert(!err); + g_assert(items); + g_assert(!strcmp(items->data, "path")); + g_assert(items->next); + g_assert(!strcmp(items->next->data, "path2")); + g_assert(!items->next->next); + g_list_free_full(items, g_free); + + /* Write somewhere else which already existed */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/relative", "moredata= "); + g_assert(!err); + + g_byte_array_set_size(data, 0); + err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data); + g_assert(!err); + g_assert(data->len =3D=3D strlen("moredata")); + g_assert(!memcmp(data->data, "moredata", data->len)); + + /* Overwrite existing data */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/relative", "otherdat= a"); + g_assert(!err); + + g_byte_array_set_size(data, 0); + err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data); + g_assert(!err); + g_assert(data->len =3D=3D strlen("otherdata")); + g_assert(!memcmp(data->data, "otherdata", data->len)); + + /* Remove the subtree */ + err =3D xs_impl_rm(s, DOMID_GUEST, XBT_NULL, "some/relative"); + g_assert(!err); + + g_byte_array_set_size(data, 0); + err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data); + g_assert(err =3D=3D ENOENT); + g_byte_array_unref(data); + + xs_node_unref(old_root); + xs_impl_delete(s); +} + + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + module_call_init(MODULE_INIT_QOM); + + g_test_add_func("/xs_node/simple", test_xs_node_simple); + + return g_test_run(); +} --=20 2.39.0 From nobody Tue May 21 02:38:50 2024 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 167526486254271.90275393162028; Wed, 1 Feb 2023 07:21:02 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pNELz-00025g-HQ; Wed, 01 Feb 2023 09:44:52 -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 1pNELN-0001Wy-QY for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:19 -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 1pNELI-0005aL-C6 for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:12 -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 1pNEKb-004oCl-2j; Wed, 01 Feb 2023 14:43:26 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.96 #2 (Red Hat Linux)) id 1pNELA-007JwF-1O; Wed, 01 Feb 2023 14:44:00 +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=dTxxDYbJeZHQrirZ1DhMopV3y9Q7U8/7REjz117W7Y0=; b=W15QghBymQZNJPupMe3j2sm+YL O72KV3YnB4ozE9R35r2i4fOq0PZQiZt+E8A/MS5MgPPRF2h9Oh/LeMuvoTzh4XkPprfeYbIlrAnIX Jf6WfqncNtq5pH1x/3z1WtKOZq7lR0P+prSCs/H28bZiJE+1Fbz1iiOnOBpEEmng3tP0OOFHiZbMm FmTAkehQZemtjW23giszkmRwu5dgI0dnMDIPsJLF+2zJJ0+HDTXe+0lQ0EBgPCUin0k2dpYyDoZv0 WTDWForpf+kAxvmXtZaX+7n7KKyroAfMH2oFoB0JBH5YmxJHJfi7ytcl0YWwB3/vFjI2PZSFlwH5L TRyfu99g==; From: David Woodhouse To: Peter Maydell , qemu-devel@nongnu.org Cc: Paolo Bonzini , Paul Durrant , Joao Martins , Ankur Arora , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Thomas Huth , =?UTF-8?q?Alex=20Benn=C3=A9e?= , Juan Quintela , "Dr . David Alan Gilbert" , Claudio Fontana , Julien Grall , "Michael S. Tsirkin" , Marcel Apfelbaum , armbru@redhat.com Subject: [RFC PATCH v1 3/8] hw/xen: Implement XenStore watches Date: Wed, 1 Feb 2023 14:43:53 +0000 Message-Id: <20230201144358.1744876-4-dwmw2@infradead.org> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230201144358.1744876-1-dwmw2@infradead.org> References: <20230201144358.1744876-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+c61c7683afee22e62f8e+7101+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: 1675264864609100003 Content-Type: text/plain; charset="utf-8" From: David Woodhouse Starts out fairly simple: a hash table of watches based on the path. Except there can be multiple watches on the same path, so the watch ends up being a simple linked list, and the head of that list is in the hash table. Which makes removal a bit of a PITA but it's not so bad; we just special-case "I had to remove the head of the list and now I have to replace it in / remove it from the hash table". And if we don't remove the head, it's a simple linked-list operation. We do need to fire watches on *deleted* nodes, so instead of just a simple xs_node_unref() on the topmost victim, we need to recurse down and fire watches on them all. Signed-off-by: David Woodhouse --- hw/i386/kvm/xenstore_impl.c | 288 ++++++++++++++++++++++++++++++++++-- tests/unit/test-xs-node.c | 80 ++++++++++ 2 files changed, 352 insertions(+), 16 deletions(-) diff --git a/hw/i386/kvm/xenstore_impl.c b/hw/i386/kvm/xenstore_impl.c index c4e47f97ba..7b2d2ddc57 100644 --- a/hw/i386/kvm/xenstore_impl.c +++ b/hw/i386/kvm/xenstore_impl.c @@ -30,8 +30,18 @@ typedef struct XsNode { #endif } XsNode; =20 +typedef struct XsWatch { + struct XsWatch *next; + xs_impl_watch_fn *cb; + void *cb_opaque; + char *token; + unsigned int dom_id; + int rel_prefix; +} XsWatch; + struct XenstoreImplState { XsNode *root; + GHashTable *watches; }; =20 static void xs_node_init(Object *obj) @@ -137,6 +147,7 @@ struct walk_op { int (*op_fn)(XsNode **n, struct walk_op *op); void *op_opaque; =20 + GList *watches; unsigned int dom_id; =20 /* @@ -154,6 +165,35 @@ struct walk_op { bool create_dirs; }; =20 +static void fire_watches(struct walk_op *op, bool parents) +{ + GList *l =3D NULL; + XsWatch *w; + + if (!op->mutating) { + return; + } + + if (parents) { + l =3D op->watches; + } + + w =3D g_hash_table_lookup(op->s->watches, op->path); + while (w || l) { + if (!w) { + /* Fire the parent nodes from 'op' if asked to */ + w =3D l->data; + l =3D l->next; + continue; + } + + assert(strlen(op->path) > w->rel_prefix); + w->cb(w->cb_opaque, op->path + w->rel_prefix, w->token); + + w =3D w->next; + } +} + static int xs_node_add_content(XsNode **n, struct walk_op *op) { /* We *are* the node to be written. Either this or a copy. */ @@ -186,10 +226,51 @@ static int xs_node_get_content(XsNode **n, struct wal= k_op *op) return 0; } =20 +static int node_rm_recurse(gpointer key, gpointer value, gpointer user_dat= a) +{ + struct walk_op *op =3D user_data; + int path_len =3D strlen(op->path); + int key_len =3D strlen(key); + XsNode *n =3D value; + bool this_inplace =3D op->inplace; + + if (n->obj.ref !=3D 1) { + op->inplace =3D 0; + } + + assert(key_len + path_len + 2 <=3D sizeof(op->path)); + op->path[path_len] =3D '/'; + memcpy(op->path + path_len + 1, key, key_len + 1); + + if (n->children) { + g_hash_table_foreach_remove(n->children, node_rm_recurse, op); + } + /* + * Fire watches on *this* node but not the parents because they are + * going to be deleted too, so the watch will fire for them anyway. + */ + fire_watches(op, false); + op->path[path_len] =3D '\0'; + + + /* + * Actually deleting the child here is just an optimisation; if we + * don't then the final unref on the topmost victim will just have + * to cascade down again repeating all the g_hash_table_foreach() + * calls. + */ + return this_inplace; +} + static int xs_node_rm(XsNode **n, struct walk_op *op) { bool this_inplace =3D op->inplace; =20 + /* Fire watches for any node in the subtree which gets deleted. */ + if ((*n)->children) { + g_hash_table_foreach_remove((*n)->children, node_rm_recurse, op); + } + if (this_inplace) { xs_node_unref(*n); } @@ -215,9 +296,11 @@ static int xs_node_walk(XsNode **n, struct walk_op *op) XsNode *old =3D *n, *child =3D NULL; bool stole_child =3D false; bool this_inplace; + XsWatch *watch; int err; =20 namelen =3D strlen(op->path); + watch =3D g_hash_table_lookup(op->s->watches, op->path); =20 /* Is there a child, or do we hit the double-NUL termination? */ if (op->path[namelen + 1]) { @@ -237,7 +320,11 @@ static int xs_node_walk(XsNode **n, struct walk_op *op) =20 if (!child_name) { /* This is the actual node on which the operation shall be perform= ed */ - return op->op_fn(n, op); + err =3D op->op_fn(n, op); + if (!err) { + fire_watches(op, true); + } + return err; } =20 /* op->inplace will be further modified during the recursion */ @@ -272,11 +359,24 @@ static int xs_node_walk(XsNode **n, struct walk_op *o= p) return ENOENT; } =20 + /* + * If there's a watch on this node, add it to the list to be fired + * (with the correct full pathname for the modified node) at the end. + */ + if (watch) { + op->watches =3D g_list_append(op->watches, watch); + } + /* * Except for the temporary child-stealing as noted, our node has not * changed yet. We don't yet know the overall operation will complete. */ err =3D xs_node_walk(&child, op); + + if (watch) { + op->watches =3D g_list_remove(op->watches, watch); + } + if (err || !op->mutating) { if (stole_child) { /* Put it back as it was. */ @@ -299,8 +399,10 @@ static int xs_node_walk(XsNode **n, struct walk_op *op) } =20 xs_node_add_child(*n, child_name, child); - op->path[namelen] =3D '\0'; + if (!namelen) { + assert(!op->watches); + } return 0; } =20 @@ -349,6 +451,7 @@ static int init_walk_op(XenstoreImplState *s, struct wa= lk_op *op, * path element for the lookup. */ op->path[strlen(op->path) + 1] =3D '\0'; + op->watches =3D NULL; op->path[0] =3D '\0'; op->inplace =3D true; op->mutating =3D false; @@ -478,38 +581,191 @@ int xs_impl_set_perms(XenstoreImplState *s, unsigned= int dom_id, int xs_impl_watch(XenstoreImplState *s, unsigned int dom_id, const char *p= ath, const char *token, xs_impl_watch_fn fn, void *opaque) { - /* - * When calling the callback @fn, note that the path should - * precisely match the relative path that the guest provided, even - * if it was a relative path which needed to be prefixed with - * /local/domain/${domid}/ - */ - return ENOSYS; + const char *effective_path; + char *fullpath =3D NULL; /* Allocated copy */ + XsWatch *w, *l; + + if (path[0] =3D=3D '/') { + if (strlen(path) > XENSTORE_ABS_PATH_MAX) { + return E2BIG; + } + effective_path =3D path; + } else { + if (strlen(path) > XENSTORE_REL_PATH_MAX) { + return E2BIG; + } + effective_path =3D fullpath =3D g_strdup_printf("/local/domain/%u/= %s", + dom_id, path); + } + + w =3D g_new0(XsWatch, 1); + w->token =3D g_strdup(token); + w->cb =3D fn; + w->cb_opaque =3D opaque; + w->dom_id =3D dom_id; + w->rel_prefix =3D strlen(effective_path) - strlen(path); + + l =3D g_hash_table_lookup(s->watches, effective_path); + if (l) { + w->next =3D l->next; + l->next =3D w; + } else { + if (!fullpath) { + fullpath =3D g_strdup(path); + } + g_hash_table_insert(s->watches, fullpath, w); + fullpath =3D NULL; /* Don't free; hash owns it */ + } + g_free(fullpath); + fn(opaque, path, token); + return 0; +} + +static XsWatch *free_watch(XsWatch *w) +{ + XsWatch *next =3D w->next; + + g_free(w->token); + g_free(w); + + return next; } =20 int xs_impl_unwatch(XenstoreImplState *s, unsigned int dom_id, const char *path, const char *token, xs_impl_watch_fn fn, void *opaque) { + const char *effective_path; + char *fullpath =3D NULL; /* Allocated copy */ + XsWatch *w, **l; + + if (path[0] =3D=3D '/') { + if (strlen(path) > XENSTORE_ABS_PATH_MAX) { + return E2BIG; + } + effective_path =3D path; + } else { + if (strlen(path) > XENSTORE_REL_PATH_MAX) { + return E2BIG; + } + effective_path =3D fullpath =3D g_strdup_printf("/local/domain/%u/= %s", + dom_id, path); + } + + w =3D g_hash_table_lookup(s->watches, effective_path); + if (!w) { + g_free(fullpath); + return ENOENT; + } + /* - * When calling the callback @fn, note that the path should - * precisely match the relative path that the guest provided, even - * if it was a relative path which needed to be prefixed with - * /local/domain/${domid}/ + * The hash table contains the first element of a list of + * watches. Removing the first element in the list is a + * special case because we have to update the hash table to + * point to the next (or remove it if there's nothing left). */ - return ENOSYS; + if (!g_strcmp0(token, w->token) && fn =3D=3D w->cb && opaque =3D=3D w-= >cb_opaque && + dom_id =3D=3D w->dom_id) { + if (w->next) { + /* Insert the previous 'next' into the hash table */ + if (!fullpath) { + fullpath =3D g_strdup(path); + } + g_hash_table_insert(s->watches, fullpath, w->next); + fullpath =3D NULL; + } else { + /* Nothing left; remove from hash table */ + g_hash_table_remove(s->watches, effective_path); + g_free(fullpath); + } + free_watch(w); + return 0; + } + + /* + * We're all done messing with the hash table because the element + * it points to has survived the cull. Now it's just a simple + * linked list removal operation. + */ + g_free(fullpath); + + for (l =3D &w->next; *l; l =3D &w->next) { + w =3D *l; + + if (!g_strcmp0(token, w->token) && fn =3D=3D w->cb && + opaque !=3D w->cb_opaque && dom_id =3D=3D w->dom_id) { + *l =3D free_watch(w); + return 0; + } + } + + return ENOENT; } =20 int xs_impl_reset_watches(XenstoreImplState *s, unsigned int dom_id) { - /* Remove the watch that matches all four criteria */ - return ENOSYS; + char **watch_paths; + guint nr_watch_paths; + guint i; + + watch_paths =3D (char **)g_hash_table_get_keys_as_array(s->watches, + &nr_watch_paths); + + for (i =3D 0; i < nr_watch_paths; i++) { + XsWatch *w1 =3D g_hash_table_lookup(s->watches, watch_paths[i]); + XsWatch *w2, *w, **l; + + /* + * w1 is the original list. The hash table has this pointer. + * w2 is the head of our newly-filtered list. + * w and l are temporary for processing. w is somewhat redundant + * with *l but makes my eyes bleed less. + */ + + w =3D w2 =3D w1; + l =3D &w; + while (w) { + if (w->dom_id =3D=3D dom_id) { + /* If we're freeing the head of the list, bump w2 */ + if (w2 =3D=3D w) { + w2 =3D w->next; + } + *l =3D free_watch(w); + } else { + l =3D &w->next; + } + w =3D *l; + } + /* + * If the head of the list survived the cull, we don't need to + * touch the hash table and we're done with this path. Else... + */ + if (w1 !=3D w2) { + g_hash_table_steal(s->watches, watch_paths[i]); + + /* + * It was already freed. (Don't worry, this whole thing is + * single-threaded and nobody saw it in the meantime). And + * having *stolen* it, we now own the watch_paths[i] string + * so if we don't give it back to the hash table, we need + * to free it. + */ + if (w2) { + g_hash_table_insert(s->watches, watch_paths[i], w2); + } else { + g_free(watch_paths[i]); + } + } + } + g_free(watch_paths); + return 0; } =20 XenstoreImplState *xs_impl_create(void) { XenstoreImplState *s =3D g_new0(XenstoreImplState, 1); =20 + s->watches =3D g_hash_table_new_full(g_str_hash, g_str_equal, g_free, = NULL); s->root =3D xs_node_new(); #ifdef XS_NODE_UNIT_TEST s->root->name =3D g_strdup("/"); diff --git a/tests/unit/test-xs-node.c b/tests/unit/test-xs-node.c index ab47936a9a..3bea23d106 100644 --- a/tests/unit/test-xs-node.c +++ b/tests/unit/test-xs-node.c @@ -23,6 +23,7 @@ static GList *xs_node_list; /* This doesn't happen in qemu but we want to make valgrind happy */ static void xs_impl_delete(XenstoreImplState *s) { + g_hash_table_unref(s->watches); xs_node_unref(s->root); g_free(s); =20 @@ -50,6 +51,14 @@ static int write_str(XenstoreImplState *s, unsigned int = dom_id, return err; } =20 +static void watch_cb(void *_str, const char *path, const char *token) +{ + GString *str =3D _str; + + g_string_append(str, path); + g_string_append(str, token); +} + static XenstoreImplState *setup(void) { XenstoreImplState *s =3D xs_impl_create(); @@ -76,6 +85,8 @@ static XenstoreImplState *setup(void) static void test_xs_node_simple(void) { XenstoreImplState *s =3D setup(); + GString *guest_watches =3D g_string_new(NULL); + GString *qemu_watches =3D g_string_new(NULL); GList *items =3D NULL; GByteArray *data =3D g_byte_array_new(); XsNode *old_root; @@ -83,6 +94,20 @@ static void test_xs_node_simple(void) =20 g_assert(s); =20 + err =3D xs_impl_watch(s, DOMID_GUEST, "some", "guestwatch", + watch_cb, guest_watches); + g_assert(!err); + g_assert(guest_watches->len =3D=3D strlen("someguestwatch")); + g_assert(!strcmp(guest_watches->str, "someguestwatch")); + g_string_truncate(guest_watches, 0); + + err =3D xs_impl_watch(s, 0, "/local/domain/1/some", "qemuwatch", + watch_cb, qemu_watches); + g_assert(!err); + g_assert(qemu_watches->len =3D=3D strlen("/local/domain/1/someqemuwatc= h")); + g_assert(!strcmp(qemu_watches->str, "/local/domain/1/someqemuwatch")); + g_string_truncate(qemu_watches, 0); + /* Read gives ENOENT when it should */ err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "foo", data); g_assert(err =3D=3D ENOENT); @@ -91,6 +116,14 @@ static void test_xs_node_simple(void) err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path", "something"); g_assert(!err); + g_assert(!strcmp(guest_watches->str, + "some/relative/pathguestwatch")); + g_assert(!strcmp(qemu_watches->str, + "/local/domain/1/some/relative/pathqemuwatch")); + + g_string_truncate(qemu_watches, 0); + g_string_truncate(guest_watches, 0); + xs_impl_reset_watches(s, 0); =20 /* Read gives back what we wrote */ err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative/path", d= ata); @@ -105,6 +138,8 @@ static void test_xs_node_simple(void) g_assert(!err); g_assert(data->len =3D=3D strlen("something")); =20 + g_assert(!qemu_watches->len); + g_assert(!guest_watches->len); /* Keep a copy, to force COW mode */ old_root =3D xs_node_ref(s->root); =20 @@ -113,11 +148,17 @@ static void test_xs_node_simple(void) "/local/domain/1/some/relative/path2", "something else"); g_assert(!err); + g_assert(!qemu_watches->len); + g_assert(!strcmp(guest_watches->str, "some/relative/path2guestwatch")); + g_string_truncate(guest_watches, 0); =20 /* Overwrite an existing node */ err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path", "another thing"); g_assert(!err); + g_assert(!qemu_watches->len); + g_assert(!strcmp(guest_watches->str, "some/relative/pathguestwatch")); + g_string_truncate(guest_watches, 0); =20 /* We can list the two files we wrote */ err =3D xs_impl_directory(s, DOMID_GUEST, XBT_NULL, "some/relative", &= items); @@ -129,10 +170,38 @@ static void test_xs_node_simple(void) g_assert(!items->next->next); g_list_free_full(items, g_free); =20 + err =3D xs_impl_unwatch(s, DOMID_GUEST, "some", "guestwatch", + watch_cb, guest_watches); + g_assert(!err); + + err =3D xs_impl_unwatch(s, DOMID_GUEST, "some", "guestwatch", + watch_cb, guest_watches); + g_assert(err =3D=3D ENOENT); + + err =3D xs_impl_watch(s, DOMID_GUEST, "some/relative/path2", "watchp2", + watch_cb, guest_watches); + g_assert(!err); + g_assert(guest_watches->len =3D=3D strlen("some/relative/path2watchp2"= )); + g_assert(!strcmp(guest_watches->str, "some/relative/path2watchp2")); + g_string_truncate(guest_watches, 0); + + err =3D xs_impl_watch(s, DOMID_GUEST, "/local/domain/1/some/relative", + "watchrel", watch_cb, guest_watches); + g_assert(!err); + g_assert(guest_watches->len =3D=3D + strlen("/local/domain/1/some/relativewatchrel")); + g_assert(!strcmp(guest_watches->str, + "/local/domain/1/some/relativewatchrel")); + g_string_truncate(guest_watches, 0); + /* Write somewhere else which already existed */ err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/relative", "moredata= "); g_assert(!err); =20 + g_assert(!strcmp(guest_watches->str, + "/local/domain/1/some/relativewatchrel")); + g_string_truncate(guest_watches, 0); + g_byte_array_set_size(data, 0); err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data); g_assert(!err); @@ -142,6 +211,7 @@ static void test_xs_node_simple(void) /* Overwrite existing data */ err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/relative", "otherdat= a"); g_assert(!err); + g_string_truncate(guest_watches, 0); =20 g_byte_array_set_size(data, 0); err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data); @@ -153,11 +223,21 @@ static void test_xs_node_simple(void) err =3D xs_impl_rm(s, DOMID_GUEST, XBT_NULL, "some/relative"); g_assert(!err); =20 + /* Each watch fires with the least specific relevant path */ + g_assert(strstr(guest_watches->str, + "some/relative/path2watchp2")); + g_assert(strstr(guest_watches->str, + "/local/domain/1/some/relativewatchrel")); + g_string_truncate(guest_watches, 0); + g_byte_array_set_size(data, 0); err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative", data); g_assert(err =3D=3D ENOENT); g_byte_array_unref(data); =20 + xs_impl_reset_watches(s, DOMID_GUEST); + g_string_free(qemu_watches, true); + g_string_free(guest_watches, true); xs_node_unref(old_root); xs_impl_delete(s); } --=20 2.39.0 From nobody Tue May 21 02:38:50 2024 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 1675265113094824.6381128668468; Wed, 1 Feb 2023 07:25:13 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pNENX-0004B1-6g; Wed, 01 Feb 2023 09:46:27 -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 1pNELN-0001X0-Qf for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:19 -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 1pNELJ-0005aM-3D for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:12 -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 1pNEKb-004oCm-2u; Wed, 01 Feb 2023 14:43:26 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.96 #2 (Red Hat Linux)) id 1pNELA-007JwJ-1Z; Wed, 01 Feb 2023 14:44:00 +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=xXeyNDds8xpKcK6zzuGCt9kfXJZs77YrSKy9r+74X3o=; b=oPGgtqOPGi9QMaoB19iUoA/sm9 uK22Nta4Uuf3knQF/0jGRx9T43+R5ibZdAIBrV0Lae0rG3ME9ksYLcPUBsoFxd7sPBcbzePlqzgSm CxyWuiwFgS7gLS6s3qUiYA9qii1uoPdkGrTduXwdBFrUO8mK5Li2nXSkhkNZvSC5bKYd+M3G5G7df iQIpaJ+79sW8/VwfGOsE84vHz8+TkjDMk1ku7jNr42Qz77Ls6znaI8sLG6WEF9vf4GrwGcQ0bm1Aj 8k5JXHr2ge1FLKvTjg8IGGfjnx8YaKG0MVvxNVKTAORyZhm21edrPfqGgsWRhQIR6zqBEmTu3KmOd oihike6Q==; From: David Woodhouse To: Peter Maydell , qemu-devel@nongnu.org Cc: Paolo Bonzini , Paul Durrant , Joao Martins , Ankur Arora , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Thomas Huth , =?UTF-8?q?Alex=20Benn=C3=A9e?= , Juan Quintela , "Dr . David Alan Gilbert" , Claudio Fontana , Julien Grall , "Michael S. Tsirkin" , Marcel Apfelbaum , armbru@redhat.com Subject: [RFC PATCH v1 4/8] hw/xen: Implement XenStore transactions Date: Wed, 1 Feb 2023 14:43:54 +0000 Message-Id: <20230201144358.1744876-5-dwmw2@infradead.org> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230201144358.1744876-1-dwmw2@infradead.org> References: <20230201144358.1744876-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+c61c7683afee22e62f8e+7101+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: 1675265115167100003 Content-Type: text/plain; charset="utf-8" From: David Woodhouse Given that the whole thing supported copy on write from the beginning, transactions end up being fairly simple. On starting a transaction, just take a ref of the existing root; swap it back in on a successful commit. The main tree has a transaction ID too, and we keep a record of the last transaction ID given out. if the main tree is ever modified when it isn't the latest, it gets a new transaction ID. A commit can only succeed if the main tree hasn't moved on since it was forked. Strictly speaking, the XenStore protocol allows a transaction to succeed as long as nothing *it* read or wrote has changed in the interim, but no implementations do that; *any* change is sufficient to abort a transaction. This does not yet fire watches on the changed nodes on a commit. That bit is more fun and will come in a follow-on commit. Signed-off-by: David Woodhouse --- hw/i386/kvm/xenstore_impl.c | 99 +++++++++++++++++++++++++++++++++++-- tests/unit/test-xs-node.c | 97 ++++++++++++++++++++++++++++++++++++ 2 files changed, 192 insertions(+), 4 deletions(-) diff --git a/hw/i386/kvm/xenstore_impl.c b/hw/i386/kvm/xenstore_impl.c index 7b2d2ddc57..c07b807d13 100644 --- a/hw/i386/kvm/xenstore_impl.c +++ b/hw/i386/kvm/xenstore_impl.c @@ -39,9 +39,19 @@ typedef struct XsWatch { int rel_prefix; } XsWatch; =20 +typedef struct XsTransaction { + XsNode *root; + unsigned int base_tx; + unsigned int tx_id; + unsigned int dom_id; +} XsTransaction; + struct XenstoreImplState { XsNode *root; GHashTable *watches; + GHashTable *transactions; + unsigned int root_tx; + unsigned int last_tx; }; =20 static void xs_node_init(Object *obj) @@ -163,6 +173,7 @@ struct walk_op { bool inplace; bool mutating; bool create_dirs; + bool in_transaction; }; =20 static void fire_watches(struct walk_op *op, bool parents) @@ -170,7 +181,7 @@ static void fire_watches(struct walk_op *op, bool paren= ts) GList *l =3D NULL; XsWatch *w; =20 - if (!op->mutating) { + if (!op->mutating || op->in_transaction) { return; } =20 @@ -402,6 +413,16 @@ static int xs_node_walk(XsNode **n, struct walk_op *op) op->path[namelen] =3D '\0'; if (!namelen) { assert(!op->watches); + /* + * If the main tree was changed, bump its tx ID so that outstanding + * transactions correctly fail. But don't bump it every time; only + * if it makes a difference. + */ + if (op->mutating && !op->in_transaction) { + if (op->s->root_tx !=3D op->s->last_tx) { + op->s->root_tx =3D ++op->s->last_tx; + } + } } return 0; } @@ -456,13 +477,20 @@ static int init_walk_op(XenstoreImplState *s, struct = walk_op *op, op->inplace =3D true; op->mutating =3D false; op->create_dirs =3D false; + op->in_transaction =3D false; op->dom_id =3D dom_id; op->s =3D s; =20 if (tx_id =3D=3D XBT_NULL) { *rootp =3D &s->root; } else { - return ENOENT; + XsTransaction *tx =3D g_hash_table_lookup(s->transactions, + GINT_TO_POINTER(tx_id)); + if (!tx) { + return ENOENT; + } + *rootp =3D &tx->root; + op->in_transaction =3D true; } =20 return 0; @@ -534,13 +562,65 @@ int xs_impl_directory(XenstoreImplState *s, unsigned = int dom_id, int xs_impl_transaction_start(XenstoreImplState *s, unsigned int dom_id, unsigned int *tx_id) { - return ENOSYS; + XsTransaction *tx; + + if (*tx_id !=3D XBT_NULL) { + return EINVAL; + } + + tx =3D g_new0(XsTransaction, 1); + + tx->tx_id =3D ++s->last_tx; + tx->base_tx =3D s->root_tx; + tx->root =3D xs_node_ref(s->root); + tx->dom_id =3D dom_id; + + g_hash_table_insert(s->transactions, GINT_TO_POINTER(tx->tx_id), tx); + *tx_id =3D tx->tx_id; + return 0; +} + +static int transaction_commit(XenstoreImplState *s, XsTransaction *tx) +{ + if (s->root_tx !=3D tx->base_tx) { + return EAGAIN; + } + xs_node_unref(s->root); + s->root =3D tx->root; + tx->root =3D NULL; + s->root_tx =3D tx->tx_id; + + /* + * XX: Walk the new root and fire watches on any node which has a + * refcount of one (which is therefore unique to this transaction). + */ + return 0; } =20 int xs_impl_transaction_end(XenstoreImplState *s, unsigned int dom_id, xs_transaction_t tx_id, bool commit) { - return ENOSYS; + int ret =3D 0; + + if (commit) { + XsTransaction *tx =3D g_hash_table_lookup(s->transactions, + GINT_TO_POINTER(tx_id)); + if (!tx || tx->dom_id !=3D dom_id) { + return ENOENT; + } + + ret =3D transaction_commit(s, tx); + /* + * It *is* in the hash table still, so g_hash_table_remove() will + * return true and we'll return ret; + */ + } + + if (g_hash_table_remove(s->transactions, GINT_TO_POINTER(tx_id))) { + return ret; + } else { + return ENOENT; + } } =20 int xs_impl_rm(XenstoreImplState *s, unsigned int dom_id, @@ -761,11 +841,22 @@ int xs_impl_reset_watches(XenstoreImplState *s, unsig= ned int dom_id) return 0; } =20 +static void xs_tx_free(void *_tx) +{ + XsTransaction *tx =3D _tx; + if (tx->root) { + xs_node_unref(tx->root); + } + g_free(tx); +} + XenstoreImplState *xs_impl_create(void) { XenstoreImplState *s =3D g_new0(XenstoreImplState, 1); =20 s->watches =3D g_hash_table_new_full(g_str_hash, g_str_equal, g_free, = NULL); + s->transactions =3D g_hash_table_new_full(g_direct_hash, g_direct_equa= l, + NULL, xs_tx_free); s->root =3D xs_node_new(); #ifdef XS_NODE_UNIT_TEST s->root->name =3D g_strdup("/"); diff --git a/tests/unit/test-xs-node.c b/tests/unit/test-xs-node.c index 3bea23d106..88ac71e83f 100644 --- a/tests/unit/test-xs-node.c +++ b/tests/unit/test-xs-node.c @@ -23,6 +23,7 @@ static GList *xs_node_list; /* This doesn't happen in qemu but we want to make valgrind happy */ static void xs_impl_delete(XenstoreImplState *s) { + g_hash_table_unref(s->transactions); g_hash_table_unref(s->watches); xs_node_unref(s->root); g_free(s); @@ -243,12 +244,108 @@ static void test_xs_node_simple(void) } =20 =20 +static void do_test_xs_node_tx(bool fail, bool commit) +{ + XenstoreImplState *s =3D setup(); + GString *watches =3D g_string_new(NULL); + GByteArray *data =3D g_byte_array_new(); + unsigned int tx_id =3D XBT_NULL; + int err; + + g_assert(s); + + /* Set a watch */ + err =3D xs_impl_watch(s, DOMID_GUEST, "some", "watch", + watch_cb, watches); + g_assert(!err); + g_assert(watches->len =3D=3D strlen("somewatch")); + g_assert(!strcmp(watches->str, "somewatch")); + g_string_truncate(watches, 0); + + /* Write something */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path", + "something"); + g_assert(!err); + g_assert(!strcmp(watches->str, + "some/relative/pathwatch")); + g_string_truncate(watches, 0); + + /* Create a transaction */ + err =3D xs_impl_transaction_start(s, DOMID_GUEST, &tx_id); + g_assert(!err); + + if (fail) { + /* Write something else in the root */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/relative/path", + "another thing"); + g_assert(!err); + g_assert(!strcmp(watches->str, + "some/relative/pathwatch")); + g_string_truncate(watches, 0); + } + + g_assert(!watches->len); + + /* Perform a write in the transaction */ + err =3D write_str(s, DOMID_GUEST, tx_id, "some/relative/path", + "something else"); + g_assert(!err); + g_assert(!watches->len); + + /* The transaction should fail */ + err =3D xs_impl_transaction_end(s, DOMID_GUEST, tx_id, commit); + if (commit && fail) { + g_assert(err =3D=3D EAGAIN); + } else { + g_assert(!err); + } + g_assert(!watches->len); + + err =3D xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", + watch_cb, watches); + g_assert(!err); + + err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/relative/path", d= ata); + g_assert(!err); + if (fail) { + g_assert(data->len =3D=3D strlen("another thing")); + g_assert(!memcmp(data->data, "another thing", data->len)); + } else if (commit) { + g_assert(data->len =3D=3D strlen("something else")); + g_assert(!memcmp(data->data, "something else", data->len)); + } else { + g_assert(data->len =3D=3D strlen("something")); + g_assert(!memcmp(data->data, "something", data->len)); + } + g_byte_array_unref(data); + g_string_free(watches, true); + xs_impl_delete(s); +} + +static void test_xs_node_tx_fail(void) +{ + do_test_xs_node_tx(true, true); +} + +static void test_xs_node_tx_abort(void) +{ + do_test_xs_node_tx(false, false); + do_test_xs_node_tx(true, false); +} +static void test_xs_node_tx_succeed(void) +{ + do_test_xs_node_tx(false, true); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); module_call_init(MODULE_INIT_QOM); =20 g_test_add_func("/xs_node/simple", test_xs_node_simple); + g_test_add_func("/xs_node/tx_abort", test_xs_node_tx_abort); + g_test_add_func("/xs_node/tx_fail", test_xs_node_tx_fail); + g_test_add_func("/xs_node/tx_succeed", test_xs_node_tx_succeed); =20 return g_test_run(); } --=20 2.39.0 From nobody Tue May 21 02:38:50 2024 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 1675268521830964.6388744222667; Wed, 1 Feb 2023 08:22:01 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pNENH-0002yT-DH; Wed, 01 Feb 2023 09:46:13 -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 1pNELL-0001Wl-82 for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:19 -0500 Received: from casper.infradead.org ([2001:8b0:10b:1236::1]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pNELG-0005aA-4C for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:10 -0500 Received: from i7.infradead.org ([2001:8b0:10b:1:21e:67ff:fecb:7a92]) by casper.infradead.org with esmtpsa (Exim 4.94.2 #2 (Red Hat Linux)) id 1pNELA-00CNSI-Hk; Wed, 01 Feb 2023 14:44:01 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.96 #2 (Red Hat Linux)) id 1pNELA-007JwN-1l; Wed, 01 Feb 2023 14:44:00 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; 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=aBAM6XEwm1PlK0PVpLZvIPjDn3HKtNEm5qnhdDtorto=; b=CqFb1RB6/HvvlJ/CYdKEa7pI9b GU5EJVBFRe2dGoGgZlsWQjh/N9e+39GfrXXpUvIqBViken2Zk6LpnEijMIaiovcOwRSLkSbYb+Ye1 fNK1d2akU9y4IAbISJNTB2irBVC2Qbj60Mbj7pAfy+zJ1cxIJeQf/fQhzSgic4yibhhCJiLCTikpk jRBY6QPXEvvyweGOzsGbOLLtc5sJKJMlfO7aZAj3kjEXnlZbAdrEqx+Iw8wKrJXuwFxHPRboSS39n dFI5dDHl6GmfOYzax086/5gO8a+AVhI+M4IUVPsURHW7GwYYh9zshEeU2WGtDQ++yWQ+Sk49euiEc j14CSQcw==; From: David Woodhouse To: Peter Maydell , qemu-devel@nongnu.org Cc: Paolo Bonzini , Paul Durrant , Joao Martins , Ankur Arora , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Thomas Huth , =?UTF-8?q?Alex=20Benn=C3=A9e?= , Juan Quintela , "Dr . David Alan Gilbert" , Claudio Fontana , Julien Grall , "Michael S. Tsirkin" , Marcel Apfelbaum , armbru@redhat.com Subject: [RFC PATCH v1 5/8] hw/xen: Watches on XenStore transactions Date: Wed, 1 Feb 2023 14:43:55 +0000 Message-Id: <20230201144358.1744876-6-dwmw2@infradead.org> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230201144358.1744876-1-dwmw2@infradead.org> References: <20230201144358.1744876-1-dwmw2@infradead.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-SRS-Rewrite: SMTP reverse-path rewritten from by casper.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:1236::1; envelope-from=BATV+8c5eeea0684575598b25+7101+infradead.org+dwmw2@casper.srs.infradead.org; helo=casper.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: 1675268523845100015 Content-Type: text/plain; charset="utf-8" From: David Woodhouse Firing watches on the nodes that still exist is relatively easy; just walk the tree and look at the nodes with refcount of one. Firing watches on *deleted* nodes is more fun. We add 'modified_in_tx' and 'deleted_in_tx' flags to each node. Nodes with those flags cannot be shared, as they will always be unique to the transaction in which they were created. When xs_node_walk would need to *create* a node as scaffolding and it encounters a deleted_in_tx node, it can resurrect it simply by clearing its deleted_in_tx flag. If that node originally had any *data*, they're gone, and the modified_in_tx flag will have been set when it was first deleted. We then attempt to send appropriate watches when the transaction is committed, properly delete the deleted_in_tx nodes, and remove the modified_in_tx flag from the others. Signed-off-by: David Woodhouse --- hw/i386/kvm/xenstore_impl.c | 130 ++++++++++++++++++++++++++++++++- tests/unit/test-xs-node.c | 142 +++++++++++++++++++++++++++++++++++- 2 files changed, 269 insertions(+), 3 deletions(-) diff --git a/hw/i386/kvm/xenstore_impl.c b/hw/i386/kvm/xenstore_impl.c index c07b807d13..ca8be5e0d4 100644 --- a/hw/i386/kvm/xenstore_impl.c +++ b/hw/i386/kvm/xenstore_impl.c @@ -25,6 +25,8 @@ typedef struct XsNode { Object obj; GByteArray *content; GHashTable *children; + bool deleted_in_tx; + bool modified_in_tx; #ifdef XS_NODE_UNIT_TEST gchar *name; /* debug only */ #endif @@ -119,6 +121,12 @@ static XsNode *xs_node_copy(XsNode *old) { XsNode *n =3D xs_node_new(); =20 +#ifdef XS_NODE_UNIT_TEST + if (n->name) { + n->name =3D g_strdup(old->name); + } +#endif + if (old->children) { n->children =3D g_hash_table_new_full(g_str_hash, g_str_equal, g_f= ree, object_unref); @@ -159,6 +167,7 @@ struct walk_op { =20 GList *watches; unsigned int dom_id; + unsigned int tx_id; =20 /* * This is maintained on the way *down* the walk to indicate @@ -174,6 +183,9 @@ struct walk_op { bool mutating; bool create_dirs; bool in_transaction; + + /* Tracking during recursion so we know which is first. */ + bool deleted_in_tx; }; =20 static void fire_watches(struct walk_op *op, bool parents) @@ -218,6 +230,9 @@ static int xs_node_add_content(XsNode **n, struct walk_= op *op) g_byte_array_unref((*n)->content); } (*n)->content =3D g_byte_array_ref(op->op_opaque); + if (op->tx_id !=3D XBT_NULL) { + (*n)->modified_in_tx =3D true; + } return 0; } =20 @@ -273,10 +288,49 @@ static int node_rm_recurse(gpointer key, gpointer val= ue, gpointer user_data) return this_inplace; } =20 +static XsNode *xs_node_copy_deleted(XsNode *old); +static void copy_deleted_recurse(gpointer key, gpointer value, + gpointer user_data) +{ + XsNode *n =3D xs_node_copy_deleted(value); + g_hash_table_insert(user_data, g_strdup(key), n); +} + +static XsNode *xs_node_copy_deleted(XsNode *old) +{ + XsNode *n =3D xs_node_new(); + +#ifdef XS_NODE_UNIT_TEST + if (old->name) { + n->name =3D g_strdup(old->name); + } +#endif + + if (old->children) { + n->children =3D g_hash_table_new_full(g_str_hash, g_str_equal, g_f= ree, + object_unref); + g_hash_table_foreach(old->children, copy_deleted_recurse, n->child= ren); + } + n->deleted_in_tx =3D true; + /* If it gets resurrected we only fire a watch if it lost its content = */ + if (old->content) { + n->modified_in_tx =3D true; + } + return n; +} + static int xs_node_rm(XsNode **n, struct walk_op *op) { bool this_inplace =3D op->inplace; =20 + if (op->tx_id !=3D XBT_NULL) { + /* It's not trivial to do inplace handling for this one */ + XsNode *old =3D *n; + *n =3D xs_node_copy_deleted(old); + xs_node_unref(old); + return 0; + } + /* Fire watches for any node in the subtree which gets deleted. */ if ((*n)->children) { g_hash_table_foreach_remove((*n)->children, node_rm_recurse, op); @@ -347,6 +401,10 @@ static int xs_node_walk(XsNode **n, struct walk_op *op) } =20 if (child) { + if (child->deleted_in_tx) { + assert(child->obj.ref =3D=3D 1); + child->deleted_in_tx =3D false; + } xs_node_ref(child); /* * Now we own it too. But if we can modify inplace, that's going to @@ -479,6 +537,7 @@ static int init_walk_op(XenstoreImplState *s, struct wa= lk_op *op, op->create_dirs =3D false; op->in_transaction =3D false; op->dom_id =3D dom_id; + op->tx_id =3D tx_id; op->s =3D s; =20 if (tx_id =3D=3D XBT_NULL) { @@ -580,8 +639,69 @@ int xs_impl_transaction_start(XenstoreImplState *s, un= signed int dom_id, return 0; } =20 +static gboolean tx_commit_walk(gpointer key, gpointer value, + gpointer user_data) +{ + struct walk_op *op =3D user_data; + int path_len =3D strlen(op->path); + int key_len =3D strlen(key); + bool fire_parents =3D true; + XsWatch *watch; + XsNode *n =3D value; + + if (n->obj.ref !=3D 1) { + return false; + } + + if (n->deleted_in_tx) { + /* + * We first watches on our parents if we are the *first* node + * to be deleted (the topmost one). This matches the behaviour + * when deleting in the live tree. + */ + fire_parents =3D !op->deleted_in_tx; + + /* Only used on the way down so no need to clear it later */ + op->deleted_in_tx =3D true; + } + + assert(key_len + path_len + 2 <=3D sizeof(op->path)); + op->path[path_len] =3D '/'; + memcpy(op->path + path_len + 1, key, key_len + 1); + + watch =3D g_hash_table_lookup(op->s->watches, op->path); + if (watch) { + op->watches =3D g_list_append(op->watches, watch); + } + + if (n->children) { + g_hash_table_foreach_remove(n->children, tx_commit_walk, op); + } + + if (watch) { + op->watches =3D g_list_remove(op->watches, watch); + } + + /* + * Don't fire watches if this node was only copied because a + * descendent was changed. The modifieD_in_tx flag indicates the + * ones which were really changed. + */ + if (n->modified_in_tx || n->deleted_in_tx) { + fire_watches(op, fire_parents); + n->modified_in_tx =3D false; + } + op->path[path_len] =3D '\0'; + + /* Deleted nodes really do get expunged when we commit */ + return n->deleted_in_tx; +} + static int transaction_commit(XenstoreImplState *s, XsTransaction *tx) { + struct walk_op op; + XsNode **n; + if (s->root_tx !=3D tx->base_tx) { return EAGAIN; } @@ -590,10 +710,18 @@ static int transaction_commit(XenstoreImplState *s, X= sTransaction *tx) tx->root =3D NULL; s->root_tx =3D tx->tx_id; =20 + init_walk_op(s, &op, XBT_NULL, tx->dom_id, "/", &n); + op.deleted_in_tx =3D false; + op.mutating =3D true; + /* - * XX: Walk the new root and fire watches on any node which has a + * Walk the new root and fire watches on any node which has a * refcount of one (which is therefore unique to this transaction). */ + if (s->root->children) { + g_hash_table_foreach_remove(s->root->children, tx_commit_walk, &op= ); + } + return 0; } =20 diff --git a/tests/unit/test-xs-node.c b/tests/unit/test-xs-node.c index 88ac71e83f..85012491a6 100644 --- a/tests/unit/test-xs-node.c +++ b/tests/unit/test-xs-node.c @@ -292,14 +292,21 @@ static void do_test_xs_node_tx(bool fail, bool commit) g_assert(!err); g_assert(!watches->len); =20 - /* The transaction should fail */ + /* Attempt to commit the transaction */ err =3D xs_impl_transaction_end(s, DOMID_GUEST, tx_id, commit); if (commit && fail) { g_assert(err =3D=3D EAGAIN); } else { g_assert(!err); } - g_assert(!watches->len); + + if (commit && !fail) { + g_assert(!strcmp(watches->str, + "some/relative/pathwatch")); + g_string_truncate(watches, 0); + } else { + g_assert(!watches->len); + } =20 err =3D xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", watch_cb, watches); @@ -337,6 +344,135 @@ static void test_xs_node_tx_succeed(void) do_test_xs_node_tx(false, true); } =20 +static void test_xs_node_tx_rm(void) +{ + XenstoreImplState *s =3D setup(); + GString *watches =3D g_string_new(NULL); + GByteArray *data =3D g_byte_array_new(); + unsigned int tx_id =3D XBT_NULL; + int err; + + g_assert(s); + + /* Set a watch */ + err =3D xs_impl_watch(s, DOMID_GUEST, "some", "watch", + watch_cb, watches); + g_assert(!err); + g_assert(watches->len =3D=3D strlen("somewatch")); + g_assert(!strcmp(watches->str, "somewatch")); + g_string_truncate(watches, 0); + + /* Write something */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/p= ath", + "something"); + g_assert(!err); + g_assert(!strcmp(watches->str, + "some/deep/dark/relative/pathwatch")); + g_string_truncate(watches, 0); + + /* Create a transaction */ + err =3D xs_impl_transaction_start(s, DOMID_GUEST, &tx_id); + g_assert(!err); + + /* Delete the tree in the transaction */ + err =3D xs_impl_rm(s, DOMID_GUEST, tx_id, "some/deep/dark"); + g_assert(!err); + g_assert(!watches->len); + + err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relativ= e/path", + data); + g_assert(!err); + g_assert(data->len =3D=3D strlen("something")); + g_assert(!memcmp(data->data, "something", data->len)); + g_byte_array_set_size(data, 0); + + /* Commit the transaction */ + err =3D xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true); + g_assert(!err); + + g_assert(!strcmp(watches->str, "some/deep/darkwatch")); + g_string_truncate(watches, 0); + + /* Now the node is gone */ + err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relativ= e/path", + data); + g_assert(err =3D=3D ENOENT); + g_byte_array_unref(data); + + err =3D xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", + watch_cb, watches); + g_assert(!err); + + g_string_free(watches, true); + xs_impl_delete(s); +} + +static void test_xs_node_tx_resurrect(void) +{ + XenstoreImplState *s =3D setup(); + GString *watches =3D g_string_new(NULL); + GByteArray *data =3D g_byte_array_new(); + unsigned int tx_id =3D XBT_NULL; + int err; + + g_assert(s); + + /* Write something */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relative/p= ath", + "something"); + g_assert(!err); + + /* This node will be wiped and resurrected */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark", + "foo"); + g_assert(!err); + + /* Set a watch */ + err =3D xs_impl_watch(s, DOMID_GUEST, "some", "watch", + watch_cb, watches); + g_assert(!err); + g_assert(watches->len =3D=3D strlen("somewatch")); + g_assert(!strcmp(watches->str, "somewatch")); + g_string_truncate(watches, 0); + + /* Create a transaction */ + err =3D xs_impl_transaction_start(s, DOMID_GUEST, &tx_id); + g_assert(!err); + + /* Delete the tree in the transaction */ + err =3D xs_impl_rm(s, DOMID_GUEST, tx_id, "some/deep"); + g_assert(!err); + g_assert(!watches->len); + + /* Resurrect part of it */ + err =3D write_str(s, DOMID_GUEST, tx_id, "some/deep/dark/different/pat= h", + "something"); + g_assert(!err); + + /* Commit the transaction */ + err =3D xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true); + g_assert(!err); + + g_assert(!strcmp(watches->str, + "some/deep/dark/different/pathwatch" /* lost data */ + "some/deep/dark/relativewatch" /* topmost deleted */ + "some/deep/darkwatch" /* lost data */)); + g_string_truncate(watches, 0); + + /* Now the node is gone */ + err =3D xs_impl_read(s, DOMID_GUEST, XBT_NULL, "some/deep/dark/relativ= e/path", + data); + g_assert(err =3D=3D ENOENT); + g_byte_array_unref(data); + + err =3D xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", + watch_cb, watches); + g_assert(!err); + + g_string_free(watches, true); + xs_impl_delete(s); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); @@ -346,6 +482,8 @@ int main(int argc, char **argv) g_test_add_func("/xs_node/tx_abort", test_xs_node_tx_abort); g_test_add_func("/xs_node/tx_fail", test_xs_node_tx_fail); g_test_add_func("/xs_node/tx_succeed", test_xs_node_tx_succeed); + g_test_add_func("/xs_node/tx_rm", test_xs_node_tx_rm); + g_test_add_func("/xs_node/tx_resurrect", test_xs_node_tx_resurrect); =20 return g_test_run(); } --=20 2.39.0 From nobody Tue May 21 02:38:50 2024 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 1675264388372418.93223111962095; Wed, 1 Feb 2023 07:13:08 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pNEMU-0002QS-Ic; Wed, 01 Feb 2023 09:45:25 -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 1pNELN-0001Wz-QV for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:19 -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 1pNELI-0005aN-3p for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:11 -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 1pNEKc-004oCn-02; Wed, 01 Feb 2023 14:43:26 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.96 #2 (Red Hat Linux)) id 1pNELA-007JwR-1w; Wed, 01 Feb 2023 14:44:00 +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=ukfz7tm1WDCkxDbl9mtoah5z+TGxq/R7Z1PH6maWv98=; b=B5RKathOlaC+Z0N7YGHR0T7tAU 2UTUz3sIb1w7bHXe60XeiIbt2mFGHk1YkDizgqDcBeQEK4iUi77GcYxhiqeIfCsMyeh+VpZaX5Lg6 A82q7VSZK2t0JKDsoZcDVo9hHR7tf9jSha0D5Y6mpawyjTk2JMSHY/HGNAjaY+g1fj6f9XtsuSa4g MC/PyoCAgnFWYycdocATBsaDy8a4MjYMNmmmH+Aijkor0OSa8v5fL+/SsRdGF2g33d1lcrH/VaFxt 7pdV0AGOBGeFDo8oSE1BpvdkxAwqvsUS/Ws0hP70PLb7cCEEpGdREctEOzHcamt5Sh6Rw9w96tnlv yzsWULew==; From: David Woodhouse To: Peter Maydell , qemu-devel@nongnu.org Cc: Paolo Bonzini , Paul Durrant , Joao Martins , Ankur Arora , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Thomas Huth , =?UTF-8?q?Alex=20Benn=C3=A9e?= , Juan Quintela , "Dr . David Alan Gilbert" , Claudio Fontana , Julien Grall , "Michael S. Tsirkin" , Marcel Apfelbaum , armbru@redhat.com Subject: [RFC PATCH v1 6/8] xenstore perms WIP Date: Wed, 1 Feb 2023 14:43:56 +0000 Message-Id: <20230201144358.1744876-7-dwmw2@infradead.org> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230201144358.1744876-1-dwmw2@infradead.org> References: <20230201144358.1744876-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+c61c7683afee22e62f8e+7101+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: 1675264390176100001 Content-Type: text/plain; charset="utf-8" From: Paul Durrant Store perms as a GList of strings, check permissions. No unit tests yet. Signed-off-by: Paul Durrant Signed-off-by: David Woodhoues --- hw/i386/kvm/xen_xenstore.c | 2 +- hw/i386/kvm/xenstore_impl.c | 237 +++++++++++++++++++++++++++++++++--- hw/i386/kvm/xenstore_impl.h | 8 +- tests/unit/test-xs-node.c | 16 ++- 4 files changed, 242 insertions(+), 21 deletions(-) diff --git a/hw/i386/kvm/xen_xenstore.c b/hw/i386/kvm/xen_xenstore.c index 705e937519..4e0d274054 100644 --- a/hw/i386/kvm/xen_xenstore.c +++ b/hw/i386/kvm/xen_xenstore.c @@ -98,7 +98,7 @@ static void xen_xenstore_realize(DeviceState *dev, Error = **errp) aio_set_fd_handler(qemu_get_aio_context(), xen_be_evtchn_fd(s->eh), tr= ue, xen_xenstore_event, NULL, NULL, NULL, s); =20 - s->impl =3D xs_impl_create(); + s->impl =3D xs_impl_create(xen_domid); } =20 static bool xen_xenstore_is_needed(void *opaque) diff --git a/hw/i386/kvm/xenstore_impl.c b/hw/i386/kvm/xenstore_impl.c index ca8be5e0d4..3a31d84b4f 100644 --- a/hw/i386/kvm/xenstore_impl.c +++ b/hw/i386/kvm/xenstore_impl.c @@ -12,6 +12,8 @@ #include "qemu/osdep.h" #include "qom/object.h" =20 +#include "hw/xen/xen.h" + #include "xen_xenstore.h" #include "xenstore_impl.h" =20 @@ -24,6 +26,7 @@ OBJECT_DECLARE_TYPE(XsNode, XsNodeClass, XS_NODE) typedef struct XsNode { Object obj; GByteArray *content; + GList *perms; GHashTable *children; bool deleted_in_tx; bool modified_in_tx; @@ -71,6 +74,9 @@ static void xs_node_finalize(Object *obj) if (n->content) { g_byte_array_unref(n->content); } + if (n->perms) { + g_list_free_full(n->perms, g_free); + } if (n->children) { g_hash_table_unref(n->children); } @@ -111,8 +117,51 @@ static inline void xs_node_unref(XsNode *n) object_unref(OBJECT(n)); } =20 +char *xs_perm_as_string(unsigned int perm, unsigned int domid) +{ + char letter; + + switch (perm) { + case XS_PERM_READ | XS_PERM_WRITE: + letter =3D 'b'; + break; + case XS_PERM_READ: + letter =3D 'r'; + break; + case XS_PERM_WRITE: + letter =3D 'w'; + break; + case XS_PERM_NONE: + default: + letter =3D 'n'; + break; + } + + return g_strdup_printf("%c%u", letter, domid); +} + +static gpointer do_perm_copy(gconstpointer src, gpointer user_data) +{ + return g_strdup(src); +} + +static XsNode *xs_node_create(const char *name, GList *perms) +{ + XsNode *n =3D xs_node_new(); + +#ifdef XS_NODE_UNIT_TEST + if (name) { + n->name =3D g_strdup(name); + } +#endif + + n->perms =3D g_list_copy_deep(perms, do_perm_copy, NULL); + + return n; +} + /* For copying from one hash table to another using g_hash_table_foreach()= */ -static void do_insert(gpointer key, gpointer value, gpointer user_data) +static void do_child_insert(gpointer key, gpointer value, gpointer user_da= ta) { g_hash_table_insert(user_data, g_strdup(key), object_ref(value)); } @@ -127,12 +176,16 @@ static XsNode *xs_node_copy(XsNode *old) } #endif =20 + assert(old); if (old->children) { n->children =3D g_hash_table_new_full(g_str_hash, g_str_equal, g_f= ree, object_unref); - g_hash_table_foreach(old->children, do_insert, n->children); + g_hash_table_foreach(old->children, do_child_insert, n->children); + } + if (old->perms) { + n->perms =3D g_list_copy_deep(old->perms, do_perm_copy, NULL); } - if (old && old->content) { + if (old->content) { n->content =3D g_byte_array_ref(old->content); } return n; @@ -311,6 +364,9 @@ static XsNode *xs_node_copy_deleted(XsNode *old) object_unref); g_hash_table_foreach(old->children, copy_deleted_recurse, n->child= ren); } + if (old->perms) { + n->perms =3D g_list_copy_deep(old->perms, do_perm_copy, NULL); + } n->deleted_in_tx =3D true; /* If it gets resurrected we only fire a watch if it lost its content = */ if (old->content) { @@ -343,6 +399,84 @@ static int xs_node_rm(XsNode **n, struct walk_op *op) return 0; } =20 +static int xs_node_get_perms(XsNode **n, struct walk_op *op) +{ + GList **perms =3D op->op_opaque; + + assert(op->inplace); + assert(*n); + + *perms =3D g_list_copy_deep((*n)->perms, do_perm_copy, NULL); + return 0; +} + +static int xs_node_set_perms(XsNode **n, struct walk_op *op) +{ + GList *perms =3D op->op_opaque; + + /* We *are* the node to be written. Either this or a copy. */ + if (!op->inplace) { + XsNode *old =3D *n; + *n =3D xs_node_copy(old); + xs_node_unref(old); + } + + if ((*n)->perms) { + g_list_free_full((*n)->perms, g_free); + } + (*n)->perms =3D g_list_copy_deep(perms, do_perm_copy, NULL); + if (op->tx_id !=3D XBT_NULL) { + (*n)->modified_in_tx =3D true; + } + return 0; +} + +static void parse_perm(const char *perm, char *letter, unsigned int *dom_i= d) +{ + unsigned int n =3D sscanf(perm, "%c%u", letter, dom_id); + + assert(n =3D=3D 2); +} + +static bool can_access(unsigned int dom_id, GList *perms, const char *lett= ers) +{ + unsigned int i, n; + char perm_letter; + unsigned int perm_dom_id; + bool access; + + if (dom_id =3D=3D 0) { + return true; + } + + n =3D g_list_length(perms); + assert(n >=3D 1); + + /* + * The dom_id of the first perm is the owner, and the owner always has + * read-write access. + */ + parse_perm(g_list_nth_data(perms, 0), &perm_letter, &perm_dom_id); + if (dom_id =3D=3D perm_dom_id) { + return true; + } + + /* + * The letter of the first perm specified the default access for all o= ther + * domains. + */ + access =3D !!strchr(letters, perm_letter); + for (i =3D 1; i < n; i++) { + parse_perm(g_list_nth_data(perms, i), &perm_letter, &perm_dom_id); + if (dom_id !=3D perm_dom_id) { + continue; + } + access =3D !!strchr(letters, perm_letter); + } + + return access; +} + /* * Passed a full reference in *n which it may free if it needs to COW. * @@ -384,6 +518,12 @@ static int xs_node_walk(XsNode **n, struct walk_op *op) } =20 if (!child_name) { + const char *letters =3D op->mutating ? "wb" : "rb"; + + if (!can_access(op->dom_id, old->perms, letters)) { + return EACCES; + } + /* This is the actual node on which the operation shall be perform= ed */ err =3D op->op_fn(n, op); if (!err) { @@ -417,7 +557,13 @@ static int xs_node_walk(XsNode **n, struct walk_op *op) stole_child =3D true; } } else if (op->create_dirs) { - child =3D xs_node_new(); + assert(op->mutating); + + if (!can_access(op->dom_id, old->perms, "wb")) { + return EACCES; + } + + child =3D xs_node_create(child_name, old->perms); /* * If we're creating a new child, we can clearly modify it (and its * children) in place from here on down. @@ -770,20 +916,73 @@ int xs_impl_rm(XenstoreImplState *s, unsigned int dom= _id, int xs_impl_get_perms(XenstoreImplState *s, unsigned int dom_id, xs_transaction_t tx_id, const char *path, GList **pe= rms) { - /* - * The perms are (char *) in the wire format to be - * freed by the caller. - */ - return ENOSYS; + struct walk_op op; + XsNode **n; + int ret; + + ret =3D init_walk_op(s, &op, tx_id, dom_id, path, &n); + if (ret) { + return ret; + } + op.op_fn =3D xs_node_get_perms; + op.op_opaque =3D perms; + return xs_node_walk(n, &op); +} + +static void is_valid_perm(gpointer data, gpointer user_data) +{ + char *perm =3D data; + bool *valid =3D user_data; + char letter; + unsigned int dom_id; + + if (!*valid) { + return; + } + + if (sscanf(perm, "%c%u", &letter, &dom_id) !=3D 2) { + *valid =3D false; + return; + } + + switch (letter) { + case 'n': + case 'r': + case 'w': + case 'b': + break; + + default: + *valid =3D false; + break; + } } =20 int xs_impl_set_perms(XenstoreImplState *s, unsigned int dom_id, xs_transaction_t tx_id, const char *path, GList *per= ms) { - /* - * The perms are (const char *) in the wire format. - */ - return ENOSYS; + struct walk_op op; + XsNode **n; + bool valid =3D true; + int ret; + + if (!g_list_length(perms)) { + return EINVAL; + } + + g_list_foreach(perms, is_valid_perm, &valid); + if (!valid) { + return EINVAL; + } + + ret =3D init_walk_op(s, &op, tx_id, dom_id, path, &n); + if (ret) { + return ret; + } + op.op_fn =3D xs_node_set_perms; + op.op_opaque =3D perms; + op.mutating =3D true; + return xs_node_walk(n, &op); } =20 int xs_impl_watch(XenstoreImplState *s, unsigned int dom_id, const char *p= ath, @@ -978,16 +1177,18 @@ static void xs_tx_free(void *_tx) g_free(tx); } =20 -XenstoreImplState *xs_impl_create(void) +XenstoreImplState *xs_impl_create(unsigned int dom_id) { XenstoreImplState *s =3D g_new0(XenstoreImplState, 1); + GList *perms; =20 s->watches =3D g_hash_table_new_full(g_str_hash, g_str_equal, g_free, = NULL); s->transactions =3D g_hash_table_new_full(g_direct_hash, g_direct_equa= l, NULL, xs_tx_free); - s->root =3D xs_node_new(); -#ifdef XS_NODE_UNIT_TEST - s->root->name =3D g_strdup("/"); -#endif + + perms =3D g_list_append(NULL, xs_perm_as_string(XS_PERM_NONE, 0)); + s->root =3D xs_node_create("/", perms); + g_list_free_full(perms, g_free); + return s; } diff --git a/hw/i386/kvm/xenstore_impl.h b/hw/i386/kvm/xenstore_impl.h index d0b98a94db..852b93ca5c 100644 --- a/hw/i386/kvm/xenstore_impl.h +++ b/hw/i386/kvm/xenstore_impl.h @@ -16,9 +16,15 @@ typedef uint32_t xs_transaction_t; =20 #define XBT_NULL 0 =20 +#define XS_PERM_NONE 0x00 +#define XS_PERM_READ 0x01 +#define XS_PERM_WRITE 0x02 + typedef struct XenstoreImplState XenstoreImplState; =20 -XenstoreImplState *xs_impl_create(void); +XenstoreImplState *xs_impl_create(unsigned int dom_id); + +char *xs_perm_as_string(unsigned int perm, unsigned int domid); =20 /* * These functions return *positive* error numbers. This is a little diff --git a/tests/unit/test-xs-node.c b/tests/unit/test-xs-node.c index 85012491a6..809f82bf95 100644 --- a/tests/unit/test-xs-node.c +++ b/tests/unit/test-xs-node.c @@ -62,8 +62,9 @@ static void watch_cb(void *_str, const char *path, const = char *token) =20 static XenstoreImplState *setup(void) { - XenstoreImplState *s =3D xs_impl_create(); + XenstoreImplState *s =3D xs_impl_create(DOMID_GUEST); char *abspath; + GList *perms; int err; =20 abspath =3D g_strdup_printf("/local/domain/%u", DOMID_GUEST); @@ -71,6 +72,13 @@ static XenstoreImplState *setup(void) err =3D write_str(s, DOMID_QEMU, XBT_NULL, abspath, ""); g_assert(!err); =20 + perms =3D g_list_append(NULL, g_strdup_printf("n%u", DOMID_QEMU)); + perms =3D g_list_append(perms, g_strdup_printf("r%u", DOMID_GUEST)); + + err =3D xs_impl_set_perms(s, DOMID_QEMU, XBT_NULL, abspath, perms); + g_assert(!err); + + g_list_free_full(perms, g_free); g_free(abspath); =20 abspath =3D g_strdup_printf("/local/domain/%u/some", DOMID_GUEST); @@ -78,6 +86,12 @@ static XenstoreImplState *setup(void) err =3D write_str(s, DOMID_QEMU, XBT_NULL, abspath, ""); g_assert(!err); =20 + perms =3D g_list_append(NULL, g_strdup_printf("n%u", DOMID_GUEST)); + + err =3D xs_impl_set_perms(s, DOMID_QEMU, XBT_NULL, abspath, perms); + g_assert(!err); + + g_list_free_full(perms, g_free); g_free(abspath); =20 return s; --=20 2.39.0 From nobody Tue May 21 02:38:50 2024 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 1675267510361724.892371399049; Wed, 1 Feb 2023 08:05:10 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pNEN3-0002ea-7R; Wed, 01 Feb 2023 09:45:57 -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 1pNELN-0001X2-Qx for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:19 -0500 Received: from casper.infradead.org ([2001:8b0:10b:1236::1]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1pNELG-0005a9-4D for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:11 -0500 Received: from i7.infradead.org ([2001:8b0:10b:1:21e:67ff:fecb:7a92]) by casper.infradead.org with esmtpsa (Exim 4.94.2 #2 (Red Hat Linux)) id 1pNELA-00CNSK-LN; Wed, 01 Feb 2023 14:44:01 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.96 #2 (Red Hat Linux)) id 1pNELA-007JwV-28; Wed, 01 Feb 2023 14:44:00 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=infradead.org; s=casper.20170209; 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=MS1had5FfhxrBA/4PTqq9S5i0YUL7PVFK+5VYcjxLhY=; b=oWDSmbnZyXtvrPp2Z9YYlK2gao gDncwq5pf0Q11TAmVpSMt6/K3jcGSoGrrEHnzwQWg9zjKoQ+okS2Ix3+C59x2XsMz+6zkXF365/1w i6I+4bdNZb1ku2gYdimhlPhsjTZOdJEeI92JMr6zOO1UHhB9rTHABEB+vD5wmzIt8Z9XYxDvWmiP6 gFHjQ5QJOyElXB5fBLJ+WCWZzAMeI35rydxo77sJPktfTfhVobQBubP0ig/ZeI4BUDpEerKuG6wU0 CgMXy1qBxzALFWdyoiOVAXigX7tNQuNfUiyykSHXIFmLugKb8EuPpCS99wYxfDVbEO4bNe7aF7+c3 TJC+B3og==; From: David Woodhouse To: Peter Maydell , qemu-devel@nongnu.org Cc: Paolo Bonzini , Paul Durrant , Joao Martins , Ankur Arora , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Thomas Huth , =?UTF-8?q?Alex=20Benn=C3=A9e?= , Juan Quintela , "Dr . David Alan Gilbert" , Claudio Fontana , Julien Grall , "Michael S. Tsirkin" , Marcel Apfelbaum , armbru@redhat.com Subject: [RFC PATCH v1 7/8] hw/xen: Implement core serialize/deserialize methods for xenstore_impl Date: Wed, 1 Feb 2023 14:43:57 +0000 Message-Id: <20230201144358.1744876-8-dwmw2@infradead.org> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230201144358.1744876-1-dwmw2@infradead.org> References: <20230201144358.1744876-1-dwmw2@infradead.org> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable X-SRS-Rewrite: SMTP reverse-path rewritten from by casper.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:1236::1; envelope-from=BATV+8c5eeea0684575598b25+7101+infradead.org+dwmw2@casper.srs.infradead.org; helo=casper.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: 1675267512122100002 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 --- hw/i386/kvm/xen_xenstore.c | 25 +- hw/i386/kvm/xenstore_impl.c | 530 ++++++++++++++++++++++++++++++++++++ hw/i386/kvm/xenstore_impl.h | 5 + tests/unit/test-xs-node.c | 212 ++++++++++++++- 4 files changed, 766 insertions(+), 6 deletions(-) diff --git a/hw/i386/kvm/xen_xenstore.c b/hw/i386/kvm/xen_xenstore.c index 4e0d274054..23d6d9b5a8 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 @@ -134,7 +147,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 { @@ -152,6 +171,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 3a31d84b4f..07877026cf 100644 --- a/hw/i386/kvm/xenstore_impl.c +++ b/hw/i386/kvm/xenstore_impl.c @@ -30,6 +30,7 @@ typedef struct XsNode { GHashTable *children; bool deleted_in_tx; bool modified_in_tx; + unsigned int serialized_tx; #ifdef XS_NODE_UNIT_TEST gchar *name; /* debug only */ #endif @@ -57,6 +58,7 @@ struct XenstoreImplState { GHashTable *transactions; unsigned int root_tx; unsigned int last_tx; + bool serialized; }; =20 static void xs_node_init(Object *obj) @@ -1190,5 +1192,533 @@ XenstoreImplState *xs_impl_create(unsigned int dom_= id) s->root =3D xs_node_create("/", perms); g_list_free_full(perms, g_free); =20 + 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) { + g_byte_array_append(ss->bytes, key, strlen(key) + 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 int consume_node(struct unsave_state *us, XsNode **nodep) +{ + 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->obj.ref++; + } 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); + *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; + } + } + + *nodep =3D n; + return 0; +} + +static int consume_tree(struct unsave_state *us, unsigned int *tx_id, + XsNode **root) +{ + int ret; + + ret =3D consume_be32(us, tx_id); + if (ret) { + return ret; + } + + if (*tx_id > us->s->last_tx) { + return -EINVAL; + } + + us->path[0] =3D '\0'; + + return consume_node(us, root); +} + +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.tx_id, &base_t.root); + 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->tx_id, &t->root); + } + if (ret) { + g_free(t); + return ret; + } + g_assert(t->root); + 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 xs_impl_watch(s, dom_id, path, token, watch_fn, watch_opaq= ue); + 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 852b93ca5c..2b543f0269 100644 --- a/hw/i386/kvm/xenstore_impl.h +++ b/hw/i386/kvm/xenstore_impl.h @@ -60,4 +60,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 809f82bf95..4491ec07f0 100644 --- a/tests/unit/test-xs-node.c +++ b/tests/unit/test-xs-node.c @@ -20,14 +20,42 @@ 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->obj.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) { g_hash_table_unref(s->transactions); g_hash_table_unref(s->watches); 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) { @@ -39,6 +67,166 @@ 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->obj.ref !=3D n2->obj.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); + } +} + +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, NULL, 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); + + xs_impl_delete(s2, false); +} + static int write_str(XenstoreImplState *s, unsigned int dom_id, unsigned int tx_id, const char *path, const char *content) @@ -254,7 +442,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 @@ -306,6 +494,8 @@ static void do_test_xs_node_tx(bool fail, bool commit) g_assert(!err); g_assert(!watches->len); =20 + check_serdes(s); + /* Attempt to commit the transaction */ err =3D xs_impl_transaction_end(s, DOMID_GUEST, tx_id, commit); if (commit && fail) { @@ -322,6 +512,8 @@ static void do_test_xs_node_tx(bool fail, bool commit) g_assert(!watches->len); } =20 + check_serdes(s); + err =3D xs_impl_unwatch(s, DOMID_GUEST, "some", "watch", watch_cb, watches); g_assert(!err); @@ -340,7 +532,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) @@ -400,6 +592,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); @@ -418,7 +612,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) @@ -436,6 +630,10 @@ static void test_xs_node_tx_resurrect(void) "something"); g_assert(!err); =20 + /* Another node to remain shared */ + err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/place/safe", "keepme= "); + g_assert(!err); + /* This node will be wiped and resurrected */ err =3D write_str(s, DOMID_GUEST, XBT_NULL, "some/deep/dark", "foo"); @@ -463,6 +661,8 @@ static void test_xs_node_tx_resurrect(void) "something"); g_assert(!err); =20 + check_serdes(s); + /* Commit the transaction */ err =3D xs_impl_transaction_end(s, DOMID_GUEST, tx_id, true); g_assert(!err); @@ -479,12 +679,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 int main(int argc, char **argv) --=20 2.39.0 From nobody Tue May 21 02:38:50 2024 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 1675264383690884.5269607144264; Wed, 1 Feb 2023 07:13:03 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1pNENU-0003Pc-6N; Wed, 01 Feb 2023 09:46:24 -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 1pNELN-0001X3-Qx for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:19 -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 1pNELK-0005aK-Pu for qemu-devel@nongnu.org; Wed, 01 Feb 2023 09:44:12 -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 1pNEKc-004oCo-0O; Wed, 01 Feb 2023 14:43:26 +0000 Received: from dwoodhou by i7.infradead.org with local (Exim 4.96 #2 (Red Hat Linux)) id 1pNELA-007JwZ-2I; Wed, 01 Feb 2023 14:44:00 +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=RDWZnTq72/w1Wip/tzQYGtWJLAbQAkFJqMIsupCr4tA=; b=HNp9nNrE21Ty5kJlUSf/Kh8wqH HqUYDuYh4XpyBhPSQb1IOzaexNhQ7urlSKutrT279VoxZWMiHJOmnOAoCFrfCWfzUikjcQbI3/Oly UMh+2V2IXtV0znUA5cBebFDdEyTMiE3XqFLTBkFOgZa1kK6MaJc9RQ+zvUuBI/BclPazQIHZStojI Ap3CPt+xIo2bBZU1ktw1zJhGqj9aQ8Ts8FjbS5lb+TGlXUDi3+i4GpdZNObvdjfLAiwCwV8JqZp9t pIb3dVe1CQJBn78Iz7MCmL3SjohBt3ctwia74aYUGTJ+g6IT1HqwruOW9fJq2nO1sCld7stLaKBo8 Nuh8kvDQ==; From: David Woodhouse To: Peter Maydell , qemu-devel@nongnu.org Cc: Paolo Bonzini , Paul Durrant , Joao Martins , Ankur Arora , =?UTF-8?q?Philippe=20Mathieu-Daud=C3=A9?= , Thomas Huth , =?UTF-8?q?Alex=20Benn=C3=A9e?= , Juan Quintela , "Dr . David Alan Gilbert" , Claudio Fontana , Julien Grall , "Michael S. Tsirkin" , Marcel Apfelbaum , armbru@redhat.com Subject: [RFC PATCH v1 8/8] hw/xen: Create initial XenStore nodes Date: Wed, 1 Feb 2023 14:43:58 +0000 Message-Id: <20230201144358.1744876-9-dwmw2@infradead.org> X-Mailer: git-send-email 2.39.0 In-Reply-To: <20230201144358.1744876-1-dwmw2@infradead.org> References: <20230201144358.1744876-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+c61c7683afee22e62f8e+7101+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: 1675264384165100001 Content-Type: text/plain; charset="utf-8" From: Paul Durrant Signed-off-by: Paul Durrant Signed-off-by: David Woodhouse --- hw/i386/kvm/xen_xenstore.c | 70 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/hw/i386/kvm/xen_xenstore.c b/hw/i386/kvm/xen_xenstore.c index 23d6d9b5a8..d72ac7093f 100644 --- a/hw/i386/kvm/xen_xenstore.c +++ b/hw/i386/kvm/xen_xenstore.c @@ -76,9 +76,39 @@ struct XenXenstoreState *xen_xenstore_singleton; static void xen_xenstore_event(void *opaque); static void fire_watch_cb(void *opaque, const char *path, const char *toke= n); =20 +static void G_GNUC_PRINTF (4, 5) relpath_printf(XenXenstoreState *s, + GList *perms, + const char *relpath, + const char *fmt, ...) +{ + gchar *abspath; + gchar *value; + va_list args; + GByteArray *data; + int err; + + abspath =3D g_strdup_printf("/local/domain/%u/%s", xen_domid, relpath); + va_start(args, fmt); + value =3D g_strdup_vprintf(fmt, args); + va_end(args); + + data =3D g_byte_array_new_take((void *)value, strlen(value)); + + err =3D xs_impl_write(s->impl, DOMID_QEMU, XBT_NULL, abspath, data); + assert(!err); + + g_byte_array_unref(data); + + err =3D xs_impl_set_perms(s->impl, DOMID_QEMU, XBT_NULL, abspath, perm= s); + assert(!err); + + g_free(abspath); +} + static void xen_xenstore_realize(DeviceState *dev, Error **errp) { XenXenstoreState *s =3D XEN_XENSTORE(dev); + GList *perms; =20 if (xen_mode !=3D XEN_EMULATE) { error_setg(errp, "Xen xenstore support is for Xen emulation"); @@ -102,6 +132,46 @@ static void xen_xenstore_realize(DeviceState *dev, Err= or **errp) xen_xenstore_event, NULL, NULL, NULL, s); =20 s->impl =3D xs_impl_create(xen_domid); + + /* Populate the default nodes */ + + /* Nodes owned by 'dom0' but readable by the guest */ + perms =3D g_list_append(NULL, xs_perm_as_string(XS_PERM_NONE, DOMID_QE= MU)); + perms =3D g_list_append(perms, xs_perm_as_string(XS_PERM_READ, xen_dom= id)); + + relpath_printf(s, perms, "", "%s", ""); + + relpath_printf(s, perms, "domid", "%u", xen_domid); + + relpath_printf(s, perms, "control/platform-feature-xs_reset_watches", = "%u", 1); + relpath_printf(s, perms, "control/platform-feature-multiprocessor-susp= end", "%u", 1); + + relpath_printf(s, perms, "platform/acpi", "%u", 1); + relpath_printf(s, perms, "platform/acpi_s3", "%u", 1); + relpath_printf(s, perms, "platform/acpi_s4", "%u", 1); + relpath_printf(s, perms, "platform/acpi_laptop_slate", "%u", 0); + + g_list_free_full(perms, g_free); + + /* Nodes owned by the guest */ + perms =3D g_list_append(NULL, xs_perm_as_string(XS_PERM_NONE, xen_domi= d)); + + relpath_printf(s, perms, "attr", "%s", ""); + + relpath_printf(s, perms, "control/shutdown", "%s", ""); + relpath_printf(s, perms, "control/feature-poweroff", "%u", 1); + relpath_printf(s, perms, "control/feature-reboot", "%u", 1); + relpath_printf(s, perms, "control/feature-suspend", "%u", 1); + relpath_printf(s, perms, "control/feature-s3", "%u", 1); + relpath_printf(s, perms, "control/feature-s4", "%u", 1); + + relpath_printf(s, perms, "data", "%s", ""); + relpath_printf(s, perms, "device", "%s", ""); + relpath_printf(s, perms, "drivers", "%s", ""); + relpath_printf(s, perms, "error", "%s", ""); + relpath_printf(s, perms, "feature", "%s", ""); + + g_list_free_full(perms, g_free); } =20 static bool xen_xenstore_is_needed(void *opaque) --=20 2.39.0