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