1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 1996, 2013 Oracle and/or its affiliates.  All rights reserved.
5  *
6  * $Id$
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
17 static const char copyright[] =
18     "Copyright (c) 1996, 2013 Oracle and/or its affiliates.  All rights reserved.\n";
19 #endif
20 
21 enum which_open { OPEN_ORIGINAL, OPEN_HOT_BACKUP };
22 
23 int env_init __P((DB_ENV **,
24      char *, char **, char ***, char *, enum which_open, int));
25 int main __P((int, char *[]));
26 int usage __P((void));
27 int version_check __P((void));
28 void __db_util_msg __P((const DB_ENV *, const char *));
29 
30 const char *progname;
31 
__db_util_msg(dbenv,msgstr)32 void __db_util_msg(dbenv, msgstr)
33 	const DB_ENV *dbenv;
34 	const char *msgstr;
35 {
36 	COMPQUIET(dbenv, NULL);
37 	printf("%s: %s\n", progname, msgstr);
38 }
39 
40 int
main(argc,argv)41 main(argc, argv)
42 	int argc;
43 	char *argv[];
44 {
45 	extern char *optarg;
46 	extern int optind;
47 	time_t now;
48 	DB_ENV *dbenv;
49 	u_int data_cnt, data_next;
50 	int ch, checkpoint, db_config, debug, env_copy, exitval;
51 	int ret, update, verbose;
52 	char *backup_dir, **data_dir, *home, *log_dir, *passwd;
53 	char home_buf[DB_MAXPATHLEN], time_buf[CTIME_BUFLEN];
54 	u_int32_t flags;
55 
56 	/*
57 	 * Make sure all verbose message are output before any error messages
58 	 * in the case where the output is being logged into a file.  This
59 	 * call has to be done before any operation is performed on the stream.
60 	 *
61 	 * Use unbuffered I/O because line-buffered I/O requires a buffer, and
62 	 * some operating systems have buffer alignment and size constraints we
63 	 * don't want to care about.  There isn't enough output for the calls
64 	 * to matter.
65 	 */
66 	setbuf(stdout, NULL);
67 
68 	if ((progname = __db_rpath(argv[0])) == NULL)
69 		progname = argv[0];
70 	else
71 		++progname;
72 
73 	if ((ret = version_check()) != 0)
74 		return (ret);
75 
76 	/* We default to the safe environment copy. */
77 	env_copy = 1;
78 
79 	checkpoint = db_config = data_cnt = data_next = debug =
80 	    exitval = update = verbose = 0;
81 	data_dir = NULL;
82 	backup_dir = home = passwd = NULL;
83 	log_dir = NULL;
84 	while ((ch = getopt(argc, argv, "b:cDd:Fgh:l:P:uVv")) != EOF)
85 		switch (ch) {
86 		case 'b':
87 			backup_dir = optarg;
88 			break;
89 		case 'c':
90 			checkpoint = 1;
91 			break;
92 		case 'D':
93 			db_config = 1;
94 			break;
95 		case 'd':
96 			/*
97 			 * User can specify a list of directories -- keep an
98 			 * array, leaving room for the trailing NULL.
99 			 */
100 			if (data_dir == NULL || data_next >= data_cnt - 2) {
101 				data_cnt = data_cnt == 0 ? 20 : data_cnt * 2;
102 				if ((data_dir = realloc(data_dir,
103 				    data_cnt * sizeof(*data_dir))) == NULL) {
104 					fprintf(stderr, "%s: %s\n",
105 					    progname, strerror(errno));
106 					exitval = (EXIT_FAILURE);
107 					goto clean;
108 				}
109 			}
110 			data_dir[data_next++] = optarg;
111 			break;
112 		case 'F':
113 			/* The default is to use environment copy. */
114 			env_copy = 0;
115 			break;
116 		case 'g':
117 			debug = 1;
118 			break;
119 		case 'h':
120 			home = optarg;
121 			break;
122 		case 'l':
123 			log_dir = optarg;
124 			break;
125 		case 'P':
126 			if (passwd != NULL) {
127 				fprintf(stderr, "%s: %s", progname,
128 				    DB_STR("5133",
129 				    "Password may not be specified twice\n"));
130 				free(passwd);
131 				return (EXIT_FAILURE);
132 			}
133 			passwd = strdup(optarg);
134 			memset(optarg, 0, strlen(optarg));
135 			if (passwd == NULL) {
136 				fprintf(stderr, "%s: ", progname);
137 				fprintf(stderr, DB_STR_A("5026",
138 				    "strdup: %s\n", "%s\n"), strerror(errno));
139 				exitval = (EXIT_FAILURE);
140 				goto clean;
141 			}
142 			break;
143 		case 'u':
144 			update = 1;
145 			break;
146 		case 'V':
147 			printf("%s\n", db_version(NULL, NULL, NULL));
148 			exitval = (EXIT_SUCCESS);
149 			goto clean;
150 		case 'v':
151 			verbose = 1;
152 			break;
153 		case '?':
154 		default:
155 			exitval = usage();
156 			goto clean;
157 		}
158 	argc -= optind;
159 	argv += optind;
160 
161 	if (argc != 0) {
162 		exitval = usage();
163 		goto clean;
164 	}
165 
166 	/* NULL-terminate any list of data directories. */
167 	if (data_dir != NULL) {
168 		data_dir[data_next] = NULL;
169 		/*
170 		 * -d is relative to the current directory, to run a checkpoint
171 		 * we must have directories relative to the environment.
172 		 */
173 		if (checkpoint == 1) {
174 			fprintf(stderr, "%s: %s",
175 			      DB_STR("5027", "cannot specify -d and -c\n"),
176 			      progname);
177 
178 			exitval = usage();
179 			goto clean;
180 		}
181 	}
182 
183 	if (db_config && (data_dir != NULL || log_dir != NULL)) {
184 		fprintf(stderr, "%s: %s", DB_STR("5028",
185 		    "cannot specify -D and -d or -l\n"), progname);
186 		exitval = usage();
187 		goto clean;
188 	}
189 
190 	/* Handle possible interruptions. */
191 	__db_util_siginit();
192 
193 	/*
194 	 * The home directory defaults to the environment variable DB_HOME.
195 	 * The log directory defaults to the home directory.
196 	 *
197 	 * We require a source database environment directory and a target
198 	 * backup directory.
199 	 */
200 	if (home == NULL) {
201 		home = home_buf;
202 		if ((ret = __os_getenv(
203 		    NULL, "DB_HOME", &home, sizeof(home_buf))) != 0) {
204 			fprintf(stderr, "%s: ", progname);
205 			fprintf(stderr, DB_STR_A("5029",
206 		    "failed to get environment variable DB_HOME: %s\n",
207 			    "%s"), db_strerror(ret));
208 			exitval = (EXIT_FAILURE);
209 			goto clean;
210 		}
211 		/*
212 		 * home set to NULL if __os_getenv failed to find DB_HOME.
213 		 */
214 	}
215 	if (home == NULL) {
216 		fprintf(stderr, "%s: %s", DB_STR("5030",
217 		    "no source database environment specified\n"), progname);
218 		exitval = usage();
219 		goto clean;
220 	}
221 	if (backup_dir == NULL) {
222 		fprintf(stderr, "%s: %s", DB_STR("5031",
223 		    "no target backup directory specified\n"),
224 		    progname);
225 		exitval = usage();
226 		goto clean;
227 	}
228 
229 	if (verbose) {
230 		(void)time(&now);
231 		printf("%s: ", progname);
232 		printf(DB_STR_A("5032", "hot backup started at %s",
233 		    "%s"), __os_ctime(&now, time_buf));
234 	}
235 
236 	/* Open the source environment. */
237 	if (env_init(&dbenv, home,
238 	     (db_config || log_dir != NULL) ? &log_dir : NULL,
239 	     &data_dir, passwd, OPEN_ORIGINAL, verbose) != 0)
240 		goto err;
241 
242 	if (env_copy) {
243 		if ((ret = dbenv->get_open_flags(dbenv, &flags)) != 0)
244 			goto err;
245 		if (flags & DB_PRIVATE) {
246 			fprintf(stderr, "%s: %s", progname,  DB_STR("5129",
247 			    "Cannot copy data from a PRIVATE environment\n"));
248 			goto err;
249 		}
250 	}
251 
252 	if (log_dir != NULL) {
253 		if (db_config && __os_abspath(log_dir)) {
254 			fprintf(stderr, "%s: %s", progname, DB_STR("5033",
255 			    "DB_CONFIG must not contain an absolute "
256 			    "path for the log directory\n"));
257 			goto err;
258 		}
259 	}
260 
261 	/*
262 	 * If the -c option is specified, checkpoint the source home
263 	 * database environment, and remove any unnecessary log files.
264 	 */
265 	if (checkpoint) {
266 		if (verbose) {
267 			printf("%s: ", progname);
268 			printf(DB_STR_A("5035", "%s: force checkpoint\n",
269 			    "%s"), home);
270 		}
271 		if ((ret =
272 		    dbenv->txn_checkpoint(dbenv, 0, 0, DB_FORCE)) != 0) {
273 			dbenv->err(dbenv, ret, "DB_ENV->txn_checkpoint");
274 			goto err;
275 		}
276 		if (!update) {
277 			if (verbose) {
278 				printf("%s: ", progname);
279 				printf(DB_STR_A("5036",
280 				    "%s: remove unnecessary log files\n",
281 				    "%s"), home);
282 			}
283 			if ((ret = dbenv->log_archive(dbenv,
284 			     NULL, DB_ARCH_REMOVE)) != 0) {
285 				dbenv->err(dbenv, ret, "DB_ENV->log_archive");
286 				goto err;
287 			}
288 		}
289 	}
290 
291 	flags = DB_CREATE | DB_BACKUP_CLEAN | DB_BACKUP_FILES;
292 	if (update)
293 		LF_SET(DB_BACKUP_UPDATE);
294 
295 	if (!db_config)
296 		LF_SET(DB_BACKUP_SINGLE_DIR);
297 	if ((ret = dbenv->backup(dbenv, backup_dir, flags)) != 0)
298 		goto err;
299 
300 	/* Close the source environment. */
301 	if ((ret = dbenv->close(dbenv, 0)) != 0) {
302 		fprintf(stderr,
303 		    "%s: dbenv->close: %s\n", progname, db_strerror(ret));
304 		dbenv = NULL;
305 		goto err;
306 	}
307 	/* Perform catastrophic recovery on the hot backup. */
308 	if (verbose) {
309 		printf("%s: ", progname);
310 		printf(DB_STR_A("5040", "%s: run catastrophic recovery\n",
311 		    "%s"), backup_dir);
312 	}
313 	if (env_init(&dbenv,
314 	    backup_dir, NULL, NULL, passwd, OPEN_HOT_BACKUP, verbose) != 0)
315 		goto err;
316 
317 	/*
318 	 * Remove any unnecessary log files from the hot backup.
319 	 * For debugging purposes, leave them around.
320 	 */
321 	if (debug == 0) {
322 		if (verbose) {
323 			printf("%s: ", progname);
324 			printf(DB_STR_A("5041",
325 			    "%s: remove unnecessary log files\n",
326 			    "%s"), backup_dir);
327 		}
328 
329 		if ((ret =
330 		    dbenv->log_archive(dbenv, NULL, DB_ARCH_REMOVE)) != 0) {
331 			dbenv->err(dbenv, ret, "DB_ENV->log_archive");
332 			goto err;
333 		}
334 	}
335 
336 	if (0) {
337 err:		exitval = 1;
338 	}
339 
340 	if (dbenv != NULL && (ret = dbenv->close(dbenv, 0)) != 0) {
341 		exitval = 1;
342 		fprintf(stderr,
343 		    "%s: dbenv->close: %s\n", progname, db_strerror(ret));
344 	}
345 
346 	if (exitval == 0) {
347 		if (verbose) {
348 			(void)time(&now);
349 			printf("%s: ", progname);
350 			printf(DB_STR_A("5042",
351 			    "hot backup completed at %s", "%s"),
352 			    __os_ctime(&now, time_buf));
353 		}
354 	} else {
355 		fprintf(stderr, "%s: %s", progname,
356 		     DB_STR("5043", "HOT BACKUP FAILED!\n"));
357 	}
358 
359 	/* Resend any caught signal. */
360 	__db_util_sigresend();
361 
362 clean:
363 	if (data_cnt > 0)
364 		free(data_dir);
365 	if (passwd != NULL)
366 		free(passwd);
367 
368 	return (exitval == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
369 }
370 
371 /*
372  * env_init --
373  *	Open a database environment.
374  */
375 int
env_init(dbenvp,home,log_dirp,data_dirp,passwd,which,verbose)376 env_init(dbenvp, home, log_dirp, data_dirp, passwd, which, verbose)
377 	DB_ENV **dbenvp;
378 	char *home, **log_dirp, ***data_dirp, *passwd;
379 	enum which_open which;
380 	int verbose;
381 {
382 	DB_ENV *dbenv;
383 	const char *log_dir, **data_dir;
384 	char buf[DB_MAXPATHLEN];
385 	int homehome, ret;
386 
387 	*dbenvp = NULL;
388 
389 	/*
390 	 * Create an environment object and initialize it for error reporting.
391 	 */
392 	if ((ret = db_env_create(&dbenv, 0)) != 0) {
393 		fprintf(stderr,
394 		    "%s: db_env_create: %s\n", progname, db_strerror(ret));
395 		return (1);
396 	}
397 
398 	if (verbose) {
399 		(void)dbenv->set_verbose(dbenv, DB_VERB_BACKUP, 1);
400 		dbenv->set_msgcall(dbenv, __db_util_msg);
401 	}
402 	dbenv->set_errfile(dbenv, stderr);
403 	setbuf(stderr, NULL);
404 	dbenv->set_errpfx(dbenv, progname);
405 
406 	/* Any created intermediate directories are created private. */
407 	if ((ret = dbenv->set_intermediate_dir_mode(dbenv, "rwx------")) != 0) {
408 		dbenv->err(dbenv, ret, "DB_ENV->set_intermediate_dir_mode");
409 		return (1);
410 	}
411 
412 	/*
413 	 * If a log directory has been specified, and it's not the same as the
414 	 * home directory, set it for the environment.
415 	 */
416 	if (log_dirp != NULL && *log_dirp != NULL &&
417 	    (ret = dbenv->set_lg_dir(dbenv, *log_dirp)) != 0) {
418 		dbenv->err(dbenv, ret, "DB_ENV->set_lg_dir: %s", *log_dirp);
419 		return (1);
420 	}
421 
422 	/* Optionally set the password. */
423 	if (passwd != NULL &&
424 	    (ret = dbenv->set_encrypt(dbenv, passwd, DB_ENCRYPT_AES)) != 0) {
425 		dbenv->err(dbenv, ret, "DB_ENV->set_encrypt");
426 		return (1);
427 	}
428 
429 	switch (which) {
430 	case OPEN_ORIGINAL:
431 		if (data_dirp != NULL && *data_dirp != NULL) {
432 			/*
433 			 * Backward compatibility: older versions
434 			 * did not have data dirs relative to home.
435 			 * Check to see if there is a sub-directory with
436 			 * the same name has the home directory.  If not
437 			 * trim the home directory from the data directory
438 			 * passed in.
439 			 */
440 			(void) sprintf(buf, "%s/%s", home, home);
441 			homehome = 0;
442 			(void)__os_exists(dbenv->env, buf, &homehome);
443 
444 			for (data_dir = (const char **)*data_dirp;
445 			    *data_dir != NULL; data_dir++) {
446 				if (!homehome &&
447 				    !strncmp(home, *data_dir, strlen(home))) {
448 					if (strchr(PATH_SEPARATOR,
449 					    (*data_dir)[strlen(home)]) != NULL)
450 						(*data_dir) += strlen(home) + 1;
451 					/* Just in case an extra / was added. */
452 					else if (strchr(PATH_SEPARATOR,
453 					    home[strlen(home) - 1]) != NULL)
454 						(*data_dir) += strlen(home);
455 				}
456 
457 			    	if ((ret = dbenv->add_data_dir(
458 				    dbenv, *data_dir)) != 0) {
459 					dbenv->err(dbenv, ret,
460 					    "DB_ENV->add_data_dir: %s",
461 					    *data_dir);
462 					return (1);
463 				}
464 			}
465 		}
466 		/*
467 		 * Opening the database environment we're trying to back up.
468 		 * We try to attach to a pre-existing environment; if that
469 		 * fails, we create a private environment and try again.
470 		 */
471 		if ((ret = dbenv->open(dbenv, home, DB_USE_ENVIRON, 0)) != 0 &&
472 		    (ret == DB_VERSION_MISMATCH || ret == DB_REP_LOCKOUT ||
473 		    (ret = dbenv->open(dbenv, home, DB_CREATE |
474 		    DB_INIT_LOG | DB_INIT_TXN | DB_PRIVATE | DB_USE_ENVIRON,
475 		    0)) != 0)) {
476 			dbenv->err(dbenv, ret, "DB_ENV->open: %s", home);
477 			return (1);
478 		}
479 		if (log_dirp != NULL) {
480 			(void)dbenv->get_lg_dir(dbenv, &log_dir);
481 			if (*log_dirp == NULL)
482 				*log_dirp = (char*)log_dir;
483 			else if (strcmp(*log_dirp, log_dir)) {
484 				fprintf(stderr, DB_STR_A("5057",
485 		    "%s: cannot specify -l with conflicting DB_CONFIG file\n",
486 				    "%s\n"), progname);
487 				return (usage());
488 			} else {
489 				/*
490 				 * Do we have -l and an existing DB_CONFIG?
491 				 * That is a usage problem, but for backward
492 				 * compatibility, keep going if log_dir happens
493 				 * to be the same as the DB_CONFIG path.
494 				 */
495 				(void)snprintf(buf, sizeof(buf), "%s%c%s",
496 				    home, PATH_SEPARATOR[0], "DB_CONFIG");
497 				if (__os_exists(dbenv->env, buf, NULL) == 0)
498 					fprintf(stderr,
499 					    "%s: %s", DB_STR("5058",
500 			    "use of -l with DB_CONFIG file is deprecated\n"),
501 					    progname);
502 			}
503 		}
504 		if (data_dirp != NULL && *data_dirp == NULL)
505 			(void)dbenv->get_data_dirs(
506 			    dbenv, (const char ***)data_dirp);
507 		break;
508 	case OPEN_HOT_BACKUP:
509 		/*
510 		 * Opening the backup copy of the database environment.  We
511 		 * better be the only user, we're running recovery.
512 		 * Ensure that there at least minimal cache for worst
513 		 * case page size.
514 		 */
515 		if ((ret =
516 		    dbenv->set_cachesize(dbenv, 0, 64 * 1024 * 10, 0)) != 0) {
517 			dbenv->err(dbenv,
518 			     ret, "DB_ENV->set_cachesize: %s", home);
519 			return (1);
520 		}
521 		if (verbose == 1)
522 			(void)dbenv->set_verbose(dbenv, DB_VERB_RECOVERY, 1);
523 		if ((ret = dbenv->open(dbenv, home, DB_CREATE |
524 		    DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_PRIVATE |
525 		    DB_RECOVER_FATAL | DB_USE_ENVIRON, 0)) != 0) {
526 			dbenv->err(dbenv, ret, "DB_ENV->open: %s", home);
527 			return (1);
528 		}
529 		break;
530 	}
531 
532 	*dbenvp = dbenv;
533 	return (0);
534 }
535 
536 int
usage()537 usage()
538 {
539 	(void)fprintf(stderr, "usage: %s [-cDuVv]\n\t%s\n", progname,
540     "[-d data_dir ...] [-h home] [-l log_dir] [-P password] -b backup_dir");
541 	return (EXIT_FAILURE);
542 }
543 
544 int
version_check()545 version_check()
546 {
547 	int v_major, v_minor, v_patch;
548 
549 	/* Make sure we're loaded with the right version of the DB library. */
550 	(void)db_version(&v_major, &v_minor, &v_patch);
551 	if (v_major != DB_VERSION_MAJOR || v_minor != DB_VERSION_MINOR) {
552 		fprintf(stderr, "%s: ", progname);
553 		fprintf(stderr, DB_STR_A("5071",
554 		    "version %d.%d doesn't match library version %d.%d\n",
555 		    "%d %d %d %d\n"),
556 		    DB_VERSION_MAJOR, DB_VERSION_MINOR,
557 		    v_major, v_minor);
558 		return (EXIT_FAILURE);
559 	}
560 	return (0);
561 }
562