mariadb/storage/bdb/dbreg/dbreg_rec.c
2005-12-05 10:27:46 -08:00

421 lines
13 KiB
C

/*-
* See the file LICENSE for redistribution information.
*
* Copyright (c) 1996-2005
* Sleepycat Software. All rights reserved.
*/
/*
* Copyright (c) 1995, 1996
* The President and Fellows of Harvard University. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $Id: dbreg_rec.c,v 12.8 2005/11/09 14:20:32 margo Exp $
*/
#include "db_config.h"
#ifndef NO_SYSTEM_INCLUDES
#include <sys/types.h>
#include <string.h>
#endif
#include "db_int.h"
#include "dbinc/db_page.h"
#include "dbinc/db_shash.h"
#include "dbinc/db_am.h"
#include "dbinc/log.h"
#include "dbinc/mp.h"
#include "dbinc/txn.h"
static int __dbreg_open_file __P((DB_ENV *,
DB_TXN *, __dbreg_register_args *, void *));
/*
* PUBLIC: int __dbreg_register_recover
* PUBLIC: __P((DB_ENV *, DBT *, DB_LSN *, db_recops, void *));
*/
int
__dbreg_register_recover(dbenv, dbtp, lsnp, op, info)
DB_ENV *dbenv;
DBT *dbtp;
DB_LSN *lsnp;
db_recops op;
void *info;
{
DB_ENTRY *dbe;
DB_LOG *dblp;
DB *dbp;
__dbreg_register_args *argp;
int do_close, do_open, do_rem, ret, t_ret;
u_int32_t status;
dblp = dbenv->lg_handle;
dbp = NULL;
#ifdef DEBUG_RECOVER
REC_PRINT(__dbreg_register_print);
#endif
do_open = do_close = 0;
if ((ret = __dbreg_register_read(dbenv, dbtp->data, &argp)) != 0)
goto out;
switch (argp->opcode) {
case DBREG_REOPEN:
case DBREG_PREOPEN:
case DBREG_OPEN:
/*
* In general, we redo the open on REDO and abort on UNDO.
* However, a reopen is a second instance of an open of
* in-memory files and we don't want to close them yet
* on abort, so just skip that here.
*/
if ((DB_REDO(op) ||
op == DB_TXN_OPENFILES || op == DB_TXN_POPENFILES))
do_open = 1;
else if (argp->opcode != DBREG_REOPEN)
do_close = 1;
break;
case DBREG_CLOSE:
if (DB_UNDO(op))
do_open = 1;
else
do_close = 1;
break;
case DBREG_RCLOSE:
/*
* DBREG_RCLOSE was generated by recover because a file was
* left open. The POPENFILES pass, which is run to open
* files to abort prepared transactions, may not include the
* open for this file so we open it here. Note that a normal
* CLOSE is not legal before the prepared transaction is
* committed or aborted.
*/
if (DB_UNDO(op) || op == DB_TXN_POPENFILES)
do_open = 1;
else
do_close = 1;
break;
case DBREG_CHKPNT:
if (DB_UNDO(op) ||
op == DB_TXN_OPENFILES || op == DB_TXN_POPENFILES)
do_open = 1;
break;
default:
DB_ASSERT(0);
ret = EINVAL;
break;
}
if (do_open) {
/*
* We must open the db even if the meta page is not
* yet written as we may be creating subdatabase.
*/
if (op == DB_TXN_OPENFILES && argp->opcode != DBREG_CHKPNT)
F_SET(dblp, DBLOG_FORCE_OPEN);
/*
* During an abort or an open pass to recover prepared txns,
* we need to make sure that we use the same locker id on the
* open. We pass the txnid along to ensure this.
*/
ret = __dbreg_open_file(dbenv,
op == DB_TXN_ABORT || op == DB_TXN_POPENFILES ?
argp->txnid : NULL, argp, info);
if (ret == DB_PAGE_NOTFOUND && argp->meta_pgno != PGNO_BASE_MD)
ret = ENOENT;
if (ret == ENOENT || ret == EINVAL) {
/*
* If this is an OPEN while rolling forward, it's
* possible that the file was recreated since last
* time we got here. In that case, we've got deleted
* set and probably shouldn't, so we need to check
* for that case and possibly retry.
*/
if (op == DB_TXN_FORWARD_ROLL &&
argp->txnid != 0 &&
dblp->dbentry[argp->fileid].deleted) {
dblp->dbentry[argp->fileid].deleted = 0;
ret =
__dbreg_open_file(dbenv, NULL, argp, info);
if (ret == DB_PAGE_NOTFOUND &&
argp->meta_pgno != PGNO_BASE_MD)
ret = ENOENT;
}
/*
* We treat ENOENT as OK since it's possible that
* the file was renamed or deleted.
* All other errors, we return.
*/
if (ret == ENOENT)
ret = 0;
}
F_CLR(dblp, DBLOG_FORCE_OPEN);
}
if (do_close) {
/*
* If we are undoing an open, or redoing a close,
* then we need to close the file. If we are simply
* revoking then we just need to grab the DBP and revoke
* the log id.
*
* If the file is deleted, then we can just ignore this close.
* Otherwise, we should usually have a valid dbp we should
* close or whose reference count should be decremented.
* However, if we shut down without closing a file, we may, in
* fact, not have the file open, and that's OK.
*/
do_rem = 0;
MUTEX_LOCK(dbenv, dblp->mtx_dbreg);
if (argp->fileid < dblp->dbentry_cnt) {
/*
* Typically, closes should match an open which means
* that if this is a close, there should be a valid
* entry in the dbentry table when we get here,
* however there are exceptions. 1. If this is an
* OPENFILES pass, then we may have started from
* a log file other than the first, and the
* corresponding open appears in an earlier file.
* 2. If we are undoing an open on an abort or
* recovery, it's possible that we failed after
* the log record, but before we actually entered
* a handle here.
* 3. If we aborted an open, then we wrote a non-txnal
* RCLOSE into the log. During the forward pass, the
* file won't be open, and that's OK.
*/
dbe = &dblp->dbentry[argp->fileid];
if (dbe->dbp == NULL && !dbe->deleted) {
/* No valid entry here. */
if ((DB_REDO(op) &&
argp->opcode != DBREG_RCLOSE) ||
argp->opcode == DBREG_CHKPNT) {
__db_err(dbenv,
"Warning: Improper file close at %lu/%lu",
(u_long)lsnp->file,
(u_long)lsnp->offset);
}
MUTEX_UNLOCK(dbenv, dblp->mtx_dbreg);
goto done;
}
/* We have either an open entry or a deleted entry. */
if ((dbp = dbe->dbp) != NULL) {
/*
* If we're a replication client, it's
* possible to get here with a dbp that
* the user opened, but which we later
* assigned a fileid to. Be sure that
* we only close dbps that we opened in
* the recovery code or that were opened
* inside a currently aborting transaction.
*/
do_rem = F_ISSET(dbp, DB_AM_RECOVER) ||
op == DB_TXN_ABORT;
MUTEX_UNLOCK(dbenv, dblp->mtx_dbreg);
if (op == DB_TXN_ABORT)
(void)__dbreg_close_id(dbp,
NULL, DBREG_RCLOSE);
else
(void)__dbreg_revoke_id(dbp, 0,
DB_LOGFILEID_INVALID);
} else if (dbe->deleted) {
MUTEX_UNLOCK(dbenv, dblp->mtx_dbreg);
if ((ret = __dbreg_rem_dbentry(
dblp, argp->fileid)) != 0)
goto out;
}
} else
MUTEX_UNLOCK(dbenv, dblp->mtx_dbreg);
/*
* During recovery, all files are closed. On an abort, we only
* close the file if we opened it during the abort
* (DB_AM_RECOVER set), otherwise we simply do a __db_refresh.
* For the close case, if remove or rename has closed the file,
* don't request a sync, because a NULL mpf would be a problem.
*
* If we are undoing a create we'd better discard any buffers
* from the memory pool. We identify creates because the
* argp->id field contains the transaction containing the file
* create; if that id is invalid, we are not creating.
*
* On the backward pass, we need to "undo" opens even if the
* transaction in which they appeared committed, because we have
* already undone the corresponding close. In that case, the
* id will be valid, but we do not want to discard buffers.
*/
if (do_rem && dbp != NULL) {
if (argp->id != TXN_INVALID) {
if ((ret = __db_txnlist_find(dbenv,
info, argp->txnid->txnid, &status))
!= DB_NOTFOUND && ret != 0)
goto out;
if (ret == DB_NOTFOUND || status != TXN_COMMIT)
F_SET(dbp, DB_AM_DISCARD);
ret = 0;
}
if (op == DB_TXN_ABORT &&
!F_ISSET(dbp, DB_AM_RECOVER)) {
if ((t_ret = __db_refresh(dbp,
NULL, DB_NOSYNC, NULL, 0)) != 0 && ret == 0)
ret = t_ret;
} else {
if (op == DB_TXN_APPLY &&
(t_ret = __db_sync(dbp)) != 0 && ret == 0)
ret = t_ret;
if ((t_ret = __db_close(
dbp, NULL, DB_NOSYNC)) != 0 && ret == 0)
ret = t_ret;
}
}
}
done: if (ret == 0)
*lsnp = argp->prev_lsn;
out: if (argp != NULL)
__os_free(dbenv, argp);
return (ret);
}
/*
* __dbreg_open_file --
* Called during log_register recovery. Make sure that we have an
* entry in the dbentry table for this ndx. Returns 0 on success,
* non-zero on error.
*/
static int
__dbreg_open_file(dbenv, txn, argp, info)
DB_ENV *dbenv;
DB_TXN *txn;
__dbreg_register_args *argp;
void *info;
{
DB_ENTRY *dbe;
DB_LOG *dblp;
DB *dbp;
u_int32_t id, status;
int ret;
dblp = (DB_LOG *)dbenv->lg_handle;
/*
* When we're opening, we have to check that the name we are opening
* is what we expect. If it's not, then we close the old file and
* open the new one.
*/
MUTEX_LOCK(dbenv, dblp->mtx_dbreg);
if (argp->fileid != DB_LOGFILEID_INVALID &&
argp->fileid < dblp->dbentry_cnt)
dbe = &dblp->dbentry[argp->fileid];
else
dbe = NULL;
if (dbe != NULL) {
if (dbe->deleted) {
MUTEX_UNLOCK(dbenv, dblp->mtx_dbreg);
return (ENOENT);
}
/*
* At the end of OPENFILES, we may have a file open. If this
* is a reopen, then we will always close and reopen. If the
* open was part of a committed transaction, so it doesn't
* get undone. However, if the fileid was previously used,
* we'll see a close that may need to get undone. There are
* three ways we can detect this. 1) the meta-pgno in the
* current file does not match that of the open file, 2) the
* file uid of the current file does not match that of the
* previously opened file, 3) the current file is unnamed, in
* which case it should never be opened during recovery.
*/
if ((dbp = dbe->dbp) != NULL) {
if (argp->opcode == DBREG_REOPEN ||
dbp->meta_pgno != argp->meta_pgno ||
argp->name.size == 0 ||
memcmp(dbp->fileid, argp->uid.data,
DB_FILE_ID_LEN) != 0) {
MUTEX_UNLOCK(dbenv, dblp->mtx_dbreg);
(void)__dbreg_revoke_id(dbp, 0,
DB_LOGFILEID_INVALID);
if (F_ISSET(dbp, DB_AM_RECOVER))
(void)__db_close(dbp, NULL, DB_NOSYNC);
goto reopen;
}
/*
* We should only get here if we already have the
* dbp from an openfiles pass, in which case, what's
* here had better be the same dbp.
*/
DB_ASSERT(dbe->dbp == dbp);
MUTEX_UNLOCK(dbenv, dblp->mtx_dbreg);
/*
* This is a successful open. We need to record that
* in the txnlist so that we know how to handle the
* subtransaction that created the file system object.
*/
if (argp->id != TXN_INVALID &&
(ret = __db_txnlist_update(dbenv, info,
argp->id, TXN_EXPECTED, NULL, &status, 1)) != 0)
return (ret);
return (0);
}
}
MUTEX_UNLOCK(dbenv, dblp->mtx_dbreg);
reopen:
/*
* We never re-open temporary files. Temp files are only useful during
* aborts in which case the dbp was entered when the file was
* registered. During recovery, we treat temp files as properly deleted
* files, allowing the open to fail and not reporting any errors when
* recovery fails to get a valid dbp from __dbreg_id_to_db.
*/
if (argp->name.size == 0) {
(void)__dbreg_add_dbentry(dbenv, dblp, NULL, argp->fileid);
return (ENOENT);
}
/*
* We are about to pass a recovery txn pointer into the main library.
* We need to make sure that any accessed fields are set appropriately.
*/
if (txn != NULL) {
id = txn->txnid;
memset(txn, 0, sizeof(DB_TXN));
txn->txnid = id;
txn->mgrp = dbenv->tx_handle;
}
return (__dbreg_do_open(dbenv,
txn, dblp, argp->uid.data, argp->name.data, argp->ftype,
argp->fileid, argp->meta_pgno, info, argp->id, argp->opcode));
}