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