1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 1996,2008 Oracle.  All rights reserved.
5 *
6 * $Id: db_hotbackup.c,v 1.59 2008/01/31 18:40:42 bostic Exp $
7 */
8
9#include "db_config.h"
10
11#include "db_int.h"
12#include "dbinc/log.h"
13#include "dbinc/db_page.h"
14#include "dbinc/qam.h"
15
16#ifndef lint
17static const char copyright[] =
18    "Copyright (c) 1996,2008 Oracle.  All rights reserved.\n";
19#endif
20
21enum which_open { OPEN_ORIGINAL, OPEN_HOT_BACKUP };
22
23int backup_dir_clean __P((DB_ENV *, char *, char *, int *, int, int));
24int data_copy __P((DB_ENV *, char *, char *, char *, int));
25int env_init __P((DB_ENV **,
26     char *, char **, char ***, char *, enum which_open));
27int main __P((int, char *[]));
28int read_data_dir __P((DB_ENV *, char *, char *, char *, int, int));
29int read_log_dir __P((DB_ENV *, char *, char *, char *, int *, int, int));
30int usage __P((void));
31int version_check __P((void));
32
33const char *progname;
34
35int
36main(argc, argv)
37	int argc;
38	char *argv[];
39{
40	extern char *optarg;
41	extern int optind;
42	time_t now;
43	DB_ENV *dbenv;
44	u_int data_cnt, data_next;
45	int ch, checkpoint, copy_min, db_config, exitval;
46	int remove_max, ret, update, verbose;
47	char *backup_dir, **data_dir, **dir, *home, *log_dir, *passwd;
48	char home_buf[DB_MAXPATHLEN], time_buf[CTIME_BUFLEN];
49
50	/*
51	 * Make sure all verbose message are output before any error messages
52	 * in the case where the output is being logged into a file.  This
53	 * call has to be done before any operation is performed on the stream.
54	 *
55	 * Use unbuffered I/O because line-buffered I/O requires a buffer, and
56	 * some operating systems have buffer alignment and size constraints we
57	 * don't want to care about.  There isn't enough output for the calls
58	 * to matter.
59	 */
60	setbuf(stdout, NULL);
61
62	if ((progname = __db_rpath(argv[0])) == NULL)
63		progname = argv[0];
64	else
65		++progname;
66
67	if ((ret = version_check()) != 0)
68		return (ret);
69
70	checkpoint = db_config = data_cnt =
71	    data_next = exitval = update = verbose = 0;
72	data_dir = NULL;
73	backup_dir = home = passwd = NULL;
74	log_dir = NULL;
75	copy_min = remove_max = 0;
76	while ((ch = getopt(argc, argv, "b:cDd:h:l:P:uVv")) != EOF)
77		switch (ch) {
78		case 'b':
79			backup_dir = optarg;
80			break;
81		case 'c':
82			checkpoint = 1;
83			break;
84		case 'D':
85			db_config = 1;
86			break;
87		case 'd':
88			/*
89			 * User can specify a list of directories -- keep an
90			 * array, leaving room for the trailing NULL.
91			 */
92			if (data_dir == NULL || data_next >= data_cnt - 2) {
93				data_cnt = data_cnt == 0 ? 20 : data_cnt * 2;
94				if ((data_dir = realloc(data_dir,
95				    data_cnt * sizeof(*data_dir))) == NULL) {
96					fprintf(stderr, "%s: %s\n",
97					    progname, strerror(errno));
98					return (EXIT_FAILURE);
99				}
100			}
101			data_dir[data_next++] = optarg;
102			break;
103		case 'h':
104			home = optarg;
105			break;
106		case 'l':
107			log_dir = optarg;
108			break;
109		case 'P':
110			passwd = strdup(optarg);
111			memset(optarg, 0, strlen(optarg));
112			if (passwd == NULL) {
113				fprintf(stderr, "%s: strdup: %s\n",
114				    progname, strerror(errno));
115				return (EXIT_FAILURE);
116			}
117			break;
118		case 'u':
119			update = 1;
120			break;
121		case 'V':
122			printf("%s\n", db_version(NULL, NULL, NULL));
123			return (EXIT_SUCCESS);
124		case 'v':
125			verbose = 1;
126			break;
127		case '?':
128		default:
129			return (usage());
130		}
131	argc -= optind;
132	argv += optind;
133
134	if (argc != 0)
135		return (usage());
136
137	/* NULL-terminate any list of data directories. */
138	if (data_dir != NULL) {
139		data_dir[data_next] = NULL;
140		/*
141		 * -d is relative to the current directory, to run a checkpoint
142		 * we must have directories relative to the environment.
143		 */
144		if (checkpoint == 1) {
145			fprintf(stderr,
146				"%s: cannot specify -d and -c\n", progname);
147			return (usage());
148		}
149	}
150
151	if (db_config && (data_dir != NULL || log_dir != NULL)) {
152		fprintf(stderr,
153		     "%s: cannot specify -D and -d or -l\n", progname);
154		return (usage());
155	}
156
157	/* Handle possible interruptions. */
158	__db_util_siginit();
159
160	/*
161	 * The home directory defaults to the environment variable DB_HOME.
162	 * The log directory defaults to the home directory.
163	 *
164	 * We require a source database environment directory and a target
165	 * backup directory.
166	 */
167	if (home == NULL) {
168		home = home_buf;
169		if ((ret = __os_getenv(
170		    NULL, "DB_HOME", &home, sizeof(home_buf))) != 0) {
171			fprintf(stderr,
172		    "%s failed to get environment variable DB_HOME: %s\n",
173			    progname, db_strerror(ret));
174			return (EXIT_FAILURE);
175		}
176		/*
177		 * home set to NULL if __os_getenv failed to find DB_HOME.
178		 */
179	}
180	if (home == NULL) {
181		fprintf(stderr,
182		    "%s: no source database environment specified\n", progname);
183		return (usage());
184	}
185	if (backup_dir == NULL) {
186		fprintf(stderr,
187		    "%s: no target backup directory specified\n", progname);
188		return (usage());
189	}
190
191	if (verbose) {
192		(void)time(&now);
193		printf("%s: hot backup started at %s",
194		    progname, __os_ctime(&now, time_buf));
195	}
196
197	/* Open the source environment. */
198	if (env_init(&dbenv, home,
199	     (db_config || log_dir != NULL) ? &log_dir : NULL,
200	     db_config ? &data_dir : NULL,
201	     passwd, OPEN_ORIGINAL) != 0)
202		goto shutdown;
203
204	if (db_config && __os_abspath(log_dir)) {
205		fprintf(stderr,
206	"%s: DB_CONFIG must not contain an absolute path for the log directory",
207		    progname);
208		goto shutdown;
209	}
210
211	/*
212	 * If the -c option is specified, checkpoint the source home
213	 * database environment, and remove any unnecessary log files.
214	 */
215	if (checkpoint) {
216		if (verbose)
217			printf("%s: %s: force checkpoint\n", progname, home);
218		if ((ret =
219		    dbenv->txn_checkpoint(dbenv, 0, 0, DB_FORCE)) != 0) {
220			dbenv->err(dbenv, ret, "DB_ENV->txn_checkpoint");
221			goto shutdown;
222		}
223		if (!update) {
224			if (verbose)
225				printf("%s: %s: remove unnecessary log files\n",
226				    progname, home);
227			if ((ret = dbenv->log_archive(dbenv,
228			     NULL, DB_ARCH_REMOVE)) != 0) {
229				dbenv->err(dbenv, ret, "DB_ENV->log_archive");
230				goto shutdown;
231			}
232		}
233	}
234
235	/*
236	 * If the target directory for the backup does not exist, create it
237	 * with mode read-write-execute for the owner.  Ignore errors here,
238	 * it's simpler and more portable to just always try the create.  If
239	 * there's a problem, we'll fail with reasonable errors later.
240	 */
241	(void)__os_mkdir(NULL, backup_dir, DB_MODE_700);
242
243	/*
244	 * If -u was specified, remove all log files; if -u was not specified,
245	 * remove all files.
246	 *
247	 * Potentially there are two directories to clean, the log directory
248	 * and the target directory.  First, clean up the log directory if
249	 * it's different from the target directory, then clean up the target
250	 * directory.
251	 */
252	if (db_config && log_dir != NULL &&
253	    backup_dir_clean(
254	    dbenv, backup_dir, log_dir, &remove_max, update, verbose) != 0)
255		goto shutdown;
256	if (backup_dir_clean(dbenv,
257	    backup_dir, NULL, &remove_max, update, verbose) != 0)
258		goto shutdown;
259
260	/*
261	 * If the -u option was not specified, copy all database files found in
262	 * the database environment home directory, or any directory specified
263	 * using the -d option, into the target directory for the backup.
264	 */
265	if (!update) {
266		if (read_data_dir(dbenv, home,
267		     backup_dir, home, verbose, db_config) != 0)
268			goto shutdown;
269		if (data_dir != NULL)
270			for (dir = data_dir; *dir != NULL; ++dir) {
271				/*
272				 * Don't allow absolute path names taken from
273				 * the DB_CONFIG file -- running recovery with
274				 * them would corrupt the source files.
275				 */
276				if (db_config && __os_abspath(*dir)) {
277					fprintf(stderr,
278     "%s: data directory '%s' is absolute path, not permitted with -D option\n",
279					     progname, *dir);
280					goto shutdown;
281				}
282				if (read_data_dir(dbenv, home,
283				     backup_dir, *dir, verbose, db_config) != 0)
284					goto shutdown;
285			}
286	}
287
288	/*
289	 * Copy all log files found in the directory specified by the -l option
290	 * (or in the database environment home directory, if no -l option was
291	 * specified), into the target directory for the backup.
292	 *
293	 * The log directory defaults to the home directory.
294	 */
295	if (read_log_dir(dbenv, db_config ? home : NULL, backup_dir,
296	     log_dir == NULL ? home : log_dir, &copy_min, update, verbose) != 0)
297		goto shutdown;
298
299	/*
300	 * If we're updating a snapshot, the lowest-numbered log file copied
301	 * into the backup directory should be less than, or equal to, the
302	 * highest-numbered log file removed from the backup directory during
303	 * cleanup.
304	 */
305	if (update && remove_max < copy_min &&
306	     !(remove_max == 0 && copy_min == 1)) {
307		fprintf(stderr,
308		    "%s: the largest log file removed (%d) must be greater\n",
309		    progname, remove_max);
310		fprintf(stderr,
311		    "%s: than or equal the smallest log file copied (%d)\n",
312		    progname, copy_min);
313		goto shutdown;
314	}
315
316	/* Close the source environment. */
317	if ((ret = dbenv->close(dbenv, 0)) != 0) {
318		fprintf(stderr,
319		    "%s: dbenv->close: %s\n", progname, db_strerror(ret));
320		dbenv = NULL;
321		goto shutdown;
322	}
323	/* Perform catastrophic recovery on the hot backup. */
324	if (verbose)
325		printf("%s: %s: run catastrophic recovery\n",
326		    progname, backup_dir);
327	if (env_init(
328	    &dbenv, backup_dir, NULL, NULL, passwd, OPEN_HOT_BACKUP) != 0)
329		goto shutdown;
330
331	/*
332	 * Remove any unnecessary log files from the hot backup.
333	 */
334	if (verbose)
335		printf("%s: %s: remove unnecessary log files\n",
336		    progname, backup_dir);
337	if ((ret =
338	    dbenv->log_archive(dbenv, NULL, DB_ARCH_REMOVE)) != 0) {
339		dbenv->err(dbenv, ret, "DB_ENV->log_archive");
340		goto shutdown;
341	}
342
343	if (0) {
344shutdown:	exitval = 1;
345	}
346	if (dbenv != NULL && (ret = dbenv->close(dbenv, 0)) != 0) {
347		exitval = 1;
348		fprintf(stderr,
349		    "%s: dbenv->close: %s\n", progname, db_strerror(ret));
350	}
351
352	if (exitval == 0) {
353		if (verbose) {
354			(void)time(&now);
355			printf("%s: hot backup completed at %s",
356			    progname, __os_ctime(&now, time_buf));
357		}
358	} else {
359		fprintf(stderr, "%s: HOT BACKUP FAILED!\n", progname);
360	}
361
362	/* Resend any caught signal. */
363	__db_util_sigresend();
364
365	return (exitval == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
366
367}
368
369/*
370 * env_init --
371 *	Open a database environment.
372 */
373int
374env_init(dbenvp, home, log_dirp, data_dirp, passwd, which)
375	DB_ENV **dbenvp;
376	char *home, **log_dirp, ***data_dirp, *passwd;
377	enum which_open which;
378{
379	DB_ENV *dbenv;
380	int ret;
381
382	*dbenvp = NULL;
383
384	/*
385	 * Create an environment object and initialize it for error reporting.
386	 */
387	if ((ret = db_env_create(&dbenv, 0)) != 0) {
388		fprintf(stderr,
389		    "%s: db_env_create: %s\n", progname, db_strerror(ret));
390		return (1);
391	}
392
393	dbenv->set_errfile(dbenv, stderr);
394	setbuf(stderr, NULL);
395	dbenv->set_errpfx(dbenv, progname);
396
397	/* Any created intermediate directories are created private. */
398	if ((ret = dbenv->set_intermediate_dir_mode(dbenv, "rwx------")) != 0) {
399		dbenv->err(dbenv, ret, "DB_ENV->set_intermediate_dir_mode");
400		return (1);
401	}
402
403	/*
404	 * If a log directory has been specified, and it's not the same as the
405	 * home directory, set it for the environment.
406	 */
407	if (log_dirp != NULL && *log_dirp != NULL &&
408	    (ret = dbenv->set_lg_dir(dbenv, *log_dirp)) != 0) {
409		dbenv->err(dbenv, ret, "DB_ENV->set_lg_dir: %s", *log_dirp);
410		return (1);
411	}
412
413	/* Optionally set the password. */
414	if (passwd != NULL &&
415	    (ret = dbenv->set_encrypt(dbenv, passwd, DB_ENCRYPT_AES)) != 0) {
416		dbenv->err(dbenv, ret, "DB_ENV->set_encrypt");
417		return (1);
418	}
419
420	switch (which) {
421	case OPEN_ORIGINAL:
422		/*
423		 * Opening the database environment we're trying to back up.
424		 * We try to attach to a pre-existing environment; if that
425		 * fails, we create a private environment and try again.
426		 */
427		if ((ret = dbenv->open(dbenv, home, DB_USE_ENVIRON, 0)) != 0 &&
428		    (ret == DB_VERSION_MISMATCH ||
429		    (ret = dbenv->open(dbenv, home, DB_CREATE |
430		    DB_INIT_LOG | DB_INIT_TXN | DB_PRIVATE | DB_USE_ENVIRON,
431		    0)) != 0)) {
432			dbenv->err(dbenv, ret, "DB_ENV->open: %s", home);
433			return (1);
434		}
435		if (log_dirp != NULL && *log_dirp == NULL)
436			(void)dbenv->get_lg_dir(dbenv, (const char **)log_dirp);
437		if (data_dirp != NULL && *data_dirp == NULL)
438			(void)dbenv->get_data_dirs(
439			    dbenv, (const char ***)data_dirp);
440		break;
441	case OPEN_HOT_BACKUP:
442		/*
443		 * Opening the backup copy of the database environment.  We
444		 * better be the only user, we're running recovery.
445		 * Ensure that there at least minimal cache for worst
446		 * case page size.
447		 */
448		if ((ret =
449		    dbenv->set_cachesize(dbenv, 0, 64 * 1024 * 10, 0)) != 0) {
450			dbenv->err(dbenv,
451			     ret, "DB_ENV->set_cachesize: %s", home);
452			return (1);
453		}
454		if ((ret = dbenv->open(dbenv, home, DB_CREATE |
455		    DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_PRIVATE |
456		    DB_RECOVER_FATAL | DB_USE_ENVIRON, 0)) != 0) {
457			dbenv->err(dbenv, ret, "DB_ENV->open: %s", home);
458			return (1);
459		}
460		break;
461	}
462
463	*dbenvp = dbenv;
464	return (0);
465}
466
467/*
468 * backup_dir_clean --
469 *	Clean out the backup directory.
470 */
471int
472backup_dir_clean(dbenv, backup_dir, log_dir, remove_maxp, update, verbose)
473	DB_ENV *dbenv;
474	char *backup_dir, *log_dir;
475	int *remove_maxp, update, verbose;
476{
477	ENV *env;
478	int cnt, fcnt, ret, v;
479	char **names, *dir, buf[DB_MAXPATHLEN], path[DB_MAXPATHLEN];
480
481	env = dbenv->env;
482
483	/* We may be cleaning a log directory separate from the target. */
484	if (log_dir != NULL) {
485		if ((size_t)snprintf(buf, sizeof(buf), "%s%c%s",
486		    backup_dir, PATH_SEPARATOR[0] ,log_dir) >= sizeof(buf)) {
487			dbenv->errx(dbenv, "%s%c%s: path too long",
488			    backup_dir, PATH_SEPARATOR[0] ,log_dir);
489			return (1);
490		}
491		dir = buf;
492	} else
493		dir = backup_dir;
494
495	/* Get a list of file names. */
496	if ((ret = __os_dirlist(env, dir, 0, &names, &fcnt)) != 0) {
497		if (log_dir != NULL && !update)
498			return (0);
499		dbenv->err(dbenv, ret, "%s: directory read", dir);
500		return (1);
501	}
502	for (cnt = fcnt; --cnt >= 0;) {
503		/*
504		 * Skip non-log files (if update was specified).
505		 */
506		if (strncmp(names[cnt], LFPREFIX, sizeof(LFPREFIX) - 1)) {
507			if (update)
508				continue;
509		} else {
510			/* Track the highest-numbered log file removed. */
511			v = atoi(names[cnt] + sizeof(LFPREFIX) - 1);
512			if (*remove_maxp < v)
513				*remove_maxp = v;
514		}
515		if ((size_t)snprintf(path, sizeof(path), "%s%c%s",
516		    dir, PATH_SEPARATOR[0], names[cnt]) >= sizeof(path)) {
517			dbenv->errx(dbenv, "%s%c%s: path too long",
518			    dir, PATH_SEPARATOR[0], names[cnt]);
519			return (1);
520		}
521		if (verbose)
522			printf("%s: removing %s\n", progname, path);
523		if (__os_unlink(env, path, 0) != 0)
524			return (1);
525	}
526
527	__os_dirfree(env, names, fcnt);
528
529	if (verbose && *remove_maxp != 0)
530		printf("%s: highest numbered log file removed: %d\n",
531		    progname, *remove_maxp);
532
533	return (0);
534}
535
536/*
537 * read_data_dir --
538 *	Read a directory looking for databases to copy.
539 */
540int
541read_data_dir(dbenv, home, backup_dir, dir, verbose, db_config)
542	DB_ENV *dbenv;
543	char *home, *backup_dir, *dir;
544	int verbose, db_config;
545{
546	ENV *env;
547	int cnt, fcnt, ret;
548	char *bd, **names;
549	char buf[DB_MAXPATHLEN], bbuf[DB_MAXPATHLEN];
550
551	env = dbenv->env;
552
553	bd = backup_dir;
554	if (db_config && dir != home) {
555		/* Build a path name to the destination. */
556		if ((size_t)(cnt = snprintf(bbuf, sizeof(bbuf), "%s%c%s%c",
557		      backup_dir, PATH_SEPARATOR[0],
558		      dir, PATH_SEPARATOR[0])) >= sizeof(buf)) {
559			dbenv->errx(dbenv, "%s%c%s: path too long",
560			     backup_dir, PATH_SEPARATOR[0], dir);
561			return (1);
562		}
563		bd = bbuf;
564
565		/* Create the path. */
566		if ((ret = __db_mkpath(env, bd)) != 0) {
567			dbenv->err(dbenv, ret, "%s: cannot create", bd);
568			return (1);
569		}
570		/* step on the trailing '/' */
571		bd[cnt - 1] = '\0';
572
573		/* Build a path name to the source. */
574		if ((size_t)snprintf(buf, sizeof(buf),
575		    "%s%c%s", home, PATH_SEPARATOR[0], dir) >= sizeof(buf)) {
576			dbenv->errx(dbenv, "%s%c%s: path too long",
577			    home, PATH_SEPARATOR[0], dir);
578			return (1);
579		}
580		dir = buf;
581	}
582	/* Get a list of file names. */
583	if ((ret = __os_dirlist(env, dir, 0, &names, &fcnt)) != 0) {
584		dbenv->err(dbenv, ret, "%s: directory read", dir);
585		return (1);
586	}
587	for (cnt = fcnt; --cnt >= 0;) {
588		/*
589		 * Skip files in DB's name space (but not Queue
590		 * extent files, we need them).
591		 */
592		if (!strncmp(names[cnt], LFPREFIX, sizeof(LFPREFIX) - 1))
593			continue;
594		if (!strncmp(names[cnt],
595		    DB_REGION_PREFIX, sizeof(DB_REGION_PREFIX) - 1) &&
596		    strncmp(names[cnt],
597		    QUEUE_EXTENT_PREFIX, sizeof(QUEUE_EXTENT_PREFIX) - 1))
598			continue;
599
600		/*
601		 * Skip DB_CONFIG.
602		 */
603		if (!db_config &&
604		     !strncmp(names[cnt], "DB_CONFIG", sizeof("DB_CONFIG")))
605			continue;
606
607		/* Copy the file. */
608		if (data_copy(dbenv, names[cnt], dir, bd, verbose) != 0)
609			return (1);
610	}
611
612	__os_dirfree(env, names, fcnt);
613
614	return (0);
615}
616
617/*
618 * read_log_dir --
619 * *	Read a directory looking for log files to copy.  If home
620 * is passed then we are possibly using a log dir in the destination,
621 * following DB_CONFIG configuration.
622 */
623int
624read_log_dir(dbenv, home, backup_dir, log_dir, copy_minp, update, verbose)
625	DB_ENV *dbenv;
626	char *home, *backup_dir, *log_dir;
627	int *copy_minp, update, verbose;
628{
629	ENV *env;
630	u_int32_t aflag;
631	int cnt, ret, v;
632	char **begin, **names, *backupd, *logd;
633	char from[DB_MAXPATHLEN], to[DB_MAXPATHLEN];
634
635	env = dbenv->env;
636
637	if (home != NULL && log_dir != NULL) {
638		if ((size_t)snprintf(from, sizeof(from), "%s%c%s",
639		    home, PATH_SEPARATOR[0], log_dir) >= sizeof(from)) {
640			dbenv->errx(dbenv, "%s%c%s: path too long",
641			    home, PATH_SEPARATOR[0], log_dir);
642			return (1);
643		}
644		logd = strdup(from);
645		if ((size_t)(cnt = snprintf(to, sizeof(to),
646		    "%s%c%s%c", backup_dir, PATH_SEPARATOR[0],
647		    log_dir, PATH_SEPARATOR[0])) >= sizeof(to)) {
648			dbenv->errx(dbenv, "%s%c%s: path too long",
649			    backup_dir, PATH_SEPARATOR[0], log_dir);
650			return (1);
651		}
652		backupd = strdup(to);
653
654		/* Create the backup log directory. */
655		if ((ret = __db_mkpath(env, backupd)) != 0) {
656			dbenv->err(dbenv, ret, "%s: cannot create", backupd);
657			return (1);
658		}
659		/* Step on the trailing '/'. */
660		backupd[cnt - 1] = '\0';
661	} else {
662		backupd = backup_dir;
663		logd = log_dir;
664	}
665
666again:	aflag = DB_ARCH_LOG;
667
668	/*
669	 * If this is an update and we are deleting files, first process
670	 * those files that can be removed, then repeat with the rest.
671	 */
672	if (update)
673		aflag = 0;
674	/* Get a list of file names to be copied. */
675	if ((ret = dbenv->log_archive(dbenv, &names, aflag)) != 0) {
676		dbenv->err(dbenv, ret, "DB_ENV->log_archive");
677		return (1);
678	}
679	if (names == NULL)
680		goto done;
681	begin = names;
682	for (; *names != NULL; names++) {
683		/* Track the lowest-numbered log file copied. */
684		v = atoi(*names + sizeof(LFPREFIX) - 1);
685		if (*copy_minp == 0 || *copy_minp > v)
686			*copy_minp = v;
687
688		if ((size_t)snprintf(from, sizeof(from), "%s%c%s",
689		    logd, PATH_SEPARATOR[0], *names) >= sizeof(from)) {
690			dbenv->errx(dbenv, "%s%c%s: path too long",
691			    logd, PATH_SEPARATOR[0], *names);
692			return (1);
693		}
694
695		/*
696		 * If we're going to remove the file, attempt to rename the
697		 * instead of copying and then removing.  The likely failure
698		 * is EXDEV (source and destination are on different volumes).
699		 * Fall back to a copy, regardless of the error.  We don't
700		 * worry about partial contents, the copy truncates the file
701		 * on open.
702		 */
703		if (update) {
704			if ((size_t)snprintf(to, sizeof(to), "%s%c%s",
705			    backupd, PATH_SEPARATOR[0], *names) >= sizeof(to)) {
706				dbenv->errx(dbenv, "%s%c%s: path too long",
707				    backupd, PATH_SEPARATOR[0], *names);
708				return (1);
709			}
710			if (__os_rename(env, from, to, 1) == 0) {
711				if (verbose)
712					printf("%s: moving %s to %s\n",
713					   progname, from, to);
714				continue;
715			}
716		}
717
718		/* Copy the file. */
719		if (data_copy(dbenv, *names, logd, backupd, verbose) != 0)
720			return (1);
721
722		if (update) {
723			if (verbose)
724				printf("%s: removing %s\n", progname, from);
725			if ((ret = __os_unlink(env, from, 0)) != 0) {
726				dbenv->err(dbenv, ret,
727				     "unlink of %s failed", from);
728				return (1);
729			}
730		}
731
732	}
733
734	free(begin);
735done:	if (update) {
736		update = 0;
737		goto again;
738	}
739
740	if (verbose && *copy_minp != 0)
741		printf("%s: lowest numbered log file copied: %d\n",
742		    progname, *copy_minp);
743	if (logd != log_dir)
744		free(logd);
745	if (backupd != backup_dir)
746		free(backupd);
747
748	return (0);
749}
750
751/*
752 * data_copy --
753 *	Copy a file into the backup directory.
754 */
755int
756data_copy(dbenv, file, from_dir, to_dir, verbose)
757	DB_ENV *dbenv;
758	char *file, *from_dir, *to_dir;
759	int verbose;
760{
761	DB_FH *rfhp, *wfhp;
762	ENV *env;
763	size_t nr, nw;
764	int ret;
765	char *buf;
766
767	rfhp = wfhp = NULL;
768	env = dbenv->env;
769	ret = 0;
770
771	if (verbose)
772		printf("%s: copying %s%c%s to %s%c%s\n", progname, from_dir,
773		    PATH_SEPARATOR[0], file, to_dir, PATH_SEPARATOR[0], file);
774
775	/*
776	 * We MUST copy multiples of the page size, atomically, to ensure a
777	 * database page is not updated by another thread of control during
778	 * the copy.
779	 *
780	 * !!!
781	 * The current maximum page size for Berkeley DB is 64KB; we will have
782	 * to increase this value if the maximum page size is ever more than a
783	 * megabyte
784	 */
785	if ((buf = malloc(MEGABYTE)) == NULL) {
786		dbenv->err(dbenv,
787		    errno, "%lu buffer allocation", (u_long)MEGABYTE);
788		return (1);
789	}
790
791	/* Open the input file. */
792	if (snprintf(buf, MEGABYTE, "%s%c%s",
793	    from_dir, PATH_SEPARATOR[0], file) >= MEGABYTE) {
794		dbenv->errx(dbenv,
795		    "%s%c%s: path too long", from_dir, PATH_SEPARATOR[0], file);
796		goto err;
797	}
798	if ((ret = __os_open(env, buf, 0, DB_OSO_RDONLY, 0, &rfhp)) != 0) {
799		dbenv->err(dbenv, ret, "%s", buf);
800		goto err;
801	}
802
803	/* Open the output file. */
804	if (snprintf(buf, MEGABYTE, "%s%c%s",
805	    to_dir, PATH_SEPARATOR[0], file) >= MEGABYTE) {
806		dbenv->errx(dbenv,
807		    "%s%c%s: path too long", to_dir, PATH_SEPARATOR[0], file);
808		goto err;
809	}
810	if ((ret = __os_open(env, buf, 0,
811	    DB_OSO_CREATE | DB_OSO_TRUNC, DB_MODE_600, &wfhp)) != 0) {
812		dbenv->err(dbenv, ret, "%s", buf);
813		goto err;
814	}
815
816	/* Copy the data. */
817	while ((ret = __os_read(env, rfhp, buf, MEGABYTE, &nr)) == 0 &&
818	    nr > 0)
819		if ((ret = __os_write(env, wfhp, buf, nr, &nw)) != 0)
820			break;
821
822	if (0) {
823err:		ret = 1;
824	}
825	if (buf != NULL)
826		free(buf);
827
828	if (rfhp != NULL && __os_closehandle(env, rfhp) != 0)
829		ret = 1;
830
831	/* We may be running on a remote filesystem; force the flush. */
832	if (wfhp != NULL) {
833		if (__os_fsync(env, wfhp) != 0)
834			ret = 1;
835		if (__os_closehandle(env, wfhp) != 0)
836			ret = 1;
837	}
838	return (ret);
839}
840
841int
842usage()
843{
844	(void)fprintf(stderr, "usage: %s [-cDuVv]\n\t%s\n", progname,
845    "[-d data_dir ...] [-h home] [-l log_dir] [-P password] -b backup_dir");
846	return (EXIT_FAILURE);
847}
848
849int
850version_check()
851{
852	int v_major, v_minor, v_patch;
853
854	/* Make sure we're loaded with the right version of the DB library. */
855	(void)db_version(&v_major, &v_minor, &v_patch);
856	if (v_major != DB_VERSION_MAJOR || v_minor != DB_VERSION_MINOR) {
857		fprintf(stderr,
858	"%s: version %d.%d doesn't match library version %d.%d\n",
859		    progname, DB_VERSION_MAJOR, DB_VERSION_MINOR,
860		    v_major, v_minor);
861		return (EXIT_FAILURE);
862	}
863	return (0);
864}
865