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