ia64/xen-unstable

view tools/xenstore/xenstored_transaction.c @ 5867:932fc8a1b38d

# HG changeset patch
# User Rusty Russell <rusty@rustcorp.com.au>
# Node ID a92163adedcfcff0d05c965d09da747f3c8aa13e
# Parent 63ab20781afa311300f3a8e832744292014ea7f6

Remove ill-conceived concept of watches blocking reply on connection which did write/mkdir/rm/setperm etc.
This causes deadlocks in real life, and I can't see a sane way of avoiding them: it is reasonable for someone to ignore watch notifications while doing other actions, and that means that we can do other writes. These writes can block pending other watchers; if one of these is the process blocked awaiting our ack, we deadlock.

diff -r 63ab20781afa -r a92163adedcf tools/xenstore/xenstored_core.c
author cl349@firebug.cl.cam.ac.uk
date Tue Jul 26 13:11:01 2005 +0000 (2005-07-26)
parents a83ac0806d6b
children 997b2b07b96d
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->blocked = talloc_strdup(conn, node);
118 return true;
119 }
120 return false;
121 }
123 /* Callers get a change node (which can fail) and only commit after they've
124 * finished. This way they don't have to unwind eg. a write. */
125 void add_change_node(struct transaction *trans, const char *node, bool recurse)
126 {
127 struct changed_node *i;
129 if (!trans)
130 return;
132 list_for_each_entry(i, &trans->changes, list)
133 if (streq(i->node, node))
134 return;
136 i = talloc(trans, struct changed_node);
137 i->node = talloc_strdup(i, node);
138 i->recurse = recurse;
139 list_add_tail(&i->list, &trans->changes);
140 }
142 char *node_dir_inside_transaction(struct transaction *trans, const char *node)
143 {
144 return talloc_asprintf(node, "%s/%s", trans->divert,
145 node + strlen(trans->node));
146 }
148 void shortest_transaction_timeout(struct timeval *tv)
149 {
150 struct transaction *i;
152 list_for_each_entry(i, &transactions, list) {
153 if (!timerisset(&i->timeout))
154 continue;
156 if (!timerisset(tv) || timercmp(&i->timeout, tv, <))
157 *tv = i->timeout;
158 }
159 }
161 void check_transaction_timeout(void)
162 {
163 struct transaction *i;
164 struct timeval now;
166 gettimeofday(&now, NULL);
168 list_for_each_entry(i, &transactions, list) {
169 if (!timerisset(&i->timeout))
170 continue;
172 if (timercmp(&i->timeout, &now, <))
173 i->destined_to_fail = true;
174 }
175 }
177 static int destroy_transaction(void *_transaction)
178 {
179 struct transaction *trans = _transaction;
181 list_del(&trans->list);
182 trace_destroy(trans, "transaction");
183 return destroy_path(trans->divert);
184 }
186 static bool copy_file(const char *src, const char *dst)
187 {
188 int *infd, *outfd;
189 void *data;
190 unsigned int size;
192 infd = talloc_open(src, O_RDONLY, 0);
193 if (!infd)
194 return false;
195 outfd = talloc_open(dst, O_WRONLY|O_CREAT|O_EXCL, 0640);
196 if (!outfd)
197 return false;
198 data = read_all(infd, &size);
199 if (!data)
200 return false;
201 return xs_write_all(*outfd, data, size);
202 }
204 static bool copy_dir(const char *src, const char *dst)
205 {
206 DIR **dir;
207 struct dirent *dirent;
209 if (mkdir(dst, 0750) != 0)
210 return false;
212 dir = talloc_opendir(src);
213 if (!dir)
214 return false;
216 while ((dirent = readdir(*dir)) != NULL) {
217 struct stat st;
218 char *newsrc, *newdst;
220 if (streq(dirent->d_name, ".") || streq(dirent->d_name, ".."))
221 continue;
223 newsrc = talloc_asprintf(src, "%s/%s", src, dirent->d_name);
224 newdst = talloc_asprintf(src, "%s/%s", dst, dirent->d_name);
225 if (stat(newsrc, &st) != 0)
226 return false;
228 if (S_ISDIR(st.st_mode)) {
229 if (!copy_dir(newsrc, newdst))
230 return false;
231 } else {
232 if (!copy_file(newsrc, newdst))
233 return false;
234 }
235 /* Free now so we don't run out of file descriptors */
236 talloc_free(newsrc);
237 talloc_free(newdst);
238 }
239 return true;
240 }
242 bool do_transaction_start(struct connection *conn, const char *node)
243 {
244 struct transaction *transaction;
245 char *dir;
247 if (conn->transaction)
248 return send_error(conn, EBUSY);
250 node = canonicalize(conn, node);
251 if (!check_node_perms(conn, node, XS_PERM_READ))
252 return send_error(conn, errno);
254 if (transaction_block(conn, node))
255 return true;
257 dir = node_dir_outside_transaction(node);
259 /* Attach transaction to node for autofree until it's complete */
260 transaction = talloc(node, struct transaction);
261 transaction->node = talloc_strdup(transaction, node);
262 transaction->divert = talloc_asprintf(transaction, "%s/%p",
263 xs_daemon_transactions(),
264 transaction);
265 INIT_LIST_HEAD(&transaction->changes);
266 transaction->conn = conn;
267 timerclear(&transaction->timeout);
268 transaction->destined_to_fail = false;
269 list_add_tail(&transaction->list, &transactions);
270 talloc_set_destructor(transaction, destroy_transaction);
271 trace_create(transaction, "transaction");
273 if (!copy_dir(dir, transaction->divert))
274 return send_error(conn, errno);
276 talloc_steal(conn, transaction);
277 conn->transaction = transaction;
278 return send_ack(transaction->conn, XS_TRANSACTION_START);
279 }
281 static bool commit_transaction(struct transaction *trans)
282 {
283 char *tmp, *dir;
284 struct changed_node *i;
286 /* Move: orig -> .old, repl -> orig. Cleanup deletes .old. */
287 dir = node_dir_outside_transaction(trans->node);
288 tmp = talloc_asprintf(trans, "%s.old", dir);
290 if (rename(dir, tmp) != 0)
291 return false;
292 if (rename(trans->divert, dir) != 0)
293 corrupt(trans->conn, "Failed rename %s to %s",
294 trans->divert, dir);
296 trans->divert = tmp;
298 /* Fire off the watches for everything that changed. */
299 list_for_each_entry(i, &trans->changes, list)
300 fire_watches(NULL, i->node, i->recurse);
301 return true;
302 }
304 bool do_transaction_end(struct connection *conn, const char *arg)
305 {
306 if (!arg || (!streq(arg, "T") && !streq(arg, "F")))
307 return send_error(conn, EINVAL);
309 if (!conn->transaction)
310 return send_error(conn, ENOENT);
312 if (streq(arg, "T")) {
313 if (conn->transaction->destined_to_fail) {
314 send_error(conn, ETIMEDOUT);
315 goto failed;
316 }
317 if (!commit_transaction(conn->transaction)) {
318 send_error(conn, errno);
319 goto failed;
320 }
321 }
323 talloc_free(conn->transaction);
324 conn->transaction = NULL;
325 return send_ack(conn, XS_TRANSACTION_END);
327 failed:
328 talloc_free(conn->transaction);
329 conn->transaction = NULL;
330 return false;
331 }