/*-
 * Copyright (c) 1990, 1993, 1994
 *  The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Margo Seltzer.
 *
 * 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. ***REMOVED*** - see
 *    ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change
 * 4. 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.
 */

#if defined(unix)
#define MY_LSEEK lseek
#else
#define MY_LSEEK new_lseek
extern long new_lseek(int fd, long pos, int start);
#endif

#if defined(LIBC_SCCS) && !defined(lint)
static char sccsid[] = "@(#)hash_page.c 8.7 (Berkeley) 8/16/94";
#endif /* LIBC_SCCS and not lint */

/*
 * PACKAGE:  hashing
 *
 * DESCRIPTION:
 *  Page manipulation for hashing package.
 *
 * ROUTINES:
 *
 * External
 *  __get_page
 *  __add_ovflpage
 * Internal
 *  overflow_page
 *  open_temp
 */
#ifndef macintosh
#include <sys/types.h>
#endif

#if defined(macintosh)
#include <unistd.h>
#endif

#include <errno.h>
#include <fcntl.h>
#if defined(_WIN32) || defined(_WINDOWS)
#include <io.h>
#endif
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#if !defined(_WIN32) && !defined(_WINDOWS) && !defined(macintosh)
#include <unistd.h>
#endif

#include <assert.h>

#include "mcom_db.h"
#include "hash.h"
#include "page.h"
/* #include "extern.h" */

extern int mkstempflags(char *path, int extraFlags);

static uint32 *fetch_bitmap(HTAB *, uint32);
static uint32 first_free(uint32);
static int open_temp(HTAB *);
static uint16 overflow_page(HTAB *);
static void squeeze_key(uint16 *, const DBT *, const DBT *);
static int ugly_split(HTAB *, uint32, BUFHEAD *, BUFHEAD *, int, int);

#define PAGE_INIT(P)                                            \
    {                                                           \
        ((uint16 *)(P))[0] = 0;                                 \
        ((uint16 *)(P))[1] = hashp->BSIZE - 3 * sizeof(uint16); \
        ((uint16 *)(P))[2] = hashp->BSIZE;                      \
    }

/* implement a new lseek using lseek that
 * writes zero's when extending a file
 * beyond the end.
 */
long
new_lseek(int fd, long offset, int origin)
{
    long cur_pos = 0;
    long end_pos = 0;
    long seek_pos = 0;

    if (origin == SEEK_CUR) {
        if (offset < 1)
            return (lseek(fd, offset, SEEK_CUR));

        cur_pos = lseek(fd, 0, SEEK_CUR);

        if (cur_pos < 0)
            return (cur_pos);
    }

    end_pos = lseek(fd, 0, SEEK_END);
    if (end_pos < 0)
        return (end_pos);

    if (origin == SEEK_SET)
        seek_pos = offset;
    else if (origin == SEEK_CUR)
        seek_pos = cur_pos + offset;
    else if (origin == SEEK_END)
        seek_pos = end_pos + offset;
    else {
        assert(0);
        return (-1);
    }

    /* the seek position desired is before the
     * end of the file.  We don't need
     * to do anything special except the seek.
     */
    if (seek_pos <= end_pos)
        return (lseek(fd, seek_pos, SEEK_SET));

    /* the seek position is beyond the end of the
     * file.  Write zero's to the end.
     *
     * we are already at the end of the file so
     * we just need to "write()" zeros for the
     * difference between seek_pos-end_pos and
     * then seek to the position to finish
     * the call
     */
    {
        char buffer[1024];
        long len = seek_pos - end_pos;
        memset(buffer, 0, 1024);
        while (len > 0) {
            if (write(fd, buffer, (size_t)(1024 > len ? len : 1024)) < 0)
                return (-1);
            len -= 1024;
        }
        return (lseek(fd, seek_pos, SEEK_SET));
    }
}

/*
 * This is called AFTER we have verified that there is room on the page for
 * the pair (PAIRFITS has returned true) so we go right ahead and start moving
 * stuff on.
 */
static void
putpair(char *p, const DBT *key, DBT *val)
{
    register uint16 *bp, n, off;

    bp = (uint16 *)p;

    /* Enter the key first. */
    n = bp[0];

    off = OFFSET(bp) - key->size;
    memmove(p + off, key->data, key->size);
    bp[++n] = off;

    /* Now the data. */
    off -= val->size;
    memmove(p + off, val->data, val->size);
    bp[++n] = off;

    /* Adjust page info. */
    bp[0] = n;
    bp[n + 1] = off - ((n + 3) * sizeof(uint16));
    bp[n + 2] = off;
}

/*
 * Returns:
 *   0 OK
 *  -1 error
 */
extern int
dbm_delpair(HTAB *hashp, BUFHEAD *bufp, int ndx)
{
    register uint16 *bp, newoff;
    register int n;
    uint16 pairlen;

    bp = (uint16 *)bufp->page;
    n = bp[0];

    if (bp[ndx + 1] < REAL_KEY)
        return (dbm_big_delete(hashp, bufp));
    if (ndx != 1)
        newoff = bp[ndx - 1];
    else
        newoff = hashp->BSIZE;
    pairlen = newoff - bp[ndx + 1];

    if (ndx != (n - 1)) {
        /* Hard Case -- need to shuffle keys */
        register int i;
        register char *src = bufp->page + (int)OFFSET(bp);
        uint32 dst_offset = (uint32)OFFSET(bp) + (uint32)pairlen;
        register char *dst = bufp->page + dst_offset;
        uint32 length = bp[ndx + 1] - OFFSET(bp);

        /*
         * +-----------+XXX+---------+XXX+---------+---------> +infinity
         * |           |             |             |
         * 0           src_offset    dst_offset    BSIZE
         *
         * Dst_offset is > src_offset, so if src_offset were bad, dst_offset
         * would be too, therefore we check only dst_offset.
         *
         * If dst_offset is >= BSIZE, either OFFSET(bp), or pairlen, or both
         * is corrupted.
         *
         * Once we know dst_offset is < BSIZE, we can subtract it from BSIZE
         * to get an upper bound on length.
         */
        if (dst_offset > (uint32)hashp->BSIZE)
            return (DATABASE_CORRUPTED_ERROR);

        if (length > (uint32)(hashp->BSIZE - dst_offset))
            return (DATABASE_CORRUPTED_ERROR);

        memmove(dst, src, length);

        /* Now adjust the pointers */
        for (i = ndx + 2; i <= n; i += 2) {
            if (bp[i + 1] == OVFLPAGE) {
                bp[i - 2] = bp[i];
                bp[i - 1] = bp[i + 1];
            } else {
                bp[i - 2] = bp[i] + pairlen;
                bp[i - 1] = bp[i + 1] + pairlen;
            }
        }
    }
    /* Finally adjust the page data */
    bp[n] = OFFSET(bp) + pairlen;
    bp[n - 1] = bp[n + 1] + pairlen + 2 * sizeof(uint16);
    bp[0] = n - 2;
    hashp->NKEYS--;

    bufp->flags |= BUF_MOD;
    return (0);
}
/*
 * Returns:
 *   0 ==> OK
 *  -1 ==> Error
 */
extern int
dbm_split_page(HTAB *hashp, uint32 obucket, uint32 nbucket)
{
    register BUFHEAD *new_bufp, *old_bufp;
    register uint16 *ino;
    register uint16 *tmp_uint16_array;
    register char *np;
    DBT key, val;
    uint16 n, ndx;
    int retval;
    uint16 copyto, diff, moved;
    size_t off;
    char *op;

    copyto = (uint16)hashp->BSIZE;
    off = (uint16)hashp->BSIZE;
    old_bufp = dbm_get_buf(hashp, obucket, NULL, 0);
    if (old_bufp == NULL)
        return (-1);
    new_bufp = dbm_get_buf(hashp, nbucket, NULL, 0);
    if (new_bufp == NULL)
        return (-1);

    old_bufp->flags |= (BUF_MOD | BUF_PIN);
    new_bufp->flags |= (BUF_MOD | BUF_PIN);

    ino = (uint16 *)(op = old_bufp->page);
    np = new_bufp->page;

    moved = 0;

    for (n = 1, ndx = 1; n < ino[0]; n += 2) {
        if (ino[n + 1] < REAL_KEY) {
            retval = ugly_split(hashp, obucket, old_bufp, new_bufp,
                                (int)copyto, (int)moved);
            old_bufp->flags &= ~BUF_PIN;
            new_bufp->flags &= ~BUF_PIN;
            return (retval);
        }
        key.data = (uint8 *)op + ino[n];

        /* check here for ino[n] being greater than
         * off.  If it is then the database has
         * been corrupted.
         */
        if (ino[n] > off)
            return (DATABASE_CORRUPTED_ERROR);

        key.size = off - ino[n];

#ifdef DEBUG
        /* make sure the size is positive */
        assert(((int)key.size) > -1);
#endif

        if (dbm_call_hash(hashp, (char *)key.data, key.size) == obucket) {
            /* Don't switch page */
            diff = copyto - off;
            if (diff) {
                copyto = ino[n + 1] + diff;
                memmove(op + copyto, op + ino[n + 1],
                        off - ino[n + 1]);
                ino[ndx] = copyto + ino[n] - ino[n + 1];
                ino[ndx + 1] = copyto;
            } else
                copyto = ino[n + 1];
            ndx += 2;
        } else {
            /* Switch page */
            val.data = (uint8 *)op + ino[n + 1];
            val.size = ino[n] - ino[n + 1];

            /* if the pair doesn't fit something is horribly
             * wrong.  LJM
             */
            tmp_uint16_array = (uint16 *)np;
            if (!PAIRFITS(tmp_uint16_array, &key, &val))
                return (DATABASE_CORRUPTED_ERROR);

            putpair(np, &key, &val);
            moved += 2;
        }

        off = ino[n + 1];
    }

    /* Now clean up the page */
    ino[0] -= moved;
    FREESPACE(ino) = copyto - sizeof(uint16) * (ino[0] + 3);
    OFFSET(ino) = copyto;

#ifdef DEBUG3
    (void)fprintf(stderr, "split %d/%d\n",
                  ((uint16 *)np)[0] / 2,
                  ((uint16 *)op)[0] / 2);
#endif
    /* unpin both pages */
    old_bufp->flags &= ~BUF_PIN;
    new_bufp->flags &= ~BUF_PIN;
    return (0);
}

/*
 * Called when we encounter an overflow or big key/data page during split
 * handling.  This is special cased since we have to begin checking whether
 * the key/data pairs fit on their respective pages and because we may need
 * overflow pages for both the old and new pages.
 *
 * The first page might be a page with regular key/data pairs in which case
 * we have a regular overflow condition and just need to go on to the next
 * page or it might be a big key/data pair in which case we need to fix the
 * big key/data pair.
 *
 * Returns:
 *   0 ==> success
 *  -1 ==> failure
 */

/* the maximum number of loops we will allow UGLY split to chew
 * on before we assume the database is corrupted and throw it
 * away.
 */
#define MAX_UGLY_SPLIT_LOOPS 10000

static int
ugly_split(HTAB *hashp, uint32 obucket, BUFHEAD *old_bufp,
           BUFHEAD *new_bufp, /* Same as __split_page. */ int copyto, int moved)
/* int copyto;   First byte on page which contains key/data values. */
/* int moved;    Number of pairs moved to new page. */
{
    register BUFHEAD *bufp; /* Buffer header for ino */
    register uint16 *ino;   /* Page keys come off of */
    register uint16 *np;    /* New page */
    register uint16 *op;    /* Page keys go on to if they aren't moving */
    uint32 loop_detection = 0;

    BUFHEAD *last_bfp; /* Last buf header OVFL needing to be freed */
    DBT key, val;
    SPLIT_RETURN ret;
    uint16 n, off, ov_addr, scopyto;
    char *cino; /* Character value of ino */
    int status;

    bufp = old_bufp;
    ino = (uint16 *)old_bufp->page;
    np = (uint16 *)new_bufp->page;
    op = (uint16 *)old_bufp->page;
    last_bfp = NULL;
    scopyto = (uint16)copyto; /* ANSI */

    if (ino[0] < 1) {
        return DATABASE_CORRUPTED_ERROR;
    }
    n = ino[0] - 1;
    while (n < ino[0]) {

        /* this function goes nuts sometimes and never returns.
         * I havent found the problem yet but I need a solution
         * so if we loop too often we assume a database curruption error
         * :LJM
         */
        loop_detection++;

        if (loop_detection > MAX_UGLY_SPLIT_LOOPS)
            return DATABASE_CORRUPTED_ERROR;

        if (ino[2] < REAL_KEY && ino[2] != OVFLPAGE) {
            if ((status = dbm_big_split(hashp, old_bufp,
                                        new_bufp, bufp, bufp->addr, obucket, &ret)))
                return (status);
            old_bufp = ret.oldp;
            if (!old_bufp)
                return (-1);
            op = (uint16 *)old_bufp->page;
            new_bufp = ret.newp;
            if (!new_bufp)
                return (-1);
            np = (uint16 *)new_bufp->page;
            bufp = ret.nextp;
            if (!bufp)
                return (0);
            cino = (char *)bufp->page;
            ino = (uint16 *)cino;
            last_bfp = ret.nextp;
        } else if (ino[n + 1] == OVFLPAGE) {
            ov_addr = ino[n];
            /*
             * Fix up the old page -- the extra 2 are the fields
             * which contained the overflow information.
             */
            if (ino[0] < (moved + 2)) {
                return DATABASE_CORRUPTED_ERROR;
            }
            ino[0] -= (moved + 2);
            if (scopyto < sizeof(uint16) * (ino[0] + 3)) {
                return DATABASE_CORRUPTED_ERROR;
            }
            FREESPACE(ino) =
                scopyto - sizeof(uint16) * (ino[0] + 3);
            OFFSET(ino) = scopyto;

            bufp = dbm_get_buf(hashp, ov_addr, bufp, 0);
            if (!bufp)
                return (-1);

            ino = (uint16 *)bufp->page;
            n = 1;
            scopyto = hashp->BSIZE;
            moved = 0;

            if (last_bfp)
                dbm_free_ovflpage(hashp, last_bfp);
            last_bfp = bufp;
        }
        /* Move regular sized pairs of there are any */
        off = hashp->BSIZE;
        for (n = 1; (n < ino[0]) && (ino[n + 1] >= REAL_KEY); n += 2) {
            cino = (char *)ino;
            key.data = (uint8 *)cino + ino[n];
            if (off < ino[n]) {
                return DATABASE_CORRUPTED_ERROR;
            }
            key.size = off - ino[n];
            val.data = (uint8 *)cino + ino[n + 1];
            if (ino[n] < ino[n + 1]) {
                return DATABASE_CORRUPTED_ERROR;
            }
            val.size = ino[n] - ino[n + 1];
            off = ino[n + 1];

            if (dbm_call_hash(hashp, (char *)key.data, key.size) == obucket) {
                /* Keep on old page */
                if (PAIRFITS(op, (&key), (&val)))
                    putpair((char *)op, &key, &val);
                else {
                    old_bufp =
                        dbm_add_ovflpage(hashp, old_bufp);
                    if (!old_bufp)
                        return (-1);
                    op = (uint16 *)old_bufp->page;
                    putpair((char *)op, &key, &val);
                }
                old_bufp->flags |= BUF_MOD;
            } else {
                /* Move to new page */
                if (PAIRFITS(np, (&key), (&val)))
                    putpair((char *)np, &key, &val);
                else {
                    new_bufp =
                        dbm_add_ovflpage(hashp, new_bufp);
                    if (!new_bufp)
                        return (-1);
                    np = (uint16 *)new_bufp->page;
                    putpair((char *)np, &key, &val);
                }
                new_bufp->flags |= BUF_MOD;
            }
        }
    }
    if (last_bfp)
        dbm_free_ovflpage(hashp, last_bfp);
    return (0);
}

/*
 * Add the given pair to the page
 *
 * Returns:
 *  0 ==> OK
 *  1 ==> failure
 */
extern int
dbm_addel(HTAB *hashp, BUFHEAD *bufp, const DBT *key, const DBT *val)
{
    register uint16 *bp, *sop;
    int do_expand;

    bp = (uint16 *)bufp->page;
    do_expand = 0;
    while (bp[0] && (bp[2] < REAL_KEY || bp[bp[0]] < REAL_KEY))
        /* Exception case */
        if (bp[2] == FULL_KEY_DATA && bp[0] == 2)
            /* This is the last page of a big key/data pair
               and we need to add another page */
            break;
        else if (bp[2] < REAL_KEY && bp[bp[0]] != OVFLPAGE) {
            bufp = dbm_get_buf(hashp, bp[bp[0] - 1], bufp, 0);
            if (!bufp) {
#ifdef DEBUG
                assert(0);
#endif
                return (-1);
            }
            bp = (uint16 *)bufp->page;
        } else
            /* Try to squeeze key on this page */
            if (FREESPACE(bp) > PAIRSIZE(key, val)) {
                {
                    squeeze_key(bp, key, val);

                    /* LJM: I added this because I think it was
                     * left out on accident.
                     * if this isn't incremented nkeys will not
                     * be the actual number of keys in the db.
                     */
                    hashp->NKEYS++;
                    return (0);
                }
            } else {
                bufp = dbm_get_buf(hashp, bp[bp[0] - 1], bufp, 0);
                if (!bufp) {
#ifdef DEBUG
                    assert(0);
#endif
                    return (-1);
                }
                bp = (uint16 *)bufp->page;
            }

    if (PAIRFITS(bp, key, val))
        putpair(bufp->page, key, (DBT *)val);
    else {
        do_expand = 1;
        bufp = dbm_add_ovflpage(hashp, bufp);
        if (!bufp) {
#ifdef DEBUG
            assert(0);
#endif
            return (-1);
        }
        sop = (uint16 *)bufp->page;

        if (PAIRFITS(sop, key, val))
            putpair((char *)sop, key, (DBT *)val);
        else if (dbm_big_insert(hashp, bufp, key, val)) {
#ifdef DEBUG
            assert(0);
#endif
            return (-1);
        }
    }
    bufp->flags |= BUF_MOD;
    /*
     * If the average number of keys per bucket exceeds the fill factor,
     * expand the table.
     */
    hashp->NKEYS++;
    if (do_expand ||
        (hashp->NKEYS / (hashp->MAX_BUCKET + 1) > hashp->FFACTOR))
        return (dbm_expand_table(hashp));
    return (0);
}

/*
 *
 * Returns:
 *  pointer on success
 *  NULL on error
 */
extern BUFHEAD *
dbm_add_ovflpage(HTAB *hashp, BUFHEAD *bufp)
{
    register uint16 *sp;
    uint16 ndx, ovfl_num;
#ifdef DEBUG1
    int tmp1, tmp2;
#endif
    sp = (uint16 *)bufp->page;

    /* Check if we are dynamically determining the fill factor */
    if (hashp->FFACTOR == DEF_FFACTOR) {
        hashp->FFACTOR = sp[0] >> 1;
        if (hashp->FFACTOR < MIN_FFACTOR)
            hashp->FFACTOR = MIN_FFACTOR;
    }
    bufp->flags |= BUF_MOD;
    ovfl_num = overflow_page(hashp);
#ifdef DEBUG1
    tmp1 = bufp->addr;
    tmp2 = bufp->ovfl ? bufp->ovfl->addr : 0;
#endif
    if (!ovfl_num || !(bufp->ovfl = dbm_get_buf(hashp, ovfl_num, bufp, 1)))
        return (NULL);
    bufp->ovfl->flags |= BUF_MOD;
#ifdef DEBUG1
    (void)fprintf(stderr, "ADDOVFLPAGE: %d->ovfl was %d is now %d\n",
                  tmp1, tmp2, bufp->ovfl->addr);
#endif
    ndx = sp[0];
    /*
     * Since a pair is allocated on a page only if there's room to add
     * an overflow page, we know that the OVFL information will fit on
     * the page.
     */
    sp[ndx + 4] = OFFSET(sp);
    sp[ndx + 3] = FREESPACE(sp) - OVFLSIZE;
    sp[ndx + 1] = ovfl_num;
    sp[ndx + 2] = OVFLPAGE;
    sp[0] = ndx + 2;
#ifdef HASH_STATISTICS
    hash_overflows++;
#endif
    return (bufp->ovfl);
}

/*
 * Returns:
 *   0 indicates SUCCESS
 *  -1 indicates FAILURE
 */
extern int
dbm_get_page(HTAB *hashp,
             char *p,
             uint32 bucket,
             int is_bucket,
             int is_disk,
             int is_bitmap)
{
    register int fd, page;
    size_t size;
    int rsize;
    uint16 *bp;

    fd = hashp->fp;
    size = hashp->BSIZE;

    if ((fd == -1) || !is_disk) {
        PAGE_INIT(p);
        return (0);
    }
    if (is_bucket)
        page = BUCKET_TO_PAGE(bucket);
    else
        page = OADDR_TO_PAGE(bucket);
    if ((MY_LSEEK(fd, (off_t)page << hashp->BSHIFT, SEEK_SET) == -1) ||
        ((rsize = read(fd, p, size)) == -1))
        return (-1);

    bp = (uint16 *)p;
    if (!rsize)
        bp[0] = 0; /* We hit the EOF, so initialize a new page */
    else if ((unsigned)rsize != size) {
        errno = EFTYPE;
        return (-1);
    }

    if (!is_bitmap && !bp[0]) {
        PAGE_INIT(p);
    } else {

        if (hashp->LORDER != BYTE_ORDER) {
            register int i, max;

            if (is_bitmap) {
                max = hashp->BSIZE >> 2; /* divide by 4 */
                for (i = 0; i < max; i++)
                    M_32_SWAP(((int *)p)[i]);
            } else {
                M_16_SWAP(bp[0]);
                max = bp[0] + 2;

                /* bound the size of max by
                 * the maximum number of entries
                 * in the array
                 */
                if ((unsigned)max > (size / sizeof(uint16)))
                    return (DATABASE_CORRUPTED_ERROR);

                /* do the byte order swap
                 */
                for (i = 1; i <= max; i++)
                    M_16_SWAP(bp[i]);
            }
        }

        /* check the validity of the page here
         * (after doing byte order swaping if necessary)
         */
        if (!is_bitmap && bp[0] != 0) {
            uint16 num_keys = bp[0];
            uint16 offset;
            uint16 i;

            /* bp[0] is supposed to be the number of
             * entries currently in the page.  If
             * bp[0] is too large (larger than the whole
             * page) then the page is corrupted
             */
            if (bp[0] > (size / sizeof(uint16)))
                return (DATABASE_CORRUPTED_ERROR);

            /* bound free space */
            if (FREESPACE(bp) > size)
                return (DATABASE_CORRUPTED_ERROR);

            /* check each key and data offset to make
             * sure they are all within bounds they
             * should all be less than the previous
             * offset as well.
             */
            offset = size;
            for (i = 1; i <= num_keys; i += 2) {
                /* ignore overflow pages etc. */
                if (bp[i + 1] >= REAL_KEY) {

                    if (bp[i] > offset || bp[i + 1] > bp[i])
                        return (DATABASE_CORRUPTED_ERROR);

                    offset = bp[i + 1];
                } else {
                    /* there are no other valid keys after
                     * seeing a non REAL_KEY
                     */
                    break;
                }
            }
        }
    }
    return (0);
}

/*
 * Write page p to disk
 *
 * Returns:
 *   0 ==> OK
 *  -1 ==>failure
 */
extern int
dbm_put_page(HTAB *hashp, char *p, uint32 bucket, int is_bucket, int is_bitmap)
{
    register int fd, page;
    size_t size;
    int wsize;
    off_t offset;

    size = hashp->BSIZE;
    if ((hashp->fp == -1) && open_temp(hashp))
        return (-1);
    fd = hashp->fp;

    if (hashp->LORDER != BYTE_ORDER) {
        register int i;
        register int max;

        if (is_bitmap) {
            max = hashp->BSIZE >> 2; /* divide by 4 */
            for (i = 0; i < max; i++)
                M_32_SWAP(((int *)p)[i]);
        } else {
            max = ((uint16 *)p)[0] + 2;

            /* bound the size of max by
             * the maximum number of entries
             * in the array
             */
            if ((unsigned)max > (size / sizeof(uint16)))
                return (DATABASE_CORRUPTED_ERROR);

            for (i = 0; i <= max; i++)
                M_16_SWAP(((uint16 *)p)[i]);
        }
    }

    if (is_bucket)
        page = BUCKET_TO_PAGE(bucket);
    else
        page = OADDR_TO_PAGE(bucket);
    offset = (off_t)page << hashp->BSHIFT;
    if ((MY_LSEEK(fd, offset, SEEK_SET) == -1) ||
        ((wsize = write(fd, p, size)) == -1))
        /* Errno is set */
        return (-1);
    if ((unsigned)wsize != size) {
        errno = EFTYPE;
        return (-1);
    }
#if defined(_WIN32) || defined(_WINDOWS)
    if (offset + size > hashp->file_size) {
        hashp->updateEOF = 1;
    }
#endif
    /* put the page back the way it was so that it isn't byteswapped
     * if it remains in memory - LJM
     */
    if (hashp->LORDER != BYTE_ORDER) {
        register int i;
        register int max;

        if (is_bitmap) {
            max = hashp->BSIZE >> 2; /* divide by 4 */
            for (i = 0; i < max; i++)
                M_32_SWAP(((int *)p)[i]);
        } else {
            uint16 *bp = (uint16 *)p;

            M_16_SWAP(bp[0]);
            max = bp[0] + 2;

            /* no need to bound the size if max again
             * since it was done already above
             */

            /* do the byte order re-swap
             */
            for (i = 1; i <= max; i++)
                M_16_SWAP(bp[i]);
        }
    }

    return (0);
}

#define BYTE_MASK ((1 << INT_BYTE_SHIFT) - 1)
/*
 * Initialize a new bitmap page.  Bitmap pages are left in memory
 * once they are read in.
 */
extern int
dbm_ibitmap(HTAB *hashp, int pnum, int nbits, int ndx)
{
    uint32 *ip;
    size_t clearbytes, clearints;

    if ((ip = (uint32 *)malloc((size_t)hashp->BSIZE)) == NULL)
        return (1);
    hashp->nmaps++;
    clearints = ((nbits - 1) >> INT_BYTE_SHIFT) + 1;
    clearbytes = clearints << INT_TO_BYTE;
    (void)memset((char *)ip, 0, clearbytes);
    (void)memset(((char *)ip) + clearbytes, 0xFF,
                 hashp->BSIZE - clearbytes);
    ip[clearints - 1] = ALL_SET << (nbits & BYTE_MASK);
    SETBIT(ip, 0);
    hashp->BITMAPS[ndx] = (uint16)pnum;
    hashp->mapp[ndx] = ip;
    return (0);
}

static uint32
first_free(uint32 map)
{
    register uint32 i, mask;

    mask = 0x1;
    for (i = 0; i < BITS_PER_MAP; i++) {
        if (!(mask & map))
            return (i);
        mask = mask << 1;
    }
    return (i);
}

static uint16
overflow_page(HTAB *hashp)
{
    register uint32 *freep = NULL;
    register int max_free, offset, splitnum;
    uint16 addr;
    uint32 i;
    int bit, first_page, free_bit, free_page, in_use_bits, j;
#ifdef DEBUG2
    int tmp1, tmp2;
#endif
    splitnum = hashp->OVFL_POINT;
    max_free = hashp->SPARES[splitnum];

    free_page = (max_free - 1) >> (hashp->BSHIFT + BYTE_SHIFT);
    free_bit = (max_free - 1) & ((hashp->BSIZE << BYTE_SHIFT) - 1);

    /* Look through all the free maps to find the first free block */
    first_page = hashp->LAST_FREED >> (hashp->BSHIFT + BYTE_SHIFT);
    for (i = first_page; i <= (unsigned)free_page; i++) {
        if (!(freep = (uint32 *)hashp->mapp[i]) &&
            !(freep = fetch_bitmap(hashp, i)))
            return (0);
        if (i == (unsigned)free_page)
            in_use_bits = free_bit;
        else
            in_use_bits = (hashp->BSIZE << BYTE_SHIFT) - 1;

        if (i == (unsigned)first_page) {
            bit = hashp->LAST_FREED &
                  ((hashp->BSIZE << BYTE_SHIFT) - 1);
            j = bit / BITS_PER_MAP;
            bit = bit & ~(BITS_PER_MAP - 1);
        } else {
            bit = 0;
            j = 0;
        }
        for (; bit <= in_use_bits; j++, bit += BITS_PER_MAP)
            if (freep[j] != ALL_SET)
                goto found;
    }

    /* No Free Page Found */
    hashp->LAST_FREED = hashp->SPARES[splitnum];
    hashp->SPARES[splitnum]++;
    offset = hashp->SPARES[splitnum] -
             (splitnum ? hashp->SPARES[splitnum - 1] : 0);

#define OVMSG "HASH: Out of overflow pages.  Increase page size\n"
    if (offset > SPLITMASK) {
        if (++splitnum >= NCACHED) {
#ifndef macintosh
            (void)fwrite(OVMSG, 1, sizeof(OVMSG) - 1, stderr);
#endif
            return (0);
        }
        hashp->OVFL_POINT = splitnum;
        hashp->SPARES[splitnum] = hashp->SPARES[splitnum - 1];
        hashp->SPARES[splitnum - 1]--;
        offset = 1;
    }

    /* Check if we need to allocate a new bitmap page */
    if (free_bit == (hashp->BSIZE << BYTE_SHIFT) - 1) {
        free_page++;
        if (free_page >= NCACHED) {
#ifndef macintosh
            (void)fwrite(OVMSG, 1, sizeof(OVMSG) - 1, stderr);
#endif
            return (0);
        }
        /*
         * This is tricky.  The 1 indicates that you want the new page
         * allocated with 1 clear bit.  Actually, you are going to
         * allocate 2 pages from this map.  The first is going to be
         * the map page, the second is the overflow page we were
         * looking for.  The init_bitmap routine automatically, sets
         * the first bit of itself to indicate that the bitmap itself
         * is in use.  We would explicitly set the second bit, but
         * don't have to if we tell init_bitmap not to leave it clear
         * in the first place.
         */
        if (dbm_ibitmap(hashp,
                        (int)OADDR_OF(splitnum, offset), 1, free_page))
            return (0);
        hashp->SPARES[splitnum]++;
#ifdef DEBUG2
        free_bit = 2;
#endif
        offset++;
        if (offset > SPLITMASK) {
            if (++splitnum >= NCACHED) {
#ifndef macintosh
                (void)fwrite(OVMSG, 1, sizeof(OVMSG) - 1, stderr);
#endif
                return (0);
            }
            hashp->OVFL_POINT = splitnum;
            hashp->SPARES[splitnum] = hashp->SPARES[splitnum - 1];
            hashp->SPARES[splitnum - 1]--;
            offset = 0;
        }
    } else {
        /*
         * Free_bit addresses the last used bit.  Bump it to address
         * the first available bit.
         */
        free_bit++;
        SETBIT(freep, free_bit);
    }

    /* Calculate address of the new overflow page */
    addr = OADDR_OF(splitnum, offset);
#ifdef DEBUG2
    (void)fprintf(stderr, "OVERFLOW_PAGE: ADDR: %d BIT: %d PAGE %d\n",
                  addr, free_bit, free_page);
#endif
    return (addr);

found:
    bit = bit + first_free(freep[j]);
    SETBIT(freep, bit);
#ifdef DEBUG2
    tmp1 = bit;
    tmp2 = i;
#endif
    /*
     * Bits are addressed starting with 0, but overflow pages are addressed
     * beginning at 1. Bit is a bit addressnumber, so we need to increment
     * it to convert it to a page number.
     */
    bit = 1 + bit + (i * (hashp->BSIZE << BYTE_SHIFT));
    if (bit >= hashp->LAST_FREED)
        hashp->LAST_FREED = bit - 1;

    /* Calculate the split number for this page */
    for (i = 0; (i < (unsigned)splitnum) && (bit > hashp->SPARES[i]); i++) {
    }
    offset = (i ? bit - hashp->SPARES[i - 1] : bit);
    if (offset >= SPLITMASK)
        return (0); /* Out of overflow pages */
    addr = OADDR_OF(i, offset);
#ifdef DEBUG2
    (void)fprintf(stderr, "OVERFLOW_PAGE: ADDR: %d BIT: %d PAGE %d\n",
                  addr, tmp1, tmp2);
#endif

    /* Allocate and return the overflow page */
    return (addr);
}

/*
 * Mark this overflow page as free.
 */
extern void
dbm_free_ovflpage(HTAB *hashp, BUFHEAD *obufp)
{
    uint16 addr;
    uint32 *freep;
    uint32 bit_address, free_page, free_bit;
    uint16 ndx;

    if (!obufp || !obufp->addr)
        return;

    addr = obufp->addr;
#ifdef DEBUG1
    (void)fprintf(stderr, "Freeing %d\n", addr);
#endif
    ndx = (((uint16)addr) >> SPLITSHIFT);
    bit_address =
        (ndx ? hashp->SPARES[ndx - 1] : 0) + (addr & SPLITMASK) - 1;
    if (bit_address < (uint32)hashp->LAST_FREED)
        hashp->LAST_FREED = bit_address;
    free_page = (bit_address >> (hashp->BSHIFT + BYTE_SHIFT));
    free_bit = bit_address & ((hashp->BSIZE << BYTE_SHIFT) - 1);

    if (!(freep = hashp->mapp[free_page]))
        freep = fetch_bitmap(hashp, free_page);

#ifdef DEBUG
    /*
     * This had better never happen.  It means we tried to read a bitmap
     * that has already had overflow pages allocated off it, and we
     * failed to read it from the file.
     */
    if (!freep) {
        assert(0);
        return;
    }
#endif
    CLRBIT(freep, free_bit);
#ifdef DEBUG2
    (void)fprintf(stderr, "FREE_OVFLPAGE: ADDR: %d BIT: %d PAGE %d\n",
                  obufp->addr, free_bit, free_page);
#endif
    dbm_reclaim_buf(hashp, obufp);
}

/*
 * Returns:
 *   0 success
 *  -1 failure
 */
static int
open_temp(HTAB *hashp)
{
#if !defined(_WIN32) && !defined(_WINDOWS) && !defined(macintosh)
    sigset_t set, oset;
#endif
#if !defined(macintosh)
    char *tmpdir;
    size_t len;
    char last;
#endif
    static const char namestr[] = "/_hashXXXXXX";
    char filename[1024];

#if !defined(_WIN32) && !defined(_WINDOWS) && !defined(macintosh)
    /* Block signals; make sure file goes away at process exit. */
    (void)sigfillset(&set);
    (void)sigprocmask(SIG_BLOCK, &set, &oset);
#endif

    filename[0] = 0;
#if defined(macintosh)
    strcat(filename, namestr + 1);
#else
    tmpdir = getenv("TMP");
    if (!tmpdir)
        tmpdir = getenv("TMPDIR");
    if (!tmpdir)
        tmpdir = getenv("TEMP");
    if (!tmpdir)
        tmpdir = ".";
    len = strlen(tmpdir);
    if (len && len < (sizeof filename - sizeof namestr)) {
        strcpy(filename, tmpdir);
    }
    len = strlen(filename);
    last = tmpdir[len - 1];
    strcat(filename, (last == '/' || last == '\\') ? namestr + 1 : namestr);
#endif

#if defined(_WIN32) || defined(_WINDOWS)
    if ((hashp->fp = mkstempflags(filename, _O_BINARY | _O_TEMPORARY)) != -1) {
        if (hashp->filename) {
            free(hashp->filename);
        }
        hashp->filename = strdup(filename);
        hashp->is_temp = 1;
    }
#else
    if ((hashp->fp = mkstemp(filename)) != -1) {
        (void)unlink(filename);
#if !defined(macintosh)
        (void)fcntl(hashp->fp, F_SETFD, 1);
#endif
    }
#endif

#if !defined(_WIN32) && !defined(_WINDOWS) && !defined(macintosh)
    (void)sigprocmask(SIG_SETMASK, &oset, (sigset_t *)NULL);
#endif
    return (hashp->fp != -1 ? 0 : -1);
}

/*
 * We have to know that the key will fit, but the last entry on the page is
 * an overflow pair, so we need to shift things.
 */
static void
squeeze_key(uint16 *sp, const DBT *key, const DBT *val)
{
    register char *p;
    uint16 free_space, n, off, pageno;

    p = (char *)sp;
    n = sp[0];
    free_space = FREESPACE(sp);
    off = OFFSET(sp);

    pageno = sp[n - 1];
    off -= key->size;
    sp[n - 1] = off;
    memmove(p + off, key->data, key->size);
    off -= val->size;
    sp[n] = off;
    memmove(p + off, val->data, val->size);
    sp[0] = n + 2;
    sp[n + 1] = pageno;
    sp[n + 2] = OVFLPAGE;
    FREESPACE(sp) = free_space - PAIRSIZE(key, val);
    OFFSET(sp) = off;
}

static uint32 *
fetch_bitmap(HTAB *hashp, uint32 ndx)
{
    if (ndx >= (unsigned)hashp->nmaps)
        return (NULL);
    if ((hashp->mapp[ndx] = (uint32 *)malloc((size_t)hashp->BSIZE)) == NULL)
        return (NULL);
    if (dbm_get_page(hashp,
                     (char *)hashp->mapp[ndx], hashp->BITMAPS[ndx], 0, 1, 1)) {
        free(hashp->mapp[ndx]);
        hashp->mapp[ndx] = NULL; /* NEW: 9-11-95 */
        return (NULL);
    }
    return (hashp->mapp[ndx]);
}

#ifdef DEBUG4
int
print_chain(int addr)
{
    BUFHEAD *bufp;
    short *bp, oaddr;

    (void)fprintf(stderr, "%d ", addr);
    bufp = dbm_get_buf(hashp, addr, NULL, 0);
    bp = (short *)bufp->page;
    while (bp[0] && ((bp[bp[0]] == OVFLPAGE) ||
                     ((bp[0] > 2) && bp[2] < REAL_KEY))) {
        oaddr = bp[bp[0] - 1];
        (void)fprintf(stderr, "%d ", (int)oaddr);
        bufp = dbm_get_buf(hashp, (int)oaddr, bufp, 0);
        bp = (short *)bufp->page;
    }
    (void)fprintf(stderr, "\n");
}
#endif
