1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 1996,2008 Oracle.  All rights reserved.
5 *
6 * $Id: db_stat.c,v 12.23 2008/01/08 20:58:16 bostic Exp $
7 */
8
9#include "db_config.h"
10
11#include "db_int.h"
12
13#ifndef lint
14static const char copyright[] =
15    "Copyright (c) 1996,2008 Oracle.  All rights reserved.\n";
16#endif
17
18typedef enum { T_NOTSET, T_DB,
19    T_ENV, T_LOCK, T_LOG, T_MPOOL, T_MUTEX, T_REP, T_TXN } test_t;
20
21int	 db_init __P((DB_ENV *, char *, test_t, u_int32_t, int *));
22int	 main __P((int, char *[]));
23int	 usage __P((void));
24int	 version_check __P((void));
25
26const char *progname;
27
28int
29main(argc, argv)
30	int argc;
31	char *argv[];
32{
33	extern char *optarg;
34	extern int optind;
35	DB_ENV	*dbenv;
36	DB *dbp;
37	test_t ttype;
38	u_int32_t cache, flags;
39	int ch, exitval;
40	int nflag, private, resize, ret;
41	char *db, *home, *p, *passwd, *subdb;
42
43	if ((progname = __db_rpath(argv[0])) == NULL)
44		progname = argv[0];
45	else
46		++progname;
47
48	if ((ret = version_check()) != 0)
49		return (ret);
50
51	dbenv = NULL;
52	dbp = NULL;
53	ttype = T_NOTSET;
54	cache = MEGABYTE;
55	exitval = flags = nflag = private = 0;
56	db = home = passwd = subdb = NULL;
57
58	while ((ch = getopt(argc,
59	    argv, "C:cd:Eefgh:L:lM:mNP:R:rs:tVxX:Z")) != EOF)
60		switch (ch) {
61		case 'C': case 'c':
62			if (ttype != T_NOTSET && ttype != T_LOCK)
63				goto argcombo;
64			ttype = T_LOCK;
65			if (ch != 'c')
66				for (p = optarg; *p; ++p)
67					switch (*p) {
68					case 'A':
69						LF_SET(DB_STAT_ALL);
70						break;
71					case 'c':
72						LF_SET(DB_STAT_LOCK_CONF);
73						break;
74					case 'l':
75						LF_SET(DB_STAT_LOCK_LOCKERS);
76						break;
77					case 'm': /* Backward compatible. */
78						break;
79					case 'o':
80						LF_SET(DB_STAT_LOCK_OBJECTS);
81						break;
82					case 'p':
83						LF_SET(DB_STAT_LOCK_PARAMS);
84						break;
85					default:
86						return (usage());
87					}
88			break;
89		case 'd':
90			if (ttype != T_NOTSET && ttype != T_DB)
91				goto argcombo;
92			ttype = T_DB;
93			db = optarg;
94			break;
95		case 'E': case 'e':
96			if (ttype != T_NOTSET && ttype != T_ENV)
97				goto argcombo;
98			ttype = T_ENV;
99			LF_SET(DB_STAT_SUBSYSTEM);
100			if (ch == 'E')
101				LF_SET(DB_STAT_ALL);
102			break;
103		case 'f':
104			if (ttype != T_NOTSET && ttype != T_DB)
105				goto argcombo;
106			ttype = T_DB;
107			LF_SET(DB_FAST_STAT);
108			break;
109		case 'h':
110			home = optarg;
111			break;
112		case 'L': case 'l':
113			if (ttype != T_NOTSET && ttype != T_LOG)
114				goto argcombo;
115			ttype = T_LOG;
116			if (ch != 'l')
117				for (p = optarg; *p; ++p)
118					switch (*p) {
119					case 'A':
120						LF_SET(DB_STAT_ALL);
121						break;
122					default:
123						return (usage());
124					}
125			break;
126		case 'M': case 'm':
127			if (ttype != T_NOTSET && ttype != T_MPOOL)
128				goto argcombo;
129			ttype = T_MPOOL;
130			if (ch != 'm')
131				for (p = optarg; *p; ++p)
132					switch (*p) {
133					case 'A':
134						LF_SET(DB_STAT_ALL);
135						break;
136					case 'h':
137						LF_SET(DB_STAT_MEMP_HASH);
138						break;
139					case 'm': /* Backward compatible. */
140						break;
141					default:
142						return (usage());
143					}
144			break;
145		case 'N':
146			nflag = 1;
147			break;
148		case 'P':
149			passwd = strdup(optarg);
150			memset(optarg, 0, strlen(optarg));
151			if (passwd == NULL) {
152				fprintf(stderr, "%s: strdup: %s\n",
153				    progname, strerror(errno));
154				return (EXIT_FAILURE);
155			}
156			break;
157		case 'R': case 'r':
158			if (ttype != T_NOTSET && ttype != T_REP)
159				goto argcombo;
160			ttype = T_REP;
161			if (ch != 'r')
162				for (p = optarg; *p; ++p)
163					switch (*p) {
164					case 'A':
165						LF_SET(DB_STAT_ALL);
166						break;
167					default:
168						return (usage());
169					}
170			break;
171		case 's':
172			if (ttype != T_NOTSET && ttype != T_DB)
173				goto argcombo;
174			ttype = T_DB;
175			subdb = optarg;
176			break;
177		case 't':
178			if (ttype != T_NOTSET) {
179argcombo:			fprintf(stderr,
180				    "%s: illegal option combination\n",
181				    progname);
182				return (usage());
183			}
184			ttype = T_TXN;
185			break;
186		case 'V':
187			printf("%s\n", db_version(NULL, NULL, NULL));
188			return (EXIT_SUCCESS);
189		case 'X': case 'x':
190			if (ttype != T_NOTSET && ttype != T_MUTEX)
191				goto argcombo;
192			ttype = T_MUTEX;
193			if (ch != 'x')
194				for (p = optarg; *p; ++p)
195					switch (*p) {
196						case 'A':
197							LF_SET(DB_STAT_ALL);
198							break;
199						default:
200							return (usage());
201					}
202			break;
203		case 'Z':
204			LF_SET(DB_STAT_CLEAR);
205			break;
206		case '?':
207		default:
208			return (usage());
209		}
210	argc -= optind;
211	argv += optind;
212
213	switch (ttype) {
214	case T_DB:
215		if (db == NULL)
216			return (usage());
217		break;
218	case T_ENV:
219	case T_LOCK:
220	case T_LOG:
221	case T_MPOOL:
222	case T_MUTEX:
223	case T_REP:
224	case T_TXN:
225		break;
226	case T_NOTSET:
227		return (usage());
228	}
229
230	/* Handle possible interruptions. */
231	__db_util_siginit();
232
233	/*
234	 * Create an environment object and initialize it for error
235	 * reporting.
236	 */
237retry:	if ((ret = db_env_create(&dbenv, 0)) != 0) {
238		fprintf(stderr,
239		    "%s: db_env_create: %s\n", progname, db_strerror(ret));
240		goto err;
241	}
242
243	dbenv->set_errfile(dbenv, stderr);
244	dbenv->set_errpfx(dbenv, progname);
245
246	if (nflag) {
247		if ((ret = dbenv->set_flags(dbenv, DB_NOLOCKING, 1)) != 0) {
248			dbenv->err(dbenv, ret, "set_flags: DB_NOLOCKING");
249			goto err;
250		}
251		if ((ret = dbenv->set_flags(dbenv, DB_NOPANIC, 1)) != 0) {
252			dbenv->err(dbenv, ret, "set_flags: DB_NOPANIC");
253			goto err;
254		}
255	}
256
257	if (passwd != NULL &&
258	    (ret = dbenv->set_encrypt(dbenv, passwd, DB_ENCRYPT_AES)) != 0) {
259		dbenv->err(dbenv, ret, "set_passwd");
260		goto err;
261	}
262
263	/* Initialize the environment. */
264	if (db_init(dbenv, home, ttype, cache, &private) != 0)
265		goto err;
266
267	switch (ttype) {
268	case T_DB:
269		/* Create the DB object and open the file. */
270		if ((ret = db_create(&dbp, dbenv, 0)) != 0) {
271			dbenv->err(dbenv, ret, "db_create");
272			goto err;
273		}
274
275		/*
276		 * We open the database for writing so we can update the cached
277		 * statistics, but it's OK to fail, we can open read-only and
278		 * proceed.
279		 *
280		 * Turn off error messages for now -- we can't open lots of
281		 * databases read-write (for example, master databases and
282		 * hash databases for which we don't know the hash function).
283		 */
284		dbenv->set_errfile(dbenv, NULL);
285		ret = dbp->open(dbp, NULL, db, subdb, DB_UNKNOWN, 0, 0);
286		dbenv->set_errfile(dbenv, stderr);
287		if (ret != 0 && (ret = dbp->open(
288		    dbp, NULL, db, subdb, DB_UNKNOWN, DB_RDONLY, 0)) != 0) {
289			dbenv->err(dbenv, ret, "DB->open: %s", db);
290			goto err;
291		}
292
293		/* Check if cache is too small for this DB's pagesize. */
294		if (private) {
295			if ((ret = __db_util_cache(dbp, &cache, &resize)) != 0)
296				goto err;
297			if (resize) {
298				(void)dbp->close(dbp, DB_NOSYNC);
299				dbp = NULL;
300
301				(void)dbenv->close(dbenv, 0);
302				dbenv = NULL;
303				goto retry;
304			}
305		}
306
307		if (dbp->stat_print(dbp, flags))
308			goto err;
309		break;
310	case T_ENV:
311		if (dbenv->stat_print(dbenv, flags))
312			goto err;
313		break;
314	case T_LOCK:
315		if (dbenv->lock_stat_print(dbenv, flags))
316			goto err;
317		break;
318	case T_LOG:
319		if (dbenv->log_stat_print(dbenv, flags))
320			goto err;
321		break;
322	case T_MPOOL:
323		if (dbenv->memp_stat_print(dbenv, flags))
324			goto err;
325		break;
326	case T_MUTEX:
327		if (dbenv->mutex_stat_print(dbenv, flags))
328			goto err;
329		break;
330	case T_REP:
331#ifdef HAVE_REPLICATION_THREADS
332		if (dbenv->repmgr_stat_print(dbenv, flags))
333			goto err;
334#endif
335		if (dbenv->rep_stat_print(dbenv, flags))
336			goto err;
337		break;
338	case T_TXN:
339		if (dbenv->txn_stat_print(dbenv, flags))
340			goto err;
341		break;
342	case T_NOTSET:
343		dbenv->errx(dbenv, "Unknown statistics flag");
344		goto err;
345	}
346
347	if (0) {
348err:		exitval = 1;
349	}
350	if (dbp != NULL && (ret = dbp->close(dbp, DB_NOSYNC)) != 0) {
351		exitval = 1;
352		dbenv->err(dbenv, ret, "close");
353	}
354	if (dbenv != NULL && (ret = dbenv->close(dbenv, 0)) != 0) {
355		exitval = 1;
356		fprintf(stderr,
357		    "%s: dbenv->close: %s\n", progname, db_strerror(ret));
358	}
359
360	if (passwd != NULL)
361		free(passwd);
362
363	/* Resend any caught signal. */
364	__db_util_sigresend();
365
366	return (exitval == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
367}
368
369/*
370 * db_init --
371 *	Initialize the environment.
372 */
373int
374db_init(dbenv, home, ttype, cache, is_private)
375	DB_ENV *dbenv;
376	char *home;
377	test_t ttype;
378	u_int32_t cache;
379	int *is_private;
380{
381	u_int32_t oflags;
382	int ret;
383
384	/*
385	 * If our environment open fails, and we're trying to look at a
386	 * shared region, it's a hard failure.
387	 *
388	 * We will probably just drop core if the environment we join does
389	 * not include a memory pool.  This is probably acceptable; trying
390	 * to use an existing environment that does not contain a memory
391	 * pool to look at a database can be safely construed as operator
392	 * error, I think.
393	 */
394	*is_private = 0;
395	if ((ret = dbenv->open(dbenv, home, DB_USE_ENVIRON, 0)) == 0)
396		return (0);
397	if (ret == DB_VERSION_MISMATCH)
398		goto err;
399	if (ttype != T_DB && ttype != T_LOG) {
400		dbenv->err(dbenv, ret, "DB_ENV->open%s%s",
401		    home == NULL ? "" : ": ", home == NULL ? "" : home);
402		return (1);
403	}
404
405	/*
406	 * We're looking at a database or set of log files and no environment
407	 * exists.  Create one, but make it private so no files are actually
408	 * created.  Declare a reasonably large cache so that we don't fail
409	 * when reporting statistics on large databases.
410	 *
411	 * An environment is required to look at databases because we may be
412	 * trying to look at databases in directories other than the current
413	 * one.
414	 */
415	if ((ret = dbenv->set_cachesize(dbenv, 0, cache, 1)) != 0) {
416		dbenv->err(dbenv, ret, "set_cachesize");
417		return (1);
418	}
419	*is_private = 1;
420	oflags = DB_CREATE | DB_PRIVATE | DB_USE_ENVIRON;
421	if (ttype == T_DB)
422		oflags |= DB_INIT_MPOOL;
423	if (ttype == T_LOG)
424		oflags |= DB_INIT_LOG;
425	if ((ret = dbenv->open(dbenv, home, oflags, 0)) == 0)
426		return (0);
427
428	/* An environment is required. */
429err:	dbenv->err(dbenv, ret, "DB_ENV->open");
430	return (1);
431}
432
433int
434usage()
435{
436	fprintf(stderr, "usage: %s %s\n", progname,
437	    "-d file [-fN] [-h home] [-P password] [-s database]");
438	fprintf(stderr, "usage: %s %s\n\t%s\n", progname,
439	    "[-cEelmNrtVxZ] [-C Aclop]",
440	    "[-h home] [-L A] [-M A] [-P password] [-R A] [-X A]");
441	return (EXIT_FAILURE);
442}
443
444int
445version_check()
446{
447	int v_major, v_minor, v_patch;
448
449	/* Make sure we're loaded with the right version of the DB library. */
450	(void)db_version(&v_major, &v_minor, &v_patch);
451	if (v_major != DB_VERSION_MAJOR || v_minor != DB_VERSION_MINOR) {
452		fprintf(stderr,
453	"%s: version %d.%d doesn't match library version %d.%d\n",
454		    progname, DB_VERSION_MAJOR, DB_VERSION_MINOR,
455		    v_major, v_minor);
456		return (EXIT_FAILURE);
457	}
458	return (0);
459}
460