make tdbbackup use transactions
[samba.git] / lib / tdb / tools / tdbbackup.c
1 /* 
2    Unix SMB/CIFS implementation.
3    low level tdb backup and restore utility
4    Copyright (C) Andrew Tridgell              2002
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
20 /*
21
22   This program is meant for backup/restore of tdb databases. Typical usage would be:
23      tdbbackup *.tdb
24   when Samba shuts down cleanly, which will make a backup of all the local databases
25   to *.bak files. Then on Samba startup you would use:
26      tdbbackup -v *.tdb
27   and this will check the databases for corruption and if corruption is detected then
28   the backup will be restored.
29
30   You may also like to do a backup on a regular basis while Samba is
31   running, perhaps using cron.
32
33   The reason this program is needed is to cope with power failures
34   while Samba is running. A power failure could lead to database
35   corruption and Samba will then not start correctly.
36
37   Note that many of the databases in Samba are transient and thus
38   don't need to be backed up, so you can optimise the above a little
39   by only running the backup on the critical databases.
40
41  */
42
43 #include "replace.h"
44 #include "system/locale.h"
45 #include "system/time.h"
46 #include "system/filesys.h"
47 #include "system/wait.h"
48 #include "tdb.h"
49
50 #ifdef HAVE_GETOPT_H
51 #include <getopt.h>
52 #endif
53
54 static int failed;
55
56 static char *add_suffix(const char *name, const char *suffix)
57 {
58         char *ret;
59         int len = strlen(name) + strlen(suffix) + 1;
60         ret = (char *)malloc(len);
61         if (!ret) {
62                 fprintf(stderr,"Out of memory!\n");
63                 exit(1);
64         }
65         snprintf(ret, len, "%s%s", name, suffix);
66         return ret;
67 }
68
69 static int copy_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
70 {
71         TDB_CONTEXT *tdb_new = (TDB_CONTEXT *)state;
72
73         if (tdb_store(tdb_new, key, dbuf, TDB_INSERT) != 0) {
74                 fprintf(stderr,"Failed to insert into %s\n", tdb_name(tdb_new));
75                 failed = 1;
76                 return 1;
77         }
78         return 0;
79 }
80
81
82 static int test_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
83 {
84         return 0;
85 }
86
87 /*
88   carefully backup a tdb, validating the contents and
89   only doing the backup if its OK
90   this function is also used for restore
91 */
92 static int backup_tdb(const char *old_name, const char *new_name, int hash_size)
93 {
94         TDB_CONTEXT *tdb;
95         TDB_CONTEXT *tdb_new;
96         char *tmp_name;
97         struct stat st;
98         int count1, count2;
99
100         tmp_name = add_suffix(new_name, ".tmp");
101
102         /* stat the old tdb to find its permissions */
103         if (stat(old_name, &st) != 0) {
104                 perror(old_name);
105                 free(tmp_name);
106                 return 1;
107         }
108
109         /* open the old tdb */
110         tdb = tdb_open(old_name, 0, 0, O_RDWR, 0);
111         if (!tdb) {
112                 printf("Failed to open %s\n", old_name);
113                 free(tmp_name);
114                 return 1;
115         }
116
117         /* create the new tdb */
118         unlink(tmp_name);
119         tdb_new = tdb_open(tmp_name,
120                            hash_size ? hash_size : tdb_hash_size(tdb),
121                            TDB_DEFAULT, O_RDWR|O_CREAT|O_EXCL, 
122                            st.st_mode & 0777);
123         if (!tdb_new) {
124                 perror(tmp_name);
125                 free(tmp_name);
126                 return 1;
127         }
128
129         if (tdb_transaction_start(tdb) != 0) {
130                 printf("Failed to start transaction on old tdb\n");
131                 tdb_close(tdb);
132                 tdb_close(tdb_new);
133                 unlink(tmp_name);
134                 free(tmp_name);
135                 return 1;
136         }
137
138         if (tdb_transaction_start(tdb_new) != 0) {
139                 printf("Failed to start transaction on new tdb\n");
140                 tdb_close(tdb);
141                 tdb_close(tdb_new);
142                 unlink(tmp_name);
143                 free(tmp_name);
144                 return 1;
145         }
146
147         failed = 0;
148
149         /* traverse and copy */
150         count1 = tdb_traverse(tdb, copy_fn, (void *)tdb_new);
151         if (count1 < 0 || failed) {
152                 fprintf(stderr,"failed to copy %s\n", old_name);
153                 tdb_close(tdb);
154                 tdb_close(tdb_new);
155                 unlink(tmp_name);
156                 free(tmp_name);
157                 return 1;
158         }
159
160         /* close the old tdb */
161         tdb_close(tdb);
162
163         if (tdb_transaction_commit(tdb_new) != 0) {
164                 fprintf(stderr, "Failed to commit new tdb\n");
165                 tdb_close(tdb_new);
166                 unlink(tmp_name);
167                 free(tmp_name);         
168                 return 1;
169         }
170
171         /* close the new tdb and re-open read-only */
172         tdb_close(tdb_new);
173         tdb_new = tdb_open(tmp_name, 0, TDB_DEFAULT, O_RDONLY, 0);
174         if (!tdb_new) {
175                 fprintf(stderr,"failed to reopen %s\n", tmp_name);
176                 unlink(tmp_name);
177                 perror(tmp_name);
178                 free(tmp_name);
179                 return 1;
180         }
181         
182         /* traverse the new tdb to confirm */
183         count2 = tdb_traverse(tdb_new, test_fn, NULL);
184         if (count2 != count1) {
185                 fprintf(stderr,"failed to copy %s\n", old_name);
186                 tdb_close(tdb_new);
187                 unlink(tmp_name);
188                 free(tmp_name);
189                 return 1;
190         }
191
192         /* close the new tdb and rename it to .bak */
193         tdb_close(tdb_new);
194         if (rename(tmp_name, new_name) != 0) {
195                 perror(new_name);
196                 free(tmp_name);
197                 return 1;
198         }
199
200         free(tmp_name);
201
202         return 0;
203 }
204
205 /*
206   verify a tdb and if it is corrupt then restore from *.bak
207 */
208 static int verify_tdb(const char *fname, const char *bak_name)
209 {
210         TDB_CONTEXT *tdb;
211         int count = -1;
212
213         /* open the tdb */
214         tdb = tdb_open(fname, 0, 0, O_RDONLY, 0);
215
216         /* traverse the tdb, then close it */
217         if (tdb) {
218                 count = tdb_traverse(tdb, test_fn, NULL);
219                 tdb_close(tdb);
220         }
221
222         /* count is < 0 means an error */
223         if (count < 0) {
224                 printf("restoring %s\n", fname);
225                 return backup_tdb(bak_name, fname, 0);
226         }
227
228         printf("%s : %d records\n", fname, count);
229
230         return 0;
231 }
232
233 /*
234   see if one file is newer than another
235 */
236 static int file_newer(const char *fname1, const char *fname2)
237 {
238         struct stat st1, st2;
239         if (stat(fname1, &st1) != 0) {
240                 return 0;
241         }
242         if (stat(fname2, &st2) != 0) {
243                 return 1;
244         }
245         return (st1.st_mtime > st2.st_mtime);
246 }
247
248 static void usage(void)
249 {
250         printf("Usage: tdbbackup [options] <fname...>\n\n");
251         printf("   -h            this help message\n");
252         printf("   -s suffix     set the backup suffix\n");
253         printf("   -v            verify mode (restore if corrupt)\n");
254         printf("   -n hashsize   set the new hash size for the backup\n");
255 }
256                 
257
258  int main(int argc, char *argv[])
259 {
260         int i;
261         int ret = 0;
262         int c;
263         int verify = 0;
264         int hashsize = 0;
265         const char *suffix = ".bak";
266
267         while ((c = getopt(argc, argv, "vhs:n:")) != -1) {
268                 switch (c) {
269                 case 'h':
270                         usage();
271                         exit(0);
272                 case 'v':
273                         verify = 1;
274                         break;
275                 case 's':
276                         suffix = optarg;
277                         break;
278                 case 'n':
279                         hashsize = atoi(optarg);
280                         break;
281                 }
282         }
283
284         argc -= optind;
285         argv += optind;
286
287         if (argc < 1) {
288                 usage();
289                 exit(1);
290         }
291
292         for (i=0; i<argc; i++) {
293                 const char *fname = argv[i];
294                 char *bak_name;
295
296                 bak_name = add_suffix(fname, suffix);
297
298                 if (verify) {
299                         if (verify_tdb(fname, bak_name) != 0) {
300                                 ret = 1;
301                         }
302                 } else {
303                         if (file_newer(fname, bak_name) &&
304                             backup_tdb(fname, bak_name, hashsize) != 0) {
305                                 ret = 1;
306                         }
307                 }
308
309                 free(bak_name);
310         }
311
312         return ret;
313 }