1/*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 1996,2008 Oracle. All rights reserved. 5 * 6 * $Id: db_printlog.c,v 12.31 2008/01/30 04:30:37 mjc Exp $ 7 */ 8 9#include "db_config.h" 10 11#include "db_int.h" 12#include "dbinc/db_page.h" 13#include "dbinc/btree.h" 14#include "dbinc/fop.h" 15#include "dbinc/hash.h" 16#include "dbinc/log.h" 17#include "dbinc/qam.h" 18#include "dbinc/txn.h" 19 20#ifndef lint 21static const char copyright[] = 22 "Copyright (c) 1996,2008 Oracle. All rights reserved.\n"; 23#endif 24 25int db_printlog_print_app_record __P((DB_ENV *, DBT *, DB_LSN *, db_recops)); 26int db_printlog_env_init_print __P((ENV *, u_int32_t, DB_DISTAB *)); 27int db_printlog_env_init_print_42 __P((ENV *, DB_DISTAB *)); 28int db_printlog_env_init_print_43 __P((ENV *, DB_DISTAB *)); 29int db_printlog_env_init_print_45 __P((ENV *, DB_DISTAB *)); 30int db_printlog_lsn_arg __P((char *, DB_LSN *)); 31int db_printlog_main __P((int, char *[])); 32int db_printlog_open_rep_db __P((DB_ENV *, DB **, DBC **)); 33int db_printlog_usage __P((void)); 34int db_printlog_version_check __P((void)); 35 36const char *progname; 37 38int 39db_printlog(args) 40 char *args; 41{ 42 int argc; 43 char **argv; 44 45 __db_util_arg("db_printlog", args, &argc, &argv); 46 return (db_printlog_main(argc, argv) ? EXIT_FAILURE : EXIT_SUCCESS); 47} 48 49#include <stdio.h> 50#define ERROR_RETURN ERROR 51 52int 53db_printlog_main(argc, argv) 54 int argc; 55 char *argv[]; 56{ 57 extern char *optarg; 58 extern int optind, __db_getopt_reset; 59 DB *dbp; 60 DBC *dbc; 61 DBT data, keydbt; 62 DB_DISTAB dtab; 63 DB_ENV *dbenv; 64 DB_LOGC *logc; 65 DB_LSN key, start, stop, verslsn; 66 ENV *env; 67 u_int32_t logcflag, newversion, version; 68 int ch, cmp, exitval, nflag, rflag, ret, repflag; 69 char *home, *passwd; 70 71 if ((progname = __db_rpath(argv[0])) == NULL) 72 progname = argv[0]; 73 else 74 ++progname; 75 76 if ((ret = db_printlog_version_check()) != 0) 77 return (ret); 78 79 dbp = NULL; 80 dbc = NULL; 81 dbenv = NULL; 82 logc = NULL; 83 ZERO_LSN(start); 84 ZERO_LSN(stop); 85 exitval = nflag = rflag = repflag = 0; 86 home = passwd = NULL; 87 88 memset(&dtab, 0, sizeof(dtab)); 89 90 __db_getopt_reset = 1; 91 while ((ch = getopt(argc, argv, "b:e:h:NP:rRV")) != EOF) 92 switch (ch) { 93 case 'b': 94 /* Don't use getsubopt(3), not all systems have it. */ 95 if (db_printlog_lsn_arg(optarg, &start)) 96 return (db_printlog_usage()); 97 break; 98 case 'e': 99 /* Don't use getsubopt(3), not all systems have it. */ 100 if (db_printlog_lsn_arg(optarg, &stop)) 101 return (db_printlog_usage()); 102 break; 103 case 'h': 104 home = optarg; 105 break; 106 case 'N': 107 nflag = 1; 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 'r': 119 rflag = 1; 120 break; 121 case 'R': /* Undocumented */ 122 repflag = 1; 123 break; 124 case 'V': 125 printf("%s\n", db_version(NULL, NULL, NULL)); 126 return (EXIT_SUCCESS); 127 case '?': 128 default: 129 return (db_printlog_usage()); 130 } 131 argc -= optind; 132 argv += optind; 133 134 if (argc > 0) 135 return (db_printlog_usage()); 136 137 /* Handle possible interruptions. */ 138 __db_util_siginit(); 139 140 /* 141 * Create an environment object and initialize it for error 142 * reporting. 143 */ 144 if ((ret = db_env_create(&dbenv, 0)) != 0) { 145 fprintf(stderr, 146 "%s: db_env_create: %s\n", progname, db_strerror(ret)); 147 goto shutdown; 148 } 149 150 dbenv->set_errfile(dbenv, stderr); 151 dbenv->set_errpfx(dbenv, progname); 152 153 if (nflag) { 154 if ((ret = dbenv->set_flags(dbenv, DB_NOLOCKING, 1)) != 0) { 155 dbenv->err(dbenv, ret, "set_flags: DB_NOLOCKING"); 156 goto shutdown; 157 } 158 if ((ret = dbenv->set_flags(dbenv, DB_NOPANIC, 1)) != 0) { 159 dbenv->err(dbenv, ret, "set_flags: DB_NOPANIC"); 160 goto shutdown; 161 } 162 } 163 164 if (passwd != NULL && (ret = dbenv->set_encrypt(dbenv, 165 passwd, DB_ENCRYPT_AES)) != 0) { 166 dbenv->err(dbenv, ret, "set_passwd"); 167 goto shutdown; 168 } 169 170 /* 171 * Set up an app-specific dispatch function so that we can gracefully 172 * handle app-specific log records. 173 */ 174 if ((ret = dbenv->set_app_dispatch( 175 dbenv, db_printlog_print_app_record)) != 0) { 176 dbenv->err(dbenv, ret, "app_dispatch"); 177 goto shutdown; 178 } 179 180 /* 181 * An environment is required, but as all we're doing is reading log 182 * files, we create one if it doesn't already exist. If we create 183 * it, create it private so it automatically goes away when we're done. 184 * If we are reading the replication database, do not open the env 185 * with logging, because we don't want to log the opens. 186 */ 187 if (repflag) { 188 if ((ret = dbenv->open(dbenv, home, 189 DB_INIT_MPOOL | DB_USE_ENVIRON, 0)) != 0 && 190 (ret == DB_VERSION_MISMATCH || 191 (ret = dbenv->open(dbenv, home, 192 DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_USE_ENVIRON, 0)) 193 != 0)) { 194 dbenv->err(dbenv, ret, "DB_ENV->open"); 195 goto shutdown; 196 } 197 } else if ((ret = dbenv->open(dbenv, home, DB_USE_ENVIRON, 0)) != 0 && 198 (ret == DB_VERSION_MISMATCH || 199 (ret = dbenv->open(dbenv, home, 200 DB_CREATE | DB_INIT_LOG | DB_PRIVATE | DB_USE_ENVIRON, 0)) != 0)) { 201 dbenv->err(dbenv, ret, "DB_ENV->open"); 202 goto shutdown; 203 } 204 env = dbenv->env; 205 206 /* Allocate a log cursor. */ 207 if (repflag) { 208 if ((ret = db_printlog_open_rep_db(dbenv, &dbp, &dbc)) != 0) 209 goto shutdown; 210 } else if ((ret = dbenv->log_cursor(dbenv, &logc, 0)) != 0) { 211 dbenv->err(dbenv, ret, "DB_ENV->log_cursor"); 212 goto shutdown; 213 } 214 215 if (IS_ZERO_LSN(start)) { 216 memset(&keydbt, 0, sizeof(keydbt)); 217 logcflag = rflag ? DB_PREV : DB_NEXT; 218 } else { 219 key = start; 220 logcflag = DB_SET; 221 } 222 memset(&data, 0, sizeof(data)); 223 224 /* 225 * If we're using the repflag, we're immediately initializing 226 * the print table. Use the current version. If we're printing 227 * the log then initialize version to 0 so that we get the 228 * correct version right away. 229 */ 230 if (repflag) 231 version = DB_LOGVERSION; 232 else 233 version = 0; 234 ZERO_LSN(verslsn); 235 236 /* Initialize print callbacks if repflag. */ 237 if (repflag && 238 (ret = db_printlog_env_init_print(env, version, &dtab)) != 0) { 239 dbenv->err(dbenv, ret, "callback: initialization"); 240 goto shutdown; 241 } 242 for (; !__db_util_interrupted(); logcflag = rflag ? DB_PREV : DB_NEXT) { 243 if (repflag) { 244 ret = dbc->get(dbc, &keydbt, &data, logcflag); 245 if (ret == 0) 246 key = ((__rep_control_args *)keydbt.data)->lsn; 247 } else 248 ret = logc->get(logc, &key, &data, logcflag); 249 if (ret != 0) { 250 if (ret == DB_NOTFOUND) 251 break; 252 dbenv->err(dbenv, 253 ret, repflag ? "DBC->get" : "DB_LOGC->get"); 254 goto shutdown; 255 } 256 257 /* 258 * We may have reached the end of the range we're displaying. 259 */ 260 if (!IS_ZERO_LSN(stop)) { 261 cmp = LOG_COMPARE(&key, &stop); 262 if ((rflag && cmp < 0) || (!rflag && cmp > 0)) 263 break; 264 } 265 if (!repflag && key.file != verslsn.file) { 266 /* 267 * If our log file changed, we need to see if the 268 * version of the log file changed as well. 269 * If it changed, reset the print table. 270 */ 271 if ((ret = logc->version(logc, &newversion, 0)) != 0) { 272 dbenv->err(dbenv, ret, "DB_LOGC->version"); 273 goto shutdown; 274 } 275 if (version != newversion) { 276 version = newversion; 277 if ((ret = db_printlog_env_init_print(env, version, 278 &dtab)) != 0) { 279 dbenv->err(dbenv, ret, 280 "callback: initialization"); 281 goto shutdown; 282 } 283 } 284 } 285 286 ret = __db_dispatch(dbenv->env, 287 &dtab, &data, &key, DB_TXN_PRINT, NULL); 288 289 /* 290 * XXX 291 * Just in case the underlying routines don't flush. 292 */ 293 (void)fflush(stdout); 294 295 if (ret != 0) { 296 dbenv->err(dbenv, ret, "tx: dispatch"); 297 goto shutdown; 298 } 299 } 300 301 if (0) { 302shutdown: exitval = 1; 303 } 304 if (logc != NULL && (ret = logc->close(logc, 0)) != 0) 305 exitval = 1; 306 307 if (dbc != NULL && (ret = dbc->close(dbc)) != 0) 308 exitval = 1; 309 310 if (dbp != NULL && (ret = dbp->close(dbp, 0)) != 0) 311 exitval = 1; 312 313 if (dbenv != NULL && (ret = dbenv->close(dbenv, 0)) != 0) { 314 exitval = 1; 315 fprintf(stderr, 316 "%s: dbenv->close: %s\n", progname, db_strerror(ret)); 317 } 318 319 if (passwd != NULL) 320 free(passwd); 321 322 /* Resend any caught signal. */ 323 __db_util_sigresend(); 324 325 return (exitval == 0 ? EXIT_SUCCESS : EXIT_FAILURE); 326} 327 328/* 329 * env_init_print -- 330 */ 331int 332db_printlog_env_init_print(env, version, dtabp) 333 ENV *env; 334 u_int32_t version; 335 DB_DISTAB *dtabp; 336{ 337 int ret; 338 339 /* 340 * We need to prime the print table with the current print 341 * functions. Then we overwrite only specific entries based on 342 * each previous version we support. 343 */ 344 if ((ret = db_printlog_env_init_print_45(env, dtabp)) != 0) 345 return (ret); 346 347 switch (version) { 348 /* 349 * There are no log record/recovery differences between 350 * 4.4 and 4.5. The log version changed due to checksum. 351 * There are no log recovery differences between 352 * 4.5 and 4.6. The name of the rep_gen in txn_checkpoint 353 * changed (to spare, since we don't use it anymore). 354 */ 355 case DB_LOGVERSION_47: 356 case DB_LOGVERSION_46: 357 case DB_LOGVERSION_45: 358 case DB_LOGVERSION_44: 359 ret = 0; 360 break; 361 case DB_LOGVERSION_43: 362 ret = db_printlog_env_init_print_43(env, dtabp); 363 break; 364 case DB_LOGVERSION_42: 365 ret = db_printlog_env_init_print_42(env, dtabp); 366 break; 367 default: 368 env->dbenv->errx(env->dbenv, 369 "Unknown version %lu", (u_long)version); 370 ret = EINVAL; 371 break; 372 } 373 return (ret); 374} 375 376int 377db_printlog_env_init_print_42(env, dtabp) 378 ENV *env; 379 DB_DISTAB *dtabp; 380{ 381 int ret; 382 383 if ((ret = __db_add_recovery_int(env, dtabp, 384 __db_relink_42_print, DB___db_relink_42)) != 0) 385 goto err; 386 if ((ret = __db_add_recovery_int(env, dtabp, 387 __db_pg_alloc_42_print, DB___db_pg_alloc_42)) != 0) 388 goto err; 389 if ((ret = __db_add_recovery_int(env, dtabp, 390 __db_pg_free_42_print, DB___db_pg_free_42)) != 0) 391 goto err; 392 if ((ret = __db_add_recovery_int(env, dtabp, 393 __db_pg_freedata_42_print, DB___db_pg_freedata_42)) != 0) 394 goto err; 395#if HAVE_HASH 396 if ((ret = __db_add_recovery_int(env, dtabp, 397 __ham_metagroup_42_print, DB___ham_metagroup_42)) != 0) 398 goto err; 399 if ((ret = __db_add_recovery_int(env, dtabp, 400 __ham_groupalloc_42_print, DB___ham_groupalloc_42)) != 0) 401 goto err; 402#endif 403 if ((ret = __db_add_recovery_int(env, dtabp, 404 __txn_ckp_42_print, DB___txn_ckp_42)) != 0) 405 goto err; 406 if ((ret = __db_add_recovery_int(env, dtabp, 407 __txn_regop_42_print, DB___txn_regop_42)) != 0) 408 goto err; 409err: 410 return (ret); 411} 412 413int 414db_printlog_env_init_print_43(env, dtabp) 415 ENV *env; 416 DB_DISTAB *dtabp; 417{ 418 int ret; 419 420 if ((ret = __db_add_recovery_int(env, dtabp, 421 __bam_relink_43_print, DB___bam_relink_43)) != 0) 422 goto err; 423 /* 424 * We want to use the 4.2-based txn_regop record. 425 */ 426 if ((ret = __db_add_recovery_int(env, dtabp, 427 __txn_regop_42_print, DB___txn_regop_42)) != 0) 428 goto err; 429err: 430 return (ret); 431} 432 433/* 434 * env_init_print_45 -- 435 * 436 */ 437int 438db_printlog_env_init_print_45(env, dtabp) 439 ENV *env; 440 DB_DISTAB *dtabp; 441{ 442 int ret; 443 444 if ((ret = __bam_init_print(env, dtabp)) != 0) 445 goto err; 446 if ((ret = __crdel_init_print(env, dtabp)) != 0) 447 goto err; 448 if ((ret = __db_init_print(env, dtabp)) != 0) 449 goto err; 450 if ((ret = __dbreg_init_print(env, dtabp)) != 0) 451 goto err; 452 if ((ret = __fop_init_print(env, dtabp)) != 0) 453 goto err; 454#ifdef HAVE_HASH 455 if ((ret = __ham_init_print(env, dtabp)) != 0) 456 goto err; 457#endif 458#ifdef HAVE_QUEUE 459 if ((ret = __qam_init_print(env, dtabp)) != 0) 460 goto err; 461#endif 462 if ((ret = __txn_init_print(env, dtabp)) != 0) 463 goto err; 464err: 465 return (ret); 466} 467 468int 469db_printlog_usage() 470{ 471 fprintf(stderr, "usage: %s %s\n", progname, 472 "[-NrV] [-b file/offset] [-e file/offset] [-h home] [-P password]"); 473 return (EXIT_FAILURE); 474} 475 476int 477db_printlog_version_check() 478{ 479 int v_major, v_minor, v_patch; 480 481 /* Make sure we're loaded with the right version of the DB library. */ 482 (void)db_version(&v_major, &v_minor, &v_patch); 483 if (v_major != DB_VERSION_MAJOR || v_minor != DB_VERSION_MINOR) { 484 fprintf(stderr, 485 "%s: version %d.%d doesn't match library version %d.%d\n", 486 progname, DB_VERSION_MAJOR, DB_VERSION_MINOR, 487 v_major, v_minor); 488 return (EXIT_FAILURE); 489 } 490 return (0); 491} 492 493/* Print an unknown, application-specific log record as best we can. */ 494int 495db_printlog_print_app_record(dbenv, dbt, lsnp, op) 496 DB_ENV *dbenv; 497 DBT *dbt; 498 DB_LSN *lsnp; 499 db_recops op; 500{ 501 u_int32_t i, rectype; 502 int ch; 503 504 DB_ASSERT(dbenv->env, op == DB_TXN_PRINT); 505 506 COMPQUIET(dbenv, NULL); 507 COMPQUIET(op, DB_TXN_PRINT); 508 509 /* 510 * Fetch the rectype, which always must be at the beginning of the 511 * record (if dispatching is to work at all). 512 */ 513 memcpy(&rectype, dbt->data, sizeof(rectype)); 514 515 /* 516 * Applications may wish to customize the output here based on the 517 * rectype. We just print the entire log record in the generic 518 * mixed-hex-and-printable format we use for binary data. 519 */ 520 printf("[%lu][%lu]application specific record: rec: %lu\n", 521 (u_long)lsnp->file, (u_long)lsnp->offset, (u_long)rectype); 522 printf("\tdata: "); 523 for (i = 0; i < dbt->size; i++) { 524 ch = ((u_int8_t *)dbt->data)[i]; 525 printf(isprint(ch) || ch == 0x0a ? "%c" : "%#x ", ch); 526 } 527 printf("\n\n"); 528 529 return (0); 530} 531 532int 533db_printlog_open_rep_db(dbenv, dbpp, dbcp) 534 DB_ENV *dbenv; 535 DB **dbpp; 536 DBC **dbcp; 537{ 538 int ret; 539 540 DB *dbp; 541 *dbpp = NULL; 542 *dbcp = NULL; 543 544 if ((ret = db_create(dbpp, dbenv, 0)) != 0) { 545 dbenv->err(dbenv, ret, "db_create"); 546 return (ret); 547 } 548 549 dbp = *dbpp; 550 if ((ret = 551 dbp->open(dbp, NULL, REPDBNAME, NULL, DB_BTREE, 0, 0)) != 0) { 552 dbenv->err(dbenv, ret, "DB->open"); 553 goto err; 554 } 555 556 if ((ret = dbp->cursor(dbp, NULL, dbcp, 0)) != 0) { 557 dbenv->err(dbenv, ret, "DB->cursor"); 558 goto err; 559 } 560 561 return (0); 562 563err: if (*dbpp != NULL) 564 (void)(*dbpp)->close(*dbpp, 0); 565 return (ret); 566} 567 568/* 569 * lsn_arg -- 570 * Parse a LSN argument. 571 */ 572int 573db_printlog_lsn_arg(arg, lsnp) 574 char *arg; 575 DB_LSN *lsnp; 576{ 577 u_long uval; 578 char *p; 579 580 /* 581 * Expected format is: lsn.file/lsn.offset. 582 */ 583 if ((p = strchr(arg, '/')) == NULL) 584 return (1); 585 *p = '\0'; 586 587 if (__db_getulong(NULL, progname, arg, 0, UINT32_MAX, &uval)) 588 return (1); 589 lsnp->file = uval; 590 if (__db_getulong(NULL, progname, p + 1, 0, UINT32_MAX, &uval)) 591 return (1); 592 lsnp->offset = uval; 593 return (0); 594} 595