summaryrefslogtreecommitdiff
path: root/src/qam/qam_verify.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/qam/qam_verify.c')
-rw-r--r--src/qam/qam_verify.c653
1 files changed, 653 insertions, 0 deletions
diff --git a/src/qam/qam_verify.c b/src/qam/qam_verify.c
new file mode 100644
index 00000000..af5ab5db
--- /dev/null
+++ b/src/qam/qam_verify.c
@@ -0,0 +1,653 @@
+/*-
+ * See the file LICENSE for redistribution information.
+ *
+ * Copyright (c) 1999, 2012 Oracle and/or its affiliates. All rights reserved.
+ *
+ * $Id$
+ */
+
+#include "db_config.h"
+
+#include "db_int.h"
+#include "dbinc/db_page.h"
+#include "dbinc/db_verify.h"
+#include "dbinc/db_am.h"
+#include "dbinc/mp.h"
+#include "dbinc/qam.h"
+/*
+ * __qam_vrfy_meta --
+ * Verify the queue-specific part of a metadata page.
+ *
+ * PUBLIC: int __qam_vrfy_meta __P((DB *, VRFY_DBINFO *, QMETA *,
+ * PUBLIC: db_pgno_t, u_int32_t));
+ */
+int
+__qam_vrfy_meta(dbp, vdp, meta, pgno, flags)
+ DB *dbp;
+ VRFY_DBINFO *vdp;
+ QMETA *meta;
+ db_pgno_t pgno;
+ u_int32_t flags;
+{
+ ENV *env;
+ QUEUE *qp;
+ VRFY_PAGEINFO *pip;
+ db_pgno_t *extents, extid, first, last;
+ size_t len;
+ int count, i, isbad, nextents, ret, t_ret;
+ char *buf, **names;
+
+ COMPQUIET(count, 0);
+
+ env = dbp->env;
+ qp = (QUEUE *)dbp->q_internal;
+ extents = NULL;
+ first = last = 0;
+ isbad = 0;
+ buf = NULL;
+ names = NULL;
+
+ if ((ret = __db_vrfy_getpageinfo(vdp, pgno, &pip)) != 0)
+ return (ret);
+
+ /*
+ * Queue can't be used in subdatabases, so if this isn't set
+ * something very odd is going on.
+ */
+ if (!F_ISSET(pip, VRFY_INCOMPLETE))
+ EPRINT((env, DB_STR_A("1146",
+ "Page %lu: queue databases must be one-per-file",
+ "%lu"), (u_long)pgno));
+
+ /*
+ * We have already checked the common fields in __db_vrfy_pagezero.
+ * However, we used the on-disk metadata page, it may have been stale.
+ * We now have the page from mpool, so check that.
+ */
+ if ((ret = __db_vrfy_meta(dbp, vdp, &meta->dbmeta, pgno, flags)) != 0) {
+ if (ret == DB_VERIFY_BAD)
+ isbad = 1;
+ else
+ goto err;
+ }
+
+ /*
+ * Because the metapage pointers are rolled forward by
+ * aborting transactions, the extent of the queue may
+ * extend beyond the allocated pages, so we do
+ * not check that meta_current is within the allocated
+ * pages.
+ */
+
+ /*
+ * re_len: If this is bad, we can't safely verify queue data pages, so
+ * return DB_VERIFY_FATAL
+ */
+ if (DB_ALIGN(meta->re_len + sizeof(QAMDATA) - 1, sizeof(u_int32_t)) *
+ meta->rec_page + QPAGE_SZ(dbp) > dbp->pgsize) {
+ EPRINT((env, DB_STR_A("1147",
+ "Page %lu: queue record length %lu too high for page size and recs/page",
+ "%lu %lu"), (u_long)pgno, (u_long)meta->re_len));
+ ret = DB_VERIFY_FATAL;
+ goto err;
+ } else {
+ /*
+ * We initialize the Queue internal pointer; we may need
+ * it when handling extents. It would get set up in open,
+ * if we called open normally, but we don't.
+ */
+ vdp->re_pad = meta->re_pad;
+ qp->re_pad = (int)meta->re_pad;
+ qp->re_len = vdp->re_len = meta->re_len;
+ qp->rec_page = vdp->rec_page = meta->rec_page;
+ qp->page_ext = vdp->page_ext = meta->page_ext;
+ }
+
+ /*
+ * There's no formal maximum extentsize, and a 0 value represents
+ * no extents, so there's nothing to verify.
+ *
+ * Note that since QUEUE databases can't have subdatabases, it's an
+ * error to see more than one QUEUE metadata page in a single
+ * verifier run. Theoretically, this should really be a structure
+ * rather than a per-page check, but since we're setting qp fields
+ * here (and have only one qp to set) we raise the alarm now if
+ * this assumption fails. (We need the qp info to be reasonable
+ * before we do per-page verification of queue extents.)
+ */
+ if (F_ISSET(vdp, VRFY_QMETA_SET)) {
+ isbad = 1;
+ EPRINT((env, DB_STR_A("1148",
+ "Page %lu: database contains multiple Queue metadata pages",
+ "%lu"), (u_long)pgno));
+ goto err;
+ }
+ F_SET(vdp, VRFY_QMETA_SET);
+ qp->page_ext = meta->page_ext;
+ dbp->pgsize = meta->dbmeta.pagesize;
+ qp->q_meta = pgno;
+ qp->q_root = pgno + 1;
+ vdp->first_recno = meta->first_recno;
+ vdp->last_recno = meta->cur_recno;
+ if (qp->page_ext != 0) {
+ first = QAM_RECNO_EXTENT(dbp, vdp->first_recno);
+ last = QAM_RECNO_EXTENT(dbp, vdp->last_recno);
+ }
+
+ /*
+ * Look in the data directory to see if there are any extents
+ * around that are not in the range of the queue. If so,
+ * then report that and look there if we are salvaging.
+ */
+
+ if ((ret = __db_appname(env,
+ DB_APP_DATA, qp->dir, NULL, &buf)) != 0)
+ goto err;
+ if ((ret = __os_dirlist(env, buf, 0, &names, &count)) != 0)
+ goto err;
+ __os_free(env, buf);
+ buf = NULL;
+
+ /* In-memory dbs cannot have extents. */
+ nextents = 0;
+ if (!F_ISSET(dbp, DB_AM_INMEM)) {
+ len = strlen(QUEUE_EXTENT_HEAD) + strlen(qp->name) + 1;
+ if ((ret = __os_malloc(env, len, &buf)) != 0)
+ goto err;
+ len = (size_t)snprintf(buf, len, QUEUE_EXTENT_HEAD, qp->name);
+ for (i = 0; i < count; i++) {
+ if (strncmp(names[i], buf, len) == 0) {
+ /* Only save extents out of bounds. */
+ extid = (db_pgno_t)strtoul(
+ &names[i][len], NULL, 10);
+ if (qp->page_ext != 0 &&
+ (last > first ?
+ (extid >= first && extid <= last) :
+ (extid >= first || extid <= last)))
+ continue;
+ if (extents == NULL && (ret = __os_malloc(
+ env, (size_t)(count - i) * sizeof(extid),
+ &extents)) != 0)
+ goto err;
+ extents[nextents] = extid;
+ nextents++;
+ }
+ }
+ }
+ if (nextents > 0)
+ __db_errx(env, DB_STR_A("1149",
+ "Warning: %d extra extent files found", "%d"), nextents);
+ vdp->nextents = nextents;
+ vdp->extents = extents;
+
+err: if ((t_ret = __db_vrfy_putpageinfo(env, vdp, pip)) != 0 && ret == 0)
+ ret = t_ret;
+ if (names != NULL)
+ __os_dirfree(env, names, count);
+ if (buf != NULL)
+ __os_free(env, buf);
+ if (ret != 0 && extents != NULL)
+ __os_free(env, extents);
+ if (LF_ISSET(DB_SALVAGE) &&
+ (t_ret = __db_salvage_markdone(vdp, pgno)) != 0 && ret == 0)
+ ret = t_ret;
+ return (ret == 0 && isbad == 1 ? DB_VERIFY_BAD : ret);
+}
+
+/*
+ * __qam_meta2pgset --
+ * For a given Queue meta page, add all of the db's pages to the pgset. Dealing
+ * with extents complicates things, as it is possible for there to be gaps in
+ * the page number sequence (the user could have re-inserted record numbers that
+ * had been on deleted extents) so we test the existence of each extent before
+ * adding its pages to the pgset. If there are no extents, just loop from
+ * first_recno to last_recno.
+ *
+ * PUBLIC: int __qam_meta2pgset __P((DB *, VRFY_DBINFO *, DB *));
+ */
+int
+__qam_meta2pgset(dbp, vdp, pgset)
+ DB *dbp;
+ VRFY_DBINFO *vdp;
+ DB *pgset;
+{
+ DBC *dbc;
+ PAGE *h;
+ db_pgno_t first, last, pgno, pg_ext, stop;
+ int ret, t_ret;
+ u_int32_t i;
+
+ ret = 0;
+ h = NULL;
+ if (vdp->last_recno <= vdp->first_recno)
+ return (0);
+
+ pg_ext = vdp->page_ext;
+
+ first = QAM_RECNO_PAGE(dbp, vdp->first_recno);
+
+ /*
+ * last_recno gives the next recno to be allocated, we want the last
+ * allocated recno.
+ */
+ last = QAM_RECNO_PAGE(dbp, vdp->last_recno - 1);
+
+ if (first == PGNO_INVALID || last == PGNO_INVALID)
+ return (DB_VERIFY_BAD);
+
+ pgno = first;
+ if (first > last)
+ stop = QAM_RECNO_PAGE(dbp, UINT32_MAX);
+ else
+ stop = last;
+
+ /*
+ * If this db doesn't have extents, just add all page numbers from first
+ * to last.
+ */
+ if (pg_ext == 0) {
+ for (pgno = first; pgno <= stop; pgno++)
+ if ((ret = __db_vrfy_pgset_inc(
+ pgset, vdp->thread_info, vdp->txn, pgno)) != 0)
+ break;
+ if (first > last)
+ for (pgno = 1; pgno <= last; pgno++)
+ if ((ret = __db_vrfy_pgset_inc(pgset,
+ vdp->thread_info, vdp->txn, pgno)) != 0)
+ break;
+
+ return (ret);
+ }
+
+ if ((ret = __db_cursor(dbp, vdp->thread_info, NULL, &dbc, 0)) != 0)
+ return (ret);
+ /*
+ * Check if we can get the first page of each extent. If we can, then
+ * add all of that extent's pages to the pgset. If we can't, assume the
+ * extent doesn't exist and don't add any pages, if we're wrong we'll
+ * find the pages in __db_vrfy_walkpages.
+ */
+begin: for (; pgno <= stop; pgno += pg_ext) {
+ if ((ret = __qam_fget(dbc, &pgno, 0, &h)) != 0) {
+ if (ret == ENOENT || ret == DB_PAGE_NOTFOUND) {
+ ret = 0;
+ continue;
+ }
+ goto err;
+ }
+ if ((ret = __qam_fput(dbc, pgno, h, dbp->priority)) != 0)
+ goto err;
+
+ for (i = 0; i < pg_ext && pgno + i <= last; i++)
+ if ((ret = __db_vrfy_pgset_inc(
+ pgset, vdp->thread_info, vdp->txn, pgno + i)) != 0)
+ goto err;
+
+ /* The first recno won't always occur on the first page of the
+ * extent. Back up to the beginning of the extent before the
+ * end of the loop so that the increment works correctly.
+ */
+ if (pgno == first)
+ pgno = pgno % pg_ext + 1;
+ }
+
+ if (first > last) {
+ pgno = 1;
+ first = last;
+ stop = last;
+ goto begin;
+ }
+
+err:
+ if ((t_ret = __dbc_close(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+
+ return (ret);
+}
+
+/*
+ * __qam_vrfy_data --
+ * Verify a queue data page.
+ *
+ * PUBLIC: int __qam_vrfy_data __P((DB *, VRFY_DBINFO *, QPAGE *,
+ * PUBLIC: db_pgno_t, u_int32_t));
+ */
+int
+__qam_vrfy_data(dbp, vdp, h, pgno, flags)
+ DB *dbp;
+ VRFY_DBINFO *vdp;
+ QPAGE *h;
+ db_pgno_t pgno;
+ u_int32_t flags;
+{
+ DB fakedb;
+ struct __queue fakeq;
+ QAMDATA *qp;
+ db_recno_t i;
+
+ /*
+ * Not much to do here, except make sure that flags are reasonable.
+ *
+ * QAM_GET_RECORD assumes a properly initialized q_internal
+ * structure, however, and we don't have one, so we play
+ * some gross games to fake it out.
+ */
+ fakedb.q_internal = &fakeq;
+ fakedb.flags = dbp->flags;
+ fakeq.re_len = vdp->re_len;
+
+ for (i = 0; i < vdp->rec_page; i++) {
+ qp = QAM_GET_RECORD(&fakedb, h, i);
+ if ((u_int8_t *)qp >= (u_int8_t *)h + dbp->pgsize) {
+ EPRINT((dbp->env, DB_STR_A("1150",
+ "Page %lu: queue record %lu extends past end of page",
+ "%lu %lu"), (u_long)pgno, (u_long)i));
+ return (DB_VERIFY_BAD);
+ }
+
+ if (qp->flags & ~(QAM_VALID | QAM_SET)) {
+ EPRINT((dbp->env, DB_STR_A("1151",
+ "Page %lu: queue record %lu has bad flags (%#lx)",
+ "%lu %lu %#lx"), (u_long)pgno, (u_long)i,
+ (u_long)qp->flags));
+ return (DB_VERIFY_BAD);
+ }
+ }
+
+ return (0);
+}
+
+/*
+ * __qam_vrfy_structure --
+ * Verify a queue database structure, such as it is.
+ *
+ * PUBLIC: int __qam_vrfy_structure __P((DB *, VRFY_DBINFO *, u_int32_t));
+ */
+int
+__qam_vrfy_structure(dbp, vdp, flags)
+ DB *dbp;
+ VRFY_DBINFO *vdp;
+ u_int32_t flags;
+{
+ VRFY_PAGEINFO *pip;
+ db_pgno_t i;
+ int ret, isbad;
+
+ isbad = 0;
+
+ if ((ret = __db_vrfy_getpageinfo(vdp, PGNO_BASE_MD, &pip)) != 0)
+ return (ret);
+
+ if (pip->type != P_QAMMETA) {
+ EPRINT((dbp->env, DB_STR_A("1152",
+ "Page %lu: queue database has no meta page", "%lu"),
+ (u_long)PGNO_BASE_MD));
+ isbad = 1;
+ goto err;
+ }
+
+ if ((ret = __db_vrfy_pgset_inc(
+ vdp->pgset, vdp->thread_info, vdp->txn, 0)) != 0)
+ goto err;
+
+ for (i = 1; i <= vdp->last_pgno; i++) {
+ /* Send feedback to the application about our progress. */
+ if (!LF_ISSET(DB_SALVAGE))
+ __db_vrfy_struct_feedback(dbp, vdp);
+
+ if ((ret = __db_vrfy_putpageinfo(dbp->env, vdp, pip)) != 0 ||
+ (ret = __db_vrfy_getpageinfo(vdp, i, &pip)) != 0)
+ return (ret);
+ if (!F_ISSET(pip, VRFY_IS_ALLZEROES) &&
+ pip->type != P_QAMDATA && !F_ISSET(pip, VRFY_NONEXISTENT)) {
+ EPRINT((dbp->env, DB_STR_A("1153",
+ "Page %lu: queue database page of incorrect type %lu",
+ "%lu %lu"), (u_long)i, (u_long)pip->type));
+ isbad = 1;
+ goto err;
+ } else if ((ret = __db_vrfy_pgset_inc(vdp->pgset,
+ vdp->thread_info, vdp->txn, i)) != 0)
+ goto err;
+ }
+
+err: if ((ret = __db_vrfy_putpageinfo(dbp->env, vdp, pip)) != 0)
+ return (ret);
+ return (isbad == 1 ? DB_VERIFY_BAD : 0);
+}
+
+/*
+ * __qam_vrfy_walkqueue --
+ * Do a "walkpages" per-page verification pass over the set of Queue
+ * extent pages.
+ *
+ * PUBLIC: int __qam_vrfy_walkqueue __P((DB *, VRFY_DBINFO *, void *,
+ * PUBLIC: int (*)(void *, const void *), u_int32_t));
+ */
+int
+__qam_vrfy_walkqueue(dbp, vdp, handle, callback, flags)
+ DB *dbp;
+ VRFY_DBINFO *vdp;
+ void *handle;
+ int (*callback) __P((void *, const void *));
+ u_int32_t flags;
+{
+ DBC *dbc;
+ ENV *env;
+ PAGE *h;
+ QUEUE *qp;
+ VRFY_PAGEINFO *pip;
+ db_pgno_t first, i, last, pg_ext, stop;
+ int isbad, nextents, ret, t_ret;
+
+ COMPQUIET(h, NULL);
+
+ env = dbp->env;
+ qp = dbp->q_internal;
+ pip = NULL;
+ pg_ext = qp->page_ext;
+ isbad = ret = t_ret = 0;
+ h = NULL;
+
+ /* If this database has no extents, we've seen all the pages already. */
+ if (pg_ext == 0)
+ return (0);
+
+ first = QAM_RECNO_PAGE(dbp, vdp->first_recno);
+ last = QAM_RECNO_PAGE(dbp, vdp->last_recno);
+
+ i = first;
+ if (first > last)
+ stop = QAM_RECNO_PAGE(dbp, UINT32_MAX);
+ else
+ stop = last;
+ nextents = vdp->nextents;
+
+ /* Verify/salvage each page. */
+ if ((ret = __db_cursor(dbp, vdp->thread_info, NULL, &dbc, 0)) != 0)
+ return (ret);
+begin: for (; i <= stop; i++) {
+ /*
+ * If DB_SALVAGE is set, we inspect our database of completed
+ * pages, and skip any we've already printed in the subdb pass.
+ */
+ if (LF_ISSET(DB_SALVAGE) && (__db_salvage_isdone(vdp, i) != 0))
+ continue;
+ if ((t_ret = __qam_fget(dbc, &i, 0, &h)) != 0) {
+ if (t_ret == ENOENT || t_ret == DB_PAGE_NOTFOUND) {
+ i += (pg_ext - ((i - 1) % pg_ext)) - 1;
+ continue;
+ }
+
+ /*
+ * If an individual page get fails, keep going iff
+ * we're salvaging.
+ */
+ if (LF_ISSET(DB_SALVAGE)) {
+ if (ret == 0)
+ ret = t_ret;
+ continue;
+ }
+ h = NULL;
+ ret = t_ret;
+ goto err;
+ }
+
+ if (LF_ISSET(DB_SALVAGE)) {
+ /*
+ * We pretty much don't want to quit unless a
+ * bomb hits. May as well return that something
+ * was screwy, however.
+ */
+ if ((t_ret = __db_salvage_pg(dbp,
+ vdp, i, h, handle, callback, flags)) != 0) {
+ if (ret == 0)
+ ret = t_ret;
+ isbad = 1;
+ }
+ } else {
+ /*
+ * If we are not salvaging, and we get any error
+ * other than DB_VERIFY_BAD, return immediately;
+ * it may not be safe to proceed. If we get
+ * DB_VERIFY_BAD, keep going; listing more errors
+ * may make it easier to diagnose problems and
+ * determine the magnitude of the corruption.
+ */
+ if ((ret = __db_vrfy_common(dbp,
+ vdp, h, i, flags)) == DB_VERIFY_BAD)
+ isbad = 1;
+ else if (ret != 0)
+ goto err;
+
+ __db_vrfy_struct_feedback(dbp, vdp);
+
+ if ((ret = __db_vrfy_getpageinfo(vdp, i, &pip)) != 0)
+ goto err;
+ if (F_ISSET(pip, VRFY_IS_ALLZEROES))
+ goto put;
+ if (pip->type != P_QAMDATA) {
+ EPRINT((env, DB_STR_A("1154",
+ "Page %lu: queue database page of incorrect type %lu",
+ "%lu %lu"), (u_long)i, (u_long)pip->type));
+ isbad = 1;
+ goto err;
+ }
+ if ((ret = __db_vrfy_pgset_inc(vdp->pgset,
+ vdp->thread_info, vdp->txn, i)) != 0)
+ goto err;
+ if ((ret = __qam_vrfy_data(dbp, vdp,
+ (QPAGE *)h, i, flags)) == DB_VERIFY_BAD)
+ isbad = 1;
+ else if (ret != 0)
+ goto err;
+
+put: if ((ret = __db_vrfy_putpageinfo(env, vdp, pip)) != 0)
+ goto err1;
+ pip = NULL;
+ }
+
+ /* Again, keep going iff we're salvaging. */
+ if ((t_ret = __qam_fput(dbc, i, h, dbp->priority)) != 0) {
+ if (LF_ISSET(DB_SALVAGE)) {
+ if (ret == 0)
+ ret = t_ret;
+ continue;
+ }
+ ret = t_ret;
+ goto err1;
+ }
+ }
+
+ if (first > last) {
+ i = 1;
+ stop = last;
+ first = last;
+ goto begin;
+ }
+
+ /*
+ * Now check to see if there were any lingering
+ * extents and dump their data.
+ */
+ if (LF_ISSET(DB_SALVAGE) && nextents != 0) {
+ nextents--;
+ i = 1 +
+ vdp->extents[nextents] * vdp->page_ext;
+ stop = i + vdp->page_ext;
+ goto begin;
+ }
+
+ if (0) {
+err: if (h != NULL && (t_ret =
+ __qam_fput(dbc, i, h, dbp->priority)) != 0 && ret == 0)
+ ret = t_ret;
+ if (pip != NULL && (t_ret =
+ __db_vrfy_putpageinfo(env, vdp, pip)) != 0 && ret == 0)
+ ret = t_ret;
+ }
+err1: if (dbc != NULL && (t_ret = __dbc_close(dbc)) != 0 && ret == 0)
+ ret = t_ret;
+ return ((isbad == 1 && ret == 0) ? DB_VERIFY_BAD : ret);
+}
+
+/*
+ * __qam_salvage --
+ * Safely dump out all recnos and data on a queue page.
+ *
+ * PUBLIC: int __qam_salvage __P((DB *, VRFY_DBINFO *, db_pgno_t, PAGE *,
+ * PUBLIC: void *, int (*)(void *, const void *), u_int32_t));
+ */
+int
+__qam_salvage(dbp, vdp, pgno, h, handle, callback, flags)
+ DB *dbp;
+ VRFY_DBINFO *vdp;
+ db_pgno_t pgno;
+ PAGE *h;
+ void *handle;
+ int (*callback) __P((void *, const void *));
+ u_int32_t flags;
+{
+ DBT dbt, key;
+ QAMDATA *qp, *qep;
+ db_recno_t recno;
+ int ret, err_ret, t_ret;
+ u_int32_t pagesize, qlen;
+ u_int32_t i;
+
+ memset(&dbt, 0, sizeof(DBT));
+ memset(&key, 0, sizeof(DBT));
+
+ err_ret = ret = 0;
+
+ pagesize = (u_int32_t)dbp->mpf->mfp->pagesize;
+ qlen = ((QUEUE *)dbp->q_internal)->re_len;
+ dbt.size = qlen;
+ key.data = &recno;
+ key.size = sizeof(recno);
+ recno = (pgno - 1) * QAM_RECNO_PER_PAGE(dbp) + 1;
+ i = 0;
+ qep = (QAMDATA *)((u_int8_t *)h + pagesize - qlen);
+ for (qp = QAM_GET_RECORD(dbp, h, i); qp < qep;
+ recno++, i++, qp = QAM_GET_RECORD(dbp, h, i)) {
+ if (F_ISSET(qp, ~(QAM_VALID|QAM_SET)))
+ continue;
+ if (!F_ISSET(qp, QAM_SET))
+ continue;
+
+ if (!LF_ISSET(DB_AGGRESSIVE) && !F_ISSET(qp, QAM_VALID))
+ continue;
+
+ dbt.data = qp->data;
+ if ((ret = __db_vrfy_prdbt(&key,
+ 0, " ", handle, callback, 1, 0, vdp)) != 0)
+ err_ret = ret;
+
+ if ((ret = __db_vrfy_prdbt(&dbt,
+ 0, " ", handle, callback, 0, 0, vdp)) != 0)
+ err_ret = ret;
+ }
+
+ if ((t_ret = __db_salvage_markdone(vdp, pgno)) != 0)
+ return (t_ret);
+ return ((ret == 0 && err_ret != 0) ? err_ret : ret);
+}