1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 2000,2008 Oracle.  All rights reserved.
5 *
6 * $Id: db_server_cxxutil.cpp,v 12.9 2008/01/08 20:58:50 bostic Exp $
7 */
8
9#include "db_config.h"
10
11#include "db_int.h"
12#include "db_cxx.h"
13#include "db_server.h"
14#include "dbinc_auto/clib_ext.h"
15
16extern "C" {
17#include "dbinc/db_server_int.h"
18#include "dbinc_auto/rpc_server_ext.h"
19#include "dbinc_auto/common_ext.h"
20
21extern int __dbsrv_main	 __P((void));
22}
23
24static int add_home __P((char *));
25static int add_passwd __P((char *));
26static int env_recover __P((char *));
27static void __dbclear_child __P((ct_entry *));
28
29static LIST_HEAD(cthead, ct_entry) __dbsrv_head;
30static LIST_HEAD(homehead, home_entry) __dbsrv_home;
31static long __dbsrv_defto = DB_SERVER_TIMEOUT;
32static long __dbsrv_maxto = DB_SERVER_MAXTIMEOUT;
33static long __dbsrv_idleto = DB_SERVER_IDLETIMEOUT;
34static char *logfile = NULL;
35static char *prog;
36
37static void usage __P((char *));
38static void version_check __P((void));
39
40int __dbsrv_verbose = 0;
41
42int
43main(
44	int argc,
45	char **argv)
46{
47	extern char *optarg;
48	CLIENT *cl;
49	int ch, ret;
50	char *passwd;
51
52	prog = argv[0];
53
54	version_check();
55
56	/*
57	 * Check whether another server is running or not.  There
58	 * is a race condition where two servers could be racing to
59	 * register with the portmapper.  The goal of this check is to
60	 * forbid running additional servers (like those started from
61	 * the test suite) if the user is already running one.
62	 *
63	 * XXX
64	 * This does not solve nor prevent two servers from being
65	 * started at the same time and running recovery at the same
66	 * time on the same environments.
67	 */
68	if ((cl = clnt_create("localhost",
69	    DB_RPC_SERVERPROG, DB_RPC_SERVERVERS, "tcp")) != NULL) {
70		fprintf(stderr,
71		    "%s: Berkeley DB RPC server already running.\n", prog);
72		clnt_destroy(cl);
73		return (EXIT_FAILURE);
74	}
75
76	LIST_INIT(&__dbsrv_home);
77	while ((ch = getopt(argc, argv, "h:I:L:P:t:T:Vv")) != EOF)
78		switch (ch) {
79		case 'h':
80			(void)add_home(optarg);
81			break;
82		case 'I':
83			if (__db_getlong(NULL, prog,
84			    optarg, 1, LONG_MAX, &__dbsrv_idleto))
85				return (EXIT_FAILURE);
86			break;
87		case 'L':
88			logfile = optarg;
89			break;
90		case 'P':
91			passwd = strdup(optarg);
92			memset(optarg, 0, strlen(optarg));
93			if (passwd == NULL) {
94				fprintf(stderr, "%s: strdup: %s\n",
95				    prog, strerror(errno));
96				return (EXIT_FAILURE);
97			}
98			if ((ret = add_passwd(passwd)) != 0) {
99				fprintf(stderr, "%s: strdup: %s\n",
100				    prog, strerror(ret));
101				return (EXIT_FAILURE);
102			}
103			break;
104		case 't':
105			if (__db_getlong(NULL, prog,
106			    optarg, 1, LONG_MAX, &__dbsrv_defto))
107				return (EXIT_FAILURE);
108			break;
109		case 'T':
110			if (__db_getlong(NULL, prog,
111			    optarg, 1, LONG_MAX, &__dbsrv_maxto))
112				return (EXIT_FAILURE);
113			break;
114		case 'V':
115			printf("%s\n", db_version(NULL, NULL, NULL));
116			return (EXIT_SUCCESS);
117		case 'v':
118			__dbsrv_verbose = 1;
119			break;
120		default:
121			usage(prog);
122		}
123	/*
124	 * Check default timeout against maximum timeout
125	 */
126	if (__dbsrv_defto > __dbsrv_maxto)
127		__dbsrv_defto = __dbsrv_maxto;
128
129	/*
130	 * Check default timeout against idle timeout
131	 * It would be bad to timeout environments sooner than txns.
132	 */
133	if (__dbsrv_defto > __dbsrv_idleto)
134		fprintf(stderr,
135	    "%s: WARNING: Idle timeout %ld is less than resource timeout %ld\n",
136		    prog, __dbsrv_idleto, __dbsrv_defto);
137
138	LIST_INIT(&__dbsrv_head);
139
140	/*
141	 * If a client crashes during an RPC, our reply to it
142	 * generates a SIGPIPE.  Ignore SIGPIPE so we don't exit unnecessarily.
143	 */
144#ifdef SIGPIPE
145	signal(SIGPIPE, SIG_IGN);
146#endif
147
148	if (logfile != NULL && __db_util_logset("berkeley_db_svc", logfile))
149		return (EXIT_FAILURE);
150
151	/*
152	 * Now that we are ready to start, run recovery on all the
153	 * environments specified.
154	 */
155	if (env_recover(prog) != 0)
156		return (EXIT_FAILURE);
157
158	/*
159	 * We've done our setup, now call the generated server loop
160	 */
161	if (__dbsrv_verbose)
162		printf("%s:  Ready to receive requests\n", prog);
163	__dbsrv_main();
164
165	/* NOTREACHED */
166	abort();
167}
168
169static void
170usage(char *prog)
171{
172	fprintf(stderr, "usage: %s %s\n\t%s\n", prog,
173	    "[-Vv] [-h home] [-P passwd]",
174	    "[-I idletimeout] [-L logfile] [-t def_timeout] [-T maxtimeout]");
175	exit(EXIT_FAILURE);
176}
177
178static void
179version_check()
180{
181	int v_major, v_minor, v_patch;
182
183	/* Make sure we're loaded with the right version of the DB library. */
184	(void)db_version(&v_major, &v_minor, &v_patch);
185	if (v_major != DB_VERSION_MAJOR ||
186	    v_minor != DB_VERSION_MINOR || v_patch != DB_VERSION_PATCH) {
187		fprintf(stderr,
188	"%s: version %d.%d.%d doesn't match library version %d.%d.%d\n",
189		    prog, DB_VERSION_MAJOR, DB_VERSION_MINOR,
190		    DB_VERSION_PATCH, v_major, v_minor, v_patch);
191		exit(EXIT_FAILURE);
192	}
193}
194
195extern "C" void
196__dbsrv_settimeout(
197	ct_entry *ctp,
198	u_int32_t to)
199{
200	if (to > (u_int32_t)__dbsrv_maxto)
201		ctp->ct_timeout = __dbsrv_maxto;
202	else if (to <= 0)
203		ctp->ct_timeout = __dbsrv_defto;
204	else
205		ctp->ct_timeout = to;
206}
207
208extern "C" void
209__dbsrv_timeout(int force)
210{
211	static long to_hint = -1;
212	time_t t;
213	long to;
214	ct_entry *ctp, *nextctp;
215
216	if ((t = time(NULL)) == -1)
217		return;
218
219	/*
220	 * Check hint.  If hint is further in the future
221	 * than now, no work to do.
222	 */
223	if (!force && to_hint > 0 && t < to_hint)
224		return;
225	to_hint = -1;
226	/*
227	 * Timeout transactions or cursors holding DB resources.
228	 * Do this before timing out envs to properly release resources.
229	 *
230	 * !!!
231	 * We can just loop through this list looking for cursors and txns.
232	 * We do not need to verify txn and cursor relationships at this
233	 * point because we maintain the list in LIFO order *and* we
234	 * maintain activity in the ultimate txn parent of any cursor
235	 * so either everything in a txn is timing out, or nothing.
236	 * So, since we are LIFO, we will correctly close/abort all the
237	 * appropriate handles, in the correct order.
238	 */
239	for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL; ctp = nextctp) {
240		nextctp = LIST_NEXT(ctp, entries);
241		switch (ctp->ct_type) {
242		case CT_TXN:
243			to = *(ctp->ct_activep) + ctp->ct_timeout;
244			/* TIMEOUT */
245			if (to < t) {
246				if (__dbsrv_verbose)
247					printf("Timing out txn id %ld\n",
248					    ctp->ct_id);
249				(void)((DbTxn *)ctp->ct_anyp)->abort();
250				__dbdel_ctp(ctp);
251				/*
252				 * If we timed out an txn, we may have closed
253				 * all sorts of ctp's.
254				 * So start over with a guaranteed good ctp.
255				 */
256				nextctp = LIST_FIRST(&__dbsrv_head);
257			} else if ((to_hint > 0 && to_hint > to) ||
258			    to_hint == -1)
259				to_hint = to;
260			break;
261		case CT_CURSOR:
262		case (CT_JOINCUR | CT_CURSOR):
263			to = *(ctp->ct_activep) + ctp->ct_timeout;
264			/* TIMEOUT */
265			if (to < t) {
266				if (__dbsrv_verbose)
267					printf("Timing out cursor %ld\n",
268					    ctp->ct_id);
269				(void)__dbc_close_int(ctp);
270				/*
271				 * Start over with a guaranteed good ctp.
272				 */
273				nextctp = LIST_FIRST(&__dbsrv_head);
274			} else if ((to_hint > 0 && to_hint > to) ||
275			    to_hint == -1)
276				to_hint = to;
277			break;
278		default:
279			break;
280		}
281	}
282	/*
283	 * Timeout idle handles.
284	 * If we are forcing a timeout, we'll close all env handles.
285	 */
286	for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL; ctp = nextctp) {
287		nextctp = LIST_NEXT(ctp, entries);
288		if (ctp->ct_type != CT_ENV)
289			continue;
290		to = *(ctp->ct_activep) + ctp->ct_idle;
291		/* TIMEOUT */
292		if (to < t || force) {
293			if (__dbsrv_verbose)
294				printf("Timing out env id %ld\n", ctp->ct_id);
295			(void)__env_close_int(ctp->ct_id, 0, 1);
296			/*
297			 * If we timed out an env, we may have closed
298			 * all sorts of ctp's (maybe even all of them.
299			 * So start over with a guaranteed good ctp.
300			 */
301			nextctp = LIST_FIRST(&__dbsrv_head);
302		}
303	}
304}
305
306/*
307 * RECURSIVE FUNCTION.  We need to clear/free any number of levels of nested
308 * layers.
309 */
310static void
311__dbclear_child(ct_entry *parent)
312{
313	ct_entry *ctp, *nextctp;
314
315	for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL;
316	    ctp = nextctp) {
317		nextctp = LIST_NEXT(ctp, entries);
318		if (ctp->ct_type == 0)
319			continue;
320		if (ctp->ct_parent == parent) {
321			__dbclear_child(ctp);
322			/*
323			 * Need to do this here because le_next may
324			 * have changed with the recursive call and we
325			 * don't want to point to a removed entry.
326			 */
327			nextctp = LIST_NEXT(ctp, entries);
328			__dbclear_ctp(ctp);
329		}
330	}
331}
332
333extern "C" void
334__dbclear_ctp(ct_entry *ctp)
335{
336	LIST_REMOVE(ctp, entries);
337	__os_free(NULL, ctp);
338}
339
340extern "C" void
341__dbdel_ctp(ct_entry *parent)
342{
343	__dbclear_child(parent);
344	__dbclear_ctp(parent);
345}
346
347extern "C" ct_entry *
348new_ct_ent(int *errp)
349{
350	time_t t;
351	ct_entry *ctp, *octp;
352	int ret;
353
354	if ((ret = __os_malloc(NULL, sizeof(ct_entry), &ctp)) != 0) {
355		*errp = ret;
356		return (NULL);
357	}
358	memset(ctp, 0, sizeof(ct_entry));
359	/*
360	 * Get the time as ID.  We may service more than one request per
361	 * second however.  If we are, then increment id value until we
362	 * find an unused one.  We insert entries in LRU fashion at the
363	 * head of the list.  So, if the first entry doesn't match, then
364	 * we know for certain that we can use our entry.
365	 */
366	if ((t = time(NULL)) == -1) {
367		*errp = __os_get_errno();
368		__os_free(NULL, ctp);
369		return (NULL);
370	}
371	octp = LIST_FIRST(&__dbsrv_head);
372	if (octp != NULL && octp->ct_id >= t)
373		t = octp->ct_id + 1;
374	ctp->ct_id = t;
375	ctp->ct_idle = __dbsrv_idleto;
376	ctp->ct_activep = &ctp->ct_active;
377	ctp->ct_origp = NULL;
378	ctp->ct_refcount = 1;
379
380	LIST_INSERT_HEAD(&__dbsrv_head, ctp, entries);
381	return (ctp);
382}
383
384extern "C" ct_entry *
385get_tableent(long id)
386{
387	ct_entry *ctp;
388
389	for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL;
390	    ctp = LIST_NEXT(ctp, entries))
391		if (ctp->ct_id == id)
392			return (ctp);
393	return (NULL);
394}
395
396extern "C" ct_entry *
397__dbsrv_sharedb(ct_entry *db_ctp,
398    const char *name, const char *subdb, DBTYPE type, u_int32_t flags)
399{
400	ct_entry *ctp;
401
402	/*
403	 * Check if we can share a db handle.  Criteria for sharing are:
404	 * If any of the non-sharable flags are set, we cannot share.
405	 * Must be a db ctp, obviously.
406	 * Must share the same env parent.
407	 * Must be the same type, or current one DB_UNKNOWN.
408	 * Must be same byteorder, or current one must not care.
409	 * All flags must match.
410	 * Must be same name, but don't share in-memory databases.
411	 * Must be same subdb name.
412	 */
413	if (flags & DB_SERVER_DBNOSHARE)
414		return (NULL);
415	for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL;
416	    ctp = LIST_NEXT(ctp, entries)) {
417		/*
418		 * Skip ourselves.
419		 */
420		if (ctp == db_ctp)
421			continue;
422		if (ctp->ct_type != CT_DB)
423			continue;
424		if (ctp->ct_envparent != db_ctp->ct_envparent)
425			continue;
426		if (type != DB_UNKNOWN && ctp->ct_dbdp.type != type)
427			continue;
428		if (ctp->ct_dbdp.dbflags != LF_ISSET(DB_SERVER_DBFLAGS))
429			continue;
430		if (db_ctp->ct_dbdp.setflags != 0 &&
431		    ctp->ct_dbdp.setflags != db_ctp->ct_dbdp.setflags)
432			continue;
433		if (name == NULL || ctp->ct_dbdp.db == NULL ||
434		    strcmp(name, ctp->ct_dbdp.db) != 0)
435			continue;
436		if (subdb != ctp->ct_dbdp.subdb &&
437		    (subdb == NULL || ctp->ct_dbdp.subdb == NULL ||
438		    strcmp(subdb, ctp->ct_dbdp.subdb) != 0))
439			continue;
440		/*
441		 * If we get here, then we match.
442		 */
443		ctp->ct_refcount++;
444		return (ctp);
445	}
446
447	return (NULL);
448}
449
450extern "C" ct_entry *
451__dbsrv_shareenv(ct_entry *env_ctp, home_entry *home, u_int32_t flags)
452{
453	ct_entry *ctp;
454
455	/*
456	 * Check if we can share an env.  Criteria for sharing are:
457	 * Must be an env ctp, obviously.
458	 * Must share the same home env.
459	 * All flags must match.
460	 */
461	for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL;
462	    ctp = LIST_NEXT(ctp, entries)) {
463		/*
464		 * Skip ourselves.
465		 */
466		if (ctp == env_ctp)
467			continue;
468		if (ctp->ct_type != CT_ENV)
469			continue;
470		if (ctp->ct_envdp.home != home)
471			continue;
472		if (ctp->ct_envdp.envflags != flags)
473			continue;
474		if (ctp->ct_envdp.onflags != env_ctp->ct_envdp.onflags)
475			continue;
476		if (ctp->ct_envdp.offflags != env_ctp->ct_envdp.offflags)
477			continue;
478		/*
479		 * If we get here, then we match.  The only thing left to
480		 * check is the timeout.  Since the server timeout set by
481		 * the client is a hint, for sharing we'll give them the
482		 * benefit of the doubt and grant them the longer timeout.
483		 */
484		if (ctp->ct_timeout < env_ctp->ct_timeout)
485			ctp->ct_timeout = env_ctp->ct_timeout;
486		ctp->ct_refcount++;
487		return (ctp);
488	}
489
490	return (NULL);
491}
492
493extern "C" void
494__dbsrv_active(ct_entry *ctp)
495{
496	time_t t;
497	ct_entry *envctp;
498
499	if (ctp == NULL)
500		return;
501	if ((t = time(NULL)) == -1)
502		return;
503	*(ctp->ct_activep) = t;
504	if ((envctp = ctp->ct_envparent) == NULL)
505		return;
506	*(envctp->ct_activep) = t;
507	return;
508}
509
510extern "C" int
511__db_close_int(long id, u_int32_t flags)
512{
513	Db *dbp;
514	int ret;
515	ct_entry *ctp;
516
517	ret = 0;
518	ctp = get_tableent(id);
519	if (ctp == NULL)
520		return (DB_NOSERVER_ID);
521	DB_ASSERT(NULL, ctp->ct_type == CT_DB);
522	if (__dbsrv_verbose && ctp->ct_refcount != 1)
523		printf("Deref'ing dbp id %ld, refcount %d\n",
524		    id, ctp->ct_refcount);
525	if (--ctp->ct_refcount != 0)
526		return (ret);
527	dbp = ctp->ct_dbp;
528	if (__dbsrv_verbose)
529		printf("Closing dbp id %ld\n", id);
530
531	ret = dbp->close(flags);
532	__dbdel_ctp(ctp);
533	return (ret);
534}
535
536extern "C" int
537__dbc_close_int(ct_entry *dbc_ctp)
538{
539	Dbc *dbc;
540	int ret;
541	ct_entry *ctp;
542
543	dbc = (Dbc *)dbc_ctp->ct_anyp;
544
545	ret = dbc->close();
546	/*
547	 * If this cursor is a join cursor then we need to fix up the
548	 * cursors that it was joined from so that they are independent again.
549	 */
550	if (dbc_ctp->ct_type & CT_JOINCUR)
551		for (ctp = LIST_FIRST(&__dbsrv_head); ctp != NULL;
552		    ctp = LIST_NEXT(ctp, entries)) {
553			/*
554			 * Test if it is a join cursor, and if it is part
555			 * of this one.
556			 */
557			if ((ctp->ct_type & CT_JOIN) &&
558			    ctp->ct_activep == &dbc_ctp->ct_active) {
559				ctp->ct_type &= ~CT_JOIN;
560				ctp->ct_activep = ctp->ct_origp;
561				__dbsrv_active(ctp);
562			}
563		}
564	__dbclear_ctp(dbc_ctp);
565	return (ret);
566
567}
568
569extern "C" int
570__env_close_int(long id, u_int32_t flags, int force)
571{
572	DbEnv *dbenv;
573	int ret;
574	ct_entry *ctp, *dbctp, *nextctp;
575
576	ret = 0;
577	ctp = get_tableent(id);
578	if (ctp == NULL)
579		return (DB_NOSERVER_ID);
580	DB_ASSERT(NULL, ctp->ct_type == CT_ENV);
581	if (__dbsrv_verbose && ctp->ct_refcount != 1)
582		printf("Deref'ing env id %ld, refcount %d\n",
583		    id, ctp->ct_refcount);
584	/*
585	 * If we are timing out, we need to force the close, no matter
586	 * what the refcount.
587	 */
588	if (--ctp->ct_refcount != 0 && !force)
589		return (ret);
590	dbenv = ctp->ct_envp;
591	if (__dbsrv_verbose)
592		printf("Closing env id %ld\n", id);
593
594	/*
595	 * If we're timing out an env, we want to close all of its
596	 * database handles as well.  All of the txns and cursors
597	 * must have been timed out prior to timing out the env.
598	 */
599	if (force)
600		for (dbctp = LIST_FIRST(&__dbsrv_head);
601		    dbctp != NULL; dbctp = nextctp) {
602			nextctp = LIST_NEXT(dbctp, entries);
603			if (dbctp->ct_type != CT_DB)
604				continue;
605			if (dbctp->ct_envparent != ctp)
606				continue;
607			/*
608			 * We found a DB handle that is part of this
609			 * environment.  Close it.
610			 */
611			__db_close_int(dbctp->ct_id, 0);
612			/*
613			 * If we timed out a dbp, we may have removed
614			 * multiple ctp entries.  Start over with a
615			 * guaranteed good ctp.
616			 */
617			nextctp = LIST_FIRST(&__dbsrv_head);
618		}
619
620	ret = dbenv->close(flags);
621	__dbdel_ctp(ctp);
622	return (ret);
623}
624
625static int
626add_home(char *home)
627{
628	home_entry *hp, *homep;
629	int ret;
630
631	if ((ret = __os_malloc(NULL, sizeof(home_entry), &hp)) != 0)
632		return (ret);
633	if ((ret = __os_malloc(NULL, strlen(home)+1, &hp->home)) != 0)
634		return (ret);
635	memcpy(hp->home, home, strlen(home)+1);
636	hp->dir = home;
637	hp->passwd = NULL;
638	/*
639	 * This loop is to remove any trailing path separators,
640	 * to assure hp->name points to the last component.
641	 */
642	hp->name = __db_rpath(home);
643	if (hp->name != NULL) {
644		*(hp->name) = '\0';
645		hp->name++;
646	} else
647		hp->name = home;
648	while (*(hp->name) == '\0') {
649		hp->name = __db_rpath(home);
650		*(hp->name) = '\0';
651		hp->name++;
652	}
653	/*
654	 * Now we have successfully added it.  Make sure there are no
655	 * identical names.
656	 */
657	for (homep = LIST_FIRST(&__dbsrv_home); homep != NULL;
658	    homep = LIST_NEXT(homep, entries))
659		if (strcmp(homep->name, hp->name) == 0) {
660			printf("Already added home name %s, at directory %s\n",
661			    hp->name, homep->dir);
662			return (-1);
663		}
664	LIST_INSERT_HEAD(&__dbsrv_home, hp, entries);
665	if (__dbsrv_verbose)
666		printf("Added home %s in dir %s\n", hp->name, hp->dir);
667	return (0);
668}
669
670static int
671add_passwd(char *passwd)
672{
673	home_entry *hp;
674
675	/*
676	 * We add the passwd to the last given home dir.  If there
677	 * isn't a home dir, or the most recent one already has a
678	 * passwd, then there is a user error.
679	 */
680	hp = LIST_FIRST(&__dbsrv_home);
681	if (hp == NULL || hp->passwd != NULL)
682		return (EINVAL);
683	/*
684	 * We've already strdup'ed the passwd above, so we don't need
685	 * to malloc new space, just point to it.
686	 */
687	hp->passwd = passwd;
688	return (0);
689}
690
691extern "C" home_entry *
692get_fullhome(char *name)
693{
694	home_entry *hp;
695
696	if (name == NULL)
697		return (NULL);
698
699	for (hp = LIST_FIRST(&__dbsrv_home); hp != NULL;
700	    hp = LIST_NEXT(hp, entries))
701		if (strcmp(name, hp->name) == 0)
702			return (hp);
703	return (NULL);
704}
705
706static int
707env_recover(char *progname)
708{
709	DbEnv *dbenv;
710	home_entry *hp;
711	u_int32_t flags;
712	int exitval, ret;
713
714	for (hp = LIST_FIRST(&__dbsrv_home); hp != NULL;
715	    hp = LIST_NEXT(hp, entries)) {
716		exitval = 0;
717		dbenv = new DbEnv(DB_CXX_NO_EXCEPTIONS);
718		if (__dbsrv_verbose == 1)
719			(void)dbenv->set_verbose(DB_VERB_RECOVERY, 1);
720		dbenv->set_errfile(stderr);
721		dbenv->set_errpfx(progname);
722		if (hp->passwd != NULL)
723			(void)dbenv->set_encrypt(hp->passwd, DB_ENCRYPT_AES);
724
725		/*
726		 * Initialize the env with DB_RECOVER.  That is all we
727		 * have to do to run recovery.
728		 */
729		if (__dbsrv_verbose)
730			printf("Running recovery on %s\n", hp->home);
731		flags = DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL |
732		    DB_INIT_TXN | DB_USE_ENVIRON | DB_RECOVER;
733		if ((ret = dbenv->open(hp->home, flags, 0)) != 0) {
734			dbenv->err(ret, "DbEnv->open");
735			goto error;
736		}
737
738		if (0) {
739error:			exitval = 1;
740		}
741		if ((ret = dbenv->close(0)) != 0) {
742			exitval = 1;
743			fprintf(stderr, "%s: dbenv->close: %s\n",
744			    progname, db_strerror(ret));
745		}
746		if (exitval)
747			return (exitval);
748	}
749	return (0);
750}
751