| /* |
| * QEMU Xen emulation: Shared/overlay pages support |
| * |
| * Copyright © 2022 Amazon.com, Inc. or its affiliates. All Rights Reserved. |
| * |
| * Authors: David Woodhouse <dwmw2@infradead.org> |
| * |
| * This work is licensed under the terms of the GNU GPL, version 2 or later. |
| * See the COPYING file in the top-level directory. |
| */ |
| |
| #include "qemu/osdep.h" |
| |
| #include "qemu/host-utils.h" |
| #include "qemu/module.h" |
| #include "qemu/main-loop.h" |
| #include "qemu/cutils.h" |
| #include "qapi/error.h" |
| #include "qom/object.h" |
| #include "migration/vmstate.h" |
| |
| #include "hw/sysbus.h" |
| #include "hw/xen/xen.h" |
| #include "hw/xen/xen_backend_ops.h" |
| #include "xen_overlay.h" |
| #include "xen_evtchn.h" |
| #include "xen_xenstore.h" |
| |
| #include "sysemu/kvm.h" |
| #include "sysemu/kvm_xen.h" |
| |
| #include "trace.h" |
| |
| #include "xenstore_impl.h" |
| |
| #include "hw/xen/interface/io/xs_wire.h" |
| #include "hw/xen/interface/event_channel.h" |
| #include "hw/xen/interface/grant_table.h" |
| |
| #define TYPE_XEN_XENSTORE "xen-xenstore" |
| OBJECT_DECLARE_SIMPLE_TYPE(XenXenstoreState, XEN_XENSTORE) |
| |
| #define ENTRIES_PER_FRAME_V1 (XEN_PAGE_SIZE / sizeof(grant_entry_v1_t)) |
| #define ENTRIES_PER_FRAME_V2 (XEN_PAGE_SIZE / sizeof(grant_entry_v2_t)) |
| |
| #define XENSTORE_HEADER_SIZE ((unsigned int)sizeof(struct xsd_sockmsg)) |
| |
| struct XenXenstoreState { |
| /*< private >*/ |
| SysBusDevice busdev; |
| /*< public >*/ |
| |
| XenstoreImplState *impl; |
| GList *watch_events; /* for the guest */ |
| |
| MemoryRegion xenstore_page; |
| struct xenstore_domain_interface *xs; |
| uint8_t req_data[XENSTORE_HEADER_SIZE + XENSTORE_PAYLOAD_MAX]; |
| uint8_t rsp_data[XENSTORE_HEADER_SIZE + XENSTORE_PAYLOAD_MAX]; |
| uint32_t req_offset; |
| uint32_t rsp_offset; |
| bool rsp_pending; |
| bool fatal_error; |
| |
| evtchn_port_t guest_port; |
| evtchn_port_t be_port; |
| struct xenevtchn_handle *eh; |
| |
| uint8_t *impl_state; |
| uint32_t impl_state_size; |
| |
| struct xengntdev_handle *gt; |
| void *granted_xs; |
| }; |
| |
| struct XenXenstoreState *xen_xenstore_singleton; |
| |
| static void xen_xenstore_event(void *opaque); |
| static void fire_watch_cb(void *opaque, const char *path, const char *token); |
| |
| static struct xenstore_backend_ops emu_xenstore_backend_ops; |
| |
| 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 = g_strdup_printf("/local/domain/%u/%s", xen_domid, relpath); |
| va_start(args, fmt); |
| value = g_strdup_vprintf(fmt, args); |
| va_end(args); |
| |
| data = g_byte_array_new_take((void *)value, strlen(value)); |
| |
| err = xs_impl_write(s->impl, DOMID_QEMU, XBT_NULL, abspath, data); |
| assert(!err); |
| |
| g_byte_array_unref(data); |
| |
| err = xs_impl_set_perms(s->impl, DOMID_QEMU, XBT_NULL, abspath, perms); |
| assert(!err); |
| |
| g_free(abspath); |
| } |
| |
| static void xen_xenstore_realize(DeviceState *dev, Error **errp) |
| { |
| XenXenstoreState *s = XEN_XENSTORE(dev); |
| GList *perms; |
| |
| if (xen_mode != XEN_EMULATE) { |
| error_setg(errp, "Xen xenstore support is for Xen emulation"); |
| return; |
| } |
| memory_region_init_ram(&s->xenstore_page, OBJECT(dev), "xen:xenstore_page", |
| XEN_PAGE_SIZE, &error_abort); |
| memory_region_set_enabled(&s->xenstore_page, true); |
| s->xs = memory_region_get_ram_ptr(&s->xenstore_page); |
| memset(s->xs, 0, XEN_PAGE_SIZE); |
| |
| /* We can't map it this early as KVM isn't ready */ |
| xen_xenstore_singleton = s; |
| |
| s->eh = xen_be_evtchn_open(); |
| if (!s->eh) { |
| error_setg(errp, "Xenstore evtchn port init failed"); |
| return; |
| } |
| aio_set_fd_handler(qemu_get_aio_context(), xen_be_evtchn_fd(s->eh), true, |
| xen_xenstore_event, NULL, NULL, NULL, s); |
| |
| s->impl = xs_impl_create(xen_domid); |
| |
| /* Populate the default nodes */ |
| |
| /* Nodes owned by 'dom0' but readable by the guest */ |
| perms = g_list_append(NULL, xs_perm_as_string(XS_PERM_NONE, DOMID_QEMU)); |
| perms = g_list_append(perms, xs_perm_as_string(XS_PERM_READ, xen_domid)); |
| |
| 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-suspend", "%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 = g_list_append(NULL, xs_perm_as_string(XS_PERM_NONE, xen_domid)); |
| |
| 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); |
| |
| xen_xenstore_ops = &emu_xenstore_backend_ops; |
| } |
| |
| static bool xen_xenstore_is_needed(void *opaque) |
| { |
| return xen_mode == XEN_EMULATE; |
| } |
| |
| static int xen_xenstore_pre_save(void *opaque) |
| { |
| XenXenstoreState *s = opaque; |
| GByteArray *save; |
| |
| if (s->eh) { |
| s->guest_port = xen_be_evtchn_get_guest_port(s->eh); |
| } |
| |
| g_free(s->impl_state); |
| save = xs_impl_serialize(s->impl); |
| s->impl_state = save->data; |
| s->impl_state_size = save->len; |
| g_byte_array_free(save, false); |
| |
| return 0; |
| } |
| |
| static int xen_xenstore_post_load(void *opaque, int ver) |
| { |
| XenXenstoreState *s = opaque; |
| GByteArray *save; |
| int ret; |
| |
| /* |
| * As qemu/dom0, rebind to the guest's port. The Windows drivers may |
| * unbind the XenStore evtchn and rebind to it, having obtained the |
| * "remote" port through EVTCHNOP_status. In the case that migration |
| * occurs while it's unbound, the "remote" port needs to be the same |
| * as before so that the guest can find it, but should remain unbound. |
| */ |
| if (s->guest_port) { |
| int be_port = xen_be_evtchn_bind_interdomain(s->eh, xen_domid, |
| s->guest_port); |
| if (be_port < 0) { |
| return be_port; |
| } |
| s->be_port = be_port; |
| } |
| |
| save = g_byte_array_new_take(s->impl_state, s->impl_state_size); |
| s->impl_state = NULL; |
| s->impl_state_size = 0; |
| |
| ret = xs_impl_deserialize(s->impl, save, xen_domid, fire_watch_cb, s); |
| return ret; |
| } |
| |
| static const VMStateDescription xen_xenstore_vmstate = { |
| .name = "xen_xenstore", |
| .unmigratable = 1, /* The PV back ends don't migrate yet */ |
| .version_id = 1, |
| .minimum_version_id = 1, |
| .needed = xen_xenstore_is_needed, |
| .pre_save = xen_xenstore_pre_save, |
| .post_load = xen_xenstore_post_load, |
| .fields = (VMStateField[]) { |
| VMSTATE_UINT8_ARRAY(req_data, XenXenstoreState, |
| sizeof_field(XenXenstoreState, req_data)), |
| VMSTATE_UINT8_ARRAY(rsp_data, XenXenstoreState, |
| sizeof_field(XenXenstoreState, rsp_data)), |
| VMSTATE_UINT32(req_offset, XenXenstoreState), |
| VMSTATE_UINT32(rsp_offset, XenXenstoreState), |
| 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() |
| } |
| }; |
| |
| static void xen_xenstore_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->realize = xen_xenstore_realize; |
| dc->vmsd = &xen_xenstore_vmstate; |
| } |
| |
| static const TypeInfo xen_xenstore_info = { |
| .name = TYPE_XEN_XENSTORE, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(XenXenstoreState), |
| .class_init = xen_xenstore_class_init, |
| }; |
| |
| void xen_xenstore_create(void) |
| { |
| DeviceState *dev = sysbus_create_simple(TYPE_XEN_XENSTORE, -1, NULL); |
| |
| xen_xenstore_singleton = XEN_XENSTORE(dev); |
| |
| /* |
| * Defer the init (xen_xenstore_reset()) until KVM is set up and the |
| * overlay page can be mapped. |
| */ |
| } |
| |
| static void xen_xenstore_register_types(void) |
| { |
| type_register_static(&xen_xenstore_info); |
| } |
| |
| type_init(xen_xenstore_register_types) |
| |
| uint16_t xen_xenstore_get_port(void) |
| { |
| XenXenstoreState *s = xen_xenstore_singleton; |
| if (!s) { |
| return 0; |
| } |
| return s->guest_port; |
| } |
| |
| static bool req_pending(XenXenstoreState *s) |
| { |
| struct xsd_sockmsg *req = (struct xsd_sockmsg *)s->req_data; |
| |
| return s->req_offset == XENSTORE_HEADER_SIZE + req->len; |
| } |
| |
| static void reset_req(XenXenstoreState *s) |
| { |
| memset(s->req_data, 0, sizeof(s->req_data)); |
| s->req_offset = 0; |
| } |
| |
| static void reset_rsp(XenXenstoreState *s) |
| { |
| s->rsp_pending = false; |
| |
| memset(s->rsp_data, 0, sizeof(s->rsp_data)); |
| s->rsp_offset = 0; |
| } |
| |
| static void xs_error(XenXenstoreState *s, unsigned int id, |
| xs_transaction_t tx_id, int errnum) |
| { |
| struct xsd_sockmsg *rsp = (struct xsd_sockmsg *)s->rsp_data; |
| const char *errstr = NULL; |
| |
| for (unsigned int i = 0; i < ARRAY_SIZE(xsd_errors); i++) { |
| struct xsd_errors *xsd_error = &xsd_errors[i]; |
| |
| if (xsd_error->errnum == errnum) { |
| errstr = xsd_error->errstring; |
| break; |
| } |
| } |
| assert(errstr); |
| |
| trace_xenstore_error(id, tx_id, errstr); |
| |
| rsp->type = XS_ERROR; |
| rsp->req_id = id; |
| rsp->tx_id = tx_id; |
| rsp->len = (uint32_t)strlen(errstr) + 1; |
| |
| memcpy(&rsp[1], errstr, rsp->len); |
| } |
| |
| static void xs_ok(XenXenstoreState *s, unsigned int type, unsigned int req_id, |
| xs_transaction_t tx_id) |
| { |
| struct xsd_sockmsg *rsp = (struct xsd_sockmsg *)s->rsp_data; |
| const char *okstr = "OK"; |
| |
| rsp->type = type; |
| rsp->req_id = req_id; |
| rsp->tx_id = tx_id; |
| rsp->len = (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. |
| * |
| * ---------- Database read, write and permissions operations ---------- |
| * |
| * READ <path>| <value|> |
| * WRITE <path>|<value|> |
| * Store and read the octet string <value> at <path>. |
| * WRITE creates any missing parent paths, with empty values. |
| * |
| * MKDIR <path>| |
| * Ensures that the <path> exists, by necessary by creating |
| * it and any missing parents with empty values. If <path> |
| * or any parent already exists, its value is left unchanged. |
| * |
| * RM <path>| |
| * Ensures that the <path> does not exist, by deleting |
| * it and all of its children. It is not an error if <path> does |
| * not exist, but it _is_ an error if <path>'s immediate parent |
| * does not exist either. |
| * |
| * DIRECTORY <path>| <child-leaf-name>|* |
| * Gives a list of the immediate children of <path>, as only the |
| * leafnames. The resulting children are each named |
| * <path>/<child-leaf-name>. |
| * |
| * DIRECTORY_PART <path>|<offset> <gencnt>|<child-leaf-name>|* |
| * Same as DIRECTORY, but to be used for children lists longer than |
| * XENSTORE_PAYLOAD_MAX. Input are <path> and the byte offset into |
| * the list of children to return. Return values are the generation |
| * count <gencnt> of the node (to be used to ensure the node hasn't |
| * changed between two reads: <gencnt> being the same for multiple |
| * reads guarantees the node hasn't changed) and the list of children |
| * starting at the specified <offset> of the complete list. |
| * |
| * GET_PERMS <path>| <perm-as-string>|+ |
| * SET_PERMS <path>|<perm-as-string>|+? |
| * <perm-as-string> is one of the following |
| * w<domid> write only |
| * r<domid> read only |
| * b<domid> both read and write |
| * n<domid> no access |
| * See https://wiki.xen.org/wiki/XenBus section |
| * `Permissions' for details of the permissions system. |
| * It is possible to set permissions for the special watch paths |
| * "@introduceDomain" and "@releaseDomain" to enable receiving those |
| * watches in unprivileged domains. |
| * |
| * ---------- Watches ---------- |
| * |
| * WATCH <wpath>|<token>|? |
| * Adds a watch. |
| * |
| * When a <path> is modified (including path creation, removal, |
| * contents change or permissions change) this generates an event |
| * on the changed <path>. 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 <wpath> if it is an child |
| * of <wpath>. |
| * |
| * <wpath> can be a <path> to watch or @<wspecial>. In the |
| * latter case <wspecial> may have any syntax but it matches |
| * (according to the rules above) only the following special |
| * events which are invented by xenstored: |
| * @introduceDomain occurs on INTRODUCE |
| * @releaseDomain occurs on any domain crash or |
| * shutdown, and also on RELEASE |
| * and domain destruction |
| * <wspecial> events are sent to privileged callers or explicitly |
| * via SET_PERMS enabled domains only. |
| * |
| * When a watch is first set up it is triggered once straight |
| * away, with <path> equal to <wpath>. 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 <epath>|<token>| |
| * Unsolicited `reply' generated for matching modification events |
| * as described above. req_id and tx_id are both 0. |
| * |
| * <epath> is the event's path, ie the actual path that was |
| * modified; however if the event was the recursive removal of an |
| * parent of <wpath>, <epath> is just |
| * <wpath> (rather than the actual path which was removed). So |
| * <epath> is a child of <wpath>, regardless. |
| * |
| * Iff <wpath> for the watch was specified as a relative pathname, |
| * the <epath> path will also be relative (with the same base, |
| * obviously). |
| * |
| * UNWATCH <wpath>|<token>|? |
| * |
| * RESET_WATCHES | |
| * Reset all watches and transactions of the caller. |
| * |
| * ---------- Transactions ---------- |
| * |
| * TRANSACTION_START | <transid>| |
| * <transid> is an opaque uint32_t allocated by xenstored |
| * represented as unsigned decimal. After this, transaction may |
| * be referenced by using <transid> (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| |
| * TRANSACTION_END F| |
| * 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. |
| * |
| */ |
| |
| static void xs_read(XenXenstoreState *s, unsigned int req_id, |
| xs_transaction_t tx_id, uint8_t *req_data, unsigned int len) |
| { |
| const char *path = (const char *)req_data; |
| struct xsd_sockmsg *rsp = (struct xsd_sockmsg *)s->rsp_data; |
| uint8_t *rsp_data = (uint8_t *)&rsp[1]; |
| g_autoptr(GByteArray) data = g_byte_array_new(); |
| int err; |
| |
| if (len == 0 || req_data[len - 1] != '\0') { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| trace_xenstore_read(tx_id, path); |
| err = xs_impl_read(s->impl, xen_domid, tx_id, path, data); |
| if (err) { |
| xs_error(s, req_id, tx_id, err); |
| return; |
| } |
| |
| rsp->type = XS_READ; |
| rsp->req_id = req_id; |
| rsp->tx_id = tx_id; |
| rsp->len = 0; |
| |
| len = 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 += len; |
| } |
| |
| static void xs_write(XenXenstoreState *s, unsigned int req_id, |
| xs_transaction_t tx_id, uint8_t *req_data, |
| unsigned int len) |
| { |
| g_autoptr(GByteArray) data = g_byte_array_new(); |
| const char *path; |
| int err; |
| |
| if (len == 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| path = (const char *)req_data; |
| |
| while (len--) { |
| if (*req_data++ == '\0') { |
| break; |
| } |
| if (len == 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 = 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, |
| xs_transaction_t tx_id, uint8_t *req_data, |
| unsigned int len) |
| { |
| g_autoptr(GByteArray) data = g_byte_array_new(); |
| const char *path; |
| int err; |
| |
| if (len == 0 || req_data[len - 1] != '\0') { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| path = (const char *)req_data; |
| |
| trace_xenstore_mkdir(tx_id, path); |
| err = xs_impl_read(s->impl, xen_domid, tx_id, path, data); |
| if (err == ENOENT) { |
| err = 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, unsigned int start, bool truncate) |
| { |
| uint8_t *rsp_data = (uint8_t *)&rsp[1]; |
| GList *l; |
| |
| for (l = strings; l; l = l->next) { |
| size_t len = strlen(l->data) + 1; /* Including the NUL termination */ |
| char *str = l->data; |
| |
| if (rsp->len + len > XENSTORE_PAYLOAD_MAX) { |
| if (truncate) { |
| len = XENSTORE_PAYLOAD_MAX - rsp->len; |
| if (!len) { |
| return; |
| } |
| } else { |
| xs_error(s, rsp->req_id, rsp->tx_id, E2BIG); |
| return; |
| } |
| } |
| |
| if (start) { |
| if (start >= len) { |
| start -= len; |
| continue; |
| } |
| |
| str += start; |
| len -= start; |
| start = 0; |
| } |
| |
| memcpy(&rsp_data[rsp->len], str, len); |
| rsp->len += len; |
| } |
| /* XS_DIRECTORY_PART wants an extra NUL to indicate the end */ |
| if (truncate && rsp->len < XENSTORE_PAYLOAD_MAX) { |
| rsp_data[rsp->len++] = '\0'; |
| } |
| } |
| |
| static void xs_directory(XenXenstoreState *s, unsigned int req_id, |
| xs_transaction_t tx_id, uint8_t *req_data, |
| unsigned int len) |
| { |
| struct xsd_sockmsg *rsp = (struct xsd_sockmsg *)s->rsp_data; |
| GList *items = NULL; |
| const char *path; |
| int err; |
| |
| if (len == 0 || req_data[len - 1] != '\0') { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| path = (const char *)req_data; |
| |
| trace_xenstore_directory(tx_id, path); |
| err = xs_impl_directory(s->impl, xen_domid, tx_id, path, NULL, &items); |
| if (err != 0) { |
| xs_error(s, req_id, tx_id, err); |
| return; |
| } |
| |
| rsp->type = XS_DIRECTORY; |
| rsp->req_id = req_id; |
| rsp->tx_id = tx_id; |
| rsp->len = 0; |
| |
| xs_append_strings(s, rsp, items, 0, false); |
| |
| g_list_free_full(items, g_free); |
| } |
| |
| static void xs_directory_part(XenXenstoreState *s, unsigned int req_id, |
| xs_transaction_t tx_id, uint8_t *req_data, |
| unsigned int len) |
| { |
| const char *offset_str, *path = (const char *)req_data; |
| struct xsd_sockmsg *rsp = (struct xsd_sockmsg *)s->rsp_data; |
| char *rsp_data = (char *)&rsp[1]; |
| uint64_t gencnt = 0; |
| unsigned int offset; |
| GList *items = NULL; |
| int err; |
| |
| if (len == 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| while (len--) { |
| if (*req_data++ == '\0') { |
| break; |
| } |
| if (len == 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| } |
| |
| offset_str = (const char *)req_data; |
| while (len--) { |
| if (*req_data++ == '\0') { |
| break; |
| } |
| if (len == 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| } |
| |
| if (len) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| if (qemu_strtoui(offset_str, NULL, 10, &offset) < 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| trace_xenstore_directory_part(tx_id, path, offset); |
| err = xs_impl_directory(s->impl, xen_domid, tx_id, path, &gencnt, &items); |
| if (err != 0) { |
| xs_error(s, req_id, tx_id, err); |
| return; |
| } |
| |
| rsp->type = XS_DIRECTORY_PART; |
| rsp->req_id = req_id; |
| rsp->tx_id = tx_id; |
| rsp->len = snprintf(rsp_data, XENSTORE_PAYLOAD_MAX, "%" PRIu64, gencnt) + 1; |
| |
| xs_append_strings(s, rsp, items, offset, true); |
| |
| g_list_free_full(items, g_free); |
| } |
| |
| static void xs_transaction_start(XenXenstoreState *s, unsigned int req_id, |
| xs_transaction_t tx_id, uint8_t *req_data, |
| unsigned int len) |
| { |
| struct xsd_sockmsg *rsp = (struct xsd_sockmsg *)s->rsp_data; |
| char *rsp_data = (char *)&rsp[1]; |
| int err; |
| |
| if (len != 1 || req_data[0] != '\0') { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| rsp->type = XS_TRANSACTION_START; |
| rsp->req_id = req_id; |
| rsp->tx_id = tx_id; |
| rsp->len = 0; |
| |
| err = 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 = 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, |
| xs_transaction_t tx_id, uint8_t *req_data, |
| unsigned int len) |
| { |
| bool commit; |
| int err; |
| |
| if (len != 2 || req_data[1] != '\0') { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| switch (req_data[0]) { |
| case 'T': |
| commit = true; |
| break; |
| case 'F': |
| commit = false; |
| break; |
| default: |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| trace_xenstore_transaction_end(tx_id, commit); |
| err = 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, |
| xs_transaction_t tx_id, uint8_t *req_data, unsigned int len) |
| { |
| const char *path = (const char *)req_data; |
| int err; |
| |
| if (len == 0 || req_data[len - 1] != '\0') { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| trace_xenstore_rm(tx_id, path); |
| err = 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, |
| xs_transaction_t tx_id, uint8_t *req_data, |
| unsigned int len) |
| { |
| const char *path = (const char *)req_data; |
| struct xsd_sockmsg *rsp = (struct xsd_sockmsg *)s->rsp_data; |
| GList *perms = NULL; |
| int err; |
| |
| if (len == 0 || req_data[len - 1] != '\0') { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| trace_xenstore_get_perms(tx_id, path); |
| err = 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 = XS_GET_PERMS; |
| rsp->req_id = req_id; |
| rsp->tx_id = tx_id; |
| rsp->len = 0; |
| |
| xs_append_strings(s, rsp, perms, 0, false); |
| |
| g_list_free_full(perms, g_free); |
| } |
| |
| static void xs_set_perms(XenXenstoreState *s, unsigned int req_id, |
| xs_transaction_t tx_id, uint8_t *req_data, |
| unsigned int len) |
| { |
| const char *path = (const char *)req_data; |
| uint8_t *perm; |
| GList *perms = NULL; |
| int err; |
| |
| if (len == 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| while (len--) { |
| if (*req_data++ == '\0') { |
| break; |
| } |
| if (len == 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| } |
| |
| perm = req_data; |
| while (len--) { |
| if (*req_data++ == '\0') { |
| perms = g_list_append(perms, perm); |
| perm = 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 definition: |
| * |
| * SET_PERMS <path>|<perm-as-string>|+? |
| */ |
| |
| trace_xenstore_set_perms(tx_id, path); |
| err = 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, |
| xs_transaction_t tx_id, uint8_t *req_data, |
| unsigned int len) |
| { |
| const char *token, *path = (const char *)req_data; |
| int err; |
| |
| if (len == 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| while (len--) { |
| if (*req_data++ == '\0') { |
| break; |
| } |
| if (len == 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| } |
| |
| token = (const char *)req_data; |
| while (len--) { |
| if (*req_data++ == '\0') { |
| break; |
| } |
| if (len == 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 definition: |
| * |
| * WATCH <wpath>|<token>|? |
| */ |
| |
| trace_xenstore_watch(path, token); |
| err = 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, |
| xs_transaction_t tx_id, uint8_t *req_data, |
| unsigned int len) |
| { |
| const char *token, *path = (const char *)req_data; |
| int err; |
| |
| if (len == 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| |
| while (len--) { |
| if (*req_data++ == '\0') { |
| break; |
| } |
| if (len == 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| } |
| |
| token = (const char *)req_data; |
| while (len--) { |
| if (*req_data++ == '\0') { |
| break; |
| } |
| if (len == 0) { |
| xs_error(s, req_id, tx_id, EINVAL); |
| return; |
| } |
| } |
| |
| trace_xenstore_unwatch(path, token); |
| err = 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, |
| xs_transaction_t tx_id, uint8_t *req_data, |
| unsigned int len) |
| { |
| if (len == 0 || req_data[len - 1] != '\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, |
| xs_transaction_t 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, |
| xs_transaction_t 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, |
| xs_transaction_t tx_id, uint8_t *data, |
| unsigned int len); |
| |
| struct xsd_req { |
| const char *name; |
| xs_impl fn; |
| }; |
| #define XSD_REQ(_type, _fn) \ |
| [_type] = { .name = #_type, .fn = _fn } |
| |
| struct xsd_req xsd_reqs[] = { |
| 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_DIRECTORY_PART, xs_directory_part), |
| 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 = (struct xsd_sockmsg *)s->req_data; |
| xs_impl handler = NULL; |
| |
| assert(req_pending(s)); |
| assert(!s->rsp_pending); |
| |
| if (req->type < ARRAY_SIZE(xsd_reqs)) { |
| handler = xsd_reqs[req->type].fn; |
| } |
| if (!handler) { |
| handler = &xs_unimpl; |
| } |
| |
| handler(s, req->req_id, req->tx_id, (uint8_t *)&req[1], req->len); |
| |
| s->rsp_pending = true; |
| reset_req(s); |
| } |
| |
| static unsigned int copy_from_ring(XenXenstoreState *s, uint8_t *ptr, |
| unsigned int len) |
| { |
| if (!len) { |
| return 0; |
| } |
| |
| XENSTORE_RING_IDX prod = qatomic_read(&s->xs->req_prod); |
| XENSTORE_RING_IDX cons = qatomic_read(&s->xs->req_cons); |
| unsigned int copied = 0; |
| |
| /* Ensure the ring contents don't cross the req_prod access. */ |
| smp_rmb(); |
| |
| while (len) { |
| unsigned int avail = prod - cons; |
| unsigned int offset = MASK_XENSTORE_IDX(cons); |
| unsigned int copylen = avail; |
| |
| if (avail > XENSTORE_RING_SIZE) { |
| error_report("XenStore ring handling error"); |
| s->fatal_error = true; |
| break; |
| } else if (avail == 0) { |
| break; |
| } |
| |
| if (copylen > len) { |
| copylen = len; |
| } |
| if (copylen > XENSTORE_RING_SIZE - offset) { |
| copylen = XENSTORE_RING_SIZE - offset; |
| } |
| |
| memcpy(ptr, &s->xs->req[offset], copylen); |
| copied += copylen; |
| |
| ptr += copylen; |
| len -= copylen; |
| |
| cons += copylen; |
| } |
| |
| /* |
| * Not sure this ever mattered except on Alpha, but this barrier |
| * is to ensure that the update to req_cons is globally visible |
| * only after we have consumed all the data from the ring, and we |
| * don't end up seeing data written to the ring *after* the other |
| * end sees the update and writes more to the ring. Xen's own |
| * xenstored has the same barrier here (although with no comment |
| * at all, obviously, because it's Xen code). |
| */ |
| smp_mb(); |
| |
| qatomic_set(&s->xs->req_cons, cons); |
| |
| return copied; |
| } |
| |
| static unsigned int copy_to_ring(XenXenstoreState *s, uint8_t *ptr, |
| unsigned int len) |
| { |
| if (!len) { |
| return 0; |
| } |
| |
| XENSTORE_RING_IDX cons = qatomic_read(&s->xs->rsp_cons); |
| XENSTORE_RING_IDX prod = qatomic_read(&s->xs->rsp_prod); |
| unsigned int copied = 0; |
| |
| /* |
| * This matches the barrier in copy_to_ring() (or the guest's |
| * equivalent) betweem writing the data to the ring and updating |
| * rsp_prod. It protects against the pathological case (which |
| * again I think never happened except on Alpha) where our |
| * subsequent writes to the ring could *cross* the read of |
| * rsp_cons and the guest could see the new data when it was |
| * intending to read the old. |
| */ |
| smp_mb(); |
| |
| while (len) { |
| unsigned int avail = cons + XENSTORE_RING_SIZE - prod; |
| unsigned int offset = MASK_XENSTORE_IDX(prod); |
| unsigned int copylen = len; |
| |
| if (avail > XENSTORE_RING_SIZE) { |
| error_report("XenStore ring handling error"); |
| s->fatal_error = true; |
| break; |
| } else if (avail == 0) { |
| break; |
| } |
| |
| if (copylen > avail) { |
| copylen = avail; |
| } |
| if (copylen > XENSTORE_RING_SIZE - offset) { |
| copylen = XENSTORE_RING_SIZE - offset; |
| } |
| |
| |
| memcpy(&s->xs->rsp[offset], ptr, copylen); |
| copied += copylen; |
| |
| ptr += copylen; |
| len -= copylen; |
| |
| prod += copylen; |
| } |
| |
| /* Ensure the ring contents are seen before rsp_prod update. */ |
| smp_wmb(); |
| |
| qatomic_set(&s->xs->rsp_prod, prod); |
| |
| return copied; |
| } |
| |
| static unsigned int get_req(XenXenstoreState *s) |
| { |
| unsigned int copied = 0; |
| |
| if (s->fatal_error) { |
| return 0; |
| } |
| |
| assert(!req_pending(s)); |
| |
| if (s->req_offset < XENSTORE_HEADER_SIZE) { |
| void *ptr = s->req_data + s->req_offset; |
| unsigned int len = XENSTORE_HEADER_SIZE; |
| unsigned int copylen = copy_from_ring(s, ptr, len); |
| |
| copied += copylen; |
| s->req_offset += copylen; |
| } |
| |
| if (s->req_offset >= XENSTORE_HEADER_SIZE) { |
| struct xsd_sockmsg *req = (struct xsd_sockmsg *)s->req_data; |
| |
| if (req->len > (uint32_t)XENSTORE_PAYLOAD_MAX) { |
| error_report("Illegal XenStore request"); |
| s->fatal_error = true; |
| return 0; |
| } |
| |
| void *ptr = s->req_data + s->req_offset; |
| unsigned int len = XENSTORE_HEADER_SIZE + req->len - s->req_offset; |
| unsigned int copylen = copy_from_ring(s, ptr, len); |
| |
| copied += copylen; |
| s->req_offset += copylen; |
| } |
| |
| return copied; |
| } |
| |
| static unsigned int put_rsp(XenXenstoreState *s) |
| { |
| if (s->fatal_error) { |
| return 0; |
| } |
| |
| assert(s->rsp_pending); |
| |
| struct xsd_sockmsg *rsp = (struct xsd_sockmsg *)s->rsp_data; |
| assert(s->rsp_offset < XENSTORE_HEADER_SIZE + rsp->len); |
| |
| void *ptr = s->rsp_data + s->rsp_offset; |
| unsigned int len = XENSTORE_HEADER_SIZE + rsp->len - s->rsp_offset; |
| unsigned int copylen = copy_to_ring(s, ptr, len); |
| |
| s->rsp_offset += copylen; |
| |
| /* Have we produced a complete response? */ |
| if (s->rsp_offset == XENSTORE_HEADER_SIZE + rsp->len) { |
| reset_rsp(s); |
| } |
| |
| return copylen; |
| } |
| |
| static void deliver_watch(XenXenstoreState *s, const char *path, |
| const char *token) |
| { |
| struct xsd_sockmsg *rsp = (struct xsd_sockmsg *)s->rsp_data; |
| uint8_t *rsp_data = (uint8_t *)&rsp[1]; |
| unsigned int len; |
| |
| assert(!s->rsp_pending); |
| |
| trace_xenstore_watch_event(path, token); |
| |
| rsp->type = XS_WATCH_EVENT; |
| rsp->req_id = 0; |
| rsp->tx_id = 0; |
| rsp->len = 0; |
| |
| len = 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 += len; |
| rsp_data[rsp->len] = '\0'; |
| rsp->len++; |
| |
| len = 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 >= XENSTORE_PAYLOAD_MAX) { |
| return; |
| } |
| |
| memcpy(&rsp_data[rsp->len], token, len); |
| rsp->len += len; |
| rsp_data[rsp->len] = '\0'; |
| rsp->len++; |
| |
| s->rsp_pending = true; |
| } |
| |
| struct watch_event { |
| char *path; |
| char *token; |
| }; |
| |
| static void free_watch_event(struct watch_event *ev) |
| { |
| if (ev) { |
| g_free(ev->path); |
| g_free(ev->token); |
| g_free(ev); |
| } |
| } |
| |
| static void queue_watch(XenXenstoreState *s, const char *path, |
| const char *token) |
| { |
| struct watch_event *ev = g_new0(struct watch_event, 1); |
| |
| ev->path = g_strdup(path); |
| ev->token = g_strdup(token); |
| |
| s->watch_events = g_list_append(s->watch_events, ev); |
| } |
| |
| static void fire_watch_cb(void *opaque, const char *path, const char *token) |
| { |
| XenXenstoreState *s = 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 activity, |
| * no need to wake the guest. But if not, we need to send the evtchn. |
| */ |
| xen_be_evtchn_notify(s->eh, s->be_port); |
| } |
| } |
| |
| static void process_watch_events(XenXenstoreState *s) |
| { |
| struct watch_event *ev = s->watch_events->data; |
| |
| deliver_watch(s, ev->path, ev->token); |
| |
| s->watch_events = g_list_remove(s->watch_events, ev); |
| free_watch_event(ev); |
| } |
| |
| static void xen_xenstore_event(void *opaque) |
| { |
| XenXenstoreState *s = opaque; |
| evtchn_port_t port = xen_be_evtchn_pending(s->eh); |
| unsigned int copied_to, copied_from; |
| bool processed, notify = false; |
| |
| if (port != s->be_port) { |
| return; |
| } |
| |
| /* We know this is a no-op. */ |
| xen_be_evtchn_unmask(s->eh, port); |
| |
| do { |
| copied_to = copied_from = 0; |
| processed = false; |
| |
| if (!s->rsp_pending && s->watch_events) { |
| process_watch_events(s); |
| } |
| |
| if (s->rsp_pending) { |
| copied_to = put_rsp(s); |
| } |
| |
| if (!req_pending(s)) { |
| copied_from = get_req(s); |
| } |
| |
| if (req_pending(s) && !s->rsp_pending && !s->watch_events) { |
| process_req(s); |
| processed = true; |
| } |
| |
| notify |= copied_to || copied_from; |
| } while (copied_to || copied_from || processed); |
| |
| if (notify) { |
| xen_be_evtchn_notify(s->eh, s->be_port); |
| } |
| } |
| |
| static void alloc_guest_port(XenXenstoreState *s) |
| { |
| struct evtchn_alloc_unbound alloc = { |
| .dom = DOMID_SELF, |
| .remote_dom = DOMID_QEMU, |
| }; |
| |
| if (!xen_evtchn_alloc_unbound_op(&alloc)) { |
| s->guest_port = alloc.port; |
| } |
| } |
| |
| int xen_xenstore_reset(void) |
| { |
| XenXenstoreState *s = xen_xenstore_singleton; |
| int err; |
| |
| if (!s) { |
| return -ENOTSUP; |
| } |
| |
| s->req_offset = s->rsp_offset = 0; |
| s->rsp_pending = false; |
| |
| if (!memory_region_is_mapped(&s->xenstore_page)) { |
| uint64_t gpa = XEN_SPECIAL_PFN(XENSTORE) << TARGET_PAGE_BITS; |
| xen_overlay_do_map_page(&s->xenstore_page, gpa); |
| } |
| |
| alloc_guest_port(s); |
| |
| /* |
| * As qemu/dom0, bind to the guest's port. For incoming migration, this |
| * will be unbound as the guest's evtchn table is overwritten. We then |
| * rebind to the correct guest port in xen_xenstore_post_load(). |
| */ |
| err = xen_be_evtchn_bind_interdomain(s->eh, xen_domid, s->guest_port); |
| if (err < 0) { |
| return err; |
| } |
| s->be_port = err; |
| |
| /* |
| * We don't actually access the guest's page through the grant, because |
| * this isn't real Xen, and we can just use the page we gave it in the |
| * first place. Map the grant anyway, mostly for cosmetic purposes so |
| * it *looks* like it's in use in the guest-visible grant table. |
| */ |
| s->gt = qemu_xen_gnttab_open(); |
| uint32_t xs_gntref = GNTTAB_RESERVED_XENSTORE; |
| s->granted_xs = qemu_xen_gnttab_map_refs(s->gt, 1, xen_domid, &xs_gntref, |
| PROT_READ | PROT_WRITE); |
| |
| return 0; |
| } |
| |
| struct qemu_xs_handle { |
| XenstoreImplState *impl; |
| GList *watches; |
| QEMUBH *watch_bh; |
| }; |
| |
| struct qemu_xs_watch { |
| struct qemu_xs_handle *h; |
| char *path; |
| xs_watch_fn fn; |
| void *opaque; |
| GList *events; |
| }; |
| |
| static char *xs_be_get_domain_path(struct qemu_xs_handle *h, unsigned int domid) |
| { |
| return g_strdup_printf("/local/domain/%u", domid); |
| } |
| |
| static char **xs_be_directory(struct qemu_xs_handle *h, xs_transaction_t t, |
| const char *path, unsigned int *num) |
| { |
| GList *items = NULL, *l; |
| unsigned int i = 0; |
| char **items_ret; |
| int err; |
| |
| err = xs_impl_directory(h->impl, DOMID_QEMU, t, path, NULL, &items); |
| if (err) { |
| errno = err; |
| return NULL; |
| } |
| |
| items_ret = g_new0(char *, g_list_length(items) + 1); |
| *num = 0; |
| for (l = items; l; l = l->next) { |
| items_ret[i++] = l->data; |
| (*num)++; |
| } |
| g_list_free(items); |
| return items_ret; |
| } |
| |
| static void *xs_be_read(struct qemu_xs_handle *h, xs_transaction_t t, |
| const char *path, unsigned int *len) |
| { |
| GByteArray *data = g_byte_array_new(); |
| bool free_segment = false; |
| int err; |
| |
| err = xs_impl_read(h->impl, DOMID_QEMU, t, path, data); |
| if (err) { |
| free_segment = true; |
| errno = err; |
| } else { |
| if (len) { |
| *len = data->len; |
| } |
| /* The xen-bus-helper code expects to get NUL terminated string! */ |
| g_byte_array_append(data, (void *)"", 1); |
| } |
| |
| return g_byte_array_free(data, free_segment); |
| } |
| |
| static bool xs_be_write(struct qemu_xs_handle *h, xs_transaction_t t, |
| const char *path, const void *data, unsigned int len) |
| { |
| GByteArray *gdata = g_byte_array_new(); |
| int err; |
| |
| g_byte_array_append(gdata, data, len); |
| err = xs_impl_write(h->impl, DOMID_QEMU, t, path, gdata); |
| g_byte_array_unref(gdata); |
| if (err) { |
| errno = err; |
| return false; |
| } |
| return true; |
| } |
| |
| static bool xs_be_create(struct qemu_xs_handle *h, xs_transaction_t t, |
| unsigned int owner, unsigned int domid, |
| unsigned int perms, const char *path) |
| { |
| g_autoptr(GByteArray) data = g_byte_array_new(); |
| GList *perms_list = NULL; |
| int err; |
| |
| /* mkdir does this */ |
| err = xs_impl_read(h->impl, DOMID_QEMU, t, path, data); |
| if (err == ENOENT) { |
| err = xs_impl_write(h->impl, DOMID_QEMU, t, path, data); |
| } |
| if (err) { |
| errno = err; |
| return false; |
| } |
| |
| perms_list = g_list_append(perms_list, |
| xs_perm_as_string(XS_PERM_NONE, owner)); |
| perms_list = g_list_append(perms_list, |
| xs_perm_as_string(perms, domid)); |
| |
| err = xs_impl_set_perms(h->impl, DOMID_QEMU, t, path, perms_list); |
| g_list_free_full(perms_list, g_free); |
| if (err) { |
| errno = err; |
| return false; |
| } |
| return true; |
| } |
| |
| static bool xs_be_destroy(struct qemu_xs_handle *h, xs_transaction_t t, |
| const char *path) |
| { |
| int err = xs_impl_rm(h->impl, DOMID_QEMU, t, path); |
| if (err) { |
| errno = err; |
| return false; |
| } |
| return true; |
| } |
| |
| static void be_watch_bh(void *_h) |
| { |
| struct qemu_xs_handle *h = _h; |
| GList *l; |
| |
| for (l = h->watches; l; l = l->next) { |
| struct qemu_xs_watch *w = l->data; |
| |
| while (w->events) { |
| struct watch_event *ev = w->events->data; |
| |
| w->fn(w->opaque, ev->path); |
| |
| w->events = g_list_remove(w->events, ev); |
| free_watch_event(ev); |
| } |
| } |
| } |
| |
| static void xs_be_watch_cb(void *opaque, const char *path, const char *token) |
| { |
| struct watch_event *ev = g_new0(struct watch_event, 1); |
| struct qemu_xs_watch *w = opaque; |
| |
| /* We don't care about the token */ |
| ev->path = g_strdup(path); |
| w->events = g_list_append(w->events, ev); |
| |
| qemu_bh_schedule(w->h->watch_bh); |
| } |
| |
| static struct qemu_xs_watch *xs_be_watch(struct qemu_xs_handle *h, |
| const char *path, xs_watch_fn fn, |
| void *opaque) |
| { |
| struct qemu_xs_watch *w = g_new0(struct qemu_xs_watch, 1); |
| int err; |
| |
| w->h = h; |
| w->fn = fn; |
| w->opaque = opaque; |
| |
| err = xs_impl_watch(h->impl, DOMID_QEMU, path, NULL, xs_be_watch_cb, w); |
| if (err) { |
| errno = err; |
| g_free(w); |
| return NULL; |
| } |
| |
| w->path = g_strdup(path); |
| h->watches = g_list_append(h->watches, w); |
| return w; |
| } |
| |
| static void xs_be_unwatch(struct qemu_xs_handle *h, struct qemu_xs_watch *w) |
| { |
| xs_impl_unwatch(h->impl, DOMID_QEMU, w->path, NULL, xs_be_watch_cb, w); |
| |
| h->watches = g_list_remove(h->watches, w); |
| g_list_free_full(w->events, (GDestroyNotify)free_watch_event); |
| g_free(w->path); |
| g_free(w); |
| } |
| |
| static xs_transaction_t xs_be_transaction_start(struct qemu_xs_handle *h) |
| { |
| unsigned int new_tx = XBT_NULL; |
| int err = xs_impl_transaction_start(h->impl, DOMID_QEMU, &new_tx); |
| if (err) { |
| errno = err; |
| return XBT_NULL; |
| } |
| return new_tx; |
| } |
| |
| static bool xs_be_transaction_end(struct qemu_xs_handle *h, xs_transaction_t t, |
| bool abort) |
| { |
| int err = xs_impl_transaction_end(h->impl, DOMID_QEMU, t, !abort); |
| if (err) { |
| errno = err; |
| return false; |
| } |
| return true; |
| } |
| |
| static struct qemu_xs_handle *xs_be_open(void) |
| { |
| XenXenstoreState *s = xen_xenstore_singleton; |
| struct qemu_xs_handle *h; |
| |
| if (!s && !s->impl) { |
| errno = -ENOSYS; |
| return NULL; |
| } |
| |
| h = g_new0(struct qemu_xs_handle, 1); |
| h->impl = s->impl; |
| |
| h->watch_bh = aio_bh_new(qemu_get_aio_context(), be_watch_bh, h); |
| |
| return h; |
| } |
| |
| static void xs_be_close(struct qemu_xs_handle *h) |
| { |
| while (h->watches) { |
| struct qemu_xs_watch *w = h->watches->data; |
| xs_be_unwatch(h, w); |
| } |
| |
| qemu_bh_delete(h->watch_bh); |
| g_free(h); |
| } |
| |
| static struct xenstore_backend_ops emu_xenstore_backend_ops = { |
| .open = xs_be_open, |
| .close = xs_be_close, |
| .get_domain_path = xs_be_get_domain_path, |
| .directory = xs_be_directory, |
| .read = xs_be_read, |
| .write = xs_be_write, |
| .create = xs_be_create, |
| .destroy = xs_be_destroy, |
| .watch = xs_be_watch, |
| .unwatch = xs_be_unwatch, |
| .transaction_start = xs_be_transaction_start, |
| .transaction_end = xs_be_transaction_end, |
| }; |