diff options
Diffstat (limited to 'src/db/db_upg.c')
-rw-r--r-- | src/db/db_upg.c | 527 |
1 files changed, 527 insertions, 0 deletions
diff --git a/src/db/db_upg.c b/src/db/db_upg.c new file mode 100644 index 00000000..de5d0dc7 --- /dev/null +++ b/src/db/db_upg.c @@ -0,0 +1,527 @@ +/*- + * See the file LICENSE for redistribution information. + * + * Copyright (c) 1996, 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_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, DB_STR("0665", "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 */ + NULL, /* P_HEAPMETA */ + NULL, /* P_HEAP */ + NULL, /* P_IHEAP */ +}; + +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 */ + NULL, /* P_HEAPMETA */ + NULL, /* P_HEAP */ + NULL, /* P_IHEAP */ +}; + +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, 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, DB_STR_A("0666", + "%s: unsupported btree version: %lu", "%s %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, DB_STR("0667", +"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, DB_STR_A("0668", + "%s: unsupported hash version: %lu", "%s %lu"), + real_name, (u_long)((DBMETA *)mbuf)->version); + ret = DB_OLD_VERSION; + goto err; + } + break; + case DB_HEAPMAGIC: + /* + * There's no upgrade needed for Heap yet. + */ + 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, DB_STR_A("0669", + "%s: unsupported queue version: %lu", + "%s %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_HEAPMAGIC: + case DB_QAMMAGIC: + __db_errx(env, DB_STR_A("0670", + "%s: DB->upgrade only supported on native byte-order systems", + "%s"), real_name); + break; + default: + __db_errx(env, DB_STR_A("0671", + "%s: unrecognized file type", "%s"), 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, DB_STR_A("0672", + "%s: file size not a multiple of the pagesize", "%s"), + 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 */ |