1/*-
2 * See the file LICENSE for redistribution information.
3 *
4 * Copyright (c) 1997,2008 Oracle.  All rights reserved.
5 *
6 * $Id: dbreg_util.c,v 12.40 2008/04/02 17:09:26 sue Exp $
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#include "dbinc/fop.h"
15#include "dbinc/log.h"
16#include "dbinc/mp.h"
17#include "dbinc/txn.h"
18
19static int __dbreg_check_master __P((ENV *, u_int8_t *, char *));
20
21/*
22 * __dbreg_add_dbentry --
23 *	Adds a DB entry to the dbreg DB entry table.
24 *
25 * PUBLIC: int __dbreg_add_dbentry __P((ENV *, DB_LOG *, DB *, int32_t));
26 */
27int
28__dbreg_add_dbentry(env, dblp, dbp, ndx)
29	ENV *env;
30	DB_LOG *dblp;
31	DB *dbp;
32	int32_t ndx;
33{
34	int32_t i;
35	int ret;
36
37	ret = 0;
38
39	MUTEX_LOCK(env, dblp->mtx_dbreg);
40
41	/*
42	 * Check if we need to grow the table.  Note, ndx is 0-based (the
43	 * index into the DB entry table) an dbentry_cnt is 1-based, the
44	 * number of available slots.
45	 */
46	if (dblp->dbentry_cnt <= ndx) {
47		if ((ret = __os_realloc(env,
48		    (size_t)(ndx + DB_GROW_SIZE) * sizeof(DB_ENTRY),
49		    &dblp->dbentry)) != 0)
50			goto err;
51
52		/* Initialize the new entries. */
53		for (i = dblp->dbentry_cnt; i < ndx + DB_GROW_SIZE; i++) {
54			dblp->dbentry[i].dbp = NULL;
55			dblp->dbentry[i].deleted = 0;
56		}
57		dblp->dbentry_cnt = i;
58	}
59
60	DB_ASSERT(env, dblp->dbentry[ndx].dbp == NULL);
61	dblp->dbentry[ndx].deleted = dbp == NULL;
62	dblp->dbentry[ndx].dbp = dbp;
63
64err:	MUTEX_UNLOCK(env, dblp->mtx_dbreg);
65	return (ret);
66}
67
68/*
69 * __dbreg_rem_dbentry
70 *	Remove an entry from the DB entry table.
71 *
72 * PUBLIC: int __dbreg_rem_dbentry __P((DB_LOG *, int32_t));
73 */
74int
75__dbreg_rem_dbentry(dblp, ndx)
76	DB_LOG *dblp;
77	int32_t ndx;
78{
79	MUTEX_LOCK(dblp->env, dblp->mtx_dbreg);
80	if (dblp->dbentry_cnt > ndx) {
81		dblp->dbentry[ndx].dbp = NULL;
82		dblp->dbentry[ndx].deleted = 0;
83	}
84	MUTEX_UNLOCK(dblp->env, dblp->mtx_dbreg);
85
86	return (0);
87}
88
89/*
90 * __dbreg_log_files --
91 *	Put a DBREG_CHKPNT/CLOSE log record for each open database.
92 *
93 * PUBLIC: int __dbreg_log_files __P((ENV *, u_int32_t));
94 */
95int
96__dbreg_log_files(env, opcode)
97	ENV *env;
98	u_int32_t opcode;
99{
100	DBT *dbtp, fid_dbt, t;
101	DB_LOG *dblp;
102	DB_LSN r_unused;
103	FNAME *fnp;
104	LOG *lp;
105	int ret;
106
107	dblp = env->lg_handle;
108	lp = dblp->reginfo.primary;
109
110	ret = 0;
111
112	MUTEX_LOCK(env, lp->mtx_filelist);
113
114	SH_TAILQ_FOREACH(fnp, &lp->fq, q, __fname) {
115		/* This id was revoked by a switch in replication master. */
116		if (fnp->id == DB_LOGFILEID_INVALID)
117			continue;
118		if (fnp->fname_off == INVALID_ROFF)
119			dbtp = NULL;
120		else {
121			memset(&t, 0, sizeof(t));
122			t.data = R_ADDR(&dblp->reginfo, fnp->fname_off);
123			t.size = (u_int32_t)strlen(t.data) + 1;
124			dbtp = &t;
125		}
126		memset(&fid_dbt, 0, sizeof(fid_dbt));
127		fid_dbt.data = fnp->ufid;
128		fid_dbt.size = DB_FILE_ID_LEN;
129		/*
130		 * Output DBREG_CHKPNT records which will be processed during
131		 * the OPENFILES pass of recovery.  At the end of recovery we
132		 * want to output the files that were open so a future recovery
133		 * run will have the correct files open during a backward pass.
134		 * For this we output DBREG_RCLOSE records so the files will be
135		 * closed on the forward pass.
136		 */
137		if ((ret = __dbreg_register_log(env, NULL, &r_unused,
138		    F_ISSET(fnp, DB_FNAME_DURABLE) ? 0 : DB_LOG_NOT_DURABLE,
139		    opcode,
140		    dbtp, &fid_dbt, fnp->id, fnp->s_type, fnp->meta_pgno,
141		    TXN_INVALID)) != 0)
142			break;
143	}
144
145	MUTEX_UNLOCK(env, lp->mtx_filelist);
146
147	return (ret);
148}
149
150/*
151 * __dbreg_close_files --
152 *	Remove the id's of open files and actually close those
153 *	files that were opened by the recovery daemon.  We sync the
154 *	file, unless its mpf pointer has been NULLed by a db_remove or
155 *	db_rename.  We may not have flushed the log_register record that
156 *	closes the file.
157 *
158 * PUBLIC: int __dbreg_close_files __P((ENV *, int));
159 */
160int
161__dbreg_close_files(env, do_restored)
162	ENV *env;
163	int do_restored;
164{
165	DB *dbp;
166	DB_LOG *dblp;
167	int ret, t_ret;
168	int32_t i;
169
170	/* If we haven't initialized logging, we have nothing to do. */
171	if (!LOGGING_ON(env))
172		return (0);
173
174	dblp = env->lg_handle;
175	ret = 0;
176
177	MUTEX_LOCK(env, dblp->mtx_dbreg);
178	for (i = 0; i < dblp->dbentry_cnt; i++) {
179		/*
180		 * We only want to close dbps that recovery opened.  Any
181		 * dbps that weren't opened by recovery but show up here
182		 * are about to be unconditionally removed from the table.
183		 * Before doing so, we need to revoke their log fileids
184		 * so that we don't end up leaving around FNAME entries
185		 * for dbps that shouldn't have them.
186		 */
187		if ((dbp = dblp->dbentry[i].dbp) != NULL) {
188			/*
189			 * It's unsafe to call DB->close or revoke_id
190			 * while holding the thread lock, because
191			 * we'll call __dbreg_rem_dbentry and grab it again.
192			 *
193			 * Just drop it.  Since dbreg ids go monotonically
194			 * upward, concurrent opens should be safe, and the
195			 * user should have no business closing files while
196			 * we're in this loop anyway--we're in the process of
197			 * making all outstanding dbps invalid.
198			 */
199			/*
200			 * If we only want to close those FNAMES marked
201			 * as restored, check now.
202			 */
203			if (do_restored &&
204			    !F_ISSET(dbp->log_filename, DB_FNAME_RESTORED))
205				continue;
206			MUTEX_UNLOCK(env, dblp->mtx_dbreg);
207			if (F_ISSET(dbp, DB_AM_RECOVER))
208				t_ret = __db_close(dbp, NULL, DB_NOSYNC);
209			else
210				t_ret = __dbreg_revoke_id(
211				     dbp, 0, DB_LOGFILEID_INVALID);
212			if (ret == 0)
213				ret = t_ret;
214			MUTEX_LOCK(env, dblp->mtx_dbreg);
215		}
216
217		dblp->dbentry[i].deleted = 0;
218		dblp->dbentry[i].dbp = NULL;
219	}
220	MUTEX_UNLOCK(env, dblp->mtx_dbreg);
221	return (ret);
222}
223
224/*
225 * __dbreg_close_file --
226 *	Close a database file opened by recovery.
227 * PUBLIC: int __dbreg_close_file __P((ENV *, FNAME *));
228 */
229int
230__dbreg_close_file(env, fnp)
231	ENV *env;
232	FNAME *fnp;
233{
234	DB *dbp;
235	DB_LOG *dblp;
236
237	dblp = env->lg_handle;
238
239	dbp = dblp->dbentry[fnp->id].dbp;
240	if (dbp == NULL)
241		return (0);
242	DB_ASSERT(env, dbp->log_filename == fnp);
243	DB_ASSERT(env, F_ISSET(dbp, DB_AM_RECOVER));
244	return (__db_close(dbp, NULL, DB_NOSYNC));
245}
246
247/*
248 * __dbreg_mark_restored --
249 *	Mark files when we change replication roles and there are outstanding
250 * prepared txns that may use these files.  These will be invalidated later
251 * when all outstanding prepared txns are resolved.
252 *
253 * PUBLIC: int __dbreg_mark_restored __P((ENV *));
254 */
255int
256__dbreg_mark_restored(env)
257	ENV *env;
258{
259	DB_LOG *dblp;
260	FNAME *fnp;
261	LOG *lp;
262
263	/* If we haven't initialized logging, we have nothing to do. */
264	if (!LOGGING_ON(env))
265		return (0);
266
267	dblp = env->lg_handle;
268	lp = dblp->reginfo.primary;
269
270	MUTEX_LOCK(env, lp->mtx_filelist);
271	SH_TAILQ_FOREACH(fnp, &lp->fq, q, __fname)
272		if (fnp->id != DB_LOGFILEID_INVALID)
273			F_SET(fnp, DB_FNAME_RESTORED);
274
275	MUTEX_UNLOCK(env, lp->mtx_filelist);
276	return (0);
277}
278
279/*
280 * __dbreg_invalidate_files --
281 *	Invalidate files when we change replication roles.  Save the
282 * id so that another process will be able to clean up the information
283 * when it notices.
284 *
285 * PUBLIC: int __dbreg_invalidate_files __P((ENV *, int));
286 */
287int
288__dbreg_invalidate_files(env, do_restored)
289	ENV *env;
290	int do_restored;
291{
292	DB_LOG *dblp;
293	FNAME *fnp;
294	LOG *lp;
295	int ret;
296
297	/* If we haven't initialized logging, we have nothing to do. */
298	if (!LOGGING_ON(env))
299		return (0);
300
301	dblp = env->lg_handle;
302	lp = dblp->reginfo.primary;
303
304	ret = 0;
305	MUTEX_LOCK(env, lp->mtx_filelist);
306	SH_TAILQ_FOREACH(fnp, &lp->fq, q, __fname) {
307		/*
308		 * Normally, skip any file with DB_FNAME_RESTORED
309		 * set.  If do_restored is set, only invalidate
310		 * those files with the flag set and skip all others.
311		 */
312		if (F_ISSET(fnp, DB_FNAME_RESTORED) && !do_restored)
313			continue;
314		if (!F_ISSET(fnp, DB_FNAME_RESTORED) && do_restored)
315			continue;
316		if (fnp->id != DB_LOGFILEID_INVALID) {
317			if ((ret = __dbreg_log_close(env,
318			    fnp, NULL, DBREG_RCLOSE)) != 0)
319				goto err;
320			fnp->old_id = fnp->id;
321			fnp->id = DB_LOGFILEID_INVALID;
322		}
323	}
324err:	MUTEX_UNLOCK(env, lp->mtx_filelist);
325	return (ret);
326}
327
328/*
329 * __dbreg_id_to_db --
330 *	Return the DB corresponding to the specified dbreg id.
331 *
332 * PUBLIC: int __dbreg_id_to_db __P((ENV *, DB_TXN *, DB **, int32_t, int));
333 */
334int
335__dbreg_id_to_db(env, txn, dbpp, ndx, tryopen)
336	ENV *env;
337	DB_TXN *txn;
338	DB **dbpp;
339	int32_t ndx;
340	int tryopen;
341{
342	DB_LOG *dblp;
343	FNAME *fname;
344	int ret;
345	char *name;
346
347	dblp = env->lg_handle;
348	ret = 0;
349
350	MUTEX_LOCK(env, dblp->mtx_dbreg);
351
352	/*
353	 * We take a final parameter that indicates whether we should attempt
354	 * to open the file if no mapping is found.  During recovery, the
355	 * recovery routines all want to try to open the file (and this is
356	 * called from __dbreg_id_to_db), however, if we have a multi-process
357	 * environment where some processes may not have the files open (e.g.,
358	 * XA), then we also get called from __dbreg_assign_id and it's OK if
359	 * there is no mapping.
360	 *
361	 * Under XA, a process different than the one issuing DB operations
362	 * may abort a transaction.  In this case, the "recovery" routines
363	 * are run by a process that does not necessarily have the file open,
364	 * so we we must open the file explicitly.
365	 */
366	if (ndx >= dblp->dbentry_cnt ||
367	    (!dblp->dbentry[ndx].deleted && dblp->dbentry[ndx].dbp == NULL)) {
368		if (!tryopen || F_ISSET(dblp, DBLOG_RECOVER)) {
369			ret = ENOENT;
370			goto err;
371		}
372
373		/*
374		 * __dbreg_id_to_fname acquires the mtx_filelist mutex, which
375		 * we can't safely acquire while we hold the thread lock.  We
376		 * no longer need it anyway--the dbentry table didn't have what
377		 * we needed.
378		 */
379		MUTEX_UNLOCK(env, dblp->mtx_dbreg);
380
381		if (__dbreg_id_to_fname(dblp, ndx, 0, &fname) != 0)
382			/*
383			 * With transactional opens, we may actually have
384			 * closed this file in the transaction in which
385			 * case this will fail too.  Then it's up to the
386			 * caller to reopen the file.
387			 */
388			return (ENOENT);
389
390		/*
391		 * Note that we're relying on fname not to change, even though
392		 * we released the mutex that protects it (mtx_filelist) inside
393		 * __dbreg_id_to_fname.  This should be a safe assumption, the
394		 * other process that has the file open shouldn't be closing it
395		 * while we're trying to abort.
396		 */
397		name = fname->fname_off == INVALID_ROFF ?
398		    NULL : R_ADDR(&dblp->reginfo, fname->fname_off);
399
400		/*
401		 * At this point, we are not holding the thread lock, so exit
402		 * directly instead of going through the exit code at the
403		 * bottom.  If the __dbreg_do_open succeeded, then we don't need
404		 * to do any of the remaining error checking at the end of this
405		 * routine.
406		 * If TXN_INVALID is passed then no txnlist is needed.
407		 */
408		if ((ret = __dbreg_do_open(env, txn, dblp,
409		    fname->ufid, name, fname->s_type, ndx, fname->meta_pgno,
410		    NULL, TXN_INVALID, F_ISSET(fname, DB_FNAME_INMEM) ?
411		    DBREG_REOPEN : DBREG_OPEN)) != 0)
412			return (ret);
413
414		*dbpp = dblp->dbentry[ndx].dbp;
415		return (*dbpp == NULL ? DB_DELETED : 0);
416	}
417
418	/*
419	 * Return DB_DELETED if the file has been deleted (it's not an error).
420	 */
421	if (dblp->dbentry[ndx].deleted) {
422		ret = DB_DELETED;
423		goto err;
424	}
425
426	/* It's an error if we don't have a corresponding writeable DB. */
427	if ((*dbpp = dblp->dbentry[ndx].dbp) == NULL)
428		ret = ENOENT;
429	else
430		/*
431		 * If we are in recovery, then set that the file has
432		 * been written.  It is possible to run recovery,
433		 * find all the pages in their post update state
434		 * in the OS buffer pool, put a checkpoint in the log
435		 * and then crash the system without forcing the pages
436		 * to disk. If this is an in-memory file, we may not have
437		 * an mpf yet.
438		 */
439		if ((*dbpp)->mpf != NULL && (*dbpp)->mpf->mfp != NULL)
440			(*dbpp)->mpf->mfp->file_written = 1;
441
442err:	MUTEX_UNLOCK(env, dblp->mtx_dbreg);
443	return (ret);
444}
445
446/*
447 * __dbreg_id_to_fname --
448 *	Traverse the shared-memory region looking for the entry that
449 *	matches the passed dbreg id.  Returns 0 on success; -1 on error.
450 *
451 * PUBLIC: int __dbreg_id_to_fname __P((DB_LOG *, int32_t, int, FNAME **));
452 */
453int
454__dbreg_id_to_fname(dblp, id, have_lock, fnamep)
455	DB_LOG *dblp;
456	int32_t id;
457	int have_lock;
458	FNAME **fnamep;
459{
460	ENV *env;
461	FNAME *fnp;
462	LOG *lp;
463	int ret;
464
465	env = dblp->env;
466	lp = dblp->reginfo.primary;
467
468	ret = -1;
469
470	if (!have_lock)
471		MUTEX_LOCK(env, lp->mtx_filelist);
472	SH_TAILQ_FOREACH(fnp, &lp->fq, q, __fname)
473		if (fnp->id == id) {
474			*fnamep = fnp;
475			ret = 0;
476			break;
477		}
478	if (!have_lock)
479		MUTEX_UNLOCK(env, lp->mtx_filelist);
480
481	return (ret);
482}
483/*
484 * __dbreg_fid_to_fname --
485 *	Traverse the shared-memory region looking for the entry that
486 *	matches the passed file unique id.  Returns 0 on success; -1 on error.
487 *
488 * PUBLIC: int __dbreg_fid_to_fname __P((DB_LOG *, u_int8_t *, int, FNAME **));
489 */
490int
491__dbreg_fid_to_fname(dblp, fid, have_lock, fnamep)
492	DB_LOG *dblp;
493	u_int8_t *fid;
494	int have_lock;
495	FNAME **fnamep;
496{
497	ENV *env;
498	FNAME *fnp;
499	LOG *lp;
500	int ret;
501
502	env = dblp->env;
503	lp = dblp->reginfo.primary;
504
505	ret = -1;
506
507	if (!have_lock)
508		MUTEX_LOCK(env, lp->mtx_filelist);
509	SH_TAILQ_FOREACH(fnp, &lp->fq, q, __fname)
510		if (memcmp(fnp->ufid, fid, DB_FILE_ID_LEN) == 0) {
511			*fnamep = fnp;
512			ret = 0;
513			break;
514		}
515	if (!have_lock)
516		MUTEX_UNLOCK(env, lp->mtx_filelist);
517
518	return (ret);
519}
520
521/*
522 * __dbreg_get_name
523 *
524 * Interface to get name of registered files.  This is mainly diagnostic
525 * and the name passed could be transient unless there is something
526 * ensuring that the file cannot be closed.
527 *
528 * PUBLIC: int __dbreg_get_name __P((ENV *, u_int8_t *, char **, char **));
529 */
530int
531__dbreg_get_name(env, fid, fnamep, dnamep)
532	ENV *env;
533	u_int8_t *fid;
534	char **fnamep, **dnamep;
535{
536	DB_LOG *dblp;
537	FNAME *fnp;
538
539	dblp = env->lg_handle;
540
541	if (dblp != NULL && __dbreg_fid_to_fname(dblp, fid, 0, &fnp) == 0) {
542		*fnamep = fnp->fname_off == INVALID_ROFF ?
543		    NULL : R_ADDR(&dblp->reginfo, fnp->fname_off);
544		*dnamep = fnp->dname_off == INVALID_ROFF ?
545		    NULL : R_ADDR(&dblp->reginfo, fnp->dname_off);
546		return (0);
547	}
548
549	*fnamep = *dnamep = NULL;
550	return (-1);
551}
552
553/*
554 * __dbreg_do_open --
555 *	Open files referenced in the log.  This is the part of the open that
556 * is not protected by the thread mutex.
557 * PUBLIC: int __dbreg_do_open __P((ENV *,
558 * PUBLIC:     DB_TXN *, DB_LOG *, u_int8_t *, char *, DBTYPE,
559 * PUBLIC:     int32_t, db_pgno_t, void *, u_int32_t, u_int32_t));
560 */
561int
562__dbreg_do_open(env,
563    txn, lp, uid, name, ftype, ndx, meta_pgno, info, id, opcode)
564	ENV *env;
565	DB_TXN *txn;
566	DB_LOG *lp;
567	u_int8_t *uid;
568	char *name;
569	DBTYPE ftype;
570	int32_t ndx;
571	db_pgno_t meta_pgno;
572	void *info;
573	u_int32_t id, opcode;
574{
575	DB *dbp;
576	u_int32_t cstat, ret_stat;
577	int ret, try_inmem;
578	char *dname, *fname;
579
580	cstat = TXN_EXPECTED;
581	fname = name;
582	dname = NULL;
583	try_inmem = 0;
584	if ((ret = __db_create_internal(&dbp, lp->env, 0)) != 0)
585		return (ret);
586
587	/*
588	 * We can open files under a number of different scenarios.
589	 * First, we can open a file during a normal txn_abort, if that file
590	 * was opened and closed during the transaction (as is the master
591	 * database of a sub-database).
592	 * Second, we might be aborting a transaction in XA and not have
593	 * it open in the process that is actually doing the abort.
594	 * Third, we might be in recovery.
595	 * In case 3, there is no locking, so there is no issue.
596	 * In cases 1 and 2, we are guaranteed to already hold any locks
597	 * that we need, since we're still in the same transaction, so by
598	 * setting DB_AM_RECOVER, we guarantee that we don't log and that
599	 * we don't try to acquire locks on behalf of a different locker id.
600	 */
601	F_SET(dbp, DB_AM_RECOVER);
602	if (meta_pgno != PGNO_BASE_MD) {
603		memcpy(dbp->fileid, uid, DB_FILE_ID_LEN);
604		dbp->meta_pgno = meta_pgno;
605	}
606	if (opcode == DBREG_PREOPEN) {
607		dbp->type = ftype;
608		if ((ret = __dbreg_setup(dbp, name, NULL, id)) != 0)
609			goto err;
610		MAKE_INMEM(dbp);
611		goto skip_open;
612	}
613
614	if (opcode == DBREG_REOPEN) {
615		MAKE_INMEM(dbp);
616		fname = NULL;
617		dname = name;
618	}
619
620retry_inmem:
621	if ((ret = __db_open(dbp, NULL, txn, fname, dname, ftype,
622	    DB_DURABLE_UNKNOWN | DB_ODDFILESIZE,
623	    DB_MODE_600, meta_pgno)) == 0) {
624skip_open:
625		/*
626		 * Verify that we are opening the same file that we were
627		 * referring to when we wrote this log record.
628		 */
629		if ((meta_pgno != PGNO_BASE_MD &&
630		    __dbreg_check_master(env, uid, name) != 0) ||
631		    memcmp(uid, dbp->fileid, DB_FILE_ID_LEN) != 0)
632			cstat = TXN_UNEXPECTED;
633		else
634			cstat = TXN_EXPECTED;
635
636		/* Assign the specific dbreg id to this dbp. */
637		if ((ret = __dbreg_assign_id(dbp, ndx)) != 0)
638			goto err;
639
640		/*
641		 * If we successfully opened this file, then we need to
642		 * convey that information to the txnlist so that we
643		 * know how to handle the subtransaction that created
644		 * the file system object.
645		 */
646		if (id != TXN_INVALID)
647			ret = __db_txnlist_update(env,
648			    info, id, cstat, NULL, &ret_stat, 1);
649
650err:		if (cstat == TXN_UNEXPECTED)
651			goto not_right;
652		return (ret);
653	} else if (ret == ENOENT) {
654		/*
655		 * If we are on a checkpoint record and open failed, try
656		 * it as a named in-mem database.  Currently checkpoint
657		 * records do not distinguish between a named in-memory
658		 * database and one on-disk.  Therefore, an internal init
659		 * via replication that is trying to open and access this
660		 * as a named in-men database will not find it on-disk, and
661		 * we need to try to open it in-memory too.
662		 */
663		if (try_inmem == 0 && opcode == DBREG_CHKPNT) {
664			MAKE_INMEM(dbp);
665			fname = NULL;
666			dname = name;
667			try_inmem = 1;
668			goto retry_inmem;
669		} else if (try_inmem != 0)
670			CLR_INMEM(dbp);
671
672		/* Record that the open failed in the txnlist. */
673		if (id != TXN_INVALID)
674			ret = __db_txnlist_update(env, info,
675			    id, TXN_UNEXPECTED, NULL, &ret_stat, 1);
676	}
677not_right:
678	(void)__db_close(dbp, NULL, DB_NOSYNC);
679	/* Add this file as deleted. */
680	(void)__dbreg_add_dbentry(env, lp, NULL, ndx);
681	return (ret);
682}
683
684static int
685__dbreg_check_master(env, uid, name)
686	ENV *env;
687	u_int8_t *uid;
688	char *name;
689{
690	DB *dbp;
691	int ret;
692
693	ret = 0;
694	if ((ret = __db_create_internal(&dbp, env, 0)) != 0)
695		return (ret);
696	F_SET(dbp, DB_AM_RECOVER);
697	ret = __db_open(dbp, NULL, NULL,
698	    name, NULL, DB_BTREE, 0, DB_MODE_600, PGNO_BASE_MD);
699
700	if (ret == 0 && memcmp(uid, dbp->fileid, DB_FILE_ID_LEN) != 0)
701		ret = EINVAL;
702
703	(void)__db_close(dbp, NULL, 0);
704	return (ret);
705}
706
707/*
708 * __dbreg_lazy_id --
709 *	When a replication client gets upgraded to being a replication master,
710 * it may have database handles open that have not been assigned an ID, but
711 * which have become legal to use for logging.
712 *
713 *	This function lazily allocates a new ID for such a function, in a
714 * new transaction created for the purpose.  We need to do this in a new
715 * transaction because we definitely wish to commit the dbreg_register, but
716 * at this point we have no way of knowing whether the log record that incited
717 * us to call this will be part of a committed transaction.
718 *
719 *	We first revoke any old id this handle may have had.  That can happen
720 * if a master becomes a client and then becomes a master again and
721 * there are other processes with valid open handles to this env.
722 *
723 * PUBLIC: int __dbreg_lazy_id __P((DB *));
724 */
725int
726__dbreg_lazy_id(dbp)
727	DB *dbp;
728{
729	DB_LOG *dblp;
730	DB_TXN *txn;
731	ENV *env;
732	FNAME *fnp;
733	LOG *lp;
734	int32_t id;
735	int ret;
736
737	env = dbp->env;
738
739	DB_ASSERT(env, IS_REP_MASTER(env));
740
741	env = dbp->env;
742	dblp = env->lg_handle;
743	lp = dblp->reginfo.primary;
744	fnp = dbp->log_filename;
745
746	/* The mtx_filelist protects the FNAME list and id management. */
747	MUTEX_LOCK(env, lp->mtx_filelist);
748	if (fnp->id != DB_LOGFILEID_INVALID) {
749		MUTEX_UNLOCK(env, lp->mtx_filelist);
750		return (0);
751	}
752	id = DB_LOGFILEID_INVALID;
753	/*
754	 * When we became master we moved the fnp->id to old_id in
755	 * every FNAME structure that was open.  If our id was changed,
756	 * we need to revoke and give back that id.
757	 */
758	if (fnp->old_id != DB_LOGFILEID_INVALID &&
759	    (ret = __dbreg_revoke_id(dbp, 1, DB_LOGFILEID_INVALID)) != 0)
760		goto err;
761	if ((ret = __txn_begin(env, NULL, NULL, &txn, 0)) != 0)
762		goto err;
763
764	if ((ret = __dbreg_get_id(dbp, txn, &id)) != 0) {
765		(void)__txn_abort(txn);
766		goto err;
767	}
768
769	if ((ret = __txn_commit(txn, DB_TXN_NOSYNC)) != 0)
770		goto err;
771
772	/*
773	 * All DB related logging routines check the id value *without*
774	 * holding the mtx_filelist to know whether we need to call
775	 * dbreg_lazy_id to begin with.  We must set the ID after a
776	 * *successful* commit so that there is no possibility of a second
777	 * modification call finding a valid ID in the dbp before the
778	 * dbreg_register and commit records are in the log.
779	 * If there was an error, then we call __dbreg_revoke_id to
780	 * remove the entry from the lists.
781	 */
782	fnp->id = id;
783err:
784	if (ret != 0 && id != DB_LOGFILEID_INVALID)
785		(void)__dbreg_revoke_id(dbp, 1, id);
786	MUTEX_UNLOCK(env, lp->mtx_filelist);
787	return (ret);
788}
789