diff --git a/tools/xenstore/xenstored_core.c b/tools/xenstore/xenstored_core.c index ee4c9e155c..c8e423700d 100644 --- a/tools/xenstore/xenstored_core.c +++ b/tools/xenstore/xenstored_core.c @@ -54,8 +54,6 @@ #include "xenstored_control.h" #include "tdb.h" -#include "hashtable.h" - #ifndef NO_SOCKETS #if defined(HAVE_SYSTEMD) #define XEN_SYSTEMD_ENABLED 1 @@ -81,9 +79,8 @@ static bool recovery = true; static int reopen_log_pipe[2]; static int reopen_log_pipe0_pollfd_idx = -1; char *tracefile = NULL; -static TDB_CONTEXT *tdb_ctx = NULL; +TDB_CONTEXT *tdb_ctx = NULL; -static void corrupt(struct connection *conn, const char *fmt, ...); static const char *sockmsg_string(enum xsd_sockmsg_type type); #define log(...) \ @@ -105,24 +102,6 @@ int quota_nb_watch_per_domain = 128; int quota_max_entry_size = 2048; /* 2K */ int quota_max_transaction = 10; -TDB_CONTEXT *tdb_context(struct connection *conn) -{ - /* conn = NULL used in manual_node at setup. */ - if (!conn || !conn->transaction) - return tdb_ctx; - return tdb_transaction_context(conn->transaction); -} - -bool replace_tdb(const char *newname, TDB_CONTEXT *newtdb) -{ - if (!(tdb_ctx->flags & TDB_INTERNAL)) - if (rename(newname, xs_daemon_tdb()) != 0) - return false; - tdb_close(tdb_ctx); - tdb_ctx = talloc_steal(talloc_autofree_context(), newtdb); - return true; -} - void trace(const char *fmt, ...) { va_list arglist; @@ -385,21 +364,6 @@ static struct node *read_node(struct connection *conn, const void *ctx, TDB_DATA key, data; struct xs_tdb_record_hdr *hdr; struct node *node; - TDB_CONTEXT * context = tdb_context(conn); - - key.dptr = (void *)name; - key.dsize = strlen(name); - data = tdb_fetch(context, key); - - if (data.dptr == NULL) { - if (tdb_error(context) == TDB_ERR_NOEXIST) - errno = ENOENT; - else { - log("TDB error on read: %s", tdb_errorstr(context)); - errno = EIO; - } - return NULL; - } node = talloc(ctx, struct node); if (!node) { @@ -412,8 +376,26 @@ static struct node *read_node(struct connection *conn, const void *ctx, errno = ENOMEM; return NULL; } + + if (transaction_prepend(conn, name, &key)) + return NULL; + + data = tdb_fetch(tdb_ctx, key); + + if (data.dptr == NULL) { + if (tdb_error(tdb_ctx) == TDB_ERR_NOEXIST) { + node->generation = NO_GENERATION; + access_node(conn, node, NODE_ACCESS_READ, NULL); + errno = ENOENT; + } else { + log("TDB error on read: %s", tdb_errorstr(tdb_ctx)); + errno = EIO; + } + talloc_free(node); + return NULL; + } + node->parent = NULL; - node->tdb = tdb_context(conn); talloc_steal(node, data.dptr); /* Datalen, childlen, number of permissions */ @@ -430,32 +412,26 @@ static struct node *read_node(struct connection *conn, const void *ctx, /* Children is strings, nul separated. */ node->children = node->data + node->datalen; + access_node(conn, node, NODE_ACCESS_READ, NULL); + return node; } -static int write_node(struct connection *conn, struct node *node) +int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node) { - /* - * conn will be null when this is called from manual_node. - * tdb_context copes with this. - */ - - TDB_DATA key, data; + TDB_DATA data; void *p; struct xs_tdb_record_hdr *hdr; - key.dptr = (void *)node->name; - key.dsize = strlen(node->name); - data.dsize = sizeof(*hdr) + node->num_perms*sizeof(node->perms[0]) + node->datalen + node->childlen; - if (domain_is_unprivileged(conn) && data.dsize >= quota_max_entry_size) - goto error; - - add_change_node(conn, node, false); - wrl_apply_debit_direct(conn); + if (domain_is_unprivileged(conn) && + data.dsize >= quota_max_entry_size) { + errno = ENOSPC; + return errno; + } data.dptr = talloc_size(node, data.dsize); hdr = (void *)data.dptr; @@ -471,14 +447,22 @@ static int write_node(struct connection *conn, struct node *node) memcpy(p, node->children, node->childlen); /* TDB should set errno, but doesn't even set ecode AFAICT. */ - if (tdb_store(tdb_context(conn), key, data, TDB_REPLACE) != 0) { - corrupt(conn, "Write of %s failed", key.dptr); - goto error; + if (tdb_store(tdb_ctx, *key, data, TDB_REPLACE) != 0) { + corrupt(conn, "Write of %s failed", key->dptr); + errno = EIO; + return errno; } return 0; - error: - errno = ENOSPC; - return errno; +} + +static int write_node(struct connection *conn, struct node *node) +{ + TDB_DATA key; + + if (access_node(conn, node, NODE_ACCESS_WRITE, &key)) + return errno; + + return write_node_raw(conn, &key, node); } static enum xs_perm_type perm_for_conn(struct connection *conn, @@ -900,24 +884,18 @@ static int do_read(struct connection *conn, struct buffered_data *in) return 0; } -static void delete_node_single(struct connection *conn, struct node *node, - bool changed) +static void delete_node_single(struct connection *conn, struct node *node) { TDB_DATA key; - key.dptr = (void *)node->name; - key.dsize = strlen(node->name); + if (access_node(conn, node, NODE_ACCESS_DELETE, &key)) + return; - if (tdb_delete(tdb_context(conn), key) != 0) { + if (tdb_delete(tdb_ctx, key) != 0) { corrupt(conn, "Could not delete '%s'", node->name); return; } - if (changed) { - add_change_node(conn, node, true); - wrl_apply_debit_direct(conn); - } - domain_entry_dec(conn, node); } @@ -965,7 +943,6 @@ static struct node *construct_node(struct connection *conn, const void *ctx, node = talloc(ctx, struct node); if (!node) goto nomem; - node->tdb = tdb_context(conn); node->name = talloc_strdup(node, name); if (!node->name) goto nomem; @@ -1002,7 +979,7 @@ static int destroy_node(void *_node) key.dptr = (void *)node->name; key.dsize = strlen(node->name); - tdb_delete(node->tdb, key); + tdb_delete(tdb_ctx, key); return 0; } @@ -1095,8 +1072,7 @@ static int do_mkdir(struct connection *conn, struct buffered_data *in) return 0; } -static void delete_node(struct connection *conn, struct node *node, - bool changed) +static void delete_node(struct connection *conn, struct node *node) { unsigned int i; char *name; @@ -1104,7 +1080,7 @@ static void delete_node(struct connection *conn, struct node *node, /* Delete self, then delete children. If we crash, then the worst that can happen is the children will continue to take up space, but will otherwise be unreachable. */ - delete_node_single(conn, node, changed); + delete_node_single(conn, node); /* Delete children, too. */ for (i = 0; i < node->childlen; i += strlen(node->children+i) + 1) { @@ -1114,7 +1090,7 @@ static void delete_node(struct connection *conn, struct node *node, node->children + i); child = name ? read_node(conn, node, name) : NULL; if (child) { - delete_node(conn, child, false); + delete_node(conn, child); } else { trace("delete_node: Error deleting child '%s/%s'!\n", @@ -1177,7 +1153,7 @@ static int _rm(struct connection *conn, const void *ctx, struct node *node, if (delete_child(conn, parent, basename(name))) return EINVAL; - delete_node(conn, node, true); + delete_node(conn, node); return 0; } @@ -1617,7 +1593,7 @@ static char *child_name(const char *s1, const char *s2) } -static int remember_string(struct hashtable *hash, const char *str) +int remember_string(struct hashtable *hash, const char *str) { char *k = malloc(strlen(str) + 1); @@ -1737,6 +1713,7 @@ static int clean_store_(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA val, void *private) { struct hashtable *reachable = private; + char *slash; char * name = talloc_strndup(NULL, key.dptr, key.dsize); if (!name) { @@ -1744,6 +1721,11 @@ static int clean_store_(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA val, return 1; } + if (name[0] != '/') { + slash = strchr(name, '/'); + if (slash) + *slash = 0; + } if (!hashtable_search(reachable, name)) { log("clean_store: '%s' is orphaned!", name); if (recovery) { @@ -1779,7 +1761,8 @@ void check_store(void) } log("Checking store ..."); - if (!check_store_(root, reachable)) + if (!check_store_(root, reachable) && + !check_transactions(reachable)) clean_store(reachable); log("Checking store complete."); @@ -1790,7 +1773,7 @@ void check_store(void) /* Something is horribly wrong: check the store. */ -static void corrupt(struct connection *conn, const char *fmt, ...) +void corrupt(struct connection *conn, const char *fmt, ...) { va_list arglist; char *str; diff --git a/tools/xenstore/xenstored_core.h b/tools/xenstore/xenstored_core.h index 05808278c3..3d7eb91254 100644 --- a/tools/xenstore/xenstored_core.h +++ b/tools/xenstore/xenstored_core.h @@ -32,6 +32,7 @@ #include "xenstore_lib.h" #include "list.h" #include "tdb.h" +#include "hashtable.h" /* DEFAULT_BUFFER_SIZE should be large enough for each errno string. */ #define DEFAULT_BUFFER_SIZE 16 @@ -113,14 +114,12 @@ extern struct list_head connections; struct node { const char *name; - /* Database I came from */ - TDB_CONTEXT *tdb; - /* Parent (optional) */ struct node *parent; /* Generation count. */ uint64_t generation; +#define NO_GENERATION ~((uint64_t)0) /* Permissions. */ unsigned int num_perms; @@ -151,20 +150,18 @@ void send_ack(struct connection *conn, enum xsd_sockmsg_type type); /* Canonicalize this path if possible. */ char *canonicalize(struct connection *conn, const void *ctx, const char *node); +/* Write a node to the tdb data base. */ +int write_node_raw(struct connection *conn, TDB_DATA *key, struct node *node); + /* 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 TDB context for this connection */ -TDB_CONTEXT *tdb_context(struct connection *conn); - -/* Replace the tdb: required for transaction code */ -bool replace_tdb(const char *newname, TDB_CONTEXT *newtdb); - struct connection *new_connection(connwritefn_t *write, connreadfn_t *read); void check_store(void); +void corrupt(struct connection *conn, const char *fmt, ...); /* Is this a valid node name? */ bool is_valid_nodename(const char *node); @@ -179,9 +176,12 @@ void close_log(void); extern char *tracefile; extern int tracefd; + +extern TDB_CONTEXT *tdb_ctx; extern int dom0_domid; extern int dom0_event; extern int priv_domid; +extern int quota_nb_entry_per_domain; /* Map the kernel's xenstore page. */ void *xenbus_map(void); @@ -208,6 +208,8 @@ void init_pipe(int reopen_log_pipe[2]); xengnttab_handle **xgt_handle; +int remember_string(struct hashtable *hash, const char *str); + #endif /* _XENSTORED_CORE_H */ /* diff --git a/tools/xenstore/xenstored_domain.c b/tools/xenstore/xenstored_domain.c index 6af219e0e9..4edd14eb4f 100644 --- a/tools/xenstore/xenstored_domain.c +++ b/tools/xenstore/xenstored_domain.c @@ -268,9 +268,15 @@ bool domain_can_read(struct connection *conn) return (intf->req_cons != intf->req_prod); } +static bool domid_is_unprivileged(unsigned int domid) +{ + return domid != 0 && domid != priv_domid; +} + bool domain_is_unprivileged(struct connection *conn) { - return (conn && conn->domain && conn->domain->domid != 0 && conn->domain->domid != priv_domid); + return conn && conn->domain && + domid_is_unprivileged(conn->domain->domid); } bool domain_can_write(struct connection *conn) @@ -699,13 +705,23 @@ void domain_entry_dec(struct connection *conn, struct node *node) } } -void domain_entry_fix(unsigned int domid, int num) +int domain_entry_fix(unsigned int domid, int num, bool update) { struct domain *d; + int cnt; d = find_domain_by_domid(domid); - if (d && ((d->nbentry += num) < 0)) - d->nbentry = 0; + if (!d) + return 0; + + cnt = d->nbentry + num; + if (cnt < 0) + cnt = 0; + + if (update) + d->nbentry = cnt; + + return domid_is_unprivileged(domid) ? cnt : 0; } int domain_entry(struct connection *conn) diff --git a/tools/xenstore/xenstored_domain.h b/tools/xenstore/xenstored_domain.h index 4aa80db2c9..56ae015974 100644 --- a/tools/xenstore/xenstored_domain.h +++ b/tools/xenstore/xenstored_domain.h @@ -59,7 +59,7 @@ bool domain_is_unprivileged(struct connection *conn); /* Quota manipulation */ void domain_entry_inc(struct connection *conn, struct node *); void domain_entry_dec(struct connection *conn, struct node *); -void domain_entry_fix(unsigned int domid, int num); +int domain_entry_fix(unsigned int domid, int num, bool update); int domain_entry(struct connection *conn); void domain_watch_inc(struct connection *conn); void domain_watch_dec(struct connection *conn); diff --git a/tools/xenstore/xenstored_transaction.c b/tools/xenstore/xenstored_transaction.c index a01f8cfe34..75816dd2c7 100644 --- a/tools/xenstore/xenstored_transaction.c +++ b/tools/xenstore/xenstored_transaction.c @@ -16,6 +16,7 @@ along with this program; If not, see . */ +#include #include #include #include @@ -35,7 +36,74 @@ #include "xenstore_lib.h" #include "utils.h" -struct changed_node +/* + * Some notes regarding detection and handling of transaction conflicts: + * + * Basic source of reference is the 'generation' count. Each writing access + * (either normal write or in a transaction) to the tdb data base will set + * the node specific generation count to the global generation count. + * For being able to identify a transaction the transaction specific generation + * count is initialized with the global generation count when starting the + * transaction. + * Each time the global generation count is copied to either a node or a + * transaction it is incremented. This ensures all nodes and/or transactions + * are having a unique generation count. + * + * Transaction conflicts are detected by checking the generation count of all + * nodes read in the transaction to match with the generation count in the + * global data base at the end of the transaction. Nodes which have been + * modified in the transaction don't have to be checked to match even if they + * have been read, as the modified node will be globally visible after the + * succeeded transaction possibly overwriting another modification which may + * have occurred concurrent to the transaction. + * + * Examples: + * --------- + * The following notation is used: + * I: initial state + * G global generation count + * g(X) generation count of node X + * G(1) generation count of transaction 1 + * g(1:Y) saved generation count of node Y in transaction 1 + * TA1: operation in transaction 1 + * X=1:X replace global node X with transaction 1 specific value of X + * + * 1. Simple transaction doing: read node A, write node B + * I: g(A) = 1, g(B) = 2, G = 3 + * Start transaction 1: G(1) = 3, G = 4 + * TA1: read node A: g(1:A) = 1 + * TA1: write node B: g(1:B) = 4, G = 5 + * End TA1: g(1:A) == g(A) => okay, B = 1:B, g(B) = 5, G = 6 + * + * 2. Transaction with conflicting write + * I: g(A) = 1, g(B) = 2, G = 3 + * Start transaction 1: G(1) = 3, G = 4 + * TA1: read node A: g(1:A) = 1 + * write node A: g(A) = 4, G = 5 + * TA1: write node B: g(1:B) = 5, G = 6 + * End TA1: g(1:A) != g(A) => EAGAIN + * + * 3. Transaction with conflicting delete + * I: g(A) = 1, g(B) = 2, G = 3 + * Start transaction 1: G(1) = 3, G = 4 + * TA1: read node A: g(1:A) = 1 + * delete node A: g(A) = ~0 + * TA1: write node B: g(1:B) = 4, G = 5 + * End TA1: g(1:A) != g(A) => EAGAIN + * + * 4. Two interfering transactions + * I: g(A) = 1, g(B) = 2, G = 3 + * Start transaction 1: G(1) = 3, G = 4 + * Start transaction 2: G(2) = 4, G = 5 + * TA1: read node A: g(1:A) = 1 + * TA2: read node B: g(2:B) = 2 + * TA1: write node B: g(1:B) = 5, G = 6 + * TA2: write node A: g(2:A) = 6, G = 7 + * End TA1: g(1:A) == g(A) => okay, B = 1:B, g(B) = 7, G = 8 + * End TA2: g(2:B) != g(B) => EAGAIN + */ + +struct accessed_node { /* List of all changed nodes in the context of this transaction. */ struct list_head list; @@ -43,8 +111,17 @@ struct changed_node /* The name of the node. */ char *node; - /* And the children? (ie. rm) */ - bool recurse; + /* Generation count (or NO_GENERATION) for conflict checking. */ + uint64_t generation; + + /* Generation count checking required? */ + bool check_gen; + + /* Modified? */ + bool modified; + + /* Transaction node in data base? */ + bool ta_node; }; struct changed_domain @@ -70,80 +147,267 @@ struct transaction /* Generation when transaction started. */ uint64_t generation; - /* Transaction internal generation. */ - uint64_t trans_gen; - - /* TDB to work on, and filename */ - TDB_CONTEXT *tdb; - char *tdb_name; - - /* List of changed nodes. */ - struct list_head changes; + /* List of accessed nodes. */ + struct list_head accessed; /* List of changed domains - to record the changed domain entry number */ struct list_head changed_domains; + + /* Flag for letting transaction fail. */ + bool fail; }; extern int quota_max_transaction; static uint64_t generation; -/* Return tdb context to use for this connection. */ -TDB_CONTEXT *tdb_transaction_context(struct transaction *trans) +static void set_tdb_key(const char *name, TDB_DATA *key) { - return trans->tdb; + key->dptr = (char *)name; + key->dsize = strlen(name); } -/* Callers get a change node (which can fail) and only commit after they've - * finished. This way they don't have to unwind eg. a write. */ -void add_change_node(struct connection *conn, struct node *node, bool recurse) +static struct accessed_node *find_accessed_node(struct transaction *trans, + const char *name) { - struct changed_node *i; + struct accessed_node *i; + + list_for_each_entry(i, &trans->accessed, list) + if (streq(i->node, name)) + return i; + + return NULL; +} + +static char *transaction_get_node_name(void *ctx, struct transaction *trans, + const char *name) +{ + return talloc_asprintf(ctx, "%"PRIu64"/%s", trans->generation, name); +} + +/* + * Prepend the transaction to name if node has been modified in the current + * transaction. + */ +int transaction_prepend(struct connection *conn, const char *name, + TDB_DATA *key) +{ + char *tdb_name; + + if (!conn || !conn->transaction || + !find_accessed_node(conn->transaction, name)) { + set_tdb_key(name, key); + return 0; + } + + tdb_name = transaction_get_node_name(conn->transaction, + conn->transaction, name); + if (!tdb_name) + return errno; + + set_tdb_key(tdb_name, key); + + return 0; +} + +/* + * A node has been accessed. + * + * Modifying accesses (write, delete) always update the generation (global and + * node->generation). + * + * Accesses in a transaction will be added to the list of accessed nodes + * if not already done. Read type accesses will copy the node to the + * transaction specific data base part, write type accesses go there + * anyway. + * + * If not NULL, key will be supplied with name and length of name of the node + * to be accessed in the data base. + */ +int access_node(struct connection *conn, struct node *node, + enum node_access_type type, TDB_DATA *key) +{ + struct accessed_node *i = NULL; struct transaction *trans; + TDB_DATA local_key; + const char *trans_name = NULL; + int ret; + bool introduce = false; + + if (type != NODE_ACCESS_READ) { + node->generation = generation++; + if (conn && !conn->transaction) + wrl_apply_debit_direct(conn); + } if (!conn || !conn->transaction) { /* They're changing the global database. */ - node->generation = generation++; - return; + if (key) + set_tdb_key(node->name, key); + return 0; } trans = conn->transaction; - node->generation = generation + trans->trans_gen++; + trans_name = transaction_get_node_name(node, trans, node->name); + if (!trans_name) + goto nomem; - list_for_each_entry(i, &trans->changes, list) { - if (streq(i->node, node->name)) { - if (recurse) - i->recurse = recurse; - return; - } - } - - i = talloc(trans, struct changed_node); + i = find_accessed_node(trans, node->name); if (!i) { - /* All we can do is let the transaction fail. */ - generation++; - return; + i = talloc_zero(trans, struct accessed_node); + if (!i) + goto nomem; + i->node = talloc_strdup(i, node->name); + if (!i->node) + goto nomem; + + introduce = true; + i->ta_node = false; + + /* + * Additional transaction-specific node for read type. We only + * have to verify read nodes if we didn't write them. + * + * The node is created and written to DB here to distinguish + * from the write types. + */ + if (type == NODE_ACCESS_READ) { + i->generation = node->generation; + i->check_gen = true; + if (node->generation != NO_GENERATION) { + set_tdb_key(trans_name, &local_key); + ret = write_node_raw(conn, &local_key, node); + if (ret) + goto err; + i->ta_node = true; + } + } + list_add_tail(&i->list, &trans->accessed); } - i->node = talloc_strdup(i, node->name); - if (!i->node) { - /* All we can do is let the transaction fail. */ - generation++; + + if (type != NODE_ACCESS_READ) + i->modified = true; + + if (introduce && type == NODE_ACCESS_DELETE) + /* Nothing to delete. */ + return -1; + + if (key) { + set_tdb_key(trans_name, key); + if (type == NODE_ACCESS_WRITE) + i->ta_node = true; + if (type == NODE_ACCESS_DELETE) + i->ta_node = false; + } + + return 0; + +nomem: + ret = ENOMEM; +err: + talloc_free((void *)trans_name); + talloc_free(i); + trans->fail = true; + errno = ret; + return ret; +} + +/* + * Finalize transaction: + * Walk through accessed nodes and check generation against global data. + * If all entries match, read the transaction entries and write them without + * transaction prepended. Delete all transaction specific nodes in the data + * base. + */ +static int finalize_transaction(struct connection *conn, + struct transaction *trans) +{ + struct accessed_node *i; + TDB_DATA key, ta_key, data; + struct xs_tdb_record_hdr *hdr; + uint64_t gen; + char *trans_name; + int ret; + + list_for_each_entry(i, &trans->accessed, list) { + if (!i->check_gen) + continue; + + set_tdb_key(i->node, &key); + data = tdb_fetch(tdb_ctx, key); + hdr = (void *)data.dptr; + if (!data.dptr) { + if (tdb_error(tdb_ctx) != TDB_ERR_NOEXIST) + return EIO; + gen = NO_GENERATION; + } else + gen = hdr->generation; + talloc_free(data.dptr); + if (i->generation != gen) + return EAGAIN; + } + + while ((i = list_top(&trans->accessed, struct accessed_node, list))) { + trans_name = transaction_get_node_name(i, trans, i->node); + if (!trans_name) + /* We are doomed: the transaction is only partial. */ + goto err; + + set_tdb_key(trans_name, &ta_key); + + if (i->modified) { + set_tdb_key(i->node, &key); + if (i->ta_node) { + data = tdb_fetch(tdb_ctx, ta_key); + if (!data.dptr) + goto err; + hdr = (void *)data.dptr; + hdr->generation = generation++; + ret = tdb_store(tdb_ctx, key, data, + TDB_REPLACE); + talloc_free(data.dptr); + if (ret) + goto err; + } else 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)) + goto err; + list_del(&i->list); talloc_free(i); - return; } - i->recurse = recurse; - list_add_tail(&i->list, &trans->changes); + + return 0; + +err: + corrupt(conn, "Partial transaction"); + return EIO; } static int destroy_transaction(void *_transaction) { struct transaction *trans = _transaction; + struct accessed_node *i; + char *trans_name; + TDB_DATA key; wrl_ntransactions--; trace_destroy(trans, "transaction"); - if (trans->tdb) - tdb_close(trans->tdb); - unlink(trans->tdb_name); + while ((i = list_top(&trans->accessed, struct accessed_node, list))) { + if (i->ta_node) { + trans_name = transaction_get_node_name(i, trans, + i->node); + if (trans_name) { + set_tdb_key(trans_name, &key); + tdb_delete(tdb_ctx, key); + } + } + list_del(&i->list); + talloc_free(i); + } + return 0; } @@ -178,18 +442,10 @@ int do_transaction_start(struct connection *conn, struct buffered_data *in) if (!trans) return ENOMEM; - INIT_LIST_HEAD(&trans->changes); + INIT_LIST_HEAD(&trans->accessed); INIT_LIST_HEAD(&trans->changed_domains); - trans->generation = generation; - trans->tdb_name = talloc_asprintf(trans, "%s.%p", - xs_daemon_tdb(), trans); - if (!trans->tdb_name) - return ENOMEM; - trans->tdb = tdb_copy(tdb_context(conn), trans->tdb_name); - if (!trans->tdb) - return errno; - /* Make it close if we go away. */ - talloc_steal(trans, trans->tdb); + trans->fail = false; + trans->generation = generation++; /* Pick an unused transaction identifier. */ do { @@ -210,12 +466,25 @@ int do_transaction_start(struct connection *conn, struct buffered_data *in) return 0; } +static int transaction_fix_domains(struct transaction *trans, bool update) +{ + struct changed_domain *d; + int cnt; + + list_for_each_entry(d, &trans->changed_domains, list) { + cnt = domain_entry_fix(d->domid, d->nbentry, update); + if (!update && cnt >= quota_nb_entry_per_domain) + return ENOSPC; + } + + return 0; +} + int do_transaction_end(struct connection *conn, struct buffered_data *in) { const char *arg = onearg(in); - struct changed_node *i; - struct changed_domain *d; struct transaction *trans; + int ret; if (!arg || (!streq(arg, "T") && !streq(arg, "F"))) return EINVAL; @@ -231,25 +500,18 @@ int do_transaction_end(struct connection *conn, struct buffered_data *in) talloc_steal(in, trans); if (streq(arg, "T")) { - /* FIXME: Merge, rather failing on any change. */ - if (trans->generation != generation) + if (trans->fail) + return ENOMEM; + ret = transaction_fix_domains(trans, false); + if (ret) + return ret; + if (finalize_transaction(conn, trans)) return EAGAIN; wrl_apply_debit_trans_commit(conn); - if (!replace_tdb(trans->tdb_name, trans->tdb)) - return errno; - /* Don't close this: we won! */ - trans->tdb = NULL; - /* fix domain entry for each changed domain */ - list_for_each_entry(d, &trans->changed_domains, list) - domain_entry_fix(d->domid, d->nbentry); - - /* Fire off the watches for everything that changed. */ - list_for_each_entry(i, &trans->changes, list) - fire_watches(conn, in, i->node, i->recurse); - generation += trans->trans_gen; + transaction_fix_domains(trans, true); } send_ack(conn, XS_TRANSACTION_END); @@ -269,7 +531,7 @@ void transaction_entry_inc(struct transaction *trans, unsigned int domid) d = talloc(trans, struct changed_domain); if (!d) { /* Let the transaction fail. */ - generation++; + trans->fail = true; return; } d->domid = domid; @@ -290,7 +552,7 @@ void transaction_entry_dec(struct transaction *trans, unsigned int domid) d = talloc(trans, struct changed_domain); if (!d) { /* Let the transaction fail. */ - generation++; + trans->fail = true; return; } d->domid = domid; @@ -313,6 +575,41 @@ void conn_delete_all_transactions(struct connection *conn) conn->transaction_started = 0; } +int check_transactions(struct hashtable *hash) +{ + struct connection *conn; + struct transaction *trans; + struct accessed_node *i; + char *tname, *tnode; + + list_for_each_entry(conn, &connections, list) { + list_for_each_entry(trans, &conn->transaction_list, list) { + tname = talloc_asprintf(trans, "%"PRIu64, + trans->generation); + if (!tname || !remember_string(hash, tname)) + goto nomem; + + list_for_each_entry(i, &trans->accessed, list) { + if (!i->ta_node) + continue; + tnode = transaction_get_node_name(tname, trans, + i->node); + if (!tnode || !remember_string(hash, tnode)) + goto nomem; + talloc_free(tnode); + } + + talloc_free(tname); + } + } + + return 0; + +nomem: + talloc_free(tname); + return ENOMEM; +} + /* * Local variables: * c-file-style: "linux" diff --git a/tools/xenstore/xenstored_transaction.h b/tools/xenstore/xenstored_transaction.h index aeeac3dced..3386bac565 100644 --- a/tools/xenstore/xenstored_transaction.h +++ b/tools/xenstore/xenstored_transaction.h @@ -19,6 +19,12 @@ #define _XENSTORED_TRANSACTION_H #include "xenstored_core.h" +enum node_access_type { + NODE_ACCESS_READ, + NODE_ACCESS_WRITE, + NODE_ACCESS_DELETE +}; + struct transaction; int do_transaction_start(struct connection *conn, struct buffered_data *node); @@ -30,13 +36,15 @@ struct transaction *transaction_lookup(struct connection *conn, uint32_t id); void transaction_entry_inc(struct transaction *trans, unsigned int domid); void transaction_entry_dec(struct transaction *trans, unsigned int domid); -/* This node was changed. */ -void add_change_node(struct connection *conn, struct node *node, - bool recurse); +/* This node was accessed. */ +int access_node(struct connection *conn, struct node *node, + enum node_access_type type, TDB_DATA *key); -/* Return tdb context to use for this connection. */ -TDB_CONTEXT *tdb_transaction_context(struct transaction *trans); +/* Prepend the transaction to name if appropriate. */ +int transaction_prepend(struct connection *conn, const char *name, + TDB_DATA *key); void conn_delete_all_transactions(struct connection *conn); +int check_transactions(struct hashtable *hash); #endif /* _XENSTORED_TRANSACTION_H */