diff options
Diffstat (limited to '')
-rw-r--r-- | security/nss/lib/dbm/src/hash.c | 1171 |
1 files changed, 1171 insertions, 0 deletions
diff --git a/security/nss/lib/dbm/src/hash.c b/security/nss/lib/dbm/src/hash.c new file mode 100644 index 0000000000..2c2ce9d873 --- /dev/null +++ b/security/nss/lib/dbm/src/hash.c @@ -0,0 +1,1171 @@ +/*- + * 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(LIBC_SCCS) && !defined(lint) +static char sccsid[] = "@(#)hash.c 8.9 (Berkeley) 6/16/94"; +#endif /* LIBC_SCCS and not lint */ + +#if !defined(_WIN32) && !defined(_WINDOWS) && !defined(macintosh) +#include <sys/param.h> +#endif + +#if !defined(macintosh) +#ifdef XP_OS2 +#include <sys/types.h> +#endif +#include <sys/stat.h> +#endif + +#if defined(macintosh) +#include <unix.h> +#include <unistd.h> +#endif + +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#if !defined(_WIN32) && !defined(_WINDOWS) && !defined(macintosh) +#include <unistd.h> +#endif +#if defined(_WIN32) || defined(_WINDOWS) +#include <windows.h> +#endif + +#include <assert.h> + +#include "mcom_db.h" +#include "hash.h" +#include "page.h" + +/* +#include "extern.h" +*/ +static int alloc_segs(HTAB *, int); +static int flush_meta(HTAB *); +static int hash_access(HTAB *, ACTION, DBT *, DBT *); +static int hash_close(DB *); +static int hash_delete(const DB *, const DBT *, uint); +static int hash_fd(const DB *); +static int hash_get(const DB *, const DBT *, DBT *, uint); +static int hash_put(const DB *, DBT *, const DBT *, uint); +static void *hash_realloc(SEGMENT **, size_t, size_t); +static int hash_seq(const DB *, DBT *, DBT *, uint); +static int hash_sync(const DB *, uint); +static int hdestroy(HTAB *); +static HTAB *init_hash(HTAB *, const char *, HASHINFO *); +static int init_htab(HTAB *, int); +#if BYTE_ORDER == LITTLE_ENDIAN +static void swap_header(HTAB *); +static void swap_header_copy(HASHHDR *, HASHHDR *); +#endif + +/* Fast arithmetic, relying on powers of 2, */ +#define MOD(x, y) ((x) & ((y)-1)) + +#define RETURN_ERROR(ERR, LOC) \ + { \ + save_errno = ERR; \ + goto LOC; \ + } + +/* Return values */ +#define SUCCESS (0) +#define DBM_ERROR (-1) +#define ABNORMAL (1) + +#ifdef HASH_STATISTICS +int hash_accesses, hash_collisions, hash_expansions, hash_overflows; +#endif + +/* A new Lou (montulli@mozilla.com) routine. + * + * The database is screwed. + * + * This closes the file, flushing buffers as appropriate. + */ +static void +dbm_remove_database(DB *dbp) +{ + HTAB *hashp = (HTAB *)dbp->internal; + + assert(0); + + if (!hashp) + return; + hdestroy(hashp); + dbp->internal = NULL; +} + +/************************** INTERFACE ROUTINES ***************************/ +/* OPEN/CLOSE */ + +extern DB * +dbm_hash_open(const char *file, int flags, int mode, const HASHINFO *info, int dflags) +{ + HTAB *hashp = NULL; + struct stat statbuf; + DB *dbp; + int bpages, hdrsize, new_table, nsegs, save_errno; + + if ((flags & O_ACCMODE) == O_WRONLY) { + errno = EINVAL; + return NULL; + } + + /* zero the statbuffer so that + * we can check it for a non-zero + * date to see if stat succeeded + */ + memset(&statbuf, 0, sizeof(struct stat)); + + if (!(hashp = (HTAB *)calloc(1, sizeof(HTAB)))) { + errno = ENOMEM; + return NULL; + } + hashp->fp = NO_FILE; + if (file) + hashp->filename = strdup(file); + + /* + * Even if user wants write only, we need to be able to read + * the actual file, so we need to open it read/write. But, the + * field in the hashp structure needs to be accurate so that + * we can check accesses. + */ + hashp->flags = flags; + + new_table = 0; + if (!file || (flags & O_TRUNC) || (stat(file, &statbuf) && (errno == ENOENT))) { + if (errno == ENOENT) + errno = 0; /* Just in case someone looks at errno */ + new_table = 1; + } else if (statbuf.st_mtime && statbuf.st_size == 0) { + /* check for a zero length file and delete it + * if it exists + */ + new_table = 1; + } + hashp->file_size = statbuf.st_size; + + if (file) { +#if defined(_WIN32) || defined(_WINDOWS) || defined(macintosh) || defined(XP_OS2) + if ((hashp->fp = DBFILE_OPEN(file, flags | O_BINARY, mode)) == -1) + RETURN_ERROR(errno, error1); +#else + if ((hashp->fp = open(file, flags, mode)) == -1) + RETURN_ERROR(errno, error1); + (void)fcntl(hashp->fp, F_SETFD, 1); +#endif + } + if (new_table) { + if (!init_hash(hashp, file, (HASHINFO *)info)) + RETURN_ERROR(errno, error1); + } else { + /* Table already exists */ + if (info && info->hash) + hashp->hash = info->hash; + else + hashp->hash = dbm_default_hash; + + hdrsize = read(hashp->fp, (char *)&hashp->hdr, sizeof(HASHHDR)); + if (hdrsize == -1) + RETURN_ERROR(errno, error1); + if (hdrsize != sizeof(HASHHDR)) + RETURN_ERROR(EFTYPE, error1); +#if BYTE_ORDER == LITTLE_ENDIAN + swap_header(hashp); +#endif + /* Verify file type, versions and hash function */ + if (hashp->MAGIC != HASHMAGIC) + RETURN_ERROR(EFTYPE, error1); +#define OLDHASHVERSION 1 + if (hashp->VERSION != HASHVERSION && + hashp->VERSION != OLDHASHVERSION) + RETURN_ERROR(EFTYPE, error1); + if (hashp->hash(CHARKEY, sizeof(CHARKEY)) != hashp->H_CHARKEY) + RETURN_ERROR(EFTYPE, error1); + if (hashp->NKEYS < 0) /* Old bad database. */ + RETURN_ERROR(EFTYPE, error1); + + /* + * Figure out how many segments we need. Max_Bucket is the + * maximum bucket number, so the number of buckets is + * max_bucket + 1. + */ + nsegs = (hashp->MAX_BUCKET + 1 + hashp->SGSIZE - 1) / + hashp->SGSIZE; + hashp->nsegs = 0; + if (alloc_segs(hashp, nsegs)) + /* If alloc_segs fails, errno will have been set. */ + RETURN_ERROR(errno, error1); + /* Read in bitmaps */ + bpages = (hashp->SPARES[hashp->OVFL_POINT] + + (hashp->BSIZE << BYTE_SHIFT) - 1) >> + (hashp->BSHIFT + BYTE_SHIFT); + + hashp->nmaps = bpages; + (void)memset(&hashp->mapp[0], 0, bpages * sizeof(uint32 *)); + } + + /* Initialize Buffer Manager */ + if (info && info->cachesize) + dbm_buf_init(hashp, (int32)info->cachesize); + else + dbm_buf_init(hashp, DEF_BUFSIZE); + + hashp->new_file = new_table; +#ifdef macintosh + hashp->save_file = file && !(hashp->flags & O_RDONLY); +#else + hashp->save_file = file && (hashp->flags & O_RDWR); +#endif + hashp->cbucket = -1; + if (!(dbp = (DB *)malloc(sizeof(DB)))) { + RETURN_ERROR(ENOMEM, error1); + } + dbp->internal = hashp; + dbp->close = hash_close; + dbp->del = hash_delete; + dbp->fd = hash_fd; + dbp->get = hash_get; + dbp->put = hash_put; + dbp->seq = hash_seq; + dbp->sync = hash_sync; + dbp->type = DB_HASH; + +#ifdef HASH_STATISTICS + hash_overflows = hash_accesses = hash_collisions = hash_expansions = 0; +#endif + return (dbp); + +error1: + hdestroy(hashp); + errno = save_errno; + return (NULL); +} + +static int +hash_close(DB *dbp) +{ + HTAB *hashp; + int retval; + + if (!dbp) + return (DBM_ERROR); + + hashp = (HTAB *)dbp->internal; + if (!hashp) + return (DBM_ERROR); + + retval = hdestroy(hashp); + free(dbp); + return (retval); +} + +static int +hash_fd(const DB *dbp) +{ + HTAB *hashp; + + if (!dbp) + return (DBM_ERROR); + + hashp = (HTAB *)dbp->internal; + if (!hashp) + return (DBM_ERROR); + + if (hashp->fp == -1) { + errno = ENOENT; + return (-1); + } + return (hashp->fp); +} + +/************************** LOCAL CREATION ROUTINES **********************/ +static HTAB * +init_hash(HTAB *hashp, const char *file, HASHINFO *info) +{ + struct stat statbuf; + int nelem; + + nelem = 1; + hashp->NKEYS = 0; + hashp->LORDER = BYTE_ORDER; + hashp->BSIZE = DEF_BUCKET_SIZE; + hashp->BSHIFT = DEF_BUCKET_SHIFT; + hashp->SGSIZE = DEF_SEGSIZE; + hashp->SSHIFT = DEF_SEGSIZE_SHIFT; + hashp->DSIZE = DEF_DIRSIZE; + hashp->FFACTOR = DEF_FFACTOR; + hashp->hash = dbm_default_hash; + memset(hashp->SPARES, 0, sizeof(hashp->SPARES)); + memset(hashp->BITMAPS, 0, sizeof(hashp->BITMAPS)); + + /* Fix bucket size to be optimal for file system */ + if (file != NULL) { + if (stat(file, &statbuf)) + return (NULL); + +#if !defined(_WIN32) && !defined(_WINDOWS) && !defined(macintosh) && !defined(XP_OS2) +#if defined(__QNX__) && !defined(__QNXNTO__) + hashp->BSIZE = 512; /* preferred blk size on qnx4 */ +#else + hashp->BSIZE = statbuf.st_blksize; +#endif + + /* new code added by Lou to reduce block + * size down below MAX_BSIZE + */ + if (hashp->BSIZE > MAX_BSIZE) + hashp->BSIZE = MAX_BSIZE; +#endif + hashp->BSHIFT = dbm_log2((uint32)hashp->BSIZE); + } + + if (info) { + if (info->bsize) { + /* Round pagesize up to power of 2 */ + hashp->BSHIFT = dbm_log2(info->bsize); + hashp->BSIZE = 1 << hashp->BSHIFT; + if (hashp->BSIZE > MAX_BSIZE) { + errno = EINVAL; + return (NULL); + } + } + if (info->ffactor) + hashp->FFACTOR = info->ffactor; + if (info->hash) + hashp->hash = info->hash; + if (info->nelem) + nelem = info->nelem; + if (info->lorder) { + if (info->lorder != BIG_ENDIAN && + info->lorder != LITTLE_ENDIAN) { + errno = EINVAL; + return (NULL); + } + hashp->LORDER = info->lorder; + } + } + /* init_htab sets errno if it fails */ + if (init_htab(hashp, nelem)) + return (NULL); + else + return (hashp); +} +/* + * This calls alloc_segs which may run out of memory. Alloc_segs will + * set errno, so we just pass the error information along. + * + * Returns 0 on No Error + */ +static int +init_htab(HTAB *hashp, int nelem) +{ + register int nbuckets, nsegs; + int l2; + + /* + * Divide number of elements by the fill factor and determine a + * desired number of buckets. Allocate space for the next greater + * power of two number of buckets. + */ + nelem = (nelem - 1) / hashp->FFACTOR + 1; + + l2 = dbm_log2((uint32)PR_MAX(nelem, 2)); + nbuckets = 1 << l2; + + hashp->SPARES[l2] = l2 + 1; + hashp->SPARES[l2 + 1] = l2 + 1; + hashp->OVFL_POINT = l2; + hashp->LAST_FREED = 2; + + /* First bitmap page is at: splitpoint l2 page offset 1 */ + if (dbm_ibitmap(hashp, (int)OADDR_OF(l2, 1), l2 + 1, 0)) + return (-1); + + hashp->MAX_BUCKET = hashp->LOW_MASK = nbuckets - 1; + hashp->HIGH_MASK = (nbuckets << 1) - 1; + hashp->HDRPAGES = ((PR_MAX(sizeof(HASHHDR), MINHDRSIZE) - 1) >> + hashp->BSHIFT) + + 1; + + nsegs = (nbuckets - 1) / hashp->SGSIZE + 1; + nsegs = 1 << dbm_log2((uint32)nsegs); + + if (nsegs > hashp->DSIZE) + hashp->DSIZE = nsegs; + return (alloc_segs(hashp, nsegs)); +} + +/********************** DESTROY/CLOSE ROUTINES ************************/ + +/* + * Flushes any changes to the file if necessary and destroys the hashp + * structure, freeing all allocated space. + */ +static int +hdestroy(HTAB *hashp) +{ + int i, save_errno; + + save_errno = 0; + +#ifdef HASH_STATISTICS + (void)fprintf(stderr, "hdestroy: accesses %ld collisions %ld\n", + hash_accesses, hash_collisions); + (void)fprintf(stderr, "hdestroy: expansions %ld\n", + hash_expansions); + (void)fprintf(stderr, "hdestroy: overflows %ld\n", + hash_overflows); + (void)fprintf(stderr, "keys %ld maxp %d segmentcount %d\n", + hashp->NKEYS, hashp->MAX_BUCKET, hashp->nsegs); + + for (i = 0; i < NCACHED; i++) + (void)fprintf(stderr, + "spares[%d] = %d\n", i, hashp->SPARES[i]); +#endif + /* + * Call on buffer manager to free buffers, and if required, + * write them to disk. + */ + if (dbm_buf_free(hashp, 1, hashp->save_file)) + save_errno = errno; + if (hashp->dir) { + free(*hashp->dir); /* Free initial segments */ + /* Free extra segments */ + while (hashp->exsegs--) + free(hashp->dir[--hashp->nsegs]); + free(hashp->dir); + } + if (flush_meta(hashp) && !save_errno) + save_errno = errno; + /* Free Bigmaps */ + for (i = 0; i < hashp->nmaps; i++) + if (hashp->mapp[i]) + free(hashp->mapp[i]); + + if (hashp->fp != -1) + (void)close(hashp->fp); + + if (hashp->filename) { +#if defined(_WIN32) || defined(_WINDOWS) || defined(XP_OS2) + if (hashp->is_temp) + (void)unlink(hashp->filename); +#endif + free(hashp->filename); + } + if (hashp->tmp_buf) + free(hashp->tmp_buf); + if (hashp->tmp_key) + free(hashp->tmp_key); + free(hashp); + if (save_errno) { + errno = save_errno; + return (DBM_ERROR); + } + return (SUCCESS); +} + +#if defined(_WIN32) || defined(_WINDOWS) +/* + * Close and reopen file to force file length update on windows. + * + * Returns: + * 0 == OK + * -1 DBM_ERROR + */ +static int +update_EOF(HTAB *hashp) +{ +#if defined(DBM_REOPEN_ON_FLUSH) + char *file = hashp->filename; + off_t file_size; + int flags; + int mode = -1; + struct stat statbuf; + + memset(&statbuf, 0, sizeof statbuf); + + /* make sure we won't lose the file by closing it. */ + if (!file || (stat(file, &statbuf) && (errno == ENOENT))) { + /* pretend we did it. */ + return 0; + } + + (void)close(hashp->fp); + + flags = hashp->flags & ~(O_TRUNC | O_CREAT | O_EXCL); + + if ((hashp->fp = DBFILE_OPEN(file, flags | O_BINARY, mode)) == -1) + return -1; + file_size = lseek(hashp->fp, (off_t)0, SEEK_END); + if (file_size == -1) + return -1; + hashp->file_size = file_size; + return 0; +#else + int fd = hashp->fp; + off_t file_size = lseek(fd, (off_t)0, SEEK_END); + HANDLE handle = (HANDLE)_get_osfhandle(fd); + BOOL cool = FlushFileBuffers(handle); +#ifdef DEBUG3 + if (!cool) { + DWORD err = GetLastError(); + (void)fprintf(stderr, + "FlushFileBuffers failed, last error = %d, 0x%08x\n", + err, err); + } +#endif + if (file_size == -1) + return -1; + hashp->file_size = file_size; + return cool ? 0 : -1; +#endif +} +#endif + +/* + * Write modified pages to disk + * + * Returns: + * 0 == OK + * -1 DBM_ERROR + */ +static int +hash_sync(const DB *dbp, uint flags) +{ + HTAB *hashp; + + if (flags != 0) { + errno = EINVAL; + return (DBM_ERROR); + } + + if (!dbp) + return (DBM_ERROR); + + hashp = (HTAB *)dbp->internal; + if (!hashp) + return (DBM_ERROR); + + if (!hashp->save_file) + return (0); + if (dbm_buf_free(hashp, 0, 1) || flush_meta(hashp)) + return (DBM_ERROR); +#if defined(_WIN32) || defined(_WINDOWS) + if (hashp->updateEOF && hashp->filename && !hashp->is_temp) { + int status = update_EOF(hashp); + hashp->updateEOF = 0; + if (status) + return status; + } +#endif + hashp->new_file = 0; + return (0); +} + +/* + * Returns: + * 0 == OK + * -1 indicates that errno should be set + */ +static int +flush_meta(HTAB *hashp) +{ + HASHHDR *whdrp; +#if BYTE_ORDER == LITTLE_ENDIAN + HASHHDR whdr; +#endif + int fp, i, wsize; + + if (!hashp->save_file) + return (0); + hashp->MAGIC = HASHMAGIC; + hashp->VERSION = HASHVERSION; + hashp->H_CHARKEY = hashp->hash(CHARKEY, sizeof(CHARKEY)); + + fp = hashp->fp; + whdrp = &hashp->hdr; +#if BYTE_ORDER == LITTLE_ENDIAN + whdrp = &whdr; + swap_header_copy(&hashp->hdr, whdrp); +#endif + if ((lseek(fp, (off_t)0, SEEK_SET) == -1) || + ((wsize = write(fp, (char *)whdrp, sizeof(HASHHDR))) == -1)) + return (-1); + else if (wsize != sizeof(HASHHDR)) { + errno = EFTYPE; + hashp->dbmerrno = errno; + return (-1); + } + for (i = 0; i < NCACHED; i++) + if (hashp->mapp[i]) + if (dbm_put_page(hashp, (char *)hashp->mapp[i], + hashp->BITMAPS[i], 0, 1)) + return (-1); + return (0); +} + +/*******************************SEARCH ROUTINES *****************************/ +/* + * All the access routines return + * + * Returns: + * 0 on SUCCESS + * 1 to indicate an external DBM_ERROR (i.e. key not found, etc) + * -1 to indicate an internal DBM_ERROR (i.e. out of memory, etc) + */ +static int +hash_get( + const DB *dbp, + const DBT *key, + DBT *data, + uint flag) +{ + HTAB *hashp; + int rv; + + hashp = (HTAB *)dbp->internal; + if (!hashp) + return (DBM_ERROR); + + if (flag) { + hashp->dbmerrno = errno = EINVAL; + return (DBM_ERROR); + } + + rv = hash_access(hashp, HASH_GET, (DBT *)key, data); + + if (rv == DATABASE_CORRUPTED_ERROR) { +#if defined(unix) && defined(DEBUG) + printf("\n\nDBM Database has been corrupted, tell Lou...\n\n"); +#endif + dbm_remove_database((DB *)dbp); + } + + return (rv); +} + +static int +hash_put( + const DB *dbp, + DBT *key, + const DBT *data, + uint flag) +{ + HTAB *hashp; + int rv; + + hashp = (HTAB *)dbp->internal; + if (!hashp) + return (DBM_ERROR); + + if (flag && flag != R_NOOVERWRITE) { + hashp->dbmerrno = errno = EINVAL; + return (DBM_ERROR); + } + if ((hashp->flags & O_ACCMODE) == O_RDONLY) { + hashp->dbmerrno = errno = EPERM; + return (DBM_ERROR); + } + + rv = hash_access(hashp, flag == R_NOOVERWRITE ? HASH_PUTNEW : HASH_PUT, + (DBT *)key, (DBT *)data); + + if (rv == DATABASE_CORRUPTED_ERROR) { +#if defined(unix) && defined(DEBUG) + printf("\n\nDBM Database has been corrupted, tell Lou...\n\n"); +#endif + dbm_remove_database((DB *)dbp); + } + + return (rv); +} + +static int +hash_delete( + const DB *dbp, + const DBT *key, + uint flag) /* Ignored */ +{ + HTAB *hashp; + int rv; + + hashp = (HTAB *)dbp->internal; + if (!hashp) + return (DBM_ERROR); + + if (flag && flag != R_CURSOR) { + hashp->dbmerrno = errno = EINVAL; + return (DBM_ERROR); + } + if ((hashp->flags & O_ACCMODE) == O_RDONLY) { + hashp->dbmerrno = errno = EPERM; + return (DBM_ERROR); + } + rv = hash_access(hashp, HASH_DELETE, (DBT *)key, NULL); + + if (rv == DATABASE_CORRUPTED_ERROR) { +#if defined(unix) && defined(DEBUG) + printf("\n\nDBM Database has been corrupted, tell Lou...\n\n"); +#endif + dbm_remove_database((DB *)dbp); + } + + return (rv); +} + +#define MAX_OVERFLOW_HASH_ACCESS_LOOPS 2000 +/* + * Assume that hashp has been set in wrapper routine. + */ +static int +hash_access( + HTAB *hashp, + ACTION action, + DBT *key, DBT *val) +{ + register BUFHEAD *rbufp; + BUFHEAD *bufp, *save_bufp; + register uint16 *bp; + register long n, ndx, off; + register size_t size; + register char *kp; + uint16 pageno; + uint32 ovfl_loop_count = 0; + int32 last_overflow_page_no = -1; + +#ifdef HASH_STATISTICS + hash_accesses++; +#endif + + off = hashp->BSIZE; + size = key->size; + kp = (char *)key->data; + rbufp = dbm_get_buf(hashp, dbm_call_hash(hashp, kp, size), NULL, 0); + if (!rbufp) + return (DATABASE_CORRUPTED_ERROR); + save_bufp = rbufp; + + /* Pin the bucket chain */ + rbufp->flags |= BUF_PIN; + for (bp = (uint16 *)rbufp->page, n = *bp++, ndx = 1; ndx < n;) { + + if (bp[1] >= REAL_KEY) { + /* Real key/data pair */ + if (size == (unsigned long)(off - *bp) && + memcmp(kp, rbufp->page + *bp, size) == 0) + goto found; + off = bp[1]; +#ifdef HASH_STATISTICS + hash_collisions++; +#endif + bp += 2; + ndx += 2; + } else if (bp[1] == OVFLPAGE) { + + /* database corruption: overflow loop detection */ + if (last_overflow_page_no == (int32)*bp) + return (DATABASE_CORRUPTED_ERROR); + + last_overflow_page_no = *bp; + + rbufp = dbm_get_buf(hashp, *bp, rbufp, 0); + if (!rbufp) { + save_bufp->flags &= ~BUF_PIN; + return (DBM_ERROR); + } + + ovfl_loop_count++; + if (ovfl_loop_count > MAX_OVERFLOW_HASH_ACCESS_LOOPS) + return (DATABASE_CORRUPTED_ERROR); + + /* FOR LOOP INIT */ + bp = (uint16 *)rbufp->page; + n = *bp++; + ndx = 1; + off = hashp->BSIZE; + } else if (bp[1] < REAL_KEY) { + if ((ndx = + dbm_find_bigpair(hashp, rbufp, ndx, kp, (int)size)) > 0) + goto found; + if (ndx == -2) { + bufp = rbufp; + if (!(pageno = + dbm_find_last_page(hashp, &bufp))) { + ndx = 0; + rbufp = bufp; + break; /* FOR */ + } + rbufp = dbm_get_buf(hashp, pageno, bufp, 0); + if (!rbufp) { + save_bufp->flags &= ~BUF_PIN; + return (DBM_ERROR); + } + /* FOR LOOP INIT */ + bp = (uint16 *)rbufp->page; + n = *bp++; + ndx = 1; + off = hashp->BSIZE; + } else { + save_bufp->flags &= ~BUF_PIN; + return (DBM_ERROR); + } + } + } + + /* Not found */ + switch (action) { + case HASH_PUT: + case HASH_PUTNEW: + if (dbm_addel(hashp, rbufp, key, val)) { + save_bufp->flags &= ~BUF_PIN; + return (DBM_ERROR); + } else { + save_bufp->flags &= ~BUF_PIN; + return (SUCCESS); + } + case HASH_GET: + case HASH_DELETE: + default: + save_bufp->flags &= ~BUF_PIN; + return (ABNORMAL); + } + +found: + switch (action) { + case HASH_PUTNEW: + save_bufp->flags &= ~BUF_PIN; + return (ABNORMAL); + case HASH_GET: + bp = (uint16 *)rbufp->page; + if (bp[ndx + 1] < REAL_KEY) { + if (dbm_big_return(hashp, rbufp, ndx, val, 0)) + return (DBM_ERROR); + } else { + val->data = (uint8 *)rbufp->page + (int)bp[ndx + 1]; + val->size = bp[ndx] - bp[ndx + 1]; + } + break; + case HASH_PUT: + if ((dbm_delpair(hashp, rbufp, ndx)) || + (dbm_addel(hashp, rbufp, key, val))) { + save_bufp->flags &= ~BUF_PIN; + return (DBM_ERROR); + } + break; + case HASH_DELETE: + if (dbm_delpair(hashp, rbufp, ndx)) + return (DBM_ERROR); + break; + default: + abort(); + } + save_bufp->flags &= ~BUF_PIN; + return (SUCCESS); +} + +static int +hash_seq( + const DB *dbp, + DBT *key, DBT *data, + uint flag) +{ + register uint32 bucket; + register BUFHEAD *bufp = NULL; + HTAB *hashp; + uint16 *bp, ndx; + + hashp = (HTAB *)dbp->internal; + if (!hashp) + return (DBM_ERROR); + + if (flag && flag != R_FIRST && flag != R_NEXT) { + hashp->dbmerrno = errno = EINVAL; + return (DBM_ERROR); + } +#ifdef HASH_STATISTICS + hash_accesses++; +#endif + if ((hashp->cbucket < 0) || (flag == R_FIRST)) { + hashp->cbucket = 0; + hashp->cndx = 1; + hashp->cpage = NULL; + } + + for (bp = NULL; !bp || !bp[0];) { + if (!(bufp = hashp->cpage)) { + for (bucket = hashp->cbucket; + bucket <= (uint32)hashp->MAX_BUCKET; + bucket++, hashp->cndx = 1) { + bufp = dbm_get_buf(hashp, bucket, NULL, 0); + if (!bufp) + return (DBM_ERROR); + hashp->cpage = bufp; + bp = (uint16 *)bufp->page; + if (bp[0]) + break; + } + hashp->cbucket = bucket; + if (hashp->cbucket > hashp->MAX_BUCKET) { + hashp->cbucket = -1; + return (ABNORMAL); + } + } else + bp = (uint16 *)hashp->cpage->page; + +#ifdef DEBUG + assert(bp); + assert(bufp); +#endif + while (bp[hashp->cndx + 1] == OVFLPAGE) { + bufp = hashp->cpage = + dbm_get_buf(hashp, bp[hashp->cndx], bufp, 0); + if (!bufp) + return (DBM_ERROR); + bp = (uint16 *)(bufp->page); + hashp->cndx = 1; + } + if (!bp[0]) { + hashp->cpage = NULL; + ++hashp->cbucket; + } + } + ndx = hashp->cndx; + if (bp[ndx + 1] < REAL_KEY) { + if (dbm_big_keydata(hashp, bufp, key, data, 1)) + return (DBM_ERROR); + } else { + key->data = (uint8 *)hashp->cpage->page + bp[ndx]; + key->size = (ndx > 1 ? bp[ndx - 1] : hashp->BSIZE) - bp[ndx]; + data->data = (uint8 *)hashp->cpage->page + bp[ndx + 1]; + data->size = bp[ndx] - bp[ndx + 1]; + ndx += 2; + if (ndx > bp[0]) { + hashp->cpage = NULL; + hashp->cbucket++; + hashp->cndx = 1; + } else + hashp->cndx = ndx; + } + return (SUCCESS); +} + +/********************************* UTILITIES ************************/ + +/* + * Returns: + * 0 ==> OK + * -1 ==> Error + */ +extern int +dbm_expand_table(HTAB *hashp) +{ + uint32 old_bucket, new_bucket; + int new_segnum, spare_ndx; + size_t dirsize; + +#ifdef HASH_STATISTICS + hash_expansions++; +#endif + new_bucket = ++hashp->MAX_BUCKET; + old_bucket = (hashp->MAX_BUCKET & hashp->LOW_MASK); + + new_segnum = new_bucket >> hashp->SSHIFT; + + /* Check if we need a new segment */ + if (new_segnum >= hashp->nsegs) { + /* Check if we need to expand directory */ + if (new_segnum >= hashp->DSIZE) { + /* Reallocate directory */ + dirsize = hashp->DSIZE * sizeof(SEGMENT *); + if (!hash_realloc(&hashp->dir, dirsize, dirsize << 1)) + return (-1); + hashp->DSIZE = dirsize << 1; + } + if ((hashp->dir[new_segnum] = + (SEGMENT)calloc((size_t)hashp->SGSIZE, sizeof(BUFHEAD *))) == NULL) + return (-1); + hashp->exsegs++; + hashp->nsegs++; + } + /* + * If the split point is increasing (MAX_BUCKET's log base 2 + * * increases), we need to copy the current contents of the spare + * split bucket to the next bucket. + */ + spare_ndx = dbm_log2((uint32)(hashp->MAX_BUCKET + 1)); + if (spare_ndx > hashp->OVFL_POINT) { + hashp->SPARES[spare_ndx] = hashp->SPARES[hashp->OVFL_POINT]; + hashp->OVFL_POINT = spare_ndx; + } + + if (new_bucket > (uint32)hashp->HIGH_MASK) { + /* Starting a new doubling */ + hashp->LOW_MASK = hashp->HIGH_MASK; + hashp->HIGH_MASK = new_bucket | hashp->LOW_MASK; + } + /* Relocate records to the new bucket */ + return (dbm_split_page(hashp, old_bucket, new_bucket)); +} + +/* + * If realloc guarantees that the pointer is not destroyed if the realloc + * fails, then this routine can go away. + */ +static void * +hash_realloc( + SEGMENT **p_ptr, + size_t oldsize, size_t newsize) +{ + register void *p; + + if ((p = malloc(newsize))) { + memmove(p, *p_ptr, oldsize); + memset((char *)p + oldsize, 0, newsize - oldsize); + free(*p_ptr); + *p_ptr = (SEGMENT *)p; + } + return (p); +} + +extern uint32 +dbm_call_hash(HTAB *hashp, char *k, size_t len) +{ + uint32 n, bucket; + + n = hashp->hash(k, len); + bucket = n & hashp->HIGH_MASK; + if (bucket > (uint32)hashp->MAX_BUCKET) + bucket = bucket & hashp->LOW_MASK; + return (bucket); +} + +/* + * Allocate segment table. On error, set errno. + * + * Returns 0 on success + */ +static int +alloc_segs( + HTAB *hashp, + int nsegs) +{ + register int i; + register SEGMENT store; + + if ((hashp->dir = + (SEGMENT *)calloc((size_t)hashp->DSIZE, sizeof(SEGMENT))) == NULL) { + errno = ENOMEM; + return (-1); + } + /* Allocate segments */ + if ((store = + (SEGMENT)calloc((size_t)nsegs << hashp->SSHIFT, sizeof(BUFHEAD *))) == NULL) { + errno = ENOMEM; + return (-1); + } + for (i = 0; i < nsegs; i++, hashp->nsegs++) + hashp->dir[i] = &store[i << hashp->SSHIFT]; + return (0); +} + +#if BYTE_ORDER == LITTLE_ENDIAN +/* + * Hashp->hdr needs to be byteswapped. + */ +static void +swap_header_copy( + HASHHDR *srcp, HASHHDR *destp) +{ + int i; + + P_32_COPY(srcp->magic, destp->magic); + P_32_COPY(srcp->version, destp->version); + P_32_COPY(srcp->lorder, destp->lorder); + P_32_COPY(srcp->bsize, destp->bsize); + P_32_COPY(srcp->bshift, destp->bshift); + P_32_COPY(srcp->dsize, destp->dsize); + P_32_COPY(srcp->ssize, destp->ssize); + P_32_COPY(srcp->sshift, destp->sshift); + P_32_COPY(srcp->ovfl_point, destp->ovfl_point); + P_32_COPY(srcp->last_freed, destp->last_freed); + P_32_COPY(srcp->max_bucket, destp->max_bucket); + P_32_COPY(srcp->high_mask, destp->high_mask); + P_32_COPY(srcp->low_mask, destp->low_mask); + P_32_COPY(srcp->ffactor, destp->ffactor); + P_32_COPY(srcp->nkeys, destp->nkeys); + P_32_COPY(srcp->hdrpages, destp->hdrpages); + P_32_COPY(srcp->h_charkey, destp->h_charkey); + for (i = 0; i < NCACHED; i++) { + P_32_COPY(srcp->spares[i], destp->spares[i]); + P_16_COPY(srcp->bitmaps[i], destp->bitmaps[i]); + } +} + +static void +swap_header(HTAB *hashp) +{ + HASHHDR *hdrp; + int i; + + hdrp = &hashp->hdr; + + M_32_SWAP(hdrp->magic); + M_32_SWAP(hdrp->version); + M_32_SWAP(hdrp->lorder); + M_32_SWAP(hdrp->bsize); + M_32_SWAP(hdrp->bshift); + M_32_SWAP(hdrp->dsize); + M_32_SWAP(hdrp->ssize); + M_32_SWAP(hdrp->sshift); + M_32_SWAP(hdrp->ovfl_point); + M_32_SWAP(hdrp->last_freed); + M_32_SWAP(hdrp->max_bucket); + M_32_SWAP(hdrp->high_mask); + M_32_SWAP(hdrp->low_mask); + M_32_SWAP(hdrp->ffactor); + M_32_SWAP(hdrp->nkeys); + M_32_SWAP(hdrp->hdrpages); + M_32_SWAP(hdrp->h_charkey); + for (i = 0; i < NCACHED; i++) { + M_32_SWAP(hdrp->spares[i]); + M_16_SWAP(hdrp->bitmaps[i]); + } +} +#endif |