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
54static int failed;
55
56static struct tdb_logging_context log_ctx;
57
58#ifdef PRINTF_ATTRIBUTE
59static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...) PRINTF_ATTRIBUTE(3,4);
60#endif
61static void tdb_log(struct tdb_context *tdb, enum tdb_debug_level level, const char *format, ...)
62{
63	va_list ap;
64
65	va_start(ap, format);
66	vfprintf(stdout, format, ap);
67	va_end(ap);
68	fflush(stdout);
69}
70
71static char *add_suffix(const char *name, const char *suffix)
72{
73	char *ret;
74	int len = strlen(name) + strlen(suffix) + 1;
75	ret = (char *)malloc(len);
76	if (!ret) {
77		fprintf(stderr,"Out of memory!\n");
78		exit(1);
79	}
80	snprintf(ret, len, "%s%s", name, suffix);
81	return ret;
82}
83
84static int copy_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
85{
86	TDB_CONTEXT *tdb_new = (TDB_CONTEXT *)state;
87
88	if (tdb_store(tdb_new, key, dbuf, TDB_INSERT) != 0) {
89		fprintf(stderr,"Failed to insert into %s\n", tdb_name(tdb_new));
90		failed = 1;
91		return 1;
92	}
93	return 0;
94}
95
96
97static int test_fn(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, void *state)
98{
99	return 0;
100}
101
102/*
103  carefully backup a tdb, validating the contents and
104  only doing the backup if its OK
105  this function is also used for restore
106*/
107static int backup_tdb(const char *old_name, const char *new_name, int hash_size)
108{
109	TDB_CONTEXT *tdb;
110	TDB_CONTEXT *tdb_new;
111	char *tmp_name;
112	struct stat st;
113	int count1, count2;
114
115	tmp_name = add_suffix(new_name, ".tmp");
116
117	/* stat the old tdb to find its permissions */
118	if (stat(old_name, &st) != 0) {
119		perror(old_name);
120		free(tmp_name);
121		return 1;
122	}
123
124	/* open the old tdb */
125	tdb = tdb_open_ex(old_name, 0, 0,
126			  O_RDWR, 0, &log_ctx, NULL);
127	if (!tdb) {
128		printf("Failed to open %s\n", old_name);
129		free(tmp_name);
130		return 1;
131	}
132
133	/* create the new tdb */
134	unlink(tmp_name);
135	tdb_new = tdb_open_ex(tmp_name,
136			      hash_size ? hash_size : tdb_hash_size(tdb),
137			      TDB_DEFAULT,
138			      O_RDWR|O_CREAT|O_EXCL, st.st_mode & 0777,
139			      &log_ctx, NULL);
140	if (!tdb_new) {
141		perror(tmp_name);
142		free(tmp_name);
143		return 1;
144	}
145
146	if (tdb_transaction_start(tdb) != 0) {
147		printf("Failed to start transaction on old tdb\n");
148		tdb_close(tdb);
149		tdb_close(tdb_new);
150		unlink(tmp_name);
151		free(tmp_name);
152		return 1;
153	}
154
155	if (tdb_transaction_start(tdb_new) != 0) {
156		printf("Failed to start transaction on new tdb\n");
157		tdb_close(tdb);
158		tdb_close(tdb_new);
159		unlink(tmp_name);
160		free(tmp_name);
161		return 1;
162	}
163
164	failed = 0;
165
166	/* traverse and copy */
167	count1 = tdb_traverse(tdb, copy_fn, (void *)tdb_new);
168	if (count1 < 0 || failed) {
169		fprintf(stderr,"failed to copy %s\n", old_name);
170		tdb_close(tdb);
171		tdb_close(tdb_new);
172		unlink(tmp_name);
173		free(tmp_name);
174		return 1;
175	}
176
177	/* close the old tdb */
178	tdb_close(tdb);
179
180	if (tdb_transaction_commit(tdb_new) != 0) {
181		fprintf(stderr, "Failed to commit new tdb\n");
182		tdb_close(tdb_new);
183		unlink(tmp_name);
184		free(tmp_name);
185		return 1;
186	}
187
188	/* close the new tdb and re-open read-only */
189	tdb_close(tdb_new);
190	tdb_new = tdb_open_ex(tmp_name,
191			      0,
192			      TDB_DEFAULT,
193			      O_RDONLY, 0,
194			      &log_ctx, NULL);
195	if (!tdb_new) {
196		fprintf(stderr,"failed to reopen %s\n", tmp_name);
197		unlink(tmp_name);
198		perror(tmp_name);
199		free(tmp_name);
200		return 1;
201	}
202
203	/* traverse the new tdb to confirm */
204	count2 = tdb_traverse(tdb_new, test_fn, NULL);
205	if (count2 != count1) {
206		fprintf(stderr,"failed to copy %s\n", old_name);
207		tdb_close(tdb_new);
208		unlink(tmp_name);
209		free(tmp_name);
210		return 1;
211	}
212
213	/* close the new tdb and rename it to .bak */
214	tdb_close(tdb_new);
215	if (rename(tmp_name, new_name) != 0) {
216		perror(new_name);
217		free(tmp_name);
218		return 1;
219	}
220
221	free(tmp_name);
222
223	return 0;
224}
225
226/*
227  verify a tdb and if it is corrupt then restore from *.bak
228*/
229static int verify_tdb(const char *fname, const char *bak_name)
230{
231	TDB_CONTEXT *tdb;
232	int count = -1;
233
234	/* open the tdb */
235	tdb = tdb_open_ex(fname, 0, 0,
236			  O_RDONLY, 0, &log_ctx, NULL);
237
238	/* traverse the tdb, then close it */
239	if (tdb) {
240		count = tdb_traverse(tdb, test_fn, NULL);
241		tdb_close(tdb);
242	}
243
244	/* count is < 0 means an error */
245	if (count < 0) {
246		printf("restoring %s\n", fname);
247		return backup_tdb(bak_name, fname, 0);
248	}
249
250	printf("%s : %d records\n", fname, count);
251
252	return 0;
253}
254
255/*
256  see if one file is newer than another
257*/
258static int file_newer(const char *fname1, const char *fname2)
259{
260	struct stat st1, st2;
261	if (stat(fname1, &st1) != 0) {
262		return 0;
263	}
264	if (stat(fname2, &st2) != 0) {
265		return 1;
266	}
267	return (st1.st_mtime > st2.st_mtime);
268}
269
270static void usage(void)
271{
272	printf("Usage: tdbbackup [options] <fname...>\n\n");
273	printf("   -h            this help message\n");
274	printf("   -s suffix     set the backup suffix\n");
275	printf("   -v            verify mode (restore if corrupt)\n");
276	printf("   -n hashsize   set the new hash size for the backup\n");
277}
278
279
280 int main(int argc, char *argv[])
281{
282	int i;
283	int ret = 0;
284	int c;
285	int verify = 0;
286	int hashsize = 0;
287	const char *suffix = ".bak";
288
289	log_ctx.log_fn = tdb_log;
290
291	while ((c = getopt(argc, argv, "vhs:n:")) != -1) {
292		switch (c) {
293		case 'h':
294			usage();
295			exit(0);
296		case 'v':
297			verify = 1;
298			break;
299		case 's':
300			suffix = optarg;
301			break;
302		case 'n':
303			hashsize = atoi(optarg);
304			break;
305		}
306	}
307
308	argc -= optind;
309	argv += optind;
310
311	if (argc < 1) {
312		usage();
313		exit(1);
314	}
315
316	for (i=0; i<argc; i++) {
317		const char *fname = argv[i];
318		char *bak_name;
319
320		bak_name = add_suffix(fname, suffix);
321
322		if (verify) {
323			if (verify_tdb(fname, bak_name) != 0) {
324				ret = 1;
325			}
326		} else {
327			if (file_newer(fname, bak_name) &&
328			    backup_tdb(fname, bak_name, hashsize) != 0) {
329				ret = 1;
330			}
331		}
332
333		free(bak_name);
334	}
335
336	return ret;
337}
338