diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c index eca04e734a..2c0f8fd99b 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -496,6 +496,117 @@ static void initialize_fds(int sock, int *p_sock_pollfd_idx, } } +static void get_acc_data(TDB_DATA *key, struct node_account_data *acc) +{ + TDB_DATA old_data; + struct xs_tdb_record_hdr *hdr; + + if (acc->memory < 0) { + old_data = tdb_fetch(tdb_ctx, *key); + /* No check for error, as the node might not exist. */ + if (old_data.dptr == NULL) { + acc->memory = 0; + } else { + hdr = (void *)old_data.dptr; + acc->memory = old_data.dsize; + acc->domid = hdr->perms[0].id; + } + talloc_free(old_data.dptr); + } +} + +/* + * Per-transaction nodes need to be accounted for the transaction owner. + * Those nodes are stored in the data base with the transaction generation + * count prepended (e.g. 123/local/domain/...). So testing for the node's + * key not to start with "/" is sufficient. + */ +static unsigned int get_acc_domid(struct connection *conn, TDB_DATA *key, + unsigned int domid) +{ + return (!conn || key->dptr[0] == '/') ? domid : conn->id; +} + +int do_tdb_write(struct connection *conn, TDB_DATA *key, TDB_DATA *data, + struct node_account_data *acc, bool no_quota_check) +{ + struct xs_tdb_record_hdr *hdr = (void *)data->dptr; + struct node_account_data old_acc = {}; + unsigned int old_domid, new_domid; + int ret; + + if (!acc) + old_acc.memory = -1; + else + old_acc = *acc; + + get_acc_data(key, &old_acc); + old_domid = get_acc_domid(conn, key, old_acc.domid); + new_domid = get_acc_domid(conn, key, hdr->perms[0].id); + + /* + * Don't check for ENOENT, as we want to be able to switch orphaned + * nodes to new owners. + */ + if (old_acc.memory) + domain_memory_add_nochk(old_domid, + -old_acc.memory - key->dsize); + ret = domain_memory_add(new_domid, data->dsize + key->dsize, + no_quota_check); + if (ret) { + /* Error path, so no quota check. */ + if (old_acc.memory) + domain_memory_add_nochk(old_domid, + old_acc.memory + key->dsize); + return ret; + } + + /* TDB should set errno, but doesn't even set ecode AFAICT. */ + if (tdb_store(tdb_ctx, *key, *data, TDB_REPLACE) != 0) { + domain_memory_add_nochk(new_domid, -data->dsize - key->dsize); + /* Error path, so no quota check. */ + if (old_acc.memory) + domain_memory_add_nochk(old_domid, + old_acc.memory + key->dsize); + errno = EIO; + return errno; + } + + if (acc) { + /* Don't use new_domid, as it might be a transaction node. */ + acc->domid = hdr->perms[0].id; + acc->memory = data->dsize; + } + + return 0; +} + +int do_tdb_delete(struct connection *conn, TDB_DATA *key, + struct node_account_data *acc) +{ + struct node_account_data tmp_acc; + unsigned int domid; + + if (!acc) { + acc = &tmp_acc; + acc->memory = -1; + } + + get_acc_data(key, acc); + + if (tdb_delete(tdb_ctx, *key)) { + errno = EIO; + return errno; + } + + if (acc->memory) { + domid = get_acc_domid(conn, key, acc->domid); + domain_memory_add_nochk(domid, -acc->memory - key->dsize); + } + + return 0; +} + /* * If it fails, returns NULL and sets errno. * Temporary memory allocations will be done with ctx. @@ -549,9 +660,15 @@ struct node *read_node(struct connection *conn, const void *ctx, /* Permissions are struct xs_permissions. */ node->perms.p = hdr->perms; + node->acc.domid = node->perms.p[0].id; + node->acc.memory = data.dsize; if (domain_adjust_node_perms(conn, node)) goto error; + /* If owner is gone reset currently accounted memory size. */ + if (node->acc.domid != node->perms.p[0].id) + node->acc.memory = 0; + /* Data is binary blob (usually ascii, no nul). */ node->data = node->perms.p + hdr->num_perms; /* Children is strings, nul separated. */ @@ -615,12 +732,9 @@ int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node, p += node->datalen; memcpy(p, node->children, node->childlen); - /* TDB should set errno, but doesn't even set ecode AFAICT. */ - if (tdb_store(tdb_ctx, *key, data, TDB_REPLACE) != 0) { - corrupt(conn, "Write of %s failed", key->dptr); - errno = EIO; - return errno; - } + if (do_tdb_write(conn, key, &data, &node->acc, no_quota_check)) + return EIO; + return 0; } @@ -1119,7 +1233,7 @@ static void delete_node_single(struct connection *conn, struct node *node) if (access_node(conn, node, NODE_ACCESS_DELETE, &key)) return; - if (tdb_delete(tdb_ctx, key) != 0) { + if (do_tdb_delete(conn, &key, &node->acc) != 0) { corrupt(conn, "Could not delete '%s'", node->name); return; } @@ -1182,6 +1296,7 @@ static struct node *construct_node(struct connection *conn, const void *ctx, /* No children, no data */ node->children = node->data = NULL; node->childlen = node->datalen = 0; + node->acc.memory = 0; node->parent = parent; return node; @@ -1190,17 +1305,17 @@ nomem: return NULL; } -static void destroy_node_rm(struct node *node) +static void destroy_node_rm(struct connection *conn, struct node *node) { if (streq(node->name, "/")) corrupt(NULL, "Destroying root node!"); - tdb_delete(tdb_ctx, node->key); + do_tdb_delete(conn, &node->key, &node->acc); } static int destroy_node(struct connection *conn, struct node *node) { - destroy_node_rm(node); + destroy_node_rm(conn, node); domain_entry_dec(conn, node); /* @@ -1252,7 +1367,7 @@ static struct node *create_node(struct connection *conn, const void *ctx, /* Account for new node */ if (i->parent) { if (domain_entry_inc(conn, i)) { - destroy_node_rm(i); + destroy_node_rm(conn, i); return NULL; } } @@ -2075,7 +2190,7 @@ static int clean_store_(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA val, if (!hashtable_search(reachable, name)) { log("clean_store: '%s' is orphaned!", name); if (recovery) { - tdb_delete(tdb, key); + do_tdb_delete(NULL, &key, NULL); } } diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h index 4e53072e63..521bc80384 100644 --- a/tools/xenstore/xenstored_core.h +++ b/tools/xenstore/xenstored_core.h @@ -141,6 +141,11 @@ struct node_perms { struct xs_permissions *p; }; +struct node_account_data { + unsigned int domid; + int memory; /* -1 if unknown */ +}; + struct node { const char *name; /* Key used to update TDB */ @@ -163,6 +168,9 @@ struct node { /* Children, each nul-terminated. */ unsigned int childlen; char *children; + + /* Allocation information for node currently in store. */ + struct node_account_data acc; }; /* Return the only argument in the input. */ @@ -258,6 +266,11 @@ extern xengnttab_handle **xgt_handle; int remember_string(struct hashtable *hash, const char *str); +int do_tdb_write(struct connection *conn, TDB_DATA *key, TDB_DATA *data, + struct node_account_data *acc, bool no_quota_check); +int do_tdb_delete(struct connection *conn, TDB_DATA *key, + struct node_account_data *acc); + void conn_free_buffered_data(struct connection *conn); #endif /* _XENSTORED_CORE_H */ diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c index 778b7e439c..c1beb40a3d 100644 --- a/tools/xenstore/xenstored_transaction.c +++ b/tools/xenstore/xenstored_transaction.c @@ -153,6 +153,9 @@ struct transaction /* List of all transactions active on this connection. */ struct list_head list; + /* Connection this transaction is associated with. */ + struct connection *conn; + /* Connection-local identifier for this transaction. */ uint32_t id; @@ -292,6 +295,8 @@ int access_node(struct connection *conn, struct node *node, introduce = true; i->ta_node = false; + /* acc.memory < 0 means "unknown, get size from TDB". */ + node->acc.memory = -1; /* * Additional transaction-specific node for read type. We only @@ -416,11 +421,11 @@ static int finalize_transaction(struct connection *conn, goto err; hdr = (void *)data.dptr; hdr->generation = ++generation; - ret = tdb_store(tdb_ctx, key, data, - TDB_REPLACE); + ret = do_tdb_write(conn, &key, &data, NULL, + true); talloc_free(data.dptr); } else { - ret = tdb_delete(tdb_ctx, key); + ret = do_tdb_delete(conn, &key, NULL); } if (ret) goto err; @@ -431,7 +436,7 @@ static int finalize_transaction(struct connection *conn, } } - if (i->ta_node && tdb_delete(tdb_ctx, ta_key)) + if (i->ta_node && do_tdb_delete(conn, &ta_key, NULL)) goto err; list_del(&i->list); talloc_free(i); @@ -459,7 +464,7 @@ static int destroy_transaction(void *_transaction) i->node); if (trans_name) { set_tdb_key(trans_name, &key); - tdb_delete(tdb_ctx, key); + do_tdb_delete(trans->conn, &key, NULL); } } list_del(&i->list); @@ -503,6 +508,7 @@ int do_transaction_start(struct connection *conn, struct buffered_data *in) INIT_LIST_HEAD(&trans->accessed); INIT_LIST_HEAD(&trans->changed_domains); + trans->conn = conn; trans->fail = false; trans->generation = ++generation;