ia64/xen-unstable

view tools/xenstore/xenstored_transaction.c @ 6946:e703abaf6e3d

Add behaviour to the remove methods to remove the transaction's path itself. This allows us to write Remove(path) to remove the specified path rather than having to slice the path ourselves.
author emellor@ewan
date Sun Sep 18 14:42:13 2005 +0100 (2005-09-18)
parents 3233e7ecfa9f
children 06d84bf87159
line source
1 /*
2 Transaction code for Xen Store Daemon.
3 Copyright (C) 2005 Rusty Russell IBM Corporation
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.
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.
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 */
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 <stdarg.h>
27 #include <stdlib.h>
28 #include <fcntl.h>
29 #include "talloc.h"
30 #include "list.h"
31 #include "xenstored_transaction.h"
32 #include "xenstored_watch.h"
33 #include "xs_lib.h"
34 #include "utils.h"
35 #include "xenstored_test.h"
37 struct changed_node
38 {
39 /* The list within this transaction. */
40 struct list_head list;
42 /* The name of the node. */
43 char *node;
45 /* And the children? (ie. rm) */
46 bool recurse;
47 };
49 struct transaction
50 {
51 /* Global list of transactions. */
52 struct list_head list;
54 /* My owner (conn->transaction == me). */
55 struct connection *conn;
57 /* Subtree this transaction covers */
58 char *node;
60 /* Base for this transaction. */
61 char *divert;
63 /* List of changed nodes. */
64 struct list_head changes;
66 /* Someone's waiting: time limit. */
67 struct timeval timeout;
69 /* We've timed out. */
70 bool destined_to_fail;
71 };
72 static LIST_HEAD(transactions);
74 bool within_transaction(struct transaction *trans, const char *node)
75 {
76 if (!trans)
77 return true;
78 return is_child(node, trans->node);
79 }
81 /* You are on notice: this transaction is blocking someone. */
82 static void start_transaction_timeout(struct transaction *trans)
83 {
84 if (timerisset(&trans->timeout))
85 return;
87 /* One second timeout. */
88 gettimeofday(&trans->timeout, NULL);
89 trans->timeout.tv_sec += 1;
90 }
92 struct transaction *transaction_covering_node(const char *node)
93 {
94 struct transaction *i;
96 list_for_each_entry(i, &transactions, list) {
97 if (i->destined_to_fail)
98 continue;
99 if (is_child(i->node, node) || is_child(node, i->node))
100 return i;
101 }
102 return NULL;
103 }
105 bool transaction_block(struct connection *conn, const char *node)
106 {
107 struct transaction *trans;
109 /* Transactions don't overlap, so we can't be blocked by
110 * others if we're in one. */
111 if (conn->transaction)
112 return false;
114 trans = transaction_covering_node(node);
115 if (trans) {
116 start_transaction_timeout(trans);
117 conn->state = BLOCKED;
118 conn->blocked_by = talloc_strdup(conn, node);
119 return true;
120 }
121 return false;
122 }
124 /* Callers get a change node (which can fail) and only commit after they've
125 * finished. This way they don't have to unwind eg. a write. */
126 void add_change_node(struct transaction *trans, const char *node, bool recurse)
127 {
128 struct changed_node *i;
130 if (!trans)
131 return;
133 list_for_each_entry(i, &trans->changes, list)
134 if (streq(i->node, node))
135 return;
137 i = talloc(trans, struct changed_node);
138 i->node = talloc_strdup(i, node);
139 i->recurse = recurse;
140 list_add_tail(&i->list, &trans->changes);
141 }
143 char *node_dir_inside_transaction(struct transaction *trans, const char *node)
144 {
145 return talloc_asprintf(node, "%s/%s", trans->divert,
146 node + strlen(trans->node));
147 }
149 void shortest_transaction_timeout(struct timeval *tv)
150 {
151 struct transaction *i;
153 list_for_each_entry(i, &transactions, list) {
154 if (!timerisset(&i->timeout))
155 continue;
157 if (!timerisset(tv) || timercmp(&i->timeout, tv, <))
158 *tv = i->timeout;
159 }
160 }
162 void check_transaction_timeout(void)
163 {
164 struct transaction *i;
165 struct timeval now;
167 gettimeofday(&now, NULL);
169 list_for_each_entry(i, &transactions, list) {
170 if (!timerisset(&i->timeout))
171 continue;
173 if (timercmp(&i->timeout, &now, <))
174 i->destined_to_fail = true;
175 }
176 }
178 static int destroy_transaction(void *_transaction)
179 {
180 struct transaction *trans = _transaction;
182 list_del(&trans->list);
183 trace_destroy(trans, "transaction");
184 return destroy_path(trans->divert);
185 }
187 static bool copy_file(const char *src, const char *dst)
188 {
189 int *infd, *outfd;
190 void *data;
191 unsigned int size;
193 infd = talloc_open(src, O_RDONLY, 0);
194 if (!infd)
195 return false;
196 outfd = talloc_open(dst, O_WRONLY|O_CREAT|O_EXCL, 0640);
197 if (!outfd)
198 return false;
199 data = read_all(infd, &size);
200 if (!data)
201 return false;
202 return xs_write_all(*outfd, data, size);
203 }
205 static bool copy_dir(const char *src, const char *dst)
206 {
207 DIR **dir;
208 struct dirent *dirent;
210 if (mkdir(dst, 0750) != 0)
211 return false;
213 dir = talloc_opendir(src);
214 if (!dir)
215 return false;
217 while ((dirent = readdir(*dir)) != NULL) {
218 struct stat st;
219 char *newsrc, *newdst;
221 if (streq(dirent->d_name, ".") || streq(dirent->d_name, ".."))
222 continue;
224 newsrc = talloc_asprintf(src, "%s/%s", src, dirent->d_name);
225 newdst = talloc_asprintf(src, "%s/%s", dst, dirent->d_name);
226 if (stat(newsrc, &st) != 0)
227 return false;
229 if (S_ISDIR(st.st_mode)) {
230 if (!copy_dir(newsrc, newdst))
231 return false;
232 } else {
233 if (!copy_file(newsrc, newdst))
234 return false;
235 }
236 /* Free now so we don't run out of file descriptors */
237 talloc_free(newsrc);
238 talloc_free(newdst);
239 }
240 return true;
241 }
243 void do_transaction_start(struct connection *conn, const char *node)
244 {
245 struct transaction *transaction;
246 char *dir;
248 if (conn->transaction) {
249 send_error(conn, EBUSY);
250 return;
251 }
253 node = canonicalize(conn, node);
254 if (!check_node_perms(conn, node, XS_PERM_READ)) {
255 send_error(conn, errno);
256 return;
257 }
259 if (transaction_block(conn, node))
260 return;
262 dir = node_dir_outside_transaction(node);
264 /* Attach transaction to node for autofree until it's complete */
265 transaction = talloc(node, struct transaction);
266 transaction->node = talloc_strdup(transaction, node);
267 transaction->divert = talloc_asprintf(transaction, "%s/%p",
268 xs_daemon_transactions(),
269 transaction);
270 INIT_LIST_HEAD(&transaction->changes);
271 transaction->conn = conn;
272 timerclear(&transaction->timeout);
273 transaction->destined_to_fail = false;
274 list_add_tail(&transaction->list, &transactions);
275 talloc_set_destructor(transaction, destroy_transaction);
276 trace_create(transaction, "transaction");
278 if (!copy_dir(dir, transaction->divert)) {
279 send_error(conn, errno);
280 return;
281 }
283 talloc_steal(conn, transaction);
284 conn->transaction = transaction;
285 send_ack(transaction->conn, XS_TRANSACTION_START);
286 }
288 static bool commit_transaction(struct transaction *trans)
289 {
290 char *tmp, *dir;
292 /* Move: orig -> .old, repl -> orig. Cleanup deletes .old. */
293 dir = node_dir_outside_transaction(trans->node);
294 tmp = talloc_asprintf(trans, "%s.old", dir);
296 if (rename(dir, tmp) != 0)
297 return false;
298 if (rename(trans->divert, dir) != 0)
299 corrupt(trans->conn, "Failed rename %s to %s",
300 trans->divert, dir);
302 trans->divert = tmp;
303 return true;
304 }
306 void do_transaction_end(struct connection *conn, const char *arg)
307 {
308 struct changed_node *i;
309 struct transaction *trans;
311 if (!arg || (!streq(arg, "T") && !streq(arg, "F"))) {
312 send_error(conn, EINVAL);
313 return;
314 }
316 if (!conn->transaction) {
317 send_error(conn, ENOENT);
318 return;
319 }
321 /* Set to NULL so fire_watches sends events. */
322 trans = conn->transaction;
323 conn->transaction = NULL;
324 /* Attach transaction to arg for auto-cleanup */
325 talloc_steal(arg, trans);
327 if (streq(arg, "T")) {
328 if (trans->destined_to_fail) {
329 send_error(conn, ETIMEDOUT);
330 return;
331 }
332 if (!commit_transaction(trans)) {
333 send_error(conn, errno);
334 return;
335 }
337 /* Fire off the watches for everything that changed. */
338 list_for_each_entry(i, &trans->changes, list)
339 fire_watches(conn, i->node, i->recurse);
340 }
341 send_ack(conn, XS_TRANSACTION_END);
342 }