* If it fails, returns NULL and sets errno.
* Temporary memory allocations will be done with ctx.
*/
-static struct node *read_node(struct connection *conn, const void *ctx,
- const char *name)
+struct node *read_node(struct connection *conn, const void *ctx,
+ const char *name)
{
TDB_DATA key, data;
struct xs_tdb_record_hdr *hdr;
* Get name of node parent.
* Temporary memory allocations are done with ctx.
*/
-static char *get_parent(const void *ctx, const char *node)
+char *get_parent(const void *ctx, const char *node)
{
char *parent;
char *slash = strrchr(node + 1, '/');
* If it fails, returns NULL and sets errno.
* Temporary memory allocations are done with ctx.
*/
-struct node *get_node(struct connection *conn,
- const void *ctx,
- const char *name,
- enum xs_perm_type perm)
+static struct node *get_node(struct connection *conn,
+ const void *ctx,
+ const char *name,
+ enum xs_perm_type perm)
{
struct node *node;
return errno;
}
- fire_watches(conn, in, name, false);
+ fire_watches(conn, in, name, node, false, NULL);
send_ack(conn, XS_WRITE);
return 0;
node = create_node(conn, in, name, NULL, 0);
if (!node)
return errno;
- fire_watches(conn, in, name, false);
+ fire_watches(conn, in, name, node, false, NULL);
}
send_ack(conn, XS_MKDIR);
talloc_free(name);
}
- fire_watches(conn, ctx, node->name, true);
+ fire_watches(conn, ctx, node->name, node, true, NULL);
delete_node_single(conn, node);
delete_child(conn, parent, basename(node->name));
talloc_free(node);
parent = read_node(conn, ctx, parentname);
if (!parent)
return (errno == ENOMEM) ? ENOMEM : EINVAL;
+ node->parent = parent;
/*
* Fire the watches now, when we can still see the node permissions.
* This fine as we are single threaded and the next possible read will
* be handled only after the node has been really removed.
*/
- fire_watches(conn, ctx, name, false);
+ fire_watches(conn, ctx, name, node, false, NULL);
return delete_node(conn, ctx, parent, node);
}
static int do_set_perms(struct connection *conn, struct buffered_data *in)
{
- struct node_perms perms;
+ struct node_perms perms, old_perms;
char *name, *permstr;
struct node *node;
perms.p[0].id != node->perms.p[0].id)
return EPERM;
+ old_perms = node->perms;
domain_entry_dec(conn, node);
node->perms = perms;
domain_entry_inc(conn, node);
if (write_node(conn, node, false))
return errno;
- fire_watches(conn, in, name, false);
+ fire_watches(conn, in, name, node, false, &old_perms);
send_ack(conn, XS_SET_PERMS);
return 0;
/* Canonicalize this path if possible. */
char *canonicalize(struct connection *conn, const void *ctx, const char *node);
+/* Get access permissions. */
+enum xs_perm_type perm_for_conn(struct connection *conn,
+ const struct node_perms *perms);
+
/* Write a node to the tdb data base. */
int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node,
bool no_quota_check);
-/* Get this node, checking we have permissions. */
-struct node *get_node(struct connection *conn,
- const void *ctx,
- const char *name,
- enum xs_perm_type perm);
+/* Get a node from the tdb data base. */
+struct node *read_node(struct connection *conn, const void *ctx,
+ const char *name);
struct connection *new_connection(connwritefn_t *write, connreadfn_t *read);
void check_store(void);
/* Is this a valid node name? */
bool is_valid_nodename(const char *node);
+/* Get name of parent node. */
+char *get_parent(const void *ctx, const char *node);
+
/* Tracing infrastructure. */
void trace_create(const void *data, const char *type);
void trace_destroy(const void *data, const char *type);
unmap_interface(domain->interface);
}
- fire_watches(NULL, domain, "@releaseDomain", false);
+ fire_watches(NULL, domain, "@releaseDomain", NULL, false, NULL);
wrl_domain_destroy(domain);
}
if (notify)
- fire_watches(NULL, NULL, "@releaseDomain", false);
+ fire_watches(NULL, NULL, "@releaseDomain", NULL, false, NULL);
}
/* We scan all domains rather than use the information given here. */
/* Now domain belongs to its connection. */
talloc_steal(domain->conn, domain);
- fire_watches(NULL, in, "@introduceDomain", false);
+ fire_watches(NULL, in, "@introduceDomain", NULL, false, NULL);
} else if ((domain->mfn == mfn) && (domain->conn != conn)) {
/* Use XS_INTRODUCE for recreating the xenbus event-channel. */
if (domain->port)
/* Generation count (or NO_GENERATION) for conflict checking. */
uint64_t generation;
+ /* Original node permissions. */
+ struct node_perms perms;
+
/* Generation count checking required? */
bool check_gen;
i->node = talloc_strdup(i, node->name);
if (!i->node)
goto nomem;
+ if (node->generation != NO_GENERATION && node->perms.num) {
+ i->perms.p = talloc_array(i, struct xs_permissions,
+ node->perms.num);
+ if (!i->perms.p)
+ goto nomem;
+ i->perms.num = node->perms.num;
+ memcpy(i->perms.p, node->perms.p,
+ i->perms.num * sizeof(*i->perms.p));
+ }
introduce = true;
i->ta_node = false;
talloc_free(data.dptr);
if (ret)
goto err;
- } else if (tdb_delete(tdb_ctx, key))
+ fire_watches(conn, trans, i->node, NULL, false,
+ i->perms.p ? &i->perms : NULL);
+ } else {
+ fire_watches(conn, trans, i->node, NULL, false,
+ i->perms.p ? &i->perms : NULL);
+ if (tdb_delete(tdb_ctx, key))
goto err;
- fire_watches(conn, trans, i->node, false);
+ }
}
if (i->ta_node && tdb_delete(tdb_ctx, ta_key))
unsigned int len;
char *data;
- if (!check_special_event(name)) {
- /* Can this conn load node, or see that it doesn't exist? */
- struct node *node = get_node(conn, ctx, name, XS_PERM_READ);
- /*
- * XXX We allow EACCES here because otherwise a non-dom0
- * backend driver cannot watch for disappearance of a frontend
- * xenstore directory. When the directory disappears, we
- * revert to permissions of the parent directory for that path,
- * which will typically disallow access for the backend.
- * But this breaks device-channel teardown!
- * Really we should fix this better...
- */
- if (!node && errno != ENOENT && errno != EACCES)
- return;
- }
-
if (watch->relative_path) {
name += strlen(watch->relative_path);
if (*name == '/') /* Could be "" */
talloc_free(data);
}
+/*
+ * Check permissions of a specific watch to fire:
+ * Either the node itself or its parent have to be readable by the connection
+ * the watch has been setup for. In case a watch event is created due to
+ * changed permissions we need to take the old permissions into account, too.
+ */
+static bool watch_permitted(struct connection *conn, const void *ctx,
+ const char *name, struct node *node,
+ struct node_perms *perms)
+{
+ enum xs_perm_type perm;
+ struct node *parent;
+ char *parent_name;
+
+ if (perms) {
+ perm = perm_for_conn(conn, perms);
+ if (perm & XS_PERM_READ)
+ return true;
+ }
+
+ if (!node) {
+ node = read_node(conn, ctx, name);
+ if (!node)
+ return false;
+ }
+
+ perm = perm_for_conn(conn, &node->perms);
+ if (perm & XS_PERM_READ)
+ return true;
+
+ parent = node->parent;
+ if (!parent) {
+ parent_name = get_parent(ctx, node->name);
+ if (!parent_name)
+ return false;
+ parent = read_node(conn, ctx, parent_name);
+ if (!parent)
+ return false;
+ }
+
+ perm = perm_for_conn(conn, &parent->perms);
+
+ return perm & XS_PERM_READ;
+}
+
/*
* Check whether any watch events are to be sent.
* Temporary memory allocations are done with ctx.
+ * We need to take the (potential) old permissions of the node into account
+ * as a watcher losing permissions to access a node should receive the
+ * watch event, too.
*/
void fire_watches(struct connection *conn, const void *ctx, const char *name,
- bool exact)
+ struct node *node, bool exact, struct node_perms *perms)
{
struct connection *i;
struct watch *watch;
/* Create an event for each watch. */
list_for_each_entry(i, &connections, list) {
/* introduce/release domain watches */
- if (check_special_event(name) && !check_perms_special(name, i))
- continue;
+ if (check_special_event(name)) {
+ if (!check_perms_special(name, i))
+ continue;
+ } else {
+ if (!watch_permitted(i, ctx, name, node, perms))
+ continue;
+ }
list_for_each_entry(watch, &i->watches, list) {
if (exact) {
/* Fire all watches: !exact means all the children are affected (ie. rm). */
void fire_watches(struct connection *conn, const void *tmp, const char *name,
- bool exact);
+ struct node *node, bool exact, struct node_perms *perms);
void conn_delete_all_watches(struct connection *conn);