merge from ronnie
[amitay/samba.git] / ctdb / server / ctdb_freeze.c
1 /* 
2    ctdb freeze handling
3
4    Copyright (C) Andrew Tridgell  2007
5
6    This program is free software; you can redistribute it and/or modify
7    it under the terms of the GNU General Public License as published by
8    the Free Software Foundation; either version 3 of the License, or
9    (at your option) any later version.
10    
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15    
16    You should have received a copy of the GNU General Public License
17    along with this program; if not, see <http://www.gnu.org/licenses/>.
18 */
19 #include "includes.h"
20 #include "lib/events/events.h"
21 #include "lib/tdb/include/tdb.h"
22 #include "system/network.h"
23 #include "system/filesys.h"
24 #include "system/wait.h"
25 #include "../include/ctdb_private.h"
26 #include "lib/util/dlinklist.h"
27 #include "db_wrap.h"
28
29
30 /*
31   lock all databases
32  */
33 static int ctdb_lock_all_databases(struct ctdb_context *ctdb)
34 {
35         struct ctdb_db_context *ctdb_db;
36         for (ctdb_db=ctdb->db_list;ctdb_db;ctdb_db=ctdb_db->next) {
37                 if (tdb_lockall(ctdb_db->ltdb->tdb) != 0) {
38                         return -1;
39                 }
40         }
41         return 0;
42 }
43
44 /*
45   a list of control requests waiting for a freeze lock child to get
46   the database locks
47  */
48 struct ctdb_freeze_waiter {
49         struct ctdb_freeze_waiter *next, *prev;
50         struct ctdb_context *ctdb;
51         struct ctdb_req_control *c;
52         int32_t status;
53 };
54
55 /* a handle to a freeze lock child process */
56 struct ctdb_freeze_handle {
57         struct ctdb_context *ctdb;
58         pid_t child;
59         int fd;
60         struct ctdb_freeze_waiter *waiters;
61         bool transaction_started;
62         uint32_t transaction_id;
63 };
64
65 /*
66   destroy a freeze handle
67  */     
68 static int ctdb_freeze_handle_destructor(struct ctdb_freeze_handle *h)
69 {
70         h->ctdb->freeze_mode = CTDB_FREEZE_NONE;
71         kill(h->child, SIGKILL);
72         waitpid(h->child, NULL, 0);
73         return 0;
74 }
75
76 /*
77   called when the child writes its status to us
78  */
79 static void ctdb_freeze_lock_handler(struct event_context *ev, struct fd_event *fde, 
80                                        uint16_t flags, void *private_data)
81 {
82         struct ctdb_freeze_handle *h = talloc_get_type(private_data, struct ctdb_freeze_handle);
83         int32_t status;
84         struct ctdb_freeze_waiter *w;
85
86         if (h->ctdb->freeze_mode == CTDB_FREEZE_FROZEN) {
87                 DEBUG(DEBUG_INFO,("freeze child died - unfreezing\n"));
88                 talloc_free(h);
89                 return;
90         }
91
92         if (read(h->fd, &status, sizeof(status)) != sizeof(status)) {
93                 DEBUG(DEBUG_ERR,("read error from freeze lock child\n"));
94                 status = -1;
95         }
96
97         if (status == -1) {
98                 DEBUG(DEBUG_ERR,("Failed to get locks in ctdb_freeze_child\n"));
99                 /* we didn't get the locks - destroy the handle */
100                 talloc_free(h);
101                 return;
102         }
103
104         h->ctdb->freeze_mode = CTDB_FREEZE_FROZEN;
105
106         /* notify the waiters */
107         while ((w = h->ctdb->freeze_handle->waiters)) {
108                 w->status = status;
109                 DLIST_REMOVE(h->ctdb->freeze_handle->waiters, w);
110                 talloc_free(w);
111         }
112 }
113
114 /*
115   create a child which gets locks on all the open databases, then calls the callback telling the parent
116   that it is done
117  */
118 static struct ctdb_freeze_handle *ctdb_freeze_lock(struct ctdb_context *ctdb)
119 {
120         struct ctdb_freeze_handle *h;
121         int fd[2];
122         struct fd_event *fde;
123
124         h = talloc_zero(ctdb, struct ctdb_freeze_handle);
125         CTDB_NO_MEMORY_VOID(ctdb, h);
126
127         h->ctdb = ctdb;
128
129         /* use socketpair() instead of pipe() so we have bi-directional fds */
130         if (socketpair(AF_UNIX, SOCK_STREAM, 0, fd) != 0) {
131                 DEBUG(DEBUG_ERR,("Failed to create pipe for ctdb_freeze_lock\n"));
132                 talloc_free(h);
133                 return NULL;
134         }
135         
136         h->child = fork();
137         if (h->child == -1) {
138                 DEBUG(DEBUG_ERR,("Failed to fork child for ctdb_freeze_lock\n"));
139                 talloc_free(h);
140                 return NULL;
141         }
142
143         if (h->child == 0) {
144                 int ret;
145                 /* in the child */
146                 close(fd[0]);
147                 ret = ctdb_lock_all_databases(ctdb);
148                 if (ret != 0) {
149                         _exit(0);
150                 }
151                 write(fd[1], &ret, sizeof(ret));
152                 /* the read here means we will die if the parent exits */
153                 read(fd[1], &ret, sizeof(ret));
154                 _exit(0);
155         }
156
157         talloc_set_destructor(h, ctdb_freeze_handle_destructor);
158
159         close(fd[1]);
160
161         h->fd = fd[0];
162
163         fde = event_add_fd(ctdb->ev, h, h->fd, EVENT_FD_READ|EVENT_FD_AUTOCLOSE, 
164                            ctdb_freeze_lock_handler, h);
165         if (fde == NULL) {
166                 DEBUG(DEBUG_ERR,("Failed to setup fd event for ctdb_freeze_lock\n"));
167                 close(fd[0]);
168                 talloc_free(h);
169                 return NULL;
170         }
171
172         return h;
173 }
174
175 /*
176   destroy a waiter for a freeze mode change
177  */
178 static int ctdb_freeze_waiter_destructor(struct ctdb_freeze_waiter *w)
179 {
180         DLIST_REMOVE(w->ctdb->freeze_handle->waiters, w);
181         ctdb_request_control_reply(w->ctdb, w->c, NULL, w->status, NULL);
182         return 0;
183 }
184
185 /*
186   start the freeze process
187  */
188 void ctdb_start_freeze(struct ctdb_context *ctdb)
189 {
190         if (ctdb->freeze_mode == CTDB_FREEZE_FROZEN) {
191                 /* we're already frozen */
192                 return;
193         }
194
195         /* if there isn't a freeze lock child then create one */
196         if (!ctdb->freeze_handle) {
197                 ctdb->freeze_handle = ctdb_freeze_lock(ctdb);
198                 CTDB_NO_MEMORY_VOID(ctdb, ctdb->freeze_handle);
199                 ctdb->freeze_mode = CTDB_FREEZE_PENDING;
200         }
201 }
202
203 /*
204   freeze the databases
205  */
206 int32_t ctdb_control_freeze(struct ctdb_context *ctdb, struct ctdb_req_control *c, bool *async_reply)
207 {
208         struct ctdb_freeze_waiter *w;
209
210         if (ctdb->freeze_mode == CTDB_FREEZE_FROZEN) {
211                 /* we're already frozen */
212                 return 0;
213         }
214
215         ctdb_start_freeze(ctdb);
216
217         /* add ourselves to list of waiters */
218         w = talloc(ctdb->freeze_handle, struct ctdb_freeze_waiter);
219         CTDB_NO_MEMORY(ctdb, w);
220         w->ctdb   = ctdb;
221         w->c      = talloc_steal(w, c);
222         w->status = -1;
223         talloc_set_destructor(w, ctdb_freeze_waiter_destructor);
224         DLIST_ADD(ctdb->freeze_handle->waiters, w);
225
226         /* we won't reply till later */
227         *async_reply = True;
228         return 0;
229 }
230
231
232 /*
233   block until we are frozen, used during daemon startup
234  */
235 bool ctdb_blocking_freeze(struct ctdb_context *ctdb)
236 {
237         ctdb_start_freeze(ctdb);
238
239         /* block until frozen */
240         while (ctdb->freeze_mode == CTDB_FREEZE_PENDING) {
241                 event_loop_once(ctdb->ev);
242         }
243
244         return ctdb->freeze_mode == CTDB_FREEZE_FROZEN;
245 }
246
247
248
249 /*
250   thaw the databases
251  */
252 int32_t ctdb_control_thaw(struct ctdb_context *ctdb)
253 {
254         /* cancel any pending transactions */
255         if (ctdb->freeze_handle && ctdb->freeze_handle->transaction_started) {
256                 struct ctdb_db_context *ctdb_db;
257
258                 for (ctdb_db=ctdb->db_list;ctdb_db;ctdb_db=ctdb_db->next) {
259                         tdb_add_flags(ctdb_db->ltdb->tdb, TDB_NOLOCK);
260                         if (tdb_transaction_cancel(ctdb_db->ltdb->tdb) != 0) {
261                                 DEBUG(DEBUG_ERR,(__location__ " Failed to cancel transaction for db '%s'\n",
262                                          ctdb_db->db_name));
263                         }
264                         tdb_remove_flags(ctdb_db->ltdb->tdb, TDB_NOLOCK);
265                 }
266         }
267
268 #if 0
269         /* this hack can be used to get a copy of the databases at the end of a recovery */
270         system("mkdir -p /var/ctdb.saved; /usr/bin/rsync --delete -a /var/ctdb/ /var/ctdb.saved/$$ 2>&1 > /dev/null");
271 #endif
272
273 #if 0
274         /* and this one for local testing */
275         system("mkdir -p test.db.saved; /usr/bin/rsync --delete -a test.db/ test.db.saved/$$ 2>&1 > /dev/null");
276 #endif
277
278
279         talloc_free(ctdb->freeze_handle);
280         ctdb->freeze_handle = NULL;
281         ctdb_call_resend_all(ctdb);
282         return 0;
283 }
284
285
286 /*
287   start a transaction on all databases - used for recovery
288  */
289 int32_t ctdb_control_transaction_start(struct ctdb_context *ctdb, uint32_t id)
290 {
291         struct ctdb_db_context *ctdb_db;
292
293         if (ctdb->freeze_mode != CTDB_FREEZE_FROZEN) {
294                 DEBUG(DEBUG_ERR,(__location__ " Failed transaction_start while not frozen\n"));
295                 return -1;
296         }
297
298
299         for (ctdb_db=ctdb->db_list;ctdb_db;ctdb_db=ctdb_db->next) {
300                 int ret;
301
302                 tdb_add_flags(ctdb_db->ltdb->tdb, TDB_NOLOCK);
303
304                 if (ctdb->freeze_handle->transaction_started) {
305                         if (tdb_transaction_cancel(ctdb_db->ltdb->tdb) != 0) {
306                                 DEBUG(DEBUG_ERR,(__location__ " Failed to cancel transaction for db '%s'\n",
307                                          ctdb_db->db_name));
308                                 /* not a fatal error */
309                         }
310                 }
311
312                 ret = tdb_transaction_start(ctdb_db->ltdb->tdb);
313
314                 tdb_remove_flags(ctdb_db->ltdb->tdb, TDB_NOLOCK);
315
316                 if (ret != 0) {
317                         DEBUG(DEBUG_ERR,(__location__ " Failed to start transaction for db '%s'\n",
318                                  ctdb_db->db_name));
319                         return -1;
320                 }
321         }
322
323         ctdb->freeze_handle->transaction_started = true;
324         ctdb->freeze_handle->transaction_id = id;
325
326         return 0;
327 }
328
329 /*
330   commit transactions on all databases
331  */
332 int32_t ctdb_control_transaction_commit(struct ctdb_context *ctdb, uint32_t id)
333 {
334         struct ctdb_db_context *ctdb_db;
335
336         if (ctdb->freeze_mode != CTDB_FREEZE_FROZEN) {
337                 DEBUG(DEBUG_ERR,(__location__ " Failed transaction_start while not frozen\n"));
338                 return -1;
339         }
340
341         if (!ctdb->freeze_handle->transaction_started) {
342                 DEBUG(DEBUG_ERR,(__location__ " transaction not started\n"));
343                 return -1;
344         }
345
346         if (id != ctdb->freeze_handle->transaction_id) {
347                 DEBUG(DEBUG_ERR,(__location__ " incorrect transaction id 0x%x in commit\n", id));
348                 return -1;
349         }
350
351         for (ctdb_db=ctdb->db_list;ctdb_db;ctdb_db=ctdb_db->next) {
352                 tdb_add_flags(ctdb_db->ltdb->tdb, TDB_NOLOCK);
353                 if (tdb_transaction_commit(ctdb_db->ltdb->tdb) != 0) {
354                         DEBUG(DEBUG_ERR,(__location__ " Failed to commit transaction for db '%s'\n",
355                                  ctdb_db->db_name));
356                         /* this has to be fatal to maintain integrity - it should only
357                            happen if we run out of disk space */
358                         ctdb_fatal(ctdb, "Unable to commit transactions\n");
359                         return -1;
360                 }
361                 tdb_remove_flags(ctdb_db->ltdb->tdb, TDB_NOLOCK);
362         }
363
364         ctdb->freeze_handle->transaction_started = false;
365         ctdb->freeze_handle->transaction_id = 0;
366
367         return 0;
368 }
369
370 /*
371   wipe a database - only possible when in a frozen transaction
372  */
373 int32_t ctdb_control_wipe_database(struct ctdb_context *ctdb, TDB_DATA indata)
374 {
375         struct ctdb_control_wipe_database w = *(struct ctdb_control_wipe_database *)indata.dptr;
376         struct ctdb_db_context *ctdb_db;
377
378         if (ctdb->freeze_mode != CTDB_FREEZE_FROZEN) {
379                 DEBUG(DEBUG_ERR,(__location__ " Failed transaction_start while not frozen\n"));
380                 return -1;
381         }
382
383         if (!ctdb->freeze_handle->transaction_started) {
384                 DEBUG(DEBUG_ERR,(__location__ " transaction not started\n"));
385                 return -1;
386         }
387
388         if (w.transaction_id != ctdb->freeze_handle->transaction_id) {
389                 DEBUG(DEBUG_ERR,(__location__ " incorrect transaction id 0x%x in commit\n", w.transaction_id));
390                 return -1;
391         }
392
393         ctdb_db = find_ctdb_db(ctdb, w.db_id);
394         if (!ctdb_db) {
395                 DEBUG(DEBUG_ERR,(__location__ " Unknown db 0x%x\n", w.db_id));
396                 return -1;
397         }
398
399         if (tdb_wipe_all(ctdb_db->ltdb->tdb) != 0) {
400                 DEBUG(DEBUG_ERR,(__location__ " Failed to wipe database for db '%s'\n",
401                          ctdb_db->db_name));
402                 return -1;
403         }
404
405         return 0;
406 }