📄 db_vrfy.c
字号:
/*- * See the file LICENSE for redistribution information. * * Copyright (c) 2000-2004 * Sleepycat Software. All rights reserved. * * $Id: db_vrfy.c,v 1.138 2004/10/11 18:47:50 bostic 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_swap.h"#include "dbinc/db_verify.h"#include "dbinc/btree.h"#include "dbinc/hash.h"#include "dbinc/lock.h"#include "dbinc/mp.h"#include "dbinc/qam.h"#include "dbinc/txn.h"/* * This is the code for DB->verify, the DB database consistency checker. * For now, it checks all subdatabases in a database, and verifies * everything it knows how to (i.e. it's all-or-nothing, and one can't * check only for a subset of possible problems). */static u_int __db_guesspgsize __P((DB_ENV *, DB_FH *));static int __db_is_valid_magicno __P((u_int32_t, DBTYPE *));static int __db_is_valid_pagetype __P((u_int32_t));static int __db_meta2pgset __P((DB *, VRFY_DBINFO *, db_pgno_t, u_int32_t, DB *));static int __db_salvage_subdbpg __P((DB *, VRFY_DBINFO *, PAGE *, void *, int (*)(void *, const void *), u_int32_t));static int __db_salvage_subdbs __P((DB *, VRFY_DBINFO *, void *, int(*)(void *, const void *), u_int32_t, int *));static int __db_salvage_unknowns __P((DB *, VRFY_DBINFO *, void *, int (*)(void *, const void *), u_int32_t));static int __db_verify __P((DB *, const char *, const char *, void *, int (*)(void *, const void *), u_int32_t));static int __db_verify_arg __P((DB *, const char *, void *, u_int32_t));static int __db_vrfy_freelist __P((DB *, VRFY_DBINFO *, db_pgno_t, u_int32_t));static int __db_vrfy_invalid __P((DB *, VRFY_DBINFO *, PAGE *, db_pgno_t, u_int32_t));static int __db_vrfy_orderchkonly __P((DB *, VRFY_DBINFO *, const char *, const char *, u_int32_t));static int __db_vrfy_pagezero __P((DB *, VRFY_DBINFO *, DB_FH *, u_int32_t));static int __db_vrfy_subdbs __P((DB *, VRFY_DBINFO *, const char *, u_int32_t));static int __db_vrfy_structure __P((DB *, VRFY_DBINFO *, const char *, db_pgno_t, u_int32_t));static int __db_vrfy_walkpages __P((DB *, VRFY_DBINFO *, void *, int (*)(void *, const void *), u_int32_t));#define VERIFY_FLAGS \ (DB_AGGRESSIVE | \ DB_NOORDERCHK | DB_ORDERCHKONLY | DB_PRINTABLE | DB_SALVAGE | DB_UNREF)/* * __db_verify_pp -- * DB->verify public interface. * * PUBLIC: int __db_verify_pp * PUBLIC: __P((DB *, const char *, const char *, FILE *, u_int32_t)); */int__db_verify_pp(dbp, file, database, outfile, flags) DB *dbp; const char *file, *database; FILE *outfile; u_int32_t flags;{ /* * __db_verify_pp is a wrapper to __db_verify_internal, which lets * us pass appropriate equivalents to FILE * in from the non-C APIs. */ return (__db_verify_internal(dbp, file, database, outfile, __db_pr_callback, flags));}/* * __db_verify_internal -- * * PUBLIC: int __db_verify_internal __P((DB *, const char *, * PUBLIC: const char *, void *, int (*)(void *, const void *), u_int32_t)); */int__db_verify_internal(dbp, fname, dname, handle, callback, flags) DB *dbp; const char *fname, *dname; void *handle; int (*callback) __P((void *, const void *)); u_int32_t flags;{ DB_ENV *dbenv; int ret, t_ret; dbenv = dbp->dbenv; PANIC_CHECK(dbenv); DB_ILLEGAL_AFTER_OPEN(dbp, "DB->verify");#ifdef HAVE_FTRUNCATE /* * If we're using ftruncate to abort page-allocation functions, there * should never be unreferenced pages. Always check for unreferenced * pages on those systems. */ if (!LF_ISSET(DB_SALVAGE)) LF_SET(DB_UNREF);#endif if ((ret = __db_verify_arg(dbp, dname, handle, flags)) != 0) goto err; /* * Forbid working in an environment that uses transactions or * locking; we're going to be looking at the file freely, * and while we're not going to modify it, we aren't obeying * locking conventions either. */ if (TXN_ON(dbenv) || LOCKING_ON(dbenv) || LOGGING_ON(dbenv)) { __db_err(dbenv, "DB->verify may not be used with transactions, logging, or locking"); ret = EINVAL; goto err; } ret = __db_verify(dbp, fname, dname, handle, callback, flags); /* Db.verify is a DB handle destructor. */err: if ((t_ret = __db_close(dbp, NULL, 0)) != 0 && ret == 0) ret = t_ret; return (ret);}/* * __db_verify_arg -- * Check DB->verify arguments. */static int__db_verify_arg(dbp, dname, handle, flags) DB *dbp; const char *dname; void *handle; u_int32_t flags;{ DB_ENV *dbenv; int ret; dbenv = dbp->dbenv; if ((ret = __db_fchk(dbenv, "DB->verify", flags, VERIFY_FLAGS)) != 0) return (ret); /* * DB_SALVAGE is mutually exclusive with the other flags except * DB_AGGRESSIVE, DB_PRINTABLE. * * DB_AGGRESSIVE and DB_PRINTABLE are only meaningful when salvaging. * * DB_SALVAGE requires an output stream. */ if (LF_ISSET(DB_SALVAGE)) { if (LF_ISSET(~(DB_AGGRESSIVE | DB_PRINTABLE | DB_SALVAGE))) return (__db_ferr(dbenv, "DB->verify", 1)); if (handle == NULL) { __db_err(dbenv, "DB_SALVAGE requires a an output handle"); return (EINVAL); } } else if (LF_ISSET(DB_AGGRESSIVE | DB_PRINTABLE)) return (__db_ferr(dbenv, "DB->verify", 1)); /* * DB_ORDERCHKONLY is mutually exclusive with DB_SALVAGE and * DB_NOORDERCHK, and requires a database name. */ if ((ret = __db_fcchk(dbenv, "DB->verify", flags, DB_ORDERCHKONLY, DB_SALVAGE | DB_NOORDERCHK)) != 0) return (ret); if (LF_ISSET(DB_ORDERCHKONLY) && dname == NULL) { __db_err(dbenv, "DB_ORDERCHKONLY requires a database name"); return (EINVAL); } return (0);}/* * __db_verify -- * Walk the entire file page-by-page, either verifying with or without * dumping in db_dump -d format, or DB_SALVAGE-ing whatever key/data * pairs can be found and dumping them in standard (db_load-ready) * dump format. * * (Salvaging isn't really a verification operation, but we put it * here anyway because it requires essentially identical top-level * code.) * * flags may be 0, DB_NOORDERCHK, DB_ORDERCHKONLY, or DB_SALVAGE * (and optionally DB_AGGRESSIVE). */static int__db_verify(dbp, name, subdb, handle, callback, flags) DB *dbp; const char *name, *subdb; void *handle; int (*callback) __P((void *, const void *)); u_int32_t flags;{ DB_ENV *dbenv; DB_FH *fhp; VRFY_DBINFO *vdp; int has, isbad, ret, t_ret; char *real_name; dbenv = dbp->dbenv; fhp = NULL; vdp = NULL; real_name = NULL; has = ret = isbad = 0; F_SET(dbp, DB_AM_VERIFYING); /* Initialize any feedback function. */ if (!LF_ISSET(DB_SALVAGE) && dbp->db_feedback != NULL) dbp->db_feedback(dbp, DB_VERIFY, 0); /* * We don't know how large the cache is, and if the database * in question uses a small page size--which we don't know * yet!--it may be uncomfortably small for the default page * size [#2143]. However, the things we need temporary * databases for in dbinfo are largely tiny, so using a * 1024-byte pagesize is probably not going to be a big hit, * and will make us fit better into small spaces. */ if ((ret = __db_vrfy_dbinfo_create(dbenv, 1024, &vdp)) != 0) goto err; /* * Note whether the user has requested that we use printable * chars where possible. We won't get here with this flag if * we're not salvaging. */ if (LF_ISSET(DB_PRINTABLE)) F_SET(vdp, SALVAGE_PRINTABLE); /* Find the real name of the file. */ if ((ret = __db_appname(dbenv, DB_APP_DATA, name, 0, NULL, &real_name)) != 0) goto err; /* * Our first order of business is to verify page 0, which is * the metadata page for the master database of subdatabases * or of the only database in the file. We want to do this by hand * rather than just calling __db_open in case it's corrupt--various * things in __db_open might act funny. * * Once we know the metadata page is healthy, I believe that it's * safe to open the database normally and then use the page swapping * code, which makes life easier. */ if ((ret = __os_open(dbenv, real_name, DB_OSO_RDONLY, 0, &fhp)) != 0) goto err; /* Verify the metadata page 0; set pagesize and type. */ if ((ret = __db_vrfy_pagezero(dbp, vdp, fhp, flags)) != 0) { if (ret == DB_VERIFY_BAD) isbad = 1; else goto err; } /* * We can assume at this point that dbp->pagesize and dbp->type are * set correctly, or at least as well as they can be, and that * locking, logging, and txns are not in use. Thus we can trust * the memp code not to look at the page, and thus to be safe * enough to use. * * The dbp is not open, but the file is open in the fhp, and we * cannot assume that __db_open is safe. Call __db_dbenv_setup, * the [safe] part of __db_open that initializes the environment-- * and the mpool--manually. */ if ((ret = __db_dbenv_setup(dbp, NULL, name, TXN_INVALID, DB_ODDFILESIZE | DB_RDONLY)) != 0) return (ret); /* * Set our name in the Queue subsystem; we may need it later * to deal with extents. */ if (dbp->type == DB_QUEUE && (ret = __qam_set_ext_data(dbp, name)) != 0) return (ret); /* Mark the dbp as opened, so that we correctly handle its close. */ F_SET(dbp, DB_AM_OPEN_CALLED); /* Find out the page number of the last page in the database. */ __memp_last_pgno(dbp->mpf, &vdp->last_pgno); /* * DB_ORDERCHKONLY is a special case; our file consists of * several subdatabases, which use different hash, bt_compare, * and/or dup_compare functions. Consequently, we couldn't verify * sorting and hashing simply by calling DB->verify() on the file. * DB_ORDERCHKONLY allows us to come back and check those things; it * requires a subdatabase, and assumes that everything but that * database's sorting/hashing is correct. */ if (LF_ISSET(DB_ORDERCHKONLY)) { ret = __db_vrfy_orderchkonly(dbp, vdp, name, subdb, flags); goto done; } /* * When salvaging, we use a db to keep track of whether we've seen a * given overflow or dup page in the course of traversing normal data. * If in the end we have not, we assume its key got lost and print it * with key "UNKNOWN". */ if (LF_ISSET(DB_SALVAGE)) { if ((ret = __db_salvage_init(vdp)) != 0) return (ret); /* * If we're not being aggressive, attempt to crack subdbs. * "has" will indicate whether the attempt has succeeded * (even in part), meaning that we have some semblance of * subdbs; on the walkpages pass, we print out * whichever data pages we have not seen. */ if (!LF_ISSET(DB_AGGRESSIVE) && (__db_salvage_subdbs(dbp, vdp, handle, callback, flags, &has)) != 0) isbad = 1; /* * If we have subdatabases, we need to signal that if * any keys are found that don't belong to a subdatabase, * they'll need to have an "__OTHER__" subdatabase header * printed first. Flag this. Else, print a header for * the normal, non-subdb database. */ if (has == 1) F_SET(vdp, SALVAGE_PRINTHEADER); } if ((ret = __db_vrfy_walkpages(dbp, vdp, handle, callback, flags)) != 0) { if (ret == DB_VERIFY_BAD) isbad = 1; else goto err; } /* If we're verifying, verify inter-page structure. */ if (!LF_ISSET(DB_SALVAGE) && isbad == 0) if ((ret = __db_vrfy_structure(dbp, vdp, name, 0, flags)) != 0) { if (ret == DB_VERIFY_BAD) isbad = 1; else goto err; } /* * If we're salvaging, output with key UNKNOWN any overflow or dup pages * we haven't been able to put in context. Then destroy the salvager's * state-saving database. */ if (LF_ISSET(DB_SALVAGE)) { if ((ret = __db_salvage_unknowns(dbp, vdp, handle, callback, flags)) != 0) isbad = 1; /* No return value, since there's little we can do. */ __db_salvage_destroy(vdp); }err: if (LF_ISSET(DB_SALVAGE) && (has == 0 || F_ISSET(vdp, SALVAGE_PRINTFOOTER))) (void)__db_prfooter(handle, callback); /* Send feedback that we're done. */done: if (!LF_ISSET(DB_SALVAGE) && dbp->db_feedback != NULL) dbp->db_feedback(dbp, DB_VERIFY, 100); if (fhp != NULL && (t_ret = __os_closehandle(dbenv, fhp)) != 0 && ret == 0) ret = t_ret; if (vdp != NULL && (t_ret = __db_vrfy_dbinfo_destroy(dbenv, vdp)) != 0 && ret == 0) ret = t_ret; if (real_name != NULL) __os_free(dbenv, real_name); /* * DB_VERIFY_FATAL is a private error, translate to a public one. * * If we didn't find a page, it's probably a page number was corrupted. * Return the standard corruption error. * * Otherwise, if we found corruption along the way, set the return. */ if (ret == DB_VERIFY_FATAL || ret == DB_PAGE_NOTFOUND || (ret == 0 && isbad == 1)) ret = DB_VERIFY_BAD; /* Make sure there's a public complaint if we found corruption. */ if (ret != 0) __db_err(dbenv, "%s: %s", name, db_strerror(ret)); return (ret);}/* * __db_vrfy_pagezero -- * Verify the master metadata page. Use seek, read, and a local buffer * rather than the DB paging code, for safety. * * Must correctly (or best-guess) set dbp->type and dbp->pagesize. */static int__db_vrfy_pagezero(dbp, vdp, fhp, flags) DB *dbp; VRFY_DBINFO *vdp; DB_FH *fhp; u_int32_t flags;{ DBMETA *meta; DB_ENV *dbenv; VRFY_PAGEINFO *pip; db_pgno_t freelist; size_t nr; int isbad, ret, swapped; u_int8_t mbuf[DBMETASIZE]; isbad = ret = swapped = 0; freelist = 0; dbenv = dbp->dbenv; meta = (DBMETA *)mbuf; dbp->type = DB_UNKNOWN; /* * Seek to the metadata page. * Note that if we're just starting a verification, dbp->pgsize * may be zero; this is okay, as we want page zero anyway and * 0*0 == 0. */ if ((ret = __os_seek(dbenv, fhp, 0, 0, 0, 0, DB_OS_SEEK_SET)) != 0 || (ret = __os_read(dbenv, fhp, mbuf, DBMETASIZE, &nr)) != 0) { __db_err(dbenv, "Metadata page %lu cannot be read: %s", (u_long)PGNO_BASE_MD, db_strerror(ret)); return (ret); } if (nr != DBMETASIZE) { EPRINT((dbenv, "Page %lu: Incomplete metadata page", (u_long)PGNO_BASE_MD)); return (DB_VERIFY_FATAL); } if ((ret = __db_chk_meta(dbenv, dbp, meta, 1)) != 0) { EPRINT((dbenv, "Page %lu: metadata page corrupted", (u_long)PGNO_BASE_MD)); isbad = 1; if (ret != -1) { EPRINT((dbenv, "Page %lu: could not check metadata page", (u_long)PGNO_BASE_MD)); return (DB_VERIFY_FATAL); } } /* * Check all of the fields that we can. * * 08-11: Current page number. Must == pgno. * Note that endianness doesn't matter--it's zero. */ if (meta->pgno != PGNO_BASE_MD) { isbad = 1; EPRINT((dbenv, "Page %lu: pgno incorrectly set to %lu", (u_long)PGNO_BASE_MD, (u_long)meta->pgno)); }
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -