1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 1996-2009 Oracle.  All rights reserved.
5 *
6 * $Id$
7 */
8
9#include "db_config.h"
10
11#include "db_int.h"
12#include "dbinc/db_page.h"
13#include "dbinc/db_am.h"
14
15#ifndef lint
16static const char copyright[] =
17    "Copyright (c) 1996-2009 Oracle.  All rights reserved.\n";
18#endif
19
20int	 db_init __P((DB_ENV *, char *, int, u_int32_t, int *));
21int	 dump_sub __P((DB_ENV *, DB *, char *, int, int));
22int	 main __P((int, char *[]));
23int	 show_subs __P((DB *));
24int	 usage __P((void));
25int	 version_check __P((void));
26
27const char *progname;
28
29int
30main(argc, argv)
31	int argc;
32	char *argv[];
33{
34	extern char *optarg;
35	extern int optind;
36	DB_ENV	*dbenv;
37	DB *dbp;
38	u_int32_t cache;
39	int ch;
40	int exitval, keyflag, lflag, mflag, nflag, pflag, sflag, private;
41	int ret, Rflag, rflag, resize;
42	char *dbname, *dopt, *filename, *home, *passwd;
43
44	if ((progname = __db_rpath(argv[0])) == NULL)
45		progname = argv[0];
46	else
47		++progname;
48
49	if ((ret = version_check()) != 0)
50		return (ret);
51
52	dbenv = NULL;
53	dbp = NULL;
54	exitval = lflag = mflag = nflag = pflag = rflag = Rflag = sflag = 0;
55	keyflag = 0;
56	cache = MEGABYTE;
57	private = 0;
58	dbname = dopt = filename = home = passwd = NULL;
59	while ((ch = getopt(argc, argv, "d:f:h:klm:NpP:rRs:V")) != EOF)
60		switch (ch) {
61		case 'd':
62			dopt = optarg;
63			break;
64		case 'f':
65			if (freopen(optarg, "w", stdout) == NULL) {
66				fprintf(stderr, "%s: %s: reopen: %s\n",
67				    progname, optarg, strerror(errno));
68				return (EXIT_FAILURE);
69			}
70			break;
71		case 'h':
72			home = optarg;
73			break;
74		case 'k':
75			keyflag = 1;
76			break;
77		case 'l':
78			lflag = 1;
79			break;
80		case 'm':
81			mflag = 1;
82			dbname = optarg;
83			break;
84		case 'N':
85			nflag = 1;
86			break;
87		case 'P':
88			passwd = strdup(optarg);
89			memset(optarg, 0, strlen(optarg));
90			if (passwd == NULL) {
91				fprintf(stderr, "%s: strdup: %s\n",
92				    progname, strerror(errno));
93				return (EXIT_FAILURE);
94			}
95			break;
96		case 'p':
97			pflag = 1;
98			break;
99		case 's':
100			sflag = 1;
101			dbname = optarg;
102			break;
103		case 'R':
104			Rflag = 1;
105			/* DB_AGGRESSIVE requires DB_SALVAGE */
106			/* FALLTHROUGH */
107		case 'r':
108			rflag = 1;
109			break;
110		case 'V':
111			printf("%s\n", db_version(NULL, NULL, NULL));
112			return (EXIT_SUCCESS);
113		case '?':
114		default:
115			return (usage());
116		}
117	argc -= optind;
118	argv += optind;
119
120	/*
121	 * A file name must be specified, unless we're looking for an in-memory
122	 * db,  in which case it must not.
123	 */
124	if (argc == 0 && mflag)
125		filename = NULL;
126	else if (argc == 1 && !mflag)
127		filename = argv[0];
128	else
129		return (usage());
130
131	if (dopt != NULL && pflag) {
132		fprintf(stderr,
133		    "%s: the -d and -p options may not both be specified\n",
134		    progname);
135		return (EXIT_FAILURE);
136	}
137	if (lflag && sflag) {
138		fprintf(stderr,
139		    "%s: the -l and -s options may not both be specified\n",
140		    progname);
141		return (EXIT_FAILURE);
142	}
143	if ((lflag || sflag) && mflag) {
144		fprintf(stderr,
145		    "%s: the -m option may not be specified with -l or -s\n",
146		    progname);
147		return (EXIT_FAILURE);
148	}
149
150	if (keyflag && rflag) {
151		fprintf(stderr, "%s: %s",
152		    "the -k and -r or -R options may not both be specified\n",
153		    progname);
154		return (EXIT_FAILURE);
155	}
156
157	if ((mflag || sflag) && rflag) {
158		fprintf(stderr, "%s: %s",
159		    "the -r or R options may not be specified with -m or -s\n",
160		    progname);
161		return (EXIT_FAILURE);
162	}
163
164	/* Handle possible interruptions. */
165	__db_util_siginit();
166
167	/*
168	 * Create an environment object and initialize it for error
169	 * reporting.
170	 */
171retry:	if ((ret = db_env_create(&dbenv, 0)) != 0) {
172		fprintf(stderr,
173		    "%s: db_env_create: %s\n", progname, db_strerror(ret));
174		goto err;
175	}
176
177	dbenv->set_errfile(dbenv, stderr);
178	dbenv->set_errpfx(dbenv, progname);
179	if (nflag) {
180		if ((ret = dbenv->set_flags(dbenv, DB_NOLOCKING, 1)) != 0) {
181			dbenv->err(dbenv, ret, "set_flags: DB_NOLOCKING");
182			goto err;
183		}
184		if ((ret = dbenv->set_flags(dbenv, DB_NOPANIC, 1)) != 0) {
185			dbenv->err(dbenv, ret, "set_flags: DB_NOPANIC");
186			goto err;
187		}
188	}
189	if (passwd != NULL && (ret = dbenv->set_encrypt(dbenv,
190	    passwd, DB_ENCRYPT_AES)) != 0) {
191		dbenv->err(dbenv, ret, "set_passwd");
192		goto err;
193	}
194
195	/* Initialize the environment. */
196	if (db_init(dbenv, home, rflag, cache, &private) != 0)
197		goto err;
198
199	/* Create the DB object and open the file. */
200	if ((ret = db_create(&dbp, dbenv, 0)) != 0) {
201		dbenv->err(dbenv, ret, "db_create");
202		goto err;
203	}
204
205#if 0
206	Set application-specific btree compression functions here. For example:
207	if ((ret = dbp->set_bt_compress(
208	    dbp, local_compress_func, local_decompress_func)) != 0) {
209		dbp->err(dbp, ret, "DB->set_bt_compress");
210		goto err;
211	}
212#endif
213
214	/*
215	 * If we're salvaging, don't do an open;  it might not be safe.
216	 * Dispatch now into the salvager.
217	 */
218	if (rflag) {
219		/* The verify method is a destructor. */
220		ret = dbp->verify(dbp, filename, NULL, stdout,
221		    DB_SALVAGE |
222		    (Rflag ? DB_AGGRESSIVE : 0) |
223		    (pflag ? DB_PRINTABLE : 0));
224		dbp = NULL;
225		if (ret != 0)
226			goto err;
227		goto done;
228	}
229
230	if ((ret = dbp->open(dbp, NULL,
231	    filename, dbname, DB_UNKNOWN, DB_RDWRMASTER|DB_RDONLY, 0)) != 0) {
232		dbp->err(dbp, ret, "open: %s",
233		    filename == NULL ? dbname : filename);
234		goto err;
235	}
236	if (private != 0) {
237		if ((ret = __db_util_cache(dbp, &cache, &resize)) != 0)
238			goto err;
239		if (resize) {
240			(void)dbp->close(dbp, 0);
241			dbp = NULL;
242
243			(void)dbenv->close(dbenv, 0);
244			dbenv = NULL;
245			goto retry;
246		}
247	}
248
249	if (dopt != NULL) {
250		if ((ret = __db_dumptree(dbp, NULL, dopt, NULL)) != 0) {
251			dbp->err(dbp, ret, "__db_dumptree: %s", filename);
252			goto err;
253		}
254	} else if (lflag) {
255		if (dbp->get_multiple(dbp)) {
256			if (show_subs(dbp))
257				goto err;
258		} else {
259			dbp->errx(dbp,
260			    "%s: does not contain multiple databases",
261			    filename);
262			goto err;
263		}
264	} else {
265		if (dbname == NULL && dbp->get_multiple(dbp)) {
266			if (dump_sub(dbenv, dbp, filename, pflag, keyflag))
267				goto err;
268		} else
269			if (dbp->dump(dbp, NULL,
270			    __db_pr_callback, stdout, pflag, keyflag))
271				goto err;
272	}
273
274	if (0) {
275err:		exitval = 1;
276	}
277done:	if (dbp != NULL && (ret = dbp->close(dbp, 0)) != 0) {
278		exitval = 1;
279		dbenv->err(dbenv, ret, "close");
280	}
281	if (dbenv != NULL && (ret = dbenv->close(dbenv, 0)) != 0) {
282		exitval = 1;
283		fprintf(stderr,
284		    "%s: dbenv->close: %s\n", progname, db_strerror(ret));
285	}
286
287	if (passwd != NULL)
288		free(passwd);
289
290	/* Resend any caught signal. */
291	__db_util_sigresend();
292
293	return (exitval == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
294}
295
296/*
297 * db_init --
298 *	Initialize the environment.
299 */
300int
301db_init(dbenv, home, is_salvage, cache, is_privatep)
302	DB_ENV *dbenv;
303	char *home;
304	int is_salvage;
305	u_int32_t cache;
306	int *is_privatep;
307{
308	int ret;
309
310	/*
311	 * Try and use the underlying environment when opening a database.
312	 * We wish to use the buffer pool so our information is as up-to-date
313	 * as possible, even if the mpool cache hasn't been flushed.
314	 *
315	 * If we are not doing a salvage, we want to join the environment;
316	 * if a locking system is present, this will let us use it and be
317	 * safe to run concurrently with other threads of control.  (We never
318	 * need to use transactions explicitly, as we're read-only.)  Note
319	 * that in CDB, too, this will configure our environment
320	 * appropriately, and our cursors will (correctly) do locking as CDB
321	 * read cursors.
322	 *
323	 * If we are doing a salvage, the verification code will protest
324	 * if we initialize transactions, logging, or locking;  do an
325	 * explicit DB_INIT_MPOOL to try to join any existing environment
326	 * before we create our own.
327	 */
328	*is_privatep = 0;
329	if ((ret = dbenv->open(dbenv, home,
330	    DB_USE_ENVIRON | (is_salvage ? DB_INIT_MPOOL : 0), 0)) == 0)
331		return (0);
332	if (ret == DB_VERSION_MISMATCH)
333		goto err;
334
335	/*
336	 * An environment is required because we may be trying to look at
337	 * databases in directories other than the current one.  We could
338	 * avoid using an environment iff the -h option wasn't specified,
339	 * but that seems like more work than it's worth.
340	 *
341	 * No environment exists (or, at least no environment that includes
342	 * an mpool region exists).  Create one, but make it private so that
343	 * no files are actually created.
344	 */
345	*is_privatep = 1;
346	if ((ret = dbenv->set_cachesize(dbenv, 0, cache, 1)) == 0 &&
347	    (ret = dbenv->open(dbenv, home,
348	    DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_USE_ENVIRON, 0)) == 0)
349		return (0);
350
351	/* An environment is required. */
352err:	dbenv->err(dbenv, ret, "DB_ENV->open");
353	return (1);
354}
355
356/*
357 * dump_sub --
358 *	Dump out the records for a DB containing subdatabases.
359 */
360int
361dump_sub(dbenv, parent_dbp, parent_name, pflag, keyflag)
362	DB_ENV *dbenv;
363	DB *parent_dbp;
364	char *parent_name;
365	int pflag, keyflag;
366{
367	DB *dbp;
368	DBC *dbcp;
369	DBT key, data;
370	int ret;
371	char *subdb;
372
373	/*
374	 * Get a cursor and step through the database, dumping out each
375	 * subdatabase.
376	 */
377	if ((ret = parent_dbp->cursor(parent_dbp, NULL, &dbcp, 0)) != 0) {
378		dbenv->err(dbenv, ret, "DB->cursor");
379		return (1);
380	}
381
382	memset(&key, 0, sizeof(key));
383	memset(&data, 0, sizeof(data));
384	while ((ret = dbcp->get(dbcp, &key, &data,
385	    DB_IGNORE_LEASE | DB_NEXT)) == 0) {
386		/* Nul terminate the subdatabase name. */
387		if ((subdb = malloc(key.size + 1)) == NULL) {
388			dbenv->err(dbenv, ENOMEM, NULL);
389			return (1);
390		}
391		memcpy(subdb, key.data, key.size);
392		subdb[key.size] = '\0';
393
394		/* Create the DB object and open the file. */
395		if ((ret = db_create(&dbp, dbenv, 0)) != 0) {
396			dbenv->err(dbenv, ret, "db_create");
397			free(subdb);
398			return (1);
399		}
400
401#if 0
402		Set application-specific btree compression functions here.
403		For example:
404
405		if ((ret = dbp->set_bt_compress(
406		    dbp, local_compress_func, local_decompress_func)) != 0) {
407			dbp->err(dbp, ret, "DB->set_bt_compress");
408			goto err;
409		}
410#endif
411
412		if ((ret = dbp->open(dbp, NULL,
413		    parent_name, subdb, DB_UNKNOWN, DB_RDONLY, 0)) != 0)
414			dbp->err(dbp, ret,
415			    "DB->open: %s:%s", parent_name, subdb);
416		if (ret == 0 && dbp->dump(
417		    dbp, subdb, __db_pr_callback, stdout, pflag, keyflag))
418			ret = 1;
419		(void)dbp->close(dbp, 0);
420		free(subdb);
421		if (ret != 0)
422			return (1);
423	}
424	if (ret != DB_NOTFOUND) {
425		parent_dbp->err(parent_dbp, ret, "DBcursor->get");
426		return (1);
427	}
428
429	if ((ret = dbcp->close(dbcp)) != 0) {
430		parent_dbp->err(parent_dbp, ret, "DBcursor->close");
431		return (1);
432	}
433
434	return (0);
435}
436
437/*
438 * show_subs --
439 *	Display the subdatabases for a database.
440 */
441int
442show_subs(dbp)
443	DB *dbp;
444{
445	DBC *dbcp;
446	DBT key, data;
447	int ret;
448
449	/*
450	 * Get a cursor and step through the database, printing out the key
451	 * of each key/data pair.
452	 */
453	if ((ret = dbp->cursor(dbp, NULL, &dbcp, 0)) != 0) {
454		dbp->err(dbp, ret, "DB->cursor");
455		return (1);
456	}
457
458	memset(&key, 0, sizeof(key));
459	memset(&data, 0, sizeof(data));
460	while ((ret = dbcp->get(dbcp, &key, &data,
461	    DB_IGNORE_LEASE | DB_NEXT)) == 0) {
462		if ((ret = dbp->dbenv->prdbt(
463		    &key, 1, NULL, stdout, __db_pr_callback, 0)) != 0) {
464			dbp->errx(dbp, NULL);
465			return (1);
466		}
467	}
468	if (ret != DB_NOTFOUND) {
469		dbp->err(dbp, ret, "DBcursor->get");
470		return (1);
471	}
472
473	if ((ret = dbcp->close(dbcp)) != 0) {
474		dbp->err(dbp, ret, "DBcursor->close");
475		return (1);
476	}
477	return (0);
478}
479
480/*
481 * usage --
482 *	Display the usage message.
483 */
484int
485usage()
486{
487	(void)fprintf(stderr, "usage: %s [-klNprRV]\n\t%s\n",
488	    progname,
489    "[-d ahr] [-f output] [-h home] [-P password] [-s database] db_file");
490	(void)fprintf(stderr, "usage: %s [-kNpV] %s\n",
491	    progname, "[-d ahr] [-f output] [-h home] -m database");
492	return (EXIT_FAILURE);
493}
494
495int
496version_check()
497{
498	int v_major, v_minor, v_patch;
499
500	/* Make sure we're loaded with the right version of the DB library. */
501	(void)db_version(&v_major, &v_minor, &v_patch);
502	if (v_major != DB_VERSION_MAJOR || v_minor != DB_VERSION_MINOR) {
503		fprintf(stderr,
504	"%s: version %d.%d doesn't match library version %d.%d\n",
505		    progname, DB_VERSION_MAJOR, DB_VERSION_MINOR,
506		    v_major, v_minor);
507		return (EXIT_FAILURE);
508	}
509	return (0);
510}
511