mirror of
https://github.com/MariaDB/server.git
synced 2025-01-22 23:04:20 +01:00
421 lines
13 KiB
C
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));
|
|
}
|