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