Import BSDDB 4.7.25 (as of svn r89086)
This commit is contained in:
510
db/db_upg.c
Normal file
510
db/db_upg.c
Normal file
@@ -0,0 +1,510 @@
|
||||
/*-
|
||||
* See the file LICENSE for redistribution information.
|
||||
*
|
||||
* Copyright (c) 1996,2008 Oracle. All rights reserved.
|
||||
*
|
||||
* $Id: db_upg.c 63573 2008-05-23 21:43:21Z trent.nelson $
|
||||
*/
|
||||
|
||||
#include "db_config.h"
|
||||
|
||||
#include "db_int.h"
|
||||
#include "dbinc/db_page.h"
|
||||
#include "dbinc/db_swap.h"
|
||||
#include "dbinc/btree.h"
|
||||
#include "dbinc/hash.h"
|
||||
#include "dbinc/qam.h"
|
||||
|
||||
/*
|
||||
* __db_upgrade_pp --
|
||||
* DB->upgrade pre/post processing.
|
||||
*
|
||||
* PUBLIC: int __db_upgrade_pp __P((DB *, const char *, u_int32_t));
|
||||
*/
|
||||
int
|
||||
__db_upgrade_pp(dbp, fname, flags)
|
||||
DB *dbp;
|
||||
const char *fname;
|
||||
u_int32_t flags;
|
||||
{
|
||||
#ifdef HAVE_UPGRADE_SUPPORT
|
||||
DB_THREAD_INFO *ip;
|
||||
ENV *env;
|
||||
int ret;
|
||||
|
||||
env = dbp->env;
|
||||
|
||||
/*
|
||||
* !!!
|
||||
* The actual argument checking is simple, do it inline.
|
||||
*/
|
||||
if ((ret = __db_fchk(env, "DB->upgrade", flags, DB_DUPSORT)) != 0)
|
||||
return (ret);
|
||||
|
||||
ENV_ENTER(env, ip);
|
||||
ret = __db_upgrade(dbp, fname, flags);
|
||||
ENV_LEAVE(env, ip);
|
||||
return (ret);
|
||||
#else
|
||||
COMPQUIET(dbp, NULL);
|
||||
COMPQUIET(fname, NULL);
|
||||
COMPQUIET(flags, 0);
|
||||
|
||||
__db_errx(dbp->env, "upgrade not supported");
|
||||
return (EINVAL);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef HAVE_UPGRADE_SUPPORT
|
||||
static int (* const func_31_list[P_PAGETYPE_MAX])
|
||||
__P((DB *, char *, u_int32_t, DB_FH *, PAGE *, int *)) = {
|
||||
NULL, /* P_INVALID */
|
||||
NULL, /* __P_DUPLICATE */
|
||||
__ham_31_hash, /* P_HASH_UNSORTED */
|
||||
NULL, /* P_IBTREE */
|
||||
NULL, /* P_IRECNO */
|
||||
__bam_31_lbtree, /* P_LBTREE */
|
||||
NULL, /* P_LRECNO */
|
||||
NULL, /* P_OVERFLOW */
|
||||
__ham_31_hashmeta, /* P_HASHMETA */
|
||||
__bam_31_btreemeta, /* P_BTREEMETA */
|
||||
NULL, /* P_QAMMETA */
|
||||
NULL, /* P_QAMDATA */
|
||||
NULL, /* P_LDUP */
|
||||
NULL, /* P_HASH */
|
||||
};
|
||||
|
||||
static int (* const func_46_list[P_PAGETYPE_MAX])
|
||||
__P((DB *, char *, u_int32_t, DB_FH *, PAGE *, int *)) = {
|
||||
NULL, /* P_INVALID */
|
||||
NULL, /* __P_DUPLICATE */
|
||||
__ham_46_hash, /* P_HASH_UNSORTED */
|
||||
NULL, /* P_IBTREE */
|
||||
NULL, /* P_IRECNO */
|
||||
NULL, /* P_LBTREE */
|
||||
NULL, /* P_LRECNO */
|
||||
NULL, /* P_OVERFLOW */
|
||||
__ham_46_hashmeta, /* P_HASHMETA */
|
||||
NULL, /* P_BTREEMETA */
|
||||
NULL, /* P_QAMMETA */
|
||||
NULL, /* P_QAMDATA */
|
||||
NULL, /* P_LDUP */
|
||||
NULL, /* P_HASH */
|
||||
};
|
||||
|
||||
static int __db_page_pass __P((DB *, char *, u_int32_t, int (* const [])
|
||||
(DB *, char *, u_int32_t, DB_FH *, PAGE *, int *), DB_FH *));
|
||||
static int __db_set_lastpgno __P((DB *, char *, DB_FH *));
|
||||
|
||||
/*
|
||||
* __db_upgrade --
|
||||
* Upgrade an existing database.
|
||||
*
|
||||
* PUBLIC: int __db_upgrade __P((DB *, const char *, u_int32_t));
|
||||
*/
|
||||
int
|
||||
__db_upgrade(dbp, fname, flags)
|
||||
DB *dbp;
|
||||
const char *fname;
|
||||
u_int32_t flags;
|
||||
{
|
||||
DBMETA *meta;
|
||||
DB_FH *fhp;
|
||||
ENV *env;
|
||||
size_t n;
|
||||
int ret, t_ret, use_mp_open;
|
||||
u_int8_t mbuf[256], tmpflags;
|
||||
char *real_name;
|
||||
|
||||
use_mp_open = 0;
|
||||
env = dbp->env;
|
||||
fhp = NULL;
|
||||
|
||||
/* Get the real backing file name. */
|
||||
if ((ret = __db_appname(env,
|
||||
DB_APP_DATA, fname, 0, NULL, &real_name)) != 0)
|
||||
return (ret);
|
||||
|
||||
/* Open the file. */
|
||||
if ((ret = __os_open(env, real_name, 0, 0, 0, &fhp)) != 0) {
|
||||
__db_err(env, ret, "%s", real_name);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/* Initialize the feedback. */
|
||||
if (dbp->db_feedback != NULL)
|
||||
dbp->db_feedback(dbp, DB_UPGRADE, 0);
|
||||
|
||||
/*
|
||||
* Read the metadata page. We read 256 bytes, which is larger than
|
||||
* any access method's metadata page and smaller than any disk sector.
|
||||
*/
|
||||
if ((ret = __os_read(env, fhp, mbuf, sizeof(mbuf), &n)) != 0)
|
||||
goto err;
|
||||
|
||||
switch (((DBMETA *)mbuf)->magic) {
|
||||
case DB_BTREEMAGIC:
|
||||
switch (((DBMETA *)mbuf)->version) {
|
||||
case 6:
|
||||
/*
|
||||
* Before V7 not all pages had page types, so we do the
|
||||
* single meta-data page by hand.
|
||||
*/
|
||||
if ((ret =
|
||||
__bam_30_btreemeta(dbp, real_name, mbuf)) != 0)
|
||||
goto err;
|
||||
if ((ret = __os_seek(env, fhp, 0, 0, 0)) != 0)
|
||||
goto err;
|
||||
if ((ret = __os_write(env, fhp, mbuf, 256, &n)) != 0)
|
||||
goto err;
|
||||
/* FALLTHROUGH */
|
||||
case 7:
|
||||
/*
|
||||
* We need the page size to do more. Rip it out of
|
||||
* the meta-data page.
|
||||
*/
|
||||
memcpy(&dbp->pgsize, mbuf + 20, sizeof(u_int32_t));
|
||||
|
||||
if ((ret = __db_page_pass(
|
||||
dbp, real_name, flags, func_31_list, fhp)) != 0)
|
||||
goto err;
|
||||
/* FALLTHROUGH */
|
||||
case 8:
|
||||
if ((ret =
|
||||
__db_set_lastpgno(dbp, real_name, fhp)) != 0)
|
||||
goto err;
|
||||
/* FALLTHROUGH */
|
||||
case 9:
|
||||
break;
|
||||
default:
|
||||
__db_errx(env, "%s: unsupported btree version: %lu",
|
||||
real_name, (u_long)((DBMETA *)mbuf)->version);
|
||||
ret = DB_OLD_VERSION;
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case DB_HASHMAGIC:
|
||||
switch (((DBMETA *)mbuf)->version) {
|
||||
case 4:
|
||||
case 5:
|
||||
/*
|
||||
* Before V6 not all pages had page types, so we do the
|
||||
* single meta-data page by hand.
|
||||
*/
|
||||
if ((ret =
|
||||
__ham_30_hashmeta(dbp, real_name, mbuf)) != 0)
|
||||
goto err;
|
||||
if ((ret = __os_seek(env, fhp, 0, 0, 0)) != 0)
|
||||
goto err;
|
||||
if ((ret = __os_write(env, fhp, mbuf, 256, &n)) != 0)
|
||||
goto err;
|
||||
|
||||
/*
|
||||
* Before V6, we created hash pages one by one as they
|
||||
* were needed, using hashhdr.ovfl_point to reserve
|
||||
* a block of page numbers for them. A consequence
|
||||
* of this was that, if no overflow pages had been
|
||||
* created, the current doubling might extend past
|
||||
* the end of the database file.
|
||||
*
|
||||
* In DB 3.X, we now create all the hash pages
|
||||
* belonging to a doubling atomically; it's not
|
||||
* safe to just save them for later, because when
|
||||
* we create an overflow page we'll just create
|
||||
* a new last page (whatever that may be). Grow
|
||||
* the database to the end of the current doubling.
|
||||
*/
|
||||
if ((ret =
|
||||
__ham_30_sizefix(dbp, fhp, real_name, mbuf)) != 0)
|
||||
goto err;
|
||||
/* FALLTHROUGH */
|
||||
case 6:
|
||||
/*
|
||||
* We need the page size to do more. Rip it out of
|
||||
* the meta-data page.
|
||||
*/
|
||||
memcpy(&dbp->pgsize, mbuf + 20, sizeof(u_int32_t));
|
||||
|
||||
if ((ret = __db_page_pass(
|
||||
dbp, real_name, flags, func_31_list, fhp)) != 0)
|
||||
goto err;
|
||||
/* FALLTHROUGH */
|
||||
case 7:
|
||||
if ((ret =
|
||||
__db_set_lastpgno(dbp, real_name, fhp)) != 0)
|
||||
goto err;
|
||||
/* FALLTHROUGH */
|
||||
case 8:
|
||||
/*
|
||||
* Any upgrade that has proceeded this far has metadata
|
||||
* pages compatible with hash version 8 metadata pages,
|
||||
* so casting mbuf to a dbmeta is safe.
|
||||
* If a newer revision moves the pagesize, checksum or
|
||||
* encrypt_alg flags in the metadata, then the
|
||||
* extraction of the fields will need to use hard coded
|
||||
* offsets.
|
||||
*/
|
||||
meta = (DBMETA*)mbuf;
|
||||
/*
|
||||
* We need the page size to do more. Extract it from
|
||||
* the meta-data page.
|
||||
*/
|
||||
memcpy(&dbp->pgsize, &meta->pagesize,
|
||||
sizeof(u_int32_t));
|
||||
/*
|
||||
* Rip out metadata and encrypt_alg fields from the
|
||||
* metadata page. So the upgrade can know how big
|
||||
* the page metadata pre-amble is. Any upgrade that has
|
||||
* proceeded this far has metadata pages compatible
|
||||
* with hash version 8 metadata pages, so extracting
|
||||
* the fields is safe.
|
||||
*/
|
||||
memcpy(&tmpflags, &meta->metaflags, sizeof(u_int8_t));
|
||||
if (FLD_ISSET(tmpflags, DBMETA_CHKSUM))
|
||||
F_SET(dbp, DB_AM_CHKSUM);
|
||||
memcpy(&tmpflags, &meta->encrypt_alg, sizeof(u_int8_t));
|
||||
if (tmpflags != 0) {
|
||||
if (!CRYPTO_ON(dbp->env)) {
|
||||
__db_errx(env,
|
||||
"Attempt to upgrade an encrypted database without providing a password.");
|
||||
ret = EINVAL;
|
||||
goto err;
|
||||
}
|
||||
F_SET(dbp, DB_AM_ENCRYPT);
|
||||
}
|
||||
|
||||
/*
|
||||
* This is ugly. It is necessary to have a usable
|
||||
* mpool in the dbp to upgrade from an unsorted
|
||||
* to a sorted hash database. The mpool file is used
|
||||
* to resolve offpage key items, which are needed to
|
||||
* determine sort order. Having mpool open and access
|
||||
* the file does not affect the page pass, since the
|
||||
* page pass only updates DB_HASH_UNSORTED pages
|
||||
* in-place, and the mpool file is only used to read
|
||||
* OFFPAGE items.
|
||||
*/
|
||||
use_mp_open = 1;
|
||||
if ((ret = __os_closehandle(env, fhp)) != 0)
|
||||
return (ret);
|
||||
dbp->type = DB_HASH;
|
||||
if ((ret = __env_mpool(dbp, fname,
|
||||
DB_AM_NOT_DURABLE | DB_AM_VERIFYING)) != 0)
|
||||
return (ret);
|
||||
fhp = dbp->mpf->fhp;
|
||||
|
||||
/* Do the actual conversion pass. */
|
||||
if ((ret = __db_page_pass(
|
||||
dbp, real_name, flags, func_46_list, fhp)) != 0)
|
||||
goto err;
|
||||
|
||||
/* FALLTHROUGH */
|
||||
case 9:
|
||||
break;
|
||||
default:
|
||||
__db_errx(env, "%s: unsupported hash version: %lu",
|
||||
real_name, (u_long)((DBMETA *)mbuf)->version);
|
||||
ret = DB_OLD_VERSION;
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
case DB_QAMMAGIC:
|
||||
switch (((DBMETA *)mbuf)->version) {
|
||||
case 1:
|
||||
/*
|
||||
* If we're in a Queue database, the only page that
|
||||
* needs upgrading is the meta-database page, don't
|
||||
* bother with a full pass.
|
||||
*/
|
||||
if ((ret = __qam_31_qammeta(dbp, real_name, mbuf)) != 0)
|
||||
return (ret);
|
||||
/* FALLTHROUGH */
|
||||
case 2:
|
||||
if ((ret = __qam_32_qammeta(dbp, real_name, mbuf)) != 0)
|
||||
return (ret);
|
||||
if ((ret = __os_seek(env, fhp, 0, 0, 0)) != 0)
|
||||
goto err;
|
||||
if ((ret = __os_write(env, fhp, mbuf, 256, &n)) != 0)
|
||||
goto err;
|
||||
/* FALLTHROUGH */
|
||||
case 3:
|
||||
case 4:
|
||||
break;
|
||||
default:
|
||||
__db_errx(env, "%s: unsupported queue version: %lu",
|
||||
real_name, (u_long)((DBMETA *)mbuf)->version);
|
||||
ret = DB_OLD_VERSION;
|
||||
goto err;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
M_32_SWAP(((DBMETA *)mbuf)->magic);
|
||||
switch (((DBMETA *)mbuf)->magic) {
|
||||
case DB_BTREEMAGIC:
|
||||
case DB_HASHMAGIC:
|
||||
case DB_QAMMAGIC:
|
||||
__db_errx(env,
|
||||
"%s: DB->upgrade only supported on native byte-order systems",
|
||||
real_name);
|
||||
break;
|
||||
default:
|
||||
__db_errx(env,
|
||||
"%s: unrecognized file type", real_name);
|
||||
break;
|
||||
}
|
||||
ret = EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = __os_fsync(env, fhp);
|
||||
|
||||
/*
|
||||
* If mp_open was used, then rely on the database close to clean up
|
||||
* any file handles.
|
||||
*/
|
||||
err: if (use_mp_open == 0 && fhp != NULL &&
|
||||
(t_ret = __os_closehandle(env, fhp)) != 0 && ret == 0)
|
||||
ret = t_ret;
|
||||
__os_free(env, real_name);
|
||||
|
||||
/* We're done. */
|
||||
if (dbp->db_feedback != NULL)
|
||||
dbp->db_feedback(dbp, DB_UPGRADE, 100);
|
||||
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* __db_page_pass --
|
||||
* Walk the pages of the database, upgrading whatever needs it.
|
||||
*/
|
||||
static int
|
||||
__db_page_pass(dbp, real_name, flags, fl, fhp)
|
||||
DB *dbp;
|
||||
char *real_name;
|
||||
u_int32_t flags;
|
||||
int (* const fl[P_PAGETYPE_MAX])
|
||||
__P((DB *, char *, u_int32_t, DB_FH *, PAGE *, int *));
|
||||
DB_FH *fhp;
|
||||
{
|
||||
ENV *env;
|
||||
PAGE *page;
|
||||
db_pgno_t i, pgno_last;
|
||||
size_t n;
|
||||
int dirty, ret;
|
||||
|
||||
env = dbp->env;
|
||||
|
||||
/* Determine the last page of the file. */
|
||||
if ((ret = __db_lastpgno(dbp, real_name, fhp, &pgno_last)) != 0)
|
||||
return (ret);
|
||||
|
||||
/* Allocate memory for a single page. */
|
||||
if ((ret = __os_malloc(env, dbp->pgsize, &page)) != 0)
|
||||
return (ret);
|
||||
|
||||
/* Walk the file, calling the underlying conversion functions. */
|
||||
for (i = 0; i < pgno_last; ++i) {
|
||||
if (dbp->db_feedback != NULL)
|
||||
dbp->db_feedback(
|
||||
dbp, DB_UPGRADE, (int)((i * 100)/pgno_last));
|
||||
if ((ret = __os_seek(env, fhp, i, dbp->pgsize, 0)) != 0)
|
||||
break;
|
||||
if ((ret = __os_read(env, fhp, page, dbp->pgsize, &n)) != 0)
|
||||
break;
|
||||
dirty = 0;
|
||||
/* Always decrypt the page. */
|
||||
if ((ret = __db_decrypt_pg(env, dbp, page)) != 0)
|
||||
break;
|
||||
if (fl[TYPE(page)] != NULL && (ret = fl[TYPE(page)]
|
||||
(dbp, real_name, flags, fhp, page, &dirty)) != 0)
|
||||
break;
|
||||
if (dirty) {
|
||||
if ((ret = __db_encrypt_and_checksum_pg(
|
||||
env, dbp, page)) != 0)
|
||||
break;
|
||||
if ((ret =
|
||||
__os_seek(env, fhp, i, dbp->pgsize, 0)) != 0)
|
||||
break;
|
||||
if ((ret = __os_write(env,
|
||||
fhp, page, dbp->pgsize, &n)) != 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
__os_free(dbp->env, page);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/*
|
||||
* __db_lastpgno --
|
||||
* Return the current last page number of the file.
|
||||
*
|
||||
* PUBLIC: int __db_lastpgno __P((DB *, char *, DB_FH *, db_pgno_t *));
|
||||
*/
|
||||
int
|
||||
__db_lastpgno(dbp, real_name, fhp, pgno_lastp)
|
||||
DB *dbp;
|
||||
char *real_name;
|
||||
DB_FH *fhp;
|
||||
db_pgno_t *pgno_lastp;
|
||||
{
|
||||
ENV *env;
|
||||
db_pgno_t pgno_last;
|
||||
u_int32_t mbytes, bytes;
|
||||
int ret;
|
||||
|
||||
env = dbp->env;
|
||||
|
||||
if ((ret = __os_ioinfo(env,
|
||||
real_name, fhp, &mbytes, &bytes, NULL)) != 0) {
|
||||
__db_err(env, ret, "%s", real_name);
|
||||
return (ret);
|
||||
}
|
||||
|
||||
/* Page sizes have to be a power-of-two. */
|
||||
if (bytes % dbp->pgsize != 0) {
|
||||
__db_errx(env,
|
||||
"%s: file size not a multiple of the pagesize", real_name);
|
||||
return (EINVAL);
|
||||
}
|
||||
pgno_last = mbytes * (MEGABYTE / dbp->pgsize);
|
||||
pgno_last += bytes / dbp->pgsize;
|
||||
|
||||
*pgno_lastp = pgno_last;
|
||||
return (0);
|
||||
}
|
||||
|
||||
/*
|
||||
* __db_set_lastpgno --
|
||||
* Update the meta->last_pgno field.
|
||||
*
|
||||
* Code assumes that we do not have checksums/crypto on the page.
|
||||
*/
|
||||
static int
|
||||
__db_set_lastpgno(dbp, real_name, fhp)
|
||||
DB *dbp;
|
||||
char *real_name;
|
||||
DB_FH *fhp;
|
||||
{
|
||||
DBMETA meta;
|
||||
ENV *env;
|
||||
int ret;
|
||||
size_t n;
|
||||
|
||||
env = dbp->env;
|
||||
if ((ret = __os_seek(env, fhp, 0, 0, 0)) != 0)
|
||||
return (ret);
|
||||
if ((ret = __os_read(env, fhp, &meta, sizeof(meta), &n)) != 0)
|
||||
return (ret);
|
||||
dbp->pgsize = meta.pagesize;
|
||||
if ((ret = __db_lastpgno(dbp, real_name, fhp, &meta.last_pgno)) != 0)
|
||||
return (ret);
|
||||
if ((ret = __os_seek(env, fhp, 0, 0, 0)) != 0)
|
||||
return (ret);
|
||||
if ((ret = __os_write(env, fhp, &meta, sizeof(meta), &n)) != 0)
|
||||
return (ret);
|
||||
|
||||
return (0);
|
||||
}
|
||||
#endif /* HAVE_UPGRADE_SUPPORT */
|
||||
Reference in New Issue
Block a user