/*
Transaction code for Xen Store Daemon.
Copyright (C) 2005 Rusty Russell IBM Corporation
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; If not, see .
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "talloc.h"
#include "list.h"
#include "transaction.h"
#include "watch.h"
#include "domain.h"
#include "xenstore_lib.h"
#include "utils.h"
/*
* 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. The increment is done _before_ the
* copy as that is needed for checking whether a domain was created before
* or after a node has been written (the domain's generation is set with the
* actual generation count without incrementing it, in order to support
* writing a node for a domain before the domain has been officially
* introduced).
*
* 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;
/* The name of the node. */
char *trans_name; /* Transaction specific name. */
char *node; /* Main data base name. */
/* 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;
/* Modified? */
bool modified;
/* Transaction node in data base? */
bool ta_node;
/* Watch event flags. */
bool fire_watch;
enum watch_match watch_match;
};
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;
/* Node counter. */
unsigned int nodes;
/* Generation when transaction started. */
uint64_t generation;
/* List of accessed nodes. */
struct list_head accessed;
/* List of changed domains - to record the changed domain entry number */
struct list_head changed_domains;
/* There was at least one node created in the transaction. */
bool node_created;
/* Flag for letting transaction fail. */
bool fail;
};
uint64_t generation;
void ta_node_created(struct transaction *trans)
{
trans->node_created = true;
}
static struct accessed_node *find_accessed_node(struct transaction *trans,
const char *name)
{
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.
*/
const char *transaction_prepend(struct connection *conn, const char *name)
{
struct accessed_node *i;
if (conn && conn->transaction) {
i = find_accessed_node(conn->transaction, name);
if (i)
return i->trans_name;
}
return name;
}
/*
* 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, const char **db_name)
{
struct accessed_node *i = NULL;
struct transaction *trans;
int ret;
bool introduce = false;
if (type != NODE_ACCESS_READ) {
node->hdr.generation = ++generation;
if (conn && !conn->transaction)
wrl_apply_debit_direct(conn);
}
if (!conn || !conn->transaction) {
/* They're changing the global database. */
if (db_name)
*db_name = node->name;
return 0;
}
trans = conn->transaction;
i = find_accessed_node(trans, node->name);
if (!i) {
if (domain_max_chk(conn, ACC_TRANSNODES, trans->nodes + 1)) {
ret = ENOSPC;
goto err;
}
i = talloc_zero(trans, struct accessed_node);
if (!i)
goto nomem;
i->trans_name = transaction_get_node_name(i, trans, node->name);
if (!i->trans_name)
goto nomem;
i->node = strchr(i->trans_name, '/') + 1;
if (node->hdr.generation != NO_GENERATION &&
node->hdr.num_perms) {
i->perms.p = talloc_array(i, struct xs_permissions,
node->hdr.num_perms);
if (!i->perms.p)
goto nomem;
i->perms.num = node->hdr.num_perms;
memcpy(i->perms.p, node->perms,
i->perms.num * sizeof(*i->perms.p));
}
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
* 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->hdr.generation;
i->check_gen = true;
if (node->hdr.generation != NO_GENERATION) {
ret = write_node_raw(conn, i->trans_name, node,
NODE_CREATE, true);
if (ret)
goto err;
i->ta_node = true;
}
}
trans->nodes++;
list_add_tail(&i->list, &trans->accessed);
}
if (type != NODE_ACCESS_READ)
i->modified = true;
if (introduce && type == NODE_ACCESS_DELETE)
/* Nothing to delete. */
return -1;
if (db_name) {
*db_name = i->trans_name;
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(i);
trans->fail = true;
errno = ret;
return ret;
}
/*
* A watch event should be fired for a node modified inside a transaction.
* Set the corresponding information. A non-exact event is replacing an exact
* one, but not the other way round.
* No special watch handling needed here, so MATCH_DEPTH is no issue.
*/
void queue_watches(struct connection *conn, const char *name,
enum watch_match watch_match)
{
struct accessed_node *i;
i = find_accessed_node(conn->transaction, name);
if (!i) {
conn->transaction->fail = true;
return;
}
if (!i->fire_watch) {
i->fire_watch = true;
i->watch_match = watch_match;
} else if (watch_match == MATCH_SUBTREE) {
i->watch_match = MATCH_SUBTREE;
}
}
/*
* 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, bool *is_corrupt)
{
struct accessed_node *i, *n;
size_t size;
const struct node_hdr *hdr;
uint64_t gen;
list_for_each_entry_safe(i, n, &trans->accessed, list) {
if (i->check_gen) {
hdr = db_fetch(i->node, &size);
if (!hdr) {
gen = NO_GENERATION;
} else {
gen = hdr->generation;
}
if (i->generation != gen)
return EAGAIN;
}
/* Entries for unmodified nodes can be removed early. */
if (!i->modified) {
if (i->ta_node)
db_delete(conn, i->trans_name, NULL);
list_del(&i->list);
talloc_free(i);
}
}
while ((i = list_top(&trans->accessed, struct accessed_node, list))) {
if (i->ta_node) {
hdr = db_fetch(i->trans_name, &size);
if (hdr) {
/*
* Delete transaction entry and write it as
* no-TA entry. As we only hold a reference
* to the data, increment its ref count, then
* delete it from the DB. Now we own it and can
* drop the const attribute for changing the
* generation count.
*/
enum write_node_mode mode;
struct node_hdr *own;
talloc_increase_ref_count(hdr);
db_delete(conn, i->trans_name, NULL);
own = (struct node_hdr *)hdr;
own->generation = ++generation;
mode = (i->generation == NO_GENERATION)
? NODE_CREATE : NODE_MODIFY;
*is_corrupt |= db_write(conn, i->node, own,
size, NULL, mode, true);
} else {
*is_corrupt = true;
}
} else {
/*
* A node having been created and later deleted
* in this transaction will have no generation
* information stored.
*/
if (i->generation != NO_GENERATION)
db_delete(conn, i->node, NULL);
}
if (i->fire_watch)
fire_watches(conn, trans, i->node, NULL, i->watch_match,
i->perms.p ? &i->perms : NULL);
list_del(&i->list);
talloc_free(i);
}
return 0;
}
static int destroy_transaction(void *_transaction)
{
struct transaction *trans = _transaction;
struct connection *conn = trans->conn;
struct accessed_node *i;
wrl_ntransactions--;
trace_destroy(trans, "transaction");
while ((i = list_top(&trans->accessed, struct accessed_node, list))) {
if (i->ta_node)
db_delete(conn, i->trans_name, NULL);
list_del(&i->list);
talloc_free(i);
}
list_del(&trans->list);
domain_transaction_dec(conn);
if (list_empty(&conn->transaction_list))
conn->ta_start_time = 0;
return 0;
}
struct transaction *transaction_lookup(struct connection *conn, uint32_t id)
{
struct transaction *trans;
if (id == 0)
return NULL;
list_for_each_entry(trans, &conn->transaction_list, list)
if (trans->id == id)
return trans;
return ERR_PTR(-ENOENT);
}
int do_transaction_start(const void *ctx, struct connection *conn,
struct buffered_data *in)
{
struct transaction *trans, *exists;
char id_str[20];
/* We don't support nested transactions. */
if (conn->transaction)
return EBUSY;
if (domain_quota_add_exceeds(conn->domain, ACC_TRANS, 1))
return ENOSPC;
/* Attach transaction to ctx for autofree until it's complete */
trans = talloc_zero(ctx, struct transaction);
if (!trans)
return ENOMEM;
trace_create(trans, "transaction");
INIT_LIST_HEAD(&trans->accessed);
INIT_LIST_HEAD(&trans->changed_domains);
trans->conn = conn;
trans->fail = false;
trans->generation = ++generation;
/* Pick an unused transaction identifier. */
do {
trans->id = conn->next_transaction_id;
exists = transaction_lookup(conn, conn->next_transaction_id++);
} while (!IS_ERR(exists));
if (list_empty(&conn->transaction_list))
conn->ta_start_time = time(NULL);
/* Now we own it. */
list_add_tail(&trans->list, &conn->transaction_list);
talloc_steal(conn, trans);
talloc_set_destructor(trans, destroy_transaction);
domain_transaction_inc(conn);
wrl_ntransactions++;
snprintf(id_str, sizeof(id_str), "%u", trans->id);
send_reply(conn, XS_TRANSACTION_START, id_str, strlen(id_str)+1);
return 0;
}
int do_transaction_end(const void *ctx, struct connection *conn,
struct buffered_data *in)
{
const char *arg = onearg(in);
struct transaction *trans;
bool is_corrupt = false;
bool chk_quota;
int ret;
if (!arg || (!streq(arg, "T") && !streq(arg, "F")))
return EINVAL;
if ((trans = conn->transaction) == NULL)
return ENOENT;
conn->transaction = NULL;
chk_quota = trans->node_created && domain_is_unprivileged(conn);
/* Attach transaction to ctx for auto-cleanup */
talloc_steal(ctx, trans);
if (streq(arg, "T")) {
if (trans->fail)
return ENOMEM;
ret = acc_fix_domains(&trans->changed_domains, chk_quota,
false);
if (ret)
return ret;
ret = finalize_transaction(conn, trans, &is_corrupt);
if (ret)
return ret;
wrl_apply_debit_trans_commit(conn);
/* fix domain entry for each changed domain */
acc_fix_domains(&trans->changed_domains, false, true);
if (is_corrupt)
corrupt(conn, "transaction inconsistency");
}
send_ack(conn, XS_TRANSACTION_END);
return 0;
}
struct list_head *transaction_get_changed_domains(struct transaction *trans)
{
return &trans->changed_domains;
}
void fail_transaction(struct transaction *trans)
{
trans->fail = true;
}
void conn_delete_all_transactions(struct connection *conn)
{
struct transaction *trans;
while ((trans = list_top(&conn->transaction_list,
struct transaction, list)))
talloc_free(trans);
conn->transaction = NULL;
}
int check_transactions(struct hashtable *hash)
{
struct connection *conn;
struct transaction *trans;
struct accessed_node *i;
char *tname;
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;
if (remember_string(hash, i->trans_name))
goto nomem;
}
talloc_free(tname);
}
}
return 0;
nomem:
talloc_free(tname);
return ENOMEM;
}
/*
* Local variables:
* mode: C
* c-file-style: "linux"
* indent-tabs-mode: t
* c-basic-offset: 8
* tab-width: 8
* End:
*/