Signed-off-by: Michael Tokarev <mjt@tls.msk.ru> Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
		
			
				
	
	
		
			1938 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1938 lines
		
	
	
		
			49 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * QEMU Xen emulation: The actual implementation of XenStore
 | 
						|
 *
 | 
						|
 * Copyright © 2023 Amazon.com, Inc. or its affiliates. All Rights Reserved.
 | 
						|
 *
 | 
						|
 * Authors: David Woodhouse <dwmw2@infradead.org>, Paul Durrant <paul@xen.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 "qom/object.h"
 | 
						|
 | 
						|
#include "hw/xen/xen.h"
 | 
						|
 | 
						|
#include "xen_xenstore.h"
 | 
						|
#include "xenstore_impl.h"
 | 
						|
 | 
						|
#include "hw/xen/interface/io/xs_wire.h"
 | 
						|
 | 
						|
#define XS_MAX_WATCHES          128
 | 
						|
#define XS_MAX_DOMAIN_NODES     1000
 | 
						|
#define XS_MAX_NODE_SIZE        2048
 | 
						|
#define XS_MAX_TRANSACTIONS     10
 | 
						|
#define XS_MAX_PERMS_PER_NODE   5
 | 
						|
 | 
						|
#define XS_VALID_CHARS "abcdefghijklmnopqrstuvwxyz" \
 | 
						|
                       "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
 | 
						|
                       "0123456789-/_"
 | 
						|
 | 
						|
typedef struct XsNode {
 | 
						|
    uint32_t ref;
 | 
						|
    GByteArray *content;
 | 
						|
    GList *perms;
 | 
						|
    GHashTable *children;
 | 
						|
    uint64_t gencnt;
 | 
						|
    bool deleted_in_tx;
 | 
						|
    bool modified_in_tx;
 | 
						|
    unsigned int serialized_tx;
 | 
						|
#ifdef XS_NODE_UNIT_TEST
 | 
						|
    gchar *name; /* debug only */
 | 
						|
#endif
 | 
						|
} XsNode;
 | 
						|
 | 
						|
typedef struct XsWatch {
 | 
						|
    struct XsWatch *next;
 | 
						|
    xs_impl_watch_fn *cb;
 | 
						|
    void *cb_opaque;
 | 
						|
    char *token;
 | 
						|
    unsigned int dom_id;
 | 
						|
    int rel_prefix;
 | 
						|
} XsWatch;
 | 
						|
 | 
						|
typedef struct XsTransaction {
 | 
						|
    XsNode *root;
 | 
						|
    unsigned int nr_nodes;
 | 
						|
    unsigned int base_tx;
 | 
						|
    unsigned int tx_id;
 | 
						|
    unsigned int dom_id;
 | 
						|
} XsTransaction;
 | 
						|
 | 
						|
struct XenstoreImplState {
 | 
						|
    XsNode *root;
 | 
						|
    unsigned int nr_nodes;
 | 
						|
    GHashTable *watches;
 | 
						|
    unsigned int nr_domu_watches;
 | 
						|
    GHashTable *transactions;
 | 
						|
    unsigned int nr_domu_transactions;
 | 
						|
    unsigned int root_tx;
 | 
						|
    unsigned int last_tx;
 | 
						|
    bool serialized;
 | 
						|
};
 | 
						|
 | 
						|
 | 
						|
static void nobble_tx(gpointer key, gpointer value, gpointer user_data)
 | 
						|
{
 | 
						|
    unsigned int *new_tx_id = user_data;
 | 
						|
    XsTransaction *tx = value;
 | 
						|
 | 
						|
    if (tx->base_tx == *new_tx_id) {
 | 
						|
        /* Transactions based on XBT_NULL will always fail */
 | 
						|
        tx->base_tx = XBT_NULL;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static inline unsigned int next_tx(struct XenstoreImplState *s)
 | 
						|
{
 | 
						|
    unsigned int tx_id;
 | 
						|
 | 
						|
    /* Find the next TX id which isn't either XBT_NULL or in use. */
 | 
						|
    do {
 | 
						|
        tx_id = ++s->last_tx;
 | 
						|
    } while (tx_id == XBT_NULL || tx_id == s->root_tx ||
 | 
						|
             g_hash_table_lookup(s->transactions, GINT_TO_POINTER(tx_id)));
 | 
						|
 | 
						|
    /*
 | 
						|
     * It is vanishingly unlikely, but ensure that no outstanding transaction
 | 
						|
     * is based on the (previous incarnation of the) newly-allocated TX id.
 | 
						|
     */
 | 
						|
    g_hash_table_foreach(s->transactions, nobble_tx, &tx_id);
 | 
						|
 | 
						|
    return tx_id;
 | 
						|
}
 | 
						|
 | 
						|
static inline XsNode *xs_node_new(void)
 | 
						|
{
 | 
						|
    XsNode *n = g_new0(XsNode, 1);
 | 
						|
    n->ref = 1;
 | 
						|
 | 
						|
#ifdef XS_NODE_UNIT_TEST
 | 
						|
    nr_xs_nodes++;
 | 
						|
    xs_node_list = g_list_prepend(xs_node_list, n);
 | 
						|
#endif
 | 
						|
    return n;
 | 
						|
}
 | 
						|
 | 
						|
static inline XsNode *xs_node_ref(XsNode *n)
 | 
						|
{
 | 
						|
    /* With just 10 transactions, it can never get anywhere near this. */
 | 
						|
    g_assert(n->ref < INT_MAX);
 | 
						|
 | 
						|
    g_assert(n->ref);
 | 
						|
    n->ref++;
 | 
						|
    return n;
 | 
						|
}
 | 
						|
 | 
						|
static inline void xs_node_unref(XsNode *n)
 | 
						|
{
 | 
						|
    if (!n) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
    g_assert(n->ref);
 | 
						|
    if (--n->ref) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    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);
 | 
						|
    }
 | 
						|
#ifdef XS_NODE_UNIT_TEST
 | 
						|
    g_free(n->name);
 | 
						|
    nr_xs_nodes--;
 | 
						|
    xs_node_list = g_list_remove(xs_node_list, n);
 | 
						|
#endif
 | 
						|
    g_free(n);
 | 
						|
}
 | 
						|
 | 
						|
char *xs_perm_as_string(unsigned int perm, unsigned int domid)
 | 
						|
{
 | 
						|
    char letter;
 | 
						|
 | 
						|
    switch (perm) {
 | 
						|
    case XS_PERM_READ | XS_PERM_WRITE:
 | 
						|
        letter = 'b';
 | 
						|
        break;
 | 
						|
    case XS_PERM_READ:
 | 
						|
        letter = 'r';
 | 
						|
        break;
 | 
						|
    case XS_PERM_WRITE:
 | 
						|
        letter = 'w';
 | 
						|
        break;
 | 
						|
    case XS_PERM_NONE:
 | 
						|
    default:
 | 
						|
        letter = '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 = xs_node_new();
 | 
						|
 | 
						|
#ifdef XS_NODE_UNIT_TEST
 | 
						|
    if (name) {
 | 
						|
        n->name = g_strdup(name);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    n->perms = 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_child_insert(gpointer key, gpointer value, gpointer user_data)
 | 
						|
{
 | 
						|
    g_hash_table_insert(user_data, g_strdup(key), xs_node_ref(value));
 | 
						|
}
 | 
						|
 | 
						|
static XsNode *xs_node_copy(XsNode *old)
 | 
						|
{
 | 
						|
    XsNode *n = xs_node_new();
 | 
						|
 | 
						|
    n->gencnt = old->gencnt;
 | 
						|
 | 
						|
#ifdef XS_NODE_UNIT_TEST
 | 
						|
    if (n->name) {
 | 
						|
        n->name = g_strdup(old->name);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    assert(old);
 | 
						|
    if (old->children) {
 | 
						|
        n->children = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
 | 
						|
                                            (GDestroyNotify)xs_node_unref);
 | 
						|
        g_hash_table_foreach(old->children, do_child_insert, n->children);
 | 
						|
    }
 | 
						|
    if (old->perms) {
 | 
						|
        n->perms = g_list_copy_deep(old->perms, do_perm_copy, NULL);
 | 
						|
    }
 | 
						|
    if (old->content) {
 | 
						|
        n->content = g_byte_array_ref(old->content);
 | 
						|
    }
 | 
						|
    return n;
 | 
						|
}
 | 
						|
 | 
						|
/* Returns true if it made a change to the hash table */
 | 
						|
static bool xs_node_add_child(XsNode *n, const char *path_elem, XsNode *child)
 | 
						|
{
 | 
						|
    assert(!strchr(path_elem, '/'));
 | 
						|
 | 
						|
    if (!child) {
 | 
						|
        assert(n->children);
 | 
						|
        return g_hash_table_remove(n->children, path_elem);
 | 
						|
    }
 | 
						|
 | 
						|
#ifdef XS_NODE_UNIT_TEST
 | 
						|
    g_free(child->name);
 | 
						|
    child->name = g_strdup(path_elem);
 | 
						|
#endif
 | 
						|
    if (!n->children) {
 | 
						|
        n->children = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
 | 
						|
                                            (GDestroyNotify)xs_node_unref);
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * The documentation for g_hash_table_insert() says that it "returns a
 | 
						|
     * boolean value to indicate whether the newly added value was already
 | 
						|
     * in the hash table or not."
 | 
						|
     *
 | 
						|
     * It could perhaps be clearer that returning TRUE means it wasn't,
 | 
						|
     */
 | 
						|
    return 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;
 | 
						|
    void *op_opaque2;
 | 
						|
 | 
						|
    GList *watches;
 | 
						|
    unsigned int dom_id;
 | 
						|
    unsigned int tx_id;
 | 
						|
 | 
						|
    /* The number of nodes which will exist in the tree if this op succeeds. */
 | 
						|
    unsigned int new_nr_nodes;
 | 
						|
 | 
						|
    /*
 | 
						|
     * 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;
 | 
						|
    bool in_transaction;
 | 
						|
 | 
						|
    /* Tracking during recursion so we know which is first. */
 | 
						|
    bool deleted_in_tx;
 | 
						|
};
 | 
						|
 | 
						|
static void fire_watches(struct walk_op *op, bool parents)
 | 
						|
{
 | 
						|
    GList *l = NULL;
 | 
						|
    XsWatch *w;
 | 
						|
 | 
						|
    if (!op->mutating || op->in_transaction) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (parents) {
 | 
						|
        l = op->watches;
 | 
						|
    }
 | 
						|
 | 
						|
    w = g_hash_table_lookup(op->s->watches, op->path);
 | 
						|
    while (w || l) {
 | 
						|
        if (!w) {
 | 
						|
            /* Fire the parent nodes from 'op' if asked to */
 | 
						|
            w = l->data;
 | 
						|
            l = l->next;
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        assert(strlen(op->path) > w->rel_prefix);
 | 
						|
        w->cb(w->cb_opaque, op->path + w->rel_prefix, w->token);
 | 
						|
 | 
						|
        w = w->next;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static int xs_node_add_content(XsNode **n, struct walk_op *op)
 | 
						|
{
 | 
						|
    GByteArray *data = op->op_opaque;
 | 
						|
 | 
						|
    if (op->dom_id) {
 | 
						|
        /*
 | 
						|
         * The real XenStored includes permissions and names of child nodes
 | 
						|
         * in the calculated datasize but life's too short. For a single
 | 
						|
         * tenant internal XenStore, we don't have to be quite as pedantic.
 | 
						|
         */
 | 
						|
        if (data->len > XS_MAX_NODE_SIZE) {
 | 
						|
            return E2BIG;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    /* We *are* the node to be written. Either this or a copy. */
 | 
						|
    if (!op->inplace) {
 | 
						|
        XsNode *old = *n;
 | 
						|
        *n = xs_node_copy(old);
 | 
						|
        xs_node_unref(old);
 | 
						|
    }
 | 
						|
 | 
						|
    if ((*n)->content) {
 | 
						|
        g_byte_array_unref((*n)->content);
 | 
						|
    }
 | 
						|
    (*n)->content = g_byte_array_ref(data);
 | 
						|
    if (op->tx_id != XBT_NULL) {
 | 
						|
        (*n)->modified_in_tx = true;
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int xs_node_get_content(XsNode **n, struct walk_op *op)
 | 
						|
{
 | 
						|
    GByteArray *data = op->op_opaque;
 | 
						|
    GByteArray *node_data;
 | 
						|
 | 
						|
    assert(op->inplace);
 | 
						|
    assert(*n);
 | 
						|
 | 
						|
    node_data = (*n)->content;
 | 
						|
    if (node_data) {
 | 
						|
        g_byte_array_append(data, node_data->data, node_data->len);
 | 
						|
    }
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int node_rm_recurse(gpointer key, gpointer value, gpointer user_data)
 | 
						|
{
 | 
						|
    struct walk_op *op = user_data;
 | 
						|
    int path_len = strlen(op->path);
 | 
						|
    int key_len = strlen(key);
 | 
						|
    XsNode *n = value;
 | 
						|
    bool this_inplace = op->inplace;
 | 
						|
 | 
						|
    if (n->ref != 1) {
 | 
						|
        op->inplace = 0;
 | 
						|
    }
 | 
						|
 | 
						|
    assert(key_len + path_len + 2 <= sizeof(op->path));
 | 
						|
    op->path[path_len] = '/';
 | 
						|
    memcpy(op->path + path_len + 1, key, key_len + 1);
 | 
						|
 | 
						|
    if (n->children) {
 | 
						|
        g_hash_table_foreach_remove(n->children, node_rm_recurse, op);
 | 
						|
    }
 | 
						|
    op->new_nr_nodes--;
 | 
						|
 | 
						|
    /*
 | 
						|
     * 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] = '\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 XsNode *xs_node_copy_deleted(XsNode *old, struct walk_op *op);
 | 
						|
static void copy_deleted_recurse(gpointer key, gpointer value,
 | 
						|
                                 gpointer user_data)
 | 
						|
{
 | 
						|
    struct walk_op *op = user_data;
 | 
						|
    GHashTable *siblings = op->op_opaque2;
 | 
						|
    XsNode *n = xs_node_copy_deleted(value, op);
 | 
						|
 | 
						|
    /*
 | 
						|
     * Reinsert the deleted_in_tx copy of the node into the parent's
 | 
						|
     * 'children' hash table. Having stashed it from op->op_opaque2
 | 
						|
     * before the recursive call to xs_node_copy_deleted() scribbled
 | 
						|
     * over it.
 | 
						|
     */
 | 
						|
    g_hash_table_insert(siblings, g_strdup(key), n);
 | 
						|
}
 | 
						|
 | 
						|
static XsNode *xs_node_copy_deleted(XsNode *old, struct walk_op *op)
 | 
						|
{
 | 
						|
    XsNode *n = xs_node_new();
 | 
						|
 | 
						|
    n->gencnt = old->gencnt;
 | 
						|
 | 
						|
#ifdef XS_NODE_UNIT_TEST
 | 
						|
    if (old->name) {
 | 
						|
        n->name = g_strdup(old->name);
 | 
						|
    }
 | 
						|
#endif
 | 
						|
 | 
						|
    if (old->children) {
 | 
						|
        n->children = g_hash_table_new_full(g_str_hash, g_str_equal, g_free,
 | 
						|
                                            (GDestroyNotify)xs_node_unref);
 | 
						|
        op->op_opaque2 = n->children;
 | 
						|
        g_hash_table_foreach(old->children, copy_deleted_recurse, op);
 | 
						|
    }
 | 
						|
    if (old->perms) {
 | 
						|
        n->perms = g_list_copy_deep(old->perms, do_perm_copy, NULL);
 | 
						|
    }
 | 
						|
    n->deleted_in_tx = true;
 | 
						|
    /* If it gets resurrected we only fire a watch if it lost its content */
 | 
						|
    if (old->content) {
 | 
						|
        n->modified_in_tx = true;
 | 
						|
    }
 | 
						|
    op->new_nr_nodes--;
 | 
						|
    return n;
 | 
						|
}
 | 
						|
 | 
						|
static int xs_node_rm(XsNode **n, struct walk_op *op)
 | 
						|
{
 | 
						|
    bool this_inplace = op->inplace;
 | 
						|
 | 
						|
    if (op->tx_id != XBT_NULL) {
 | 
						|
        /* It's not trivial to do inplace handling for this one */
 | 
						|
        XsNode *old = *n;
 | 
						|
        *n = xs_node_copy_deleted(old, op);
 | 
						|
        xs_node_unref(old);
 | 
						|
        return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    /* Fire watches for, and count, nodes in the subtree which get deleted */
 | 
						|
    if ((*n)->children) {
 | 
						|
        g_hash_table_foreach_remove((*n)->children, node_rm_recurse, op);
 | 
						|
    }
 | 
						|
    op->new_nr_nodes--;
 | 
						|
 | 
						|
    if (this_inplace) {
 | 
						|
        xs_node_unref(*n);
 | 
						|
    }
 | 
						|
    *n = NULL;
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int xs_node_get_perms(XsNode **n, struct walk_op *op)
 | 
						|
{
 | 
						|
    GList **perms = op->op_opaque;
 | 
						|
 | 
						|
    assert(op->inplace);
 | 
						|
    assert(*n);
 | 
						|
 | 
						|
    *perms = g_list_copy_deep((*n)->perms, do_perm_copy, NULL);
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static void parse_perm(const char *perm, char *letter, unsigned int *dom_id)
 | 
						|
{
 | 
						|
    unsigned int n = sscanf(perm, "%c%u", letter, dom_id);
 | 
						|
 | 
						|
    assert(n == 2);
 | 
						|
}
 | 
						|
 | 
						|
static bool can_access(unsigned int dom_id, GList *perms, const char *letters)
 | 
						|
{
 | 
						|
    unsigned int i, n;
 | 
						|
    char perm_letter;
 | 
						|
    unsigned int perm_dom_id;
 | 
						|
    bool access;
 | 
						|
 | 
						|
    if (dom_id == 0) {
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    n = g_list_length(perms);
 | 
						|
    assert(n >= 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 == perm_dom_id) {
 | 
						|
        return true;
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * The letter of the first perm specified the default access for all other
 | 
						|
     * domains.
 | 
						|
     */
 | 
						|
    access = !!strchr(letters, perm_letter);
 | 
						|
    for (i = 1; i < n; i++) {
 | 
						|
        parse_perm(g_list_nth_data(perms, i), &perm_letter, &perm_dom_id);
 | 
						|
        if (dom_id != perm_dom_id) {
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
        access = !!strchr(letters, perm_letter);
 | 
						|
    }
 | 
						|
 | 
						|
    return access;
 | 
						|
}
 | 
						|
 | 
						|
static int xs_node_set_perms(XsNode **n, struct walk_op *op)
 | 
						|
{
 | 
						|
    GList *perms = op->op_opaque;
 | 
						|
 | 
						|
    if (op->dom_id) {
 | 
						|
        unsigned int perm_dom_id;
 | 
						|
        char perm_letter;
 | 
						|
 | 
						|
        /* A guest may not change permissions on nodes it does not own */
 | 
						|
        if (!can_access(op->dom_id, (*n)->perms, "")) {
 | 
						|
            return EPERM;
 | 
						|
        }
 | 
						|
 | 
						|
        /* A guest may not change the owner of a node it owns. */
 | 
						|
        parse_perm(perms->data, &perm_letter, &perm_dom_id);
 | 
						|
        if (perm_dom_id != op->dom_id) {
 | 
						|
            return EPERM;
 | 
						|
        }
 | 
						|
 | 
						|
        if (g_list_length(perms) > XS_MAX_PERMS_PER_NODE) {
 | 
						|
            return ENOSPC;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /* We *are* the node to be written. Either this or a copy. */
 | 
						|
    if (!op->inplace) {
 | 
						|
        XsNode *old = *n;
 | 
						|
        *n = xs_node_copy(old);
 | 
						|
        xs_node_unref(old);
 | 
						|
    }
 | 
						|
 | 
						|
    if ((*n)->perms) {
 | 
						|
        g_list_free_full((*n)->perms, g_free);
 | 
						|
    }
 | 
						|
    (*n)->perms = g_list_copy_deep(perms, do_perm_copy, NULL);
 | 
						|
    if (op->tx_id != XBT_NULL) {
 | 
						|
        (*n)->modified_in_tx = true;
 | 
						|
    }
 | 
						|
    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 = NULL;
 | 
						|
    size_t namelen;
 | 
						|
    XsNode *old = *n, *child = NULL;
 | 
						|
    bool stole_child = false;
 | 
						|
    bool this_inplace;
 | 
						|
    XsWatch *watch;
 | 
						|
    int err;
 | 
						|
 | 
						|
    namelen = strlen(op->path);
 | 
						|
    watch = g_hash_table_lookup(op->s->watches, op->path);
 | 
						|
 | 
						|
    /* Is there a child, or do we hit the double-NUL termination? */
 | 
						|
    if (op->path[namelen + 1]) {
 | 
						|
        char *slash;
 | 
						|
        child_name = op->path + namelen + 1;
 | 
						|
        slash = strchr(child_name, '/');
 | 
						|
        if (slash) {
 | 
						|
            *slash = '\0';
 | 
						|
        }
 | 
						|
        op->path[namelen] = '/';
 | 
						|
    }
 | 
						|
 | 
						|
    /* If we walk into a subtree which is shared, we must COW */
 | 
						|
    if (op->mutating && old->ref != 1) {
 | 
						|
        op->inplace = false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (!child_name) {
 | 
						|
        const char *letters = op->mutating ? "wb" : "rb";
 | 
						|
 | 
						|
        if (!can_access(op->dom_id, old->perms, letters)) {
 | 
						|
            err = EACCES;
 | 
						|
            goto out;
 | 
						|
        }
 | 
						|
 | 
						|
        /* This is the actual node on which the operation shall be performed */
 | 
						|
        err = op->op_fn(n, op);
 | 
						|
        if (!err) {
 | 
						|
            fire_watches(op, true);
 | 
						|
        }
 | 
						|
        goto out;
 | 
						|
    }
 | 
						|
 | 
						|
    /* op->inplace will be further modified during the recursion */
 | 
						|
    this_inplace = op->inplace;
 | 
						|
 | 
						|
    if (old && old->children) {
 | 
						|
        child = g_hash_table_lookup(old->children, child_name);
 | 
						|
        /* This is a *weak* reference to 'child', owned by the hash table */
 | 
						|
    }
 | 
						|
 | 
						|
    if (child) {
 | 
						|
        if (child->deleted_in_tx) {
 | 
						|
            assert(child->ref == 1);
 | 
						|
            /* Cannot actually set child->deleted_in_tx = false until later */
 | 
						|
        }
 | 
						|
        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* owner
 | 
						|
         * 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 later.
 | 
						|
         */
 | 
						|
        if (op->mutating && this_inplace) {
 | 
						|
            g_hash_table_remove(old->children, child_name);
 | 
						|
            stole_child = true;
 | 
						|
        }
 | 
						|
    } else if (op->create_dirs) {
 | 
						|
        assert(op->mutating);
 | 
						|
 | 
						|
        if (!can_access(op->dom_id, old->perms, "wb")) {
 | 
						|
            err = EACCES;
 | 
						|
            goto out;
 | 
						|
        }
 | 
						|
 | 
						|
        if (op->dom_id && op->new_nr_nodes >= XS_MAX_DOMAIN_NODES) {
 | 
						|
            err = ENOSPC;
 | 
						|
            goto out;
 | 
						|
        }
 | 
						|
 | 
						|
        child = xs_node_create(child_name, old->perms);
 | 
						|
        op->new_nr_nodes++;
 | 
						|
 | 
						|
        /*
 | 
						|
         * If we're creating a new child, we can clearly modify it (and its
 | 
						|
         * children) in place from here on down.
 | 
						|
         */
 | 
						|
        op->inplace = true;
 | 
						|
    } else {
 | 
						|
        err = ENOENT;
 | 
						|
        goto out;
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * 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 = 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 = xs_node_walk(&child, op);
 | 
						|
 | 
						|
    if (watch) {
 | 
						|
        op->watches = g_list_remove(op->watches, watch);
 | 
						|
    }
 | 
						|
 | 
						|
    if (err || !op->mutating) {
 | 
						|
        if (stole_child) {
 | 
						|
            /* Put it back as it was. */
 | 
						|
            g_hash_table_replace(old->children, g_strdup(child_name), child);
 | 
						|
        } else {
 | 
						|
            xs_node_unref(child);
 | 
						|
        }
 | 
						|
        goto out;
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * 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 = xs_node_copy(old);
 | 
						|
        xs_node_unref(old);
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * If we resurrected a deleted_in_tx node, we can mark it as no longer
 | 
						|
     * deleted now that we know the overall operation has succeeded.
 | 
						|
     */
 | 
						|
    if (op->create_dirs && child && child->deleted_in_tx) {
 | 
						|
        op->new_nr_nodes++;
 | 
						|
        child->deleted_in_tx = false;
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * The child may be NULL here, for a remove operation. Either way,
 | 
						|
     * xs_node_add_child() will do the right thing and return a value
 | 
						|
     * indicating whether it changed the parent's hash table or not.
 | 
						|
     *
 | 
						|
     * We bump the parent gencnt if it adds a child that we *didn't*
 | 
						|
     * steal from it in the first place, or if child==NULL and was
 | 
						|
     * thus removed (whether we stole it earlier and didn't put it
 | 
						|
     * back, or xs_node_add_child() actually removed it now).
 | 
						|
     */
 | 
						|
    if ((xs_node_add_child(*n, child_name, child) && !stole_child) || !child) {
 | 
						|
        (*n)->gencnt++;
 | 
						|
    }
 | 
						|
 | 
						|
 out:
 | 
						|
    op->path[namelen] = '\0';
 | 
						|
    if (!namelen) {
 | 
						|
        assert(!op->watches);
 | 
						|
        /*
 | 
						|
         * On completing the recursion back up the path walk and reaching the
 | 
						|
         * top, assign the new node count if the operation was successful. 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 (!err && op->mutating) {
 | 
						|
            if (!op->in_transaction) {
 | 
						|
                if (op->s->root_tx != op->s->last_tx) {
 | 
						|
                    op->s->root_tx = next_tx(op->s);
 | 
						|
                }
 | 
						|
                op->s->nr_nodes = op->new_nr_nodes;
 | 
						|
            } else {
 | 
						|
                XsTransaction *tx = g_hash_table_lookup(op->s->transactions,
 | 
						|
                                                        GINT_TO_POINTER(op->tx_id));
 | 
						|
                assert(tx);
 | 
						|
                tx->nr_nodes = op->new_nr_nodes;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
    return err;
 | 
						|
}
 | 
						|
 | 
						|
static void append_directory_item(gpointer key, gpointer value,
 | 
						|
                                  gpointer user_data)
 | 
						|
{
 | 
						|
    GList **items = user_data;
 | 
						|
 | 
						|
    *items = g_list_insert_sorted(*items, g_strdup(key), (GCompareFunc)strcmp);
 | 
						|
}
 | 
						|
 | 
						|
/* Populates items with char * names which caller must free. */
 | 
						|
static int xs_node_directory(XsNode **n, struct walk_op *op)
 | 
						|
{
 | 
						|
    GList **items = op->op_opaque;
 | 
						|
 | 
						|
    assert(op->inplace);
 | 
						|
    assert(*n);
 | 
						|
 | 
						|
    if ((*n)->children) {
 | 
						|
        g_hash_table_foreach((*n)->children, append_directory_item, items);
 | 
						|
    }
 | 
						|
 | 
						|
    if (op->op_opaque2) {
 | 
						|
        *(uint64_t *)op->op_opaque2 = (*n)->gencnt;
 | 
						|
    }
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int validate_path(char *outpath, const char *userpath,
 | 
						|
                         unsigned int dom_id)
 | 
						|
{
 | 
						|
    size_t i, pathlen = strlen(userpath);
 | 
						|
 | 
						|
    if (!pathlen || userpath[pathlen] == '/' || strstr(userpath, "//")) {
 | 
						|
        return EINVAL;
 | 
						|
    }
 | 
						|
    for (i = 0; i < pathlen; i++) {
 | 
						|
        if (!strchr(XS_VALID_CHARS, userpath[i])) {
 | 
						|
            return EINVAL;
 | 
						|
        }
 | 
						|
    }
 | 
						|
    if (userpath[0] == '/') {
 | 
						|
        if (pathlen > XENSTORE_ABS_PATH_MAX) {
 | 
						|
            return E2BIG;
 | 
						|
        }
 | 
						|
        memcpy(outpath, userpath, pathlen + 1);
 | 
						|
    } else {
 | 
						|
        if (pathlen > XENSTORE_REL_PATH_MAX) {
 | 
						|
            return E2BIG;
 | 
						|
        }
 | 
						|
        snprintf(outpath, XENSTORE_ABS_PATH_MAX, "/local/domain/%u/%s", dom_id,
 | 
						|
                 userpath);
 | 
						|
    }
 | 
						|
    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)
 | 
						|
{
 | 
						|
    int ret = validate_path(op->path, path, dom_id);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * 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] = '\0';
 | 
						|
    op->watches = NULL;
 | 
						|
    op->path[0] = '\0';
 | 
						|
    op->inplace = true;
 | 
						|
    op->mutating = false;
 | 
						|
    op->create_dirs = false;
 | 
						|
    op->in_transaction = false;
 | 
						|
    op->dom_id = dom_id;
 | 
						|
    op->tx_id = tx_id;
 | 
						|
    op->s = s;
 | 
						|
 | 
						|
    if (tx_id == XBT_NULL) {
 | 
						|
        *rootp = &s->root;
 | 
						|
        op->new_nr_nodes = s->nr_nodes;
 | 
						|
    } else {
 | 
						|
        XsTransaction *tx = g_hash_table_lookup(s->transactions,
 | 
						|
                                                GINT_TO_POINTER(tx_id));
 | 
						|
        if (!tx) {
 | 
						|
            return ENOENT;
 | 
						|
        }
 | 
						|
        *rootp = &tx->root;
 | 
						|
        op->new_nr_nodes = tx->nr_nodes;
 | 
						|
        op->in_transaction = true;
 | 
						|
    }
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_read(XenstoreImplState *s, unsigned int dom_id,
 | 
						|
                 xs_transaction_t tx_id, const char *path, GByteArray *data)
 | 
						|
{
 | 
						|
    /*
 | 
						|
     * The data GByteArray shall exist, and will be freed by caller.
 | 
						|
     * Just g_byte_array_append() to it.
 | 
						|
     */
 | 
						|
    struct walk_op op;
 | 
						|
    XsNode **n;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    ret = init_walk_op(s, &op, tx_id, dom_id, path, &n);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
    op.op_fn = xs_node_get_content;
 | 
						|
    op.op_opaque = data;
 | 
						|
    return xs_node_walk(n, &op);
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_write(XenstoreImplState *s, unsigned int dom_id,
 | 
						|
                  xs_transaction_t 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. Or just ref it.
 | 
						|
     */
 | 
						|
    struct walk_op op;
 | 
						|
    XsNode **n;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    ret = init_walk_op(s, &op, tx_id, dom_id, path, &n);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
    op.op_fn = xs_node_add_content;
 | 
						|
    op.op_opaque = data;
 | 
						|
    op.mutating = true;
 | 
						|
    op.create_dirs = true;
 | 
						|
    return xs_node_walk(n, &op);
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_directory(XenstoreImplState *s, unsigned int dom_id,
 | 
						|
                      xs_transaction_t tx_id, const char *path,
 | 
						|
                      uint64_t *gencnt, 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.
 | 
						|
     */
 | 
						|
    struct walk_op op;
 | 
						|
    XsNode **n;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    ret = init_walk_op(s, &op, tx_id, dom_id, path, &n);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
    op.op_fn = xs_node_directory;
 | 
						|
    op.op_opaque = items;
 | 
						|
    op.op_opaque2 = gencnt;
 | 
						|
    return xs_node_walk(n, &op);
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_transaction_start(XenstoreImplState *s, unsigned int dom_id,
 | 
						|
                              xs_transaction_t *tx_id)
 | 
						|
{
 | 
						|
    XsTransaction *tx;
 | 
						|
 | 
						|
    if (*tx_id != XBT_NULL) {
 | 
						|
        return EINVAL;
 | 
						|
    }
 | 
						|
 | 
						|
    if (dom_id && s->nr_domu_transactions >= XS_MAX_TRANSACTIONS) {
 | 
						|
        return ENOSPC;
 | 
						|
    }
 | 
						|
 | 
						|
    tx = g_new0(XsTransaction, 1);
 | 
						|
 | 
						|
    tx->nr_nodes = s->nr_nodes;
 | 
						|
    tx->tx_id = next_tx(s);
 | 
						|
    tx->base_tx = s->root_tx;
 | 
						|
    tx->root = xs_node_ref(s->root);
 | 
						|
    tx->dom_id = dom_id;
 | 
						|
 | 
						|
    g_hash_table_insert(s->transactions, GINT_TO_POINTER(tx->tx_id), tx);
 | 
						|
    if (dom_id) {
 | 
						|
        s->nr_domu_transactions++;
 | 
						|
    }
 | 
						|
    *tx_id = tx->tx_id;
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static gboolean tx_commit_walk(gpointer key, gpointer value,
 | 
						|
                               gpointer user_data)
 | 
						|
{
 | 
						|
    struct walk_op *op = user_data;
 | 
						|
    int path_len = strlen(op->path);
 | 
						|
    int key_len = strlen(key);
 | 
						|
    bool fire_parents = true;
 | 
						|
    XsWatch *watch;
 | 
						|
    XsNode *n = value;
 | 
						|
 | 
						|
    if (n->ref != 1) {
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    if (n->deleted_in_tx) {
 | 
						|
        /*
 | 
						|
         * We fire 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 = !op->deleted_in_tx;
 | 
						|
 | 
						|
        /* Only used on the way down so no need to clear it later */
 | 
						|
        op->deleted_in_tx = true;
 | 
						|
    }
 | 
						|
 | 
						|
    assert(key_len + path_len + 2 <= sizeof(op->path));
 | 
						|
    op->path[path_len] = '/';
 | 
						|
    memcpy(op->path + path_len + 1, key, key_len + 1);
 | 
						|
 | 
						|
    watch = g_hash_table_lookup(op->s->watches, op->path);
 | 
						|
    if (watch) {
 | 
						|
        op->watches = g_list_append(op->watches, watch);
 | 
						|
    }
 | 
						|
 | 
						|
    if (n->children) {
 | 
						|
        g_hash_table_foreach_remove(n->children, tx_commit_walk, op);
 | 
						|
    }
 | 
						|
 | 
						|
    if (watch) {
 | 
						|
        op->watches = 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 = false;
 | 
						|
    }
 | 
						|
    op->path[path_len] = '\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;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    if (s->root_tx != tx->base_tx) {
 | 
						|
        return EAGAIN;
 | 
						|
    }
 | 
						|
    xs_node_unref(s->root);
 | 
						|
    s->root = tx->root;
 | 
						|
    tx->root = NULL;
 | 
						|
    s->root_tx = tx->tx_id;
 | 
						|
    s->nr_nodes = tx->nr_nodes;
 | 
						|
 | 
						|
    ret = init_walk_op(s, &op, XBT_NULL, tx->dom_id, "/", &n);
 | 
						|
    /*
 | 
						|
     * There are two reasons why init_walk_op() may fail: an invalid tx_id,
 | 
						|
     * or an invalid path. We pass XBT_NULL and "/", and it cannot fail.
 | 
						|
     * If it does, the world is broken. And returning 'ret' would be weird
 | 
						|
     * because the transaction *was* committed, and all this tree walk is
 | 
						|
     * trying to do is fire the resulting watches on newly-committed nodes.
 | 
						|
     */
 | 
						|
    g_assert(!ret);
 | 
						|
 | 
						|
    op.deleted_in_tx = false;
 | 
						|
    op.mutating = true;
 | 
						|
 | 
						|
    /*
 | 
						|
     * 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;
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_transaction_end(XenstoreImplState *s, unsigned int dom_id,
 | 
						|
                            xs_transaction_t tx_id, bool commit)
 | 
						|
{
 | 
						|
    int ret = 0;
 | 
						|
    XsTransaction *tx = g_hash_table_lookup(s->transactions,
 | 
						|
                                            GINT_TO_POINTER(tx_id));
 | 
						|
 | 
						|
    if (!tx || tx->dom_id != dom_id) {
 | 
						|
        return ENOENT;
 | 
						|
    }
 | 
						|
 | 
						|
    if (commit) {
 | 
						|
        ret = transaction_commit(s, tx);
 | 
						|
    }
 | 
						|
 | 
						|
    g_hash_table_remove(s->transactions, GINT_TO_POINTER(tx_id));
 | 
						|
    if (dom_id) {
 | 
						|
        assert(s->nr_domu_transactions);
 | 
						|
        s->nr_domu_transactions--;
 | 
						|
    }
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_rm(XenstoreImplState *s, unsigned int dom_id,
 | 
						|
               xs_transaction_t tx_id, const char *path)
 | 
						|
{
 | 
						|
    struct walk_op op;
 | 
						|
    XsNode **n;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    ret = init_walk_op(s, &op, tx_id, dom_id, path, &n);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
    op.op_fn = xs_node_rm;
 | 
						|
    op.mutating = true;
 | 
						|
    return xs_node_walk(n, &op);
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_get_perms(XenstoreImplState *s, unsigned int dom_id,
 | 
						|
                      xs_transaction_t tx_id, const char *path, GList **perms)
 | 
						|
{
 | 
						|
    struct walk_op op;
 | 
						|
    XsNode **n;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    ret = init_walk_op(s, &op, tx_id, dom_id, path, &n);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
    op.op_fn = xs_node_get_perms;
 | 
						|
    op.op_opaque = perms;
 | 
						|
    return xs_node_walk(n, &op);
 | 
						|
}
 | 
						|
 | 
						|
static void is_valid_perm(gpointer data, gpointer user_data)
 | 
						|
{
 | 
						|
    char *perm = data;
 | 
						|
    bool *valid = user_data;
 | 
						|
    char letter;
 | 
						|
    unsigned int dom_id;
 | 
						|
 | 
						|
    if (!*valid) {
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    if (sscanf(perm, "%c%u", &letter, &dom_id) != 2) {
 | 
						|
        *valid = false;
 | 
						|
        return;
 | 
						|
    }
 | 
						|
 | 
						|
    switch (letter) {
 | 
						|
    case 'n':
 | 
						|
    case 'r':
 | 
						|
    case 'w':
 | 
						|
    case 'b':
 | 
						|
        break;
 | 
						|
 | 
						|
    default:
 | 
						|
        *valid = false;
 | 
						|
        break;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_set_perms(XenstoreImplState *s, unsigned int dom_id,
 | 
						|
                      xs_transaction_t tx_id, const char *path, GList *perms)
 | 
						|
{
 | 
						|
    struct walk_op op;
 | 
						|
    XsNode **n;
 | 
						|
    bool valid = true;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    if (!g_list_length(perms)) {
 | 
						|
        return EINVAL;
 | 
						|
    }
 | 
						|
 | 
						|
    g_list_foreach(perms, is_valid_perm, &valid);
 | 
						|
    if (!valid) {
 | 
						|
        return EINVAL;
 | 
						|
    }
 | 
						|
 | 
						|
    ret = init_walk_op(s, &op, tx_id, dom_id, path, &n);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
    op.op_fn = xs_node_set_perms;
 | 
						|
    op.op_opaque = perms;
 | 
						|
    op.mutating = true;
 | 
						|
    return xs_node_walk(n, &op);
 | 
						|
}
 | 
						|
 | 
						|
static int do_xs_impl_watch(XenstoreImplState *s, unsigned int dom_id,
 | 
						|
                            const char *path, const char *token,
 | 
						|
                            xs_impl_watch_fn fn, void *opaque)
 | 
						|
 | 
						|
{
 | 
						|
    char abspath[XENSTORE_ABS_PATH_MAX + 1];
 | 
						|
    XsWatch *w, *l;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    ret = validate_path(abspath, path, dom_id);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    /* Check for duplicates */
 | 
						|
    l = w = g_hash_table_lookup(s->watches, abspath);
 | 
						|
    while (w) {
 | 
						|
        if (!g_strcmp0(token, w->token) &&  opaque == w->cb_opaque &&
 | 
						|
            fn == w->cb && dom_id == w->dom_id) {
 | 
						|
            return EEXIST;
 | 
						|
        }
 | 
						|
        w = w->next;
 | 
						|
    }
 | 
						|
 | 
						|
    if (dom_id && s->nr_domu_watches >= XS_MAX_WATCHES) {
 | 
						|
        return E2BIG;
 | 
						|
    }
 | 
						|
 | 
						|
    w = g_new0(XsWatch, 1);
 | 
						|
    w->token = g_strdup(token);
 | 
						|
    w->cb = fn;
 | 
						|
    w->cb_opaque = opaque;
 | 
						|
    w->dom_id = dom_id;
 | 
						|
    w->rel_prefix = strlen(abspath) - strlen(path);
 | 
						|
 | 
						|
    /* l was looked up above when checking for duplicates */
 | 
						|
    if (l) {
 | 
						|
        w->next = l->next;
 | 
						|
        l->next = w;
 | 
						|
    } else {
 | 
						|
        g_hash_table_insert(s->watches, g_strdup(abspath), w);
 | 
						|
    }
 | 
						|
    if (dom_id) {
 | 
						|
        s->nr_domu_watches++;
 | 
						|
    }
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_watch(XenstoreImplState *s, unsigned int dom_id, const char *path,
 | 
						|
                  const char *token, xs_impl_watch_fn fn, void *opaque)
 | 
						|
{
 | 
						|
    int ret = do_xs_impl_watch(s, dom_id, path, token, fn, opaque);
 | 
						|
 | 
						|
    if (!ret) {
 | 
						|
        /* A new watch should fire immediately */
 | 
						|
        fn(opaque, path, token);
 | 
						|
    }
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
static XsWatch *free_watch(XenstoreImplState *s, XsWatch *w)
 | 
						|
{
 | 
						|
    XsWatch *next = w->next;
 | 
						|
 | 
						|
    if (w->dom_id) {
 | 
						|
        assert(s->nr_domu_watches);
 | 
						|
        s->nr_domu_watches--;
 | 
						|
    }
 | 
						|
 | 
						|
    g_free(w->token);
 | 
						|
    g_free(w);
 | 
						|
 | 
						|
    return next;
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_unwatch(XenstoreImplState *s, unsigned int dom_id,
 | 
						|
                    const char *path, const char *token,
 | 
						|
                    xs_impl_watch_fn fn, void *opaque)
 | 
						|
{
 | 
						|
    char abspath[XENSTORE_ABS_PATH_MAX + 1];
 | 
						|
    XsWatch *w, **l;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    ret = validate_path(abspath, path, dom_id);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    w = g_hash_table_lookup(s->watches, abspath);
 | 
						|
    if (!w) {
 | 
						|
        return ENOENT;
 | 
						|
    }
 | 
						|
 | 
						|
    /*
 | 
						|
     * 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).
 | 
						|
     */
 | 
						|
    if (!g_strcmp0(token, w->token) && fn == w->cb && opaque == w->cb_opaque &&
 | 
						|
        dom_id == w->dom_id) {
 | 
						|
        if (w->next) {
 | 
						|
            /* Insert the previous 'next' into the hash table */
 | 
						|
            g_hash_table_insert(s->watches, g_strdup(abspath), w->next);
 | 
						|
        } else {
 | 
						|
            /* Nothing left; remove from hash table */
 | 
						|
            g_hash_table_remove(s->watches, abspath);
 | 
						|
        }
 | 
						|
        free_watch(s, 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.
 | 
						|
     */
 | 
						|
    for (l = &w->next; *l; l = &w->next) {
 | 
						|
        w = *l;
 | 
						|
 | 
						|
        if (!g_strcmp0(token, w->token) && fn == w->cb &&
 | 
						|
            opaque != w->cb_opaque && dom_id == w->dom_id) {
 | 
						|
            *l = free_watch(s, w);
 | 
						|
            return 0;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    return ENOENT;
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_reset_watches(XenstoreImplState *s, unsigned int dom_id)
 | 
						|
{
 | 
						|
    char **watch_paths;
 | 
						|
    guint nr_watch_paths;
 | 
						|
    guint i;
 | 
						|
 | 
						|
    watch_paths = (char **)g_hash_table_get_keys_as_array(s->watches,
 | 
						|
                                                          &nr_watch_paths);
 | 
						|
 | 
						|
    for (i = 0; i < nr_watch_paths; i++) {
 | 
						|
        XsWatch *w1 = 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 = w2 = w1;
 | 
						|
        l = &w;
 | 
						|
        while (w) {
 | 
						|
            if (w->dom_id == dom_id) {
 | 
						|
                /* If we're freeing the head of the list, bump w2 */
 | 
						|
                if (w2 == w) {
 | 
						|
                    w2 = w->next;
 | 
						|
                }
 | 
						|
                *l = free_watch(s, w);
 | 
						|
            } else {
 | 
						|
                l = &w->next;
 | 
						|
            }
 | 
						|
            w = *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 != 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;
 | 
						|
}
 | 
						|
 | 
						|
static void xs_tx_free(void *_tx)
 | 
						|
{
 | 
						|
    XsTransaction *tx = _tx;
 | 
						|
    if (tx->root) {
 | 
						|
        xs_node_unref(tx->root);
 | 
						|
    }
 | 
						|
    g_free(tx);
 | 
						|
}
 | 
						|
 | 
						|
XenstoreImplState *xs_impl_create(unsigned int dom_id)
 | 
						|
{
 | 
						|
    XenstoreImplState *s = g_new0(XenstoreImplState, 1);
 | 
						|
    GList *perms;
 | 
						|
 | 
						|
    s->watches = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
 | 
						|
    s->transactions = g_hash_table_new_full(g_direct_hash, g_direct_equal,
 | 
						|
                                            NULL, xs_tx_free);
 | 
						|
 | 
						|
    perms = g_list_append(NULL, xs_perm_as_string(XS_PERM_NONE, 0));
 | 
						|
    s->root = xs_node_create("/", perms);
 | 
						|
    g_list_free_full(perms, g_free);
 | 
						|
    s->nr_nodes = 1;
 | 
						|
 | 
						|
    s->root_tx = s->last_tx = 1;
 | 
						|
    return s;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
static void clear_serialized_tx(gpointer key, gpointer value, gpointer opaque)
 | 
						|
{
 | 
						|
    XsNode *n = value;
 | 
						|
 | 
						|
    n->serialized_tx = 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 = value;
 | 
						|
 | 
						|
    clear_serialized_tx(NULL, t->root, NULL);
 | 
						|
}
 | 
						|
 | 
						|
static void write_be32(GByteArray *save, uint32_t val)
 | 
						|
{
 | 
						|
    uint32_t be = 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 = opaque;
 | 
						|
    XsNode *n = value;
 | 
						|
    char *name = key;
 | 
						|
    uint8_t flag = 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 transaction in which it exists. Which
 | 
						|
     * may be the root tx.
 | 
						|
     */
 | 
						|
    if (n->serialized_tx != XBT_NULL) {
 | 
						|
        flag = NODE_REF;
 | 
						|
        g_byte_array_append(ss->bytes, &flag, 1);
 | 
						|
        write_be32(ss->bytes, n->serialized_tx);
 | 
						|
    } else {
 | 
						|
        GList *l;
 | 
						|
        n->serialized_tx = ss->tx_id;
 | 
						|
 | 
						|
        if (n->modified_in_tx) {
 | 
						|
            flag |= MODIFIED_IN_TX;
 | 
						|
        }
 | 
						|
        if (n->deleted_in_tx) {
 | 
						|
            flag |= 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->len);
 | 
						|
        } else {
 | 
						|
            write_be32(ss->bytes, 0);
 | 
						|
        }
 | 
						|
 | 
						|
        for (l = n->perms; l; l = 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 = tx_id;
 | 
						|
    save_node(NULL, root, ss);
 | 
						|
}
 | 
						|
 | 
						|
static void save_tx(gpointer key, gpointer value, gpointer opaque)
 | 
						|
{
 | 
						|
    uint32_t tx_id = GPOINTER_TO_INT(key);
 | 
						|
    struct save_state *ss = opaque;
 | 
						|
    XsTransaction *n = 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 = opaque;
 | 
						|
    XsWatch *w = value;
 | 
						|
 | 
						|
    /* We only save the *guest* watches. */
 | 
						|
    if (w->dom_id) {
 | 
						|
        gpointer relpath = key + w->rel_prefix;
 | 
						|
        g_byte_array_append(ss->bytes, relpath, strlen(relpath) + 1);
 | 
						|
        g_byte_array_append(ss->bytes, (void *)w->token, strlen(w->token) + 1);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
GByteArray *xs_impl_serialize(XenstoreImplState *s)
 | 
						|
{
 | 
						|
    struct save_state ss;
 | 
						|
 | 
						|
    ss.bytes = g_byte_array_new();
 | 
						|
 | 
						|
    /*
 | 
						|
     * node = flags [ real_node / node_ref ]
 | 
						|
     *   flags = uint8_t (MODIFIED_IN_TX | DELETED_IN_TX | NODE_REF)
 | 
						|
     *   node_ref = tx_id (in which the original version of this node exists)
 | 
						|
     *   real_node = content perms child* NUL
 | 
						|
     *     content = len data
 | 
						|
     *       len = uint32_t
 | 
						|
     *       data = uint8_t{len}
 | 
						|
     *     perms = perm* NUL
 | 
						|
     *       perm = asciiz
 | 
						|
     *   child = name node
 | 
						|
     *     name = asciiz
 | 
						|
     *
 | 
						|
     * tree = tx_id node
 | 
						|
     *   tx_id = uint32_t
 | 
						|
     *
 | 
						|
     * transaction = base_tx_id dom_id tree
 | 
						|
     *   base_tx_id = uint32_t
 | 
						|
     *   dom_id = uint32_t
 | 
						|
     *
 | 
						|
     * tx_list = tree transaction* XBT_NULL
 | 
						|
     *
 | 
						|
     * watch = path token
 | 
						|
     *   path = asciiz
 | 
						|
     *   token = asciiz
 | 
						|
     *
 | 
						|
     * watch_list = watch* NUL
 | 
						|
     *
 | 
						|
     * xs_serialize_stream = last_tx tx_list watch_list
 | 
						|
     *   last_tx = 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 = 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 = ntohl(d);
 | 
						|
    us->d += sizeof(d);
 | 
						|
    us->l -= 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 = strnlen((void *)us->d, us->l);
 | 
						|
    if (l == us->l) {
 | 
						|
        return -EINVAL;
 | 
						|
    }
 | 
						|
 | 
						|
    if (str) {
 | 
						|
        *str = (void *)us->d;
 | 
						|
    }
 | 
						|
    if (len) {
 | 
						|
        *len = l;
 | 
						|
    }
 | 
						|
 | 
						|
    us->d += l + 1;
 | 
						|
    us->l -= l + 1;
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static XsNode *lookup_node(XsNode *n, char *path)
 | 
						|
{
 | 
						|
    char *slash = strchr(path, '/');
 | 
						|
    XsNode *child;
 | 
						|
 | 
						|
    if (path[0] == '\0') {
 | 
						|
        return n;
 | 
						|
    }
 | 
						|
 | 
						|
    if (slash) {
 | 
						|
        *slash = '\0';
 | 
						|
    }
 | 
						|
 | 
						|
    if (!n->children) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
    child = g_hash_table_lookup(n->children, path);
 | 
						|
    if (!slash) {
 | 
						|
        return child;
 | 
						|
    }
 | 
						|
 | 
						|
    *slash = '/';
 | 
						|
    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 == us->s->root_tx) {
 | 
						|
        return lookup_node(us->s->root, us->path + 1);
 | 
						|
    }
 | 
						|
 | 
						|
    t = g_hash_table_lookup(us->s->transactions, GINT_TO_POINTER(tx_id));
 | 
						|
    if (!t) {
 | 
						|
        return NULL;
 | 
						|
    }
 | 
						|
    g_assert(t->root);
 | 
						|
    return lookup_node(t->root, us->path + 1);
 | 
						|
}
 | 
						|
 | 
						|
static void count_child_nodes(gpointer key, gpointer value, gpointer user_data)
 | 
						|
{
 | 
						|
    unsigned int *nr_nodes = user_data;
 | 
						|
    XsNode *n = value;
 | 
						|
 | 
						|
    (*nr_nodes)++;
 | 
						|
 | 
						|
    if (n->children) {
 | 
						|
        g_hash_table_foreach(n->children, count_child_nodes, nr_nodes);
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
static int consume_node(struct unsave_state *us, XsNode **nodep,
 | 
						|
                        unsigned int *nr_nodes)
 | 
						|
{
 | 
						|
    XsNode *n = NULL;
 | 
						|
    uint8_t flags;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    if (us->l < 1) {
 | 
						|
        return -EINVAL;
 | 
						|
    }
 | 
						|
    flags = us->d[0];
 | 
						|
    us->d++;
 | 
						|
    us->l--;
 | 
						|
 | 
						|
    if (flags == NODE_REF) {
 | 
						|
        unsigned int tx;
 | 
						|
 | 
						|
        ret = consume_be32(us, &tx);
 | 
						|
        if (ret) {
 | 
						|
            return ret;
 | 
						|
        }
 | 
						|
 | 
						|
        n = lookup_tx_node(us, tx);
 | 
						|
        if (!n) {
 | 
						|
            return -EINVAL;
 | 
						|
        }
 | 
						|
        n->ref++;
 | 
						|
        if (n->children) {
 | 
						|
            g_hash_table_foreach(n->children, count_child_nodes, nr_nodes);
 | 
						|
        }
 | 
						|
    } else {
 | 
						|
        uint32_t datalen;
 | 
						|
 | 
						|
        if (flags & ~(DELETED_IN_TX | MODIFIED_IN_TX)) {
 | 
						|
            return -EINVAL;
 | 
						|
        }
 | 
						|
        n = xs_node_new();
 | 
						|
 | 
						|
        if (flags & DELETED_IN_TX) {
 | 
						|
            n->deleted_in_tx = true;
 | 
						|
        }
 | 
						|
        if (flags & MODIFIED_IN_TX) {
 | 
						|
            n->modified_in_tx = true;
 | 
						|
        }
 | 
						|
        ret = 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 = g_byte_array_new();
 | 
						|
            g_byte_array_append(node_data, us->d, datalen);
 | 
						|
            us->d += datalen;
 | 
						|
            us->l -= datalen;
 | 
						|
            n->content = node_data;
 | 
						|
 | 
						|
            if (us->root_walk) {
 | 
						|
                n->modified_in_tx = true;
 | 
						|
            }
 | 
						|
        }
 | 
						|
        while (1) {
 | 
						|
            char *perm = NULL;
 | 
						|
            size_t permlen = 0;
 | 
						|
 | 
						|
            ret = consume_string(us, &perm, &permlen);
 | 
						|
            if (ret) {
 | 
						|
                xs_node_unref(n);
 | 
						|
                return ret;
 | 
						|
            }
 | 
						|
 | 
						|
            if (!permlen) {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            n->perms = g_list_append(n->perms, g_strdup(perm));
 | 
						|
        }
 | 
						|
 | 
						|
        /* Now children */
 | 
						|
        while (1) {
 | 
						|
            size_t childlen;
 | 
						|
            char *childname;
 | 
						|
            char *pathend;
 | 
						|
            XsNode *child = NULL;
 | 
						|
 | 
						|
            ret = consume_string(us, &childname, &childlen);
 | 
						|
            if (ret) {
 | 
						|
                xs_node_unref(n);
 | 
						|
                return ret;
 | 
						|
            }
 | 
						|
 | 
						|
            if (!childlen) {
 | 
						|
                break;
 | 
						|
            }
 | 
						|
 | 
						|
            pathend = us->path + strlen(us->path);
 | 
						|
            strncat(us->path, "/", sizeof(us->path) - 1);
 | 
						|
            strncat(us->path, childname, sizeof(us->path) - 1);
 | 
						|
 | 
						|
            ret = consume_node(us, &child, nr_nodes);
 | 
						|
            *pathend = '\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 = true;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (!n->deleted_in_tx) {
 | 
						|
        (*nr_nodes)++;
 | 
						|
    }
 | 
						|
 | 
						|
    *nodep = n;
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int consume_tree(struct unsave_state *us, XsTransaction *t)
 | 
						|
{
 | 
						|
    int ret;
 | 
						|
 | 
						|
    ret = consume_be32(us, &t->tx_id);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    if (t->tx_id > us->s->last_tx) {
 | 
						|
        return -EINVAL;
 | 
						|
    }
 | 
						|
 | 
						|
    us->path[0] = '\0';
 | 
						|
 | 
						|
    return consume_node(us, &t->root, &t->nr_nodes);
 | 
						|
}
 | 
						|
 | 
						|
int xs_impl_deserialize(XenstoreImplState *s, GByteArray *bytes,
 | 
						|
                        unsigned int dom_id, xs_impl_watch_fn watch_fn,
 | 
						|
                        void *watch_opaque)
 | 
						|
{
 | 
						|
    struct unsave_state us;
 | 
						|
    XsTransaction base_t = { 0 };
 | 
						|
    int ret;
 | 
						|
 | 
						|
    us.s = s;
 | 
						|
    us.bytes = bytes;
 | 
						|
    us.d = bytes->data;
 | 
						|
    us.l = bytes->len;
 | 
						|
 | 
						|
    xs_impl_reset_watches(s, dom_id);
 | 
						|
    g_hash_table_remove_all(s->transactions);
 | 
						|
 | 
						|
    xs_node_unref(s->root);
 | 
						|
    s->root = NULL;
 | 
						|
    s->root_tx = s->last_tx = XBT_NULL;
 | 
						|
 | 
						|
    ret = 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 = dom_id;
 | 
						|
    base_t.base_tx = XBT_NULL;
 | 
						|
    us.root_walk = true;
 | 
						|
    ret = consume_tree(&us, &base_t);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
    us.root_walk = 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 = transaction_commit(s, &base_t);
 | 
						|
    if (ret) {
 | 
						|
        return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    while (1) {
 | 
						|
        unsigned int base_tx;
 | 
						|
        XsTransaction *t;
 | 
						|
 | 
						|
        ret = consume_be32(&us, &base_tx);
 | 
						|
        if (ret) {
 | 
						|
            return ret;
 | 
						|
        }
 | 
						|
        if (base_tx == XBT_NULL) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        t = g_new0(XsTransaction, 1);
 | 
						|
        t->base_tx = base_tx;
 | 
						|
 | 
						|
        ret = consume_be32(&us, &t->dom_id);
 | 
						|
        if (!ret) {
 | 
						|
            ret = consume_tree(&us, t);
 | 
						|
        }
 | 
						|
        if (ret) {
 | 
						|
            g_free(t);
 | 
						|
            return ret;
 | 
						|
        }
 | 
						|
        g_assert(t->root);
 | 
						|
        if (t->dom_id) {
 | 
						|
            s->nr_domu_transactions++;
 | 
						|
        }
 | 
						|
        g_hash_table_insert(s->transactions, GINT_TO_POINTER(t->tx_id), t);
 | 
						|
    }
 | 
						|
 | 
						|
    while (1) {
 | 
						|
        char *path, *token;
 | 
						|
        size_t pathlen, toklen;
 | 
						|
 | 
						|
        ret = consume_string(&us, &path, &pathlen);
 | 
						|
        if (ret) {
 | 
						|
            return ret;
 | 
						|
        }
 | 
						|
        if (!pathlen) {
 | 
						|
            break;
 | 
						|
        }
 | 
						|
 | 
						|
        ret = consume_string(&us, &token, &toklen);
 | 
						|
        if (ret) {
 | 
						|
            return ret;
 | 
						|
        }
 | 
						|
 | 
						|
        if (!watch_fn) {
 | 
						|
            continue;
 | 
						|
        }
 | 
						|
 | 
						|
        ret = do_xs_impl_watch(s, dom_id, path, token, watch_fn, watch_opaque);
 | 
						|
        if (ret) {
 | 
						|
            return ret;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    if (us.l) {
 | 
						|
        return -EINVAL;
 | 
						|
    }
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 |