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, ©_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