1 | /* |
---|
2 | Transaction code for Xen Store Daemon. |
---|
3 | Copyright (C) 2005 Rusty Russell IBM Corporation |
---|
4 | |
---|
5 | This program is free software; you can redistribute it and/or modify |
---|
6 | it under the terms of the GNU General Public License as published by |
---|
7 | the Free Software Foundation; either version 2 of the License, or |
---|
8 | (at your option) any later version. |
---|
9 | |
---|
10 | This program is distributed in the hope that it will be useful, |
---|
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
13 | GNU General Public License for more details. |
---|
14 | |
---|
15 | You should have received a copy of the GNU General Public License |
---|
16 | along with this program; if not, write to the Free Software |
---|
17 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
---|
18 | */ |
---|
19 | |
---|
20 | #include <stdio.h> |
---|
21 | #include <sys/types.h> |
---|
22 | #include <sys/stat.h> |
---|
23 | #include <sys/wait.h> |
---|
24 | #include <sys/time.h> |
---|
25 | #include <time.h> |
---|
26 | #include <assert.h> |
---|
27 | #include <stdarg.h> |
---|
28 | #include <stdlib.h> |
---|
29 | #include <fcntl.h> |
---|
30 | #include <unistd.h> |
---|
31 | #include "talloc.h" |
---|
32 | #include "list.h" |
---|
33 | #include "xenstored_transaction.h" |
---|
34 | #include "xenstored_watch.h" |
---|
35 | #include "xs_lib.h" |
---|
36 | #include "utils.h" |
---|
37 | #include "xenstored_test.h" |
---|
38 | |
---|
39 | struct changed_node |
---|
40 | { |
---|
41 | /* List of all changed nodes in the context of this transaction. */ |
---|
42 | struct list_head list; |
---|
43 | |
---|
44 | /* The name of the node. */ |
---|
45 | char *node; |
---|
46 | |
---|
47 | /* And the children? (ie. rm) */ |
---|
48 | bool recurse; |
---|
49 | }; |
---|
50 | |
---|
51 | struct transaction |
---|
52 | { |
---|
53 | /* List of all transactions active on this connection. */ |
---|
54 | struct list_head list; |
---|
55 | |
---|
56 | /* Connection-local identifier for this transaction. */ |
---|
57 | uint32_t id; |
---|
58 | |
---|
59 | /* Generation when transaction started. */ |
---|
60 | unsigned int generation; |
---|
61 | |
---|
62 | /* TDB to work on, and filename */ |
---|
63 | TDB_CONTEXT *tdb; |
---|
64 | char *tdb_name; |
---|
65 | |
---|
66 | /* List of changed nodes. */ |
---|
67 | struct list_head changes; |
---|
68 | }; |
---|
69 | |
---|
70 | extern int quota_max_transaction; |
---|
71 | static unsigned int generation; |
---|
72 | |
---|
73 | /* Return tdb context to use for this connection. */ |
---|
74 | TDB_CONTEXT *tdb_transaction_context(struct transaction *trans) |
---|
75 | { |
---|
76 | return trans->tdb; |
---|
77 | } |
---|
78 | |
---|
79 | /* Callers get a change node (which can fail) and only commit after they've |
---|
80 | * finished. This way they don't have to unwind eg. a write. */ |
---|
81 | void add_change_node(struct transaction *trans, const char *node, bool recurse) |
---|
82 | { |
---|
83 | struct changed_node *i; |
---|
84 | |
---|
85 | if (!trans) { |
---|
86 | /* They're changing the global database. */ |
---|
87 | generation++; |
---|
88 | return; |
---|
89 | } |
---|
90 | |
---|
91 | list_for_each_entry(i, &trans->changes, list) |
---|
92 | if (streq(i->node, node)) |
---|
93 | return; |
---|
94 | |
---|
95 | i = talloc(trans, struct changed_node); |
---|
96 | i->node = talloc_strdup(i, node); |
---|
97 | i->recurse = recurse; |
---|
98 | list_add_tail(&i->list, &trans->changes); |
---|
99 | } |
---|
100 | |
---|
101 | static int destroy_transaction(void *_transaction) |
---|
102 | { |
---|
103 | struct transaction *trans = _transaction; |
---|
104 | |
---|
105 | trace_destroy(trans, "transaction"); |
---|
106 | if (trans->tdb) |
---|
107 | tdb_close(trans->tdb); |
---|
108 | unlink(trans->tdb_name); |
---|
109 | return 0; |
---|
110 | } |
---|
111 | |
---|
112 | struct transaction *transaction_lookup(struct connection *conn, uint32_t id) |
---|
113 | { |
---|
114 | struct transaction *trans; |
---|
115 | |
---|
116 | if (id == 0) |
---|
117 | return NULL; |
---|
118 | |
---|
119 | list_for_each_entry(trans, &conn->transaction_list, list) |
---|
120 | if (trans->id == id) |
---|
121 | return trans; |
---|
122 | |
---|
123 | return ERR_PTR(-ENOENT); |
---|
124 | } |
---|
125 | |
---|
126 | void do_transaction_start(struct connection *conn, struct buffered_data *in) |
---|
127 | { |
---|
128 | struct transaction *trans, *exists; |
---|
129 | char id_str[20]; |
---|
130 | |
---|
131 | /* We don't support nested transactions. */ |
---|
132 | if (conn->transaction) { |
---|
133 | send_error(conn, EBUSY); |
---|
134 | return; |
---|
135 | } |
---|
136 | |
---|
137 | if (conn->id && conn->transaction_started > quota_max_transaction) { |
---|
138 | send_error(conn, ENOSPC); |
---|
139 | return; |
---|
140 | } |
---|
141 | |
---|
142 | /* Attach transaction to input for autofree until it's complete */ |
---|
143 | trans = talloc(in, struct transaction); |
---|
144 | INIT_LIST_HEAD(&trans->changes); |
---|
145 | trans->generation = generation; |
---|
146 | trans->tdb_name = talloc_asprintf(trans, "%s.%p", |
---|
147 | xs_daemon_tdb(), trans); |
---|
148 | trans->tdb = tdb_copy(tdb_context(conn), trans->tdb_name); |
---|
149 | if (!trans->tdb) { |
---|
150 | send_error(conn, errno); |
---|
151 | return; |
---|
152 | } |
---|
153 | /* Make it close if we go away. */ |
---|
154 | talloc_steal(trans, trans->tdb); |
---|
155 | |
---|
156 | /* Pick an unused transaction identifier. */ |
---|
157 | do { |
---|
158 | trans->id = conn->next_transaction_id; |
---|
159 | exists = transaction_lookup(conn, conn->next_transaction_id++); |
---|
160 | } while (!IS_ERR(exists)); |
---|
161 | |
---|
162 | /* Now we own it. */ |
---|
163 | list_add_tail(&trans->list, &conn->transaction_list); |
---|
164 | talloc_steal(conn, trans); |
---|
165 | talloc_set_destructor(trans, destroy_transaction); |
---|
166 | conn->transaction_started++; |
---|
167 | |
---|
168 | sprintf(id_str, "%u", trans->id); |
---|
169 | send_reply(conn, XS_TRANSACTION_START, id_str, strlen(id_str)+1); |
---|
170 | } |
---|
171 | |
---|
172 | void do_transaction_end(struct connection *conn, const char *arg) |
---|
173 | { |
---|
174 | struct changed_node *i; |
---|
175 | struct transaction *trans; |
---|
176 | |
---|
177 | if (!arg || (!streq(arg, "T") && !streq(arg, "F"))) { |
---|
178 | send_error(conn, EINVAL); |
---|
179 | return; |
---|
180 | } |
---|
181 | |
---|
182 | if ((trans = conn->transaction) == NULL) { |
---|
183 | send_error(conn, ENOENT); |
---|
184 | return; |
---|
185 | } |
---|
186 | |
---|
187 | conn->transaction = NULL; |
---|
188 | list_del(&trans->list); |
---|
189 | conn->transaction_started--; |
---|
190 | |
---|
191 | /* Attach transaction to arg for auto-cleanup */ |
---|
192 | talloc_steal(arg, trans); |
---|
193 | |
---|
194 | if (streq(arg, "T")) { |
---|
195 | /* FIXME: Merge, rather failing on any change. */ |
---|
196 | if (trans->generation != generation) { |
---|
197 | send_error(conn, EAGAIN); |
---|
198 | return; |
---|
199 | } |
---|
200 | if (!replace_tdb(trans->tdb_name, trans->tdb)) { |
---|
201 | send_error(conn, errno); |
---|
202 | return; |
---|
203 | } |
---|
204 | /* Don't close this: we won! */ |
---|
205 | trans->tdb = NULL; |
---|
206 | |
---|
207 | /* Fire off the watches for everything that changed. */ |
---|
208 | list_for_each_entry(i, &trans->changes, list) |
---|
209 | fire_watches(conn, i->node, i->recurse); |
---|
210 | generation++; |
---|
211 | } |
---|
212 | send_ack(conn, XS_TRANSACTION_END); |
---|
213 | } |
---|
214 | |
---|
215 | void conn_delete_all_transactions(struct connection *conn) |
---|
216 | { |
---|
217 | struct transaction *trans; |
---|
218 | |
---|
219 | while ((trans = list_top(&conn->transaction_list, |
---|
220 | struct transaction, list))) { |
---|
221 | list_del(&trans->list); |
---|
222 | talloc_free(trans); |
---|
223 | } |
---|
224 | |
---|
225 | assert(conn->transaction == NULL); |
---|
226 | |
---|
227 | conn->transaction_started = 0; |
---|
228 | } |
---|
229 | |
---|
230 | /* |
---|
231 | * Local variables: |
---|
232 | * c-file-style: "linux" |
---|
233 | * indent-tabs-mode: t |
---|
234 | * c-indent-level: 8 |
---|
235 | * c-basic-offset: 8 |
---|
236 | * tab-width: 8 |
---|
237 | * End: |
---|
238 | */ |
---|