/*- * See the file LICENSE for redistribution information. * * Copyright (c) 1996, 2012 Oracle and/or its affiliates. All rights reserved. * * $Id$ */ #include "db_config.h" #include "db_int.h" #include "dbinc/log.h" #include "dbinc/db_page.h" #include "dbinc/qam.h" #ifndef lint static const char copyright[] = "Copyright (c) 1996, 2012 Oracle and/or its affiliates. All rights reserved.\n"; #endif enum which_open { OPEN_ORIGINAL, OPEN_HOT_BACKUP }; int env_init __P((DB_ENV **, char *, char **, char ***, char *, enum which_open, int)); int main __P((int, char *[])); int usage __P((void)); int version_check __P((void)); void __db_util_msg __P((const DB_ENV *, const char *)); const char *progname; void __db_util_msg(dbenv, msgstr) const DB_ENV *dbenv; const char *msgstr; { COMPQUIET(dbenv, NULL); printf("%s: %s\n", progname, msgstr); } int main(argc, argv) int argc; char *argv[]; { extern char *optarg; extern int optind; time_t now; DB_ENV *dbenv; u_int data_cnt, data_next; int ch, checkpoint, db_config, debug, env_copy, exitval; int ret, update, verbose; char *backup_dir, **data_dir, *home, *log_dir, *passwd; char home_buf[DB_MAXPATHLEN], time_buf[CTIME_BUFLEN]; u_int32_t flags; /* * Make sure all verbose message are output before any error messages * in the case where the output is being logged into a file. This * call has to be done before any operation is performed on the stream. * * Use unbuffered I/O because line-buffered I/O requires a buffer, and * some operating systems have buffer alignment and size constraints we * don't want to care about. There isn't enough output for the calls * to matter. */ setbuf(stdout, NULL); if ((progname = __db_rpath(argv[0])) == NULL) progname = argv[0]; else ++progname; if ((ret = version_check()) != 0) return (ret); /* We default to the safe environment copy. */ env_copy = 1; checkpoint = db_config = data_cnt = data_next = debug = exitval = update = verbose = 0; data_dir = NULL; backup_dir = home = passwd = NULL; log_dir = NULL; while ((ch = getopt(argc, argv, "b:cDd:Fgh:l:P:uVv")) != EOF) switch (ch) { case 'b': backup_dir = optarg; break; case 'c': checkpoint = 1; break; case 'D': db_config = 1; break; case 'd': /* * User can specify a list of directories -- keep an * array, leaving room for the trailing NULL. */ if (data_dir == NULL || data_next >= data_cnt - 2) { data_cnt = data_cnt == 0 ? 20 : data_cnt * 2; if ((data_dir = realloc(data_dir, data_cnt * sizeof(*data_dir))) == NULL) { fprintf(stderr, "%s: %s\n", progname, strerror(errno)); exitval = (EXIT_FAILURE); goto clean; } } data_dir[data_next++] = optarg; break; case 'F': /* The default is to use environment copy. */ env_copy = 0; break; case 'g': debug = 1; break; case 'h': home = optarg; break; case 'l': log_dir = optarg; break; case 'P': if (passwd != NULL) { fprintf(stderr, "%s: %s", progname, DB_STR("5133", "Password may not be specified twice\n")); free(passwd); return (EXIT_FAILURE); } passwd = strdup(optarg); memset(optarg, 0, strlen(optarg)); if (passwd == NULL) { fprintf(stderr, "%s: ", progname); fprintf(stderr, DB_STR_A("5026", "strdup: %s\n", "%s\n"), strerror(errno)); exitval = (EXIT_FAILURE); goto clean; } break; case 'u': update = 1; break; case 'V': printf("%s\n", db_version(NULL, NULL, NULL)); exitval = (EXIT_SUCCESS); goto clean; case 'v': verbose = 1; break; case '?': default: exitval = usage(); goto clean; } argc -= optind; argv += optind; if (argc != 0) { exitval = usage(); goto clean; } /* NULL-terminate any list of data directories. */ if (data_dir != NULL) { data_dir[data_next] = NULL; /* * -d is relative to the current directory, to run a checkpoint * we must have directories relative to the environment. */ if (checkpoint == 1) { fprintf(stderr, "%s: %s", DB_STR("5027", "cannot specify -d and -c\n"), progname); exitval = usage(); goto clean; } } if (db_config && (data_dir != NULL || log_dir != NULL)) { fprintf(stderr, "%s: %s", DB_STR("5028", "cannot specify -D and -d or -l\n"), progname); exitval = usage(); goto clean; } /* Handle possible interruptions. */ __db_util_siginit(); /* * The home directory defaults to the environment variable DB_HOME. * The log directory defaults to the home directory. * * We require a source database environment directory and a target * backup directory. */ if (home == NULL) { home = home_buf; if ((ret = __os_getenv( NULL, "DB_HOME", &home, sizeof(home_buf))) != 0) { fprintf(stderr, "%s: ", progname); fprintf(stderr, DB_STR_A("5029", "failed to get environment variable DB_HOME: %s\n", "%s"), db_strerror(ret)); exitval = (EXIT_FAILURE); goto clean; } /* * home set to NULL if __os_getenv failed to find DB_HOME. */ } if (home == NULL) { fprintf(stderr, "%s: %s", DB_STR("5030", "no source database environment specified\n"), progname); exitval = usage(); goto clean; } if (backup_dir == NULL) { fprintf(stderr, "%s: %s", DB_STR("5031", "no target backup directory specified\n"), progname); exitval = usage(); goto clean; } if (verbose) { (void)time(&now); printf("%s: ", progname); printf(DB_STR_A("5032", "hot backup started at %s", "%s"), __os_ctime(&now, time_buf)); } /* Open the source environment. */ if (env_init(&dbenv, home, (db_config || log_dir != NULL) ? &log_dir : NULL, &data_dir, passwd, OPEN_ORIGINAL, verbose) != 0) goto err; if (env_copy) { if ((ret = dbenv->get_open_flags(dbenv, &flags)) != 0) goto err; if (flags & DB_PRIVATE) { fprintf(stderr, "%s: %s", progname, DB_STR("5129", "Cannot copy data from a PRIVATE environment\n")); goto err; } } if (log_dir != NULL) { if (db_config && __os_abspath(log_dir)) { fprintf(stderr, "%s: %s", progname, DB_STR("5033", "DB_CONFIG must not contain an absolute " "path for the log directory\n")); goto err; } } /* * If the -c option is specified, checkpoint the source home * database environment, and remove any unnecessary log files. */ if (checkpoint) { if (verbose) { printf("%s: ", progname); printf(DB_STR_A("5035", "%s: force checkpoint\n", "%s"), home); } if ((ret = dbenv->txn_checkpoint(dbenv, 0, 0, DB_FORCE)) != 0) { dbenv->err(dbenv, ret, "DB_ENV->txn_checkpoint"); goto err; } if (!update) { if (verbose) { printf("%s: ", progname); printf(DB_STR_A("5036", "%s: remove unnecessary log files\n", "%s"), home); } if ((ret = dbenv->log_archive(dbenv, NULL, DB_ARCH_REMOVE)) != 0) { dbenv->err(dbenv, ret, "DB_ENV->log_archive"); goto err; } } } flags = DB_CREATE | DB_BACKUP_CLEAN | DB_BACKUP_FILES; if (update) LF_SET(DB_BACKUP_UPDATE); if (!db_config) LF_SET(DB_BACKUP_SINGLE_DIR); if ((ret = dbenv->backup(dbenv, backup_dir, flags)) != 0) goto err; /* Close the source environment. */ if ((ret = dbenv->close(dbenv, 0)) != 0) { fprintf(stderr, "%s: dbenv->close: %s\n", progname, db_strerror(ret)); dbenv = NULL; goto err; } /* Perform catastrophic recovery on the hot backup. */ if (verbose) { printf("%s: ", progname); printf(DB_STR_A("5040", "%s: run catastrophic recovery\n", "%s"), backup_dir); } if (env_init(&dbenv, backup_dir, NULL, NULL, passwd, OPEN_HOT_BACKUP, verbose) != 0) goto err; /* * Remove any unnecessary log files from the hot backup. * For debugging purposes, leave them around. */ if (debug == 0) { if (verbose) { printf("%s: ", progname); printf(DB_STR_A("5041", "%s: remove unnecessary log files\n", "%s"), backup_dir); } if ((ret = dbenv->log_archive(dbenv, NULL, DB_ARCH_REMOVE)) != 0) { dbenv->err(dbenv, ret, "DB_ENV->log_archive"); goto err; } } if (0) { err: exitval = 1; } if (dbenv != NULL && (ret = dbenv->close(dbenv, 0)) != 0) { exitval = 1; fprintf(stderr, "%s: dbenv->close: %s\n", progname, db_strerror(ret)); } if (exitval == 0) { if (verbose) { (void)time(&now); printf("%s: ", progname); printf(DB_STR_A("5042", "hot backup completed at %s", "%s"), __os_ctime(&now, time_buf)); } } else { fprintf(stderr, "%s: %s", progname, DB_STR("5043", "HOT BACKUP FAILED!\n")); } /* Resend any caught signal. */ __db_util_sigresend(); clean: if (data_cnt > 0) free(data_dir); if (passwd != NULL) free(passwd); return (exitval == 0 ? EXIT_SUCCESS : EXIT_FAILURE); } /* * env_init -- * Open a database environment. */ int env_init(dbenvp, home, log_dirp, data_dirp, passwd, which, verbose) DB_ENV **dbenvp; char *home, **log_dirp, ***data_dirp, *passwd; enum which_open which; int verbose; { DB_ENV *dbenv; const char *log_dir, **data_dir; char buf[DB_MAXPATHLEN]; int homehome, ret; *dbenvp = NULL; /* * Create an environment object and initialize it for error reporting. */ if ((ret = db_env_create(&dbenv, 0)) != 0) { fprintf(stderr, "%s: db_env_create: %s\n", progname, db_strerror(ret)); return (1); } if (verbose) { (void)dbenv->set_verbose(dbenv, DB_VERB_BACKUP, 1); dbenv->set_msgcall(dbenv, __db_util_msg); } dbenv->set_errfile(dbenv, stderr); setbuf(stderr, NULL); dbenv->set_errpfx(dbenv, progname); /* Any created intermediate directories are created private. */ if ((ret = dbenv->set_intermediate_dir_mode(dbenv, "rwx------")) != 0) { dbenv->err(dbenv, ret, "DB_ENV->set_intermediate_dir_mode"); return (1); } /* * If a log directory has been specified, and it's not the same as the * home directory, set it for the environment. */ if (log_dirp != NULL && *log_dirp != NULL && (ret = dbenv->set_lg_dir(dbenv, *log_dirp)) != 0) { dbenv->err(dbenv, ret, "DB_ENV->set_lg_dir: %s", *log_dirp); return (1); } /* Optionally set the password. */ if (passwd != NULL && (ret = dbenv->set_encrypt(dbenv, passwd, DB_ENCRYPT_AES)) != 0) { dbenv->err(dbenv, ret, "DB_ENV->set_encrypt"); return (1); } switch (which) { case OPEN_ORIGINAL: if (data_dirp != NULL && *data_dirp != NULL) { /* * Backward compatibility: older versions * did not have data dirs relative to home. * Check to see if there is a sub-directory with * the same name has the home directory. If not * trim the home directory from the data directory * passed in. */ (void) sprintf(buf, "%s/%s", home, home); homehome = 0; (void)__os_exists(dbenv->env, buf, &homehome); for (data_dir = (const char **)*data_dirp; *data_dir != NULL; data_dir++) { if (!homehome && !strncmp(home, *data_dir, strlen(home))) { if (strchr(PATH_SEPARATOR, (*data_dir)[strlen(home)]) != NULL) (*data_dir) += strlen(home) + 1; /* Just in case an extra / was added. */ else if (strchr(PATH_SEPARATOR, home[strlen(home) - 1]) != NULL) (*data_dir) += strlen(home); } if ((ret = dbenv->add_data_dir( dbenv, *data_dir)) != 0) { dbenv->err(dbenv, ret, "DB_ENV->add_data_dir: %s", *data_dir); return (1); } } } /* * Opening the database environment we're trying to back up. * We try to attach to a pre-existing environment; if that * fails, we create a private environment and try again. */ if ((ret = dbenv->open(dbenv, home, DB_USE_ENVIRON, 0)) != 0 && (ret == DB_VERSION_MISMATCH || (ret = dbenv->open(dbenv, home, DB_CREATE | DB_INIT_LOG | DB_INIT_TXN | DB_PRIVATE | DB_USE_ENVIRON, 0)) != 0)) { dbenv->err(dbenv, ret, "DB_ENV->open: %s", home); return (1); } if (log_dirp != NULL) { (void)dbenv->get_lg_dir(dbenv, &log_dir); if (*log_dirp == NULL) *log_dirp = (char*)log_dir; else if (strcmp(*log_dirp, log_dir)) { fprintf(stderr, DB_STR_A("5057", "%s: cannot specify -l with conflicting DB_CONFIG file\n", "%s\n"), progname); return (usage()); } else { fprintf(stderr, "%s: %s", DB_STR("5058", "use of -l with DB_CONFIG file is deprecated\n"), progname); } } if (data_dirp != NULL && *data_dirp == NULL) (void)dbenv->get_data_dirs( dbenv, (const char ***)data_dirp); break; case OPEN_HOT_BACKUP: /* * Opening the backup copy of the database environment. We * better be the only user, we're running recovery. * Ensure that there at least minimal cache for worst * case page size. */ if ((ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024 * 10, 0)) != 0) { dbenv->err(dbenv, ret, "DB_ENV->set_cachesize: %s", home); return (1); } if (verbose == 1) (void)dbenv->set_verbose(dbenv, DB_VERB_RECOVERY, 1); if ((ret = dbenv->open(dbenv, home, DB_CREATE | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_PRIVATE | DB_RECOVER_FATAL | DB_USE_ENVIRON, 0)) != 0) { dbenv->err(dbenv, ret, "DB_ENV->open: %s", home); return (1); } break; } *dbenvp = dbenv; return (0); } int usage() { (void)fprintf(stderr, "usage: %s [-cDuVv]\n\t%s\n", progname, "[-d data_dir ...] [-h home] [-l log_dir] [-P password] -b backup_dir"); return (EXIT_FAILURE); } int version_check() { int v_major, v_minor, v_patch; /* Make sure we're loaded with the right version of the DB library. */ (void)db_version(&v_major, &v_minor, &v_patch); if (v_major != DB_VERSION_MAJOR || v_minor != DB_VERSION_MINOR) { fprintf(stderr, "%s: ", progname); fprintf(stderr, DB_STR_A("5071", "version %d.%d doesn't match library version %d.%d\n", "%d %d %d %d\n"), DB_VERSION_MAJOR, DB_VERSION_MINOR, v_major, v_minor); return (EXIT_FAILURE); } return (0); }