michael@0: /*- michael@0: * Copyright (c) 1990, 1993, 1994 michael@0: * The Regents of the University of California. All rights reserved. michael@0: * michael@0: * This code is derived from software contributed to Berkeley by michael@0: * Margo Seltzer. michael@0: * michael@0: * Redistribution and use in source and binary forms, with or without michael@0: * modification, are permitted provided that the following conditions michael@0: * are met: michael@0: * 1. Redistributions of source code must retain the above copyright michael@0: * notice, this list of conditions and the following disclaimer. michael@0: * 2. Redistributions in binary form must reproduce the above copyright michael@0: * notice, this list of conditions and the following disclaimer in the michael@0: * documentation and/or other materials provided with the distribution. michael@0: * 3. ***REMOVED*** - see michael@0: * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change michael@0: * 4. Neither the name of the University nor the names of its contributors michael@0: * may be used to endorse or promote products derived from this software michael@0: * without specific prior written permission. michael@0: * michael@0: * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND michael@0: * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE michael@0: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE michael@0: * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE michael@0: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL michael@0: * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS michael@0: * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) michael@0: * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT michael@0: * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY michael@0: * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF michael@0: * SUCH DAMAGE. michael@0: */ michael@0: michael@0: #if defined(LIBC_SCCS) && !defined(lint) michael@0: static char sccsid[] = "@(#)hash.c 8.9 (Berkeley) 6/16/94"; michael@0: #endif /* LIBC_SCCS and not lint */ michael@0: michael@0: #if !defined(_WIN32) && !defined(_WINDOWS) && !defined(macintosh) michael@0: #include michael@0: #endif michael@0: michael@0: #if !defined(macintosh) michael@0: #ifdef XP_OS2 michael@0: #include michael@0: #endif michael@0: #include michael@0: #endif michael@0: michael@0: #if defined(macintosh) michael@0: #include michael@0: #include michael@0: #endif michael@0: michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: #include michael@0: michael@0: #if !defined(_WIN32) && !defined(_WINDOWS) && !defined(macintosh) michael@0: #include michael@0: #endif michael@0: #if defined(_WIN32) || defined(_WINDOWS) michael@0: #include michael@0: #endif michael@0: michael@0: #include michael@0: michael@0: #include "mcom_db.h" michael@0: #include "hash.h" michael@0: #include "page.h" michael@0: michael@0: /* michael@0: #include "extern.h" michael@0: */ michael@0: static int alloc_segs __P((HTAB *, int)); michael@0: static int flush_meta __P((HTAB *)); michael@0: static int hash_access __P((HTAB *, ACTION, DBT *, DBT *)); michael@0: static int hash_close __P((DB *)); michael@0: static int hash_delete __P((const DB *, const DBT *, uint)); michael@0: static int hash_fd __P((const DB *)); michael@0: static int hash_get __P((const DB *, const DBT *, DBT *, uint)); michael@0: static int hash_put __P((const DB *, DBT *, const DBT *, uint)); michael@0: static void *hash_realloc __P((SEGMENT **, size_t, size_t)); michael@0: static int hash_seq __P((const DB *, DBT *, DBT *, uint)); michael@0: static int hash_sync __P((const DB *, uint)); michael@0: static int hdestroy __P((HTAB *)); michael@0: static HTAB *init_hash __P((HTAB *, const char *, HASHINFO *)); michael@0: static int init_htab __P((HTAB *, int)); michael@0: #if BYTE_ORDER == LITTLE_ENDIAN michael@0: static void swap_header __P((HTAB *)); michael@0: static void swap_header_copy __P((HASHHDR *, HASHHDR *)); michael@0: #endif michael@0: michael@0: /* Fast arithmetic, relying on powers of 2, */ michael@0: #define MOD(x, y) ((x) & ((y) - 1)) michael@0: michael@0: #define RETURN_ERROR(ERR, LOC) { save_errno = ERR; goto LOC; } michael@0: michael@0: /* Return values */ michael@0: #define SUCCESS (0) michael@0: #define DBM_ERROR (-1) michael@0: #define ABNORMAL (1) michael@0: michael@0: #ifdef HASH_STATISTICS michael@0: int hash_accesses, hash_collisions, hash_expansions, hash_overflows; michael@0: #endif michael@0: michael@0: /* A new Lou (montulli@mozilla.com) routine. michael@0: * michael@0: * The database is screwed. michael@0: * michael@0: * This closes the file, flushing buffers as appropriate. michael@0: */ michael@0: static void michael@0: __remove_database(DB *dbp) michael@0: { michael@0: HTAB *hashp = (HTAB *)dbp->internal; michael@0: michael@0: assert(0); michael@0: michael@0: if (!hashp) michael@0: return; michael@0: hdestroy(hashp); michael@0: dbp->internal = NULL; michael@0: } michael@0: michael@0: /************************** INTERFACE ROUTINES ***************************/ michael@0: /* OPEN/CLOSE */ michael@0: michael@0: michael@0: extern DB * michael@0: __hash_open(const char *file, int flags, int mode, const HASHINFO *info, int dflags) michael@0: { michael@0: HTAB *hashp=NULL; michael@0: struct stat statbuf; michael@0: DB *dbp; michael@0: int bpages, hdrsize, new_table, nsegs, save_errno; michael@0: michael@0: if ((flags & O_ACCMODE) == O_WRONLY) { michael@0: errno = EINVAL; michael@0: return NULL; michael@0: } michael@0: michael@0: /* zero the statbuffer so that michael@0: * we can check it for a non-zero michael@0: * date to see if stat succeeded michael@0: */ michael@0: memset(&statbuf, 0, sizeof(struct stat)); michael@0: michael@0: if (!(hashp = (HTAB *)calloc(1, sizeof(HTAB)))) { michael@0: errno = ENOMEM; michael@0: return NULL; michael@0: } michael@0: hashp->fp = NO_FILE; michael@0: if(file) michael@0: hashp->filename = strdup(file); michael@0: michael@0: /* michael@0: * Even if user wants write only, we need to be able to read michael@0: * the actual file, so we need to open it read/write. But, the michael@0: * field in the hashp structure needs to be accurate so that michael@0: * we can check accesses. michael@0: */ michael@0: hashp->flags = flags; michael@0: michael@0: new_table = 0; michael@0: if (!file || (flags & O_TRUNC) || (stat(file, &statbuf) && (errno == ENOENT))) michael@0: { michael@0: if (errno == ENOENT) michael@0: errno = 0; /* Just in case someone looks at errno */ michael@0: new_table = 1; michael@0: } michael@0: else if(statbuf.st_mtime && statbuf.st_size == 0) michael@0: { michael@0: /* check for a zero length file and delete it michael@0: * if it exists michael@0: */ michael@0: new_table = 1; michael@0: } michael@0: hashp->file_size = statbuf.st_size; michael@0: michael@0: if (file) { michael@0: #if defined(_WIN32) || defined(_WINDOWS) || defined (macintosh) || defined(XP_OS2) michael@0: if ((hashp->fp = DBFILE_OPEN(file, flags | O_BINARY, mode)) == -1) michael@0: RETURN_ERROR(errno, error1); michael@0: #else michael@0: if ((hashp->fp = open(file, flags, mode)) == -1) michael@0: RETURN_ERROR(errno, error1); michael@0: (void)fcntl(hashp->fp, F_SETFD, 1); michael@0: #endif michael@0: } michael@0: if (new_table) { michael@0: if (!init_hash(hashp, file, (HASHINFO *)info)) michael@0: RETURN_ERROR(errno, error1); michael@0: } else { michael@0: /* Table already exists */ michael@0: if (info && info->hash) michael@0: hashp->hash = info->hash; michael@0: else michael@0: hashp->hash = __default_hash; michael@0: michael@0: hdrsize = read(hashp->fp, (char *)&hashp->hdr, sizeof(HASHHDR)); michael@0: if (hdrsize == -1) michael@0: RETURN_ERROR(errno, error1); michael@0: if (hdrsize != sizeof(HASHHDR)) michael@0: RETURN_ERROR(EFTYPE, error1); michael@0: #if BYTE_ORDER == LITTLE_ENDIAN michael@0: swap_header(hashp); michael@0: #endif michael@0: /* Verify file type, versions and hash function */ michael@0: if (hashp->MAGIC != HASHMAGIC) michael@0: RETURN_ERROR(EFTYPE, error1); michael@0: #define OLDHASHVERSION 1 michael@0: if (hashp->VERSION != HASHVERSION && michael@0: hashp->VERSION != OLDHASHVERSION) michael@0: RETURN_ERROR(EFTYPE, error1); michael@0: if (hashp->hash(CHARKEY, sizeof(CHARKEY)) != hashp->H_CHARKEY) michael@0: RETURN_ERROR(EFTYPE, error1); michael@0: if (hashp->NKEYS < 0) /* Old bad database. */ michael@0: RETURN_ERROR(EFTYPE, error1); michael@0: michael@0: /* michael@0: * Figure out how many segments we need. Max_Bucket is the michael@0: * maximum bucket number, so the number of buckets is michael@0: * max_bucket + 1. michael@0: */ michael@0: nsegs = (hashp->MAX_BUCKET + 1 + hashp->SGSIZE - 1) / michael@0: hashp->SGSIZE; michael@0: hashp->nsegs = 0; michael@0: if (alloc_segs(hashp, nsegs)) michael@0: /* If alloc_segs fails, errno will have been set. */ michael@0: RETURN_ERROR(errno, error1); michael@0: /* Read in bitmaps */ michael@0: bpages = (hashp->SPARES[hashp->OVFL_POINT] + michael@0: (hashp->BSIZE << BYTE_SHIFT) - 1) >> michael@0: (hashp->BSHIFT + BYTE_SHIFT); michael@0: michael@0: hashp->nmaps = bpages; michael@0: (void)memset(&hashp->mapp[0], 0, bpages * sizeof(uint32 *)); michael@0: } michael@0: michael@0: /* Initialize Buffer Manager */ michael@0: if (info && info->cachesize) michael@0: __buf_init(hashp, (int32) info->cachesize); michael@0: else michael@0: __buf_init(hashp, DEF_BUFSIZE); michael@0: michael@0: hashp->new_file = new_table; michael@0: #ifdef macintosh michael@0: hashp->save_file = file && !(hashp->flags & O_RDONLY); michael@0: #else michael@0: hashp->save_file = file && (hashp->flags & O_RDWR); michael@0: #endif michael@0: hashp->cbucket = -1; michael@0: if (!(dbp = (DB *)malloc(sizeof(DB)))) { michael@0: RETURN_ERROR(ENOMEM, error1); michael@0: } michael@0: dbp->internal = hashp; michael@0: dbp->close = hash_close; michael@0: dbp->del = hash_delete; michael@0: dbp->fd = hash_fd; michael@0: dbp->get = hash_get; michael@0: dbp->put = hash_put; michael@0: dbp->seq = hash_seq; michael@0: dbp->sync = hash_sync; michael@0: dbp->type = DB_HASH; michael@0: michael@0: #ifdef HASH_STATISTICS michael@0: hash_overflows = hash_accesses = hash_collisions = hash_expansions = 0; michael@0: #endif michael@0: return (dbp); michael@0: michael@0: error1: michael@0: hdestroy(hashp); michael@0: errno = save_errno; michael@0: return (NULL); michael@0: } michael@0: michael@0: static int michael@0: hash_close(DB *dbp) michael@0: { michael@0: HTAB *hashp; michael@0: int retval; michael@0: michael@0: if (!dbp) michael@0: return (DBM_ERROR); michael@0: michael@0: hashp = (HTAB *)dbp->internal; michael@0: if(!hashp) michael@0: return (DBM_ERROR); michael@0: michael@0: retval = hdestroy(hashp); michael@0: free(dbp); michael@0: return (retval); michael@0: } michael@0: michael@0: static int hash_fd(const DB *dbp) michael@0: { michael@0: HTAB *hashp; michael@0: michael@0: if (!dbp) michael@0: return (DBM_ERROR); michael@0: michael@0: hashp = (HTAB *)dbp->internal; michael@0: if(!hashp) michael@0: return (DBM_ERROR); michael@0: michael@0: if (hashp->fp == -1) { michael@0: errno = ENOENT; michael@0: return (-1); michael@0: } michael@0: return (hashp->fp); michael@0: } michael@0: michael@0: /************************** LOCAL CREATION ROUTINES **********************/ michael@0: static HTAB * michael@0: init_hash(HTAB *hashp, const char *file, HASHINFO *info) michael@0: { michael@0: struct stat statbuf; michael@0: int nelem; michael@0: michael@0: nelem = 1; michael@0: hashp->NKEYS = 0; michael@0: hashp->LORDER = BYTE_ORDER; michael@0: hashp->BSIZE = DEF_BUCKET_SIZE; michael@0: hashp->BSHIFT = DEF_BUCKET_SHIFT; michael@0: hashp->SGSIZE = DEF_SEGSIZE; michael@0: hashp->SSHIFT = DEF_SEGSIZE_SHIFT; michael@0: hashp->DSIZE = DEF_DIRSIZE; michael@0: hashp->FFACTOR = DEF_FFACTOR; michael@0: hashp->hash = __default_hash; michael@0: memset(hashp->SPARES, 0, sizeof(hashp->SPARES)); michael@0: memset(hashp->BITMAPS, 0, sizeof (hashp->BITMAPS)); michael@0: michael@0: /* Fix bucket size to be optimal for file system */ michael@0: if (file != NULL) { michael@0: if (stat(file, &statbuf)) michael@0: return (NULL); michael@0: michael@0: #if !defined(_WIN32) && !defined(_WINDOWS) && !defined(macintosh) && !defined(XP_OS2) michael@0: #if defined(__QNX__) && !defined(__QNXNTO__) michael@0: hashp->BSIZE = 512; /* preferred blk size on qnx4 */ michael@0: #else michael@0: hashp->BSIZE = statbuf.st_blksize; michael@0: #endif michael@0: michael@0: /* new code added by Lou to reduce block michael@0: * size down below MAX_BSIZE michael@0: */ michael@0: if (hashp->BSIZE > MAX_BSIZE) michael@0: hashp->BSIZE = MAX_BSIZE; michael@0: #endif michael@0: hashp->BSHIFT = __log2((uint32)hashp->BSIZE); michael@0: } michael@0: michael@0: if (info) { michael@0: if (info->bsize) { michael@0: /* Round pagesize up to power of 2 */ michael@0: hashp->BSHIFT = __log2(info->bsize); michael@0: hashp->BSIZE = 1 << hashp->BSHIFT; michael@0: if (hashp->BSIZE > MAX_BSIZE) { michael@0: errno = EINVAL; michael@0: return (NULL); michael@0: } michael@0: } michael@0: if (info->ffactor) michael@0: hashp->FFACTOR = info->ffactor; michael@0: if (info->hash) michael@0: hashp->hash = info->hash; michael@0: if (info->nelem) michael@0: nelem = info->nelem; michael@0: if (info->lorder) { michael@0: if (info->lorder != BIG_ENDIAN && michael@0: info->lorder != LITTLE_ENDIAN) { michael@0: errno = EINVAL; michael@0: return (NULL); michael@0: } michael@0: hashp->LORDER = info->lorder; michael@0: } michael@0: } michael@0: /* init_htab sets errno if it fails */ michael@0: if (init_htab(hashp, nelem)) michael@0: return (NULL); michael@0: else michael@0: return (hashp); michael@0: } michael@0: /* michael@0: * This calls alloc_segs which may run out of memory. Alloc_segs will michael@0: * set errno, so we just pass the error information along. michael@0: * michael@0: * Returns 0 on No Error michael@0: */ michael@0: static int michael@0: init_htab(HTAB *hashp, int nelem) michael@0: { michael@0: register int nbuckets, nsegs; michael@0: int l2; michael@0: michael@0: /* michael@0: * Divide number of elements by the fill factor and determine a michael@0: * desired number of buckets. Allocate space for the next greater michael@0: * power of two number of buckets. michael@0: */ michael@0: nelem = (nelem - 1) / hashp->FFACTOR + 1; michael@0: michael@0: l2 = __log2((uint32)PR_MAX(nelem, 2)); michael@0: nbuckets = 1 << l2; michael@0: michael@0: hashp->SPARES[l2] = l2 + 1; michael@0: hashp->SPARES[l2 + 1] = l2 + 1; michael@0: hashp->OVFL_POINT = l2; michael@0: hashp->LAST_FREED = 2; michael@0: michael@0: /* First bitmap page is at: splitpoint l2 page offset 1 */ michael@0: if (__ibitmap(hashp, (int)OADDR_OF(l2, 1), l2 + 1, 0)) michael@0: return (-1); michael@0: michael@0: hashp->MAX_BUCKET = hashp->LOW_MASK = nbuckets - 1; michael@0: hashp->HIGH_MASK = (nbuckets << 1) - 1; michael@0: hashp->HDRPAGES = ((PR_MAX(sizeof(HASHHDR), MINHDRSIZE) - 1) >> michael@0: hashp->BSHIFT) + 1; michael@0: michael@0: nsegs = (nbuckets - 1) / hashp->SGSIZE + 1; michael@0: nsegs = 1 << __log2((uint32)nsegs); michael@0: michael@0: if (nsegs > hashp->DSIZE) michael@0: hashp->DSIZE = nsegs; michael@0: return (alloc_segs(hashp, nsegs)); michael@0: } michael@0: michael@0: /********************** DESTROY/CLOSE ROUTINES ************************/ michael@0: michael@0: /* michael@0: * Flushes any changes to the file if necessary and destroys the hashp michael@0: * structure, freeing all allocated space. michael@0: */ michael@0: static int michael@0: hdestroy(HTAB *hashp) michael@0: { michael@0: int i, save_errno; michael@0: michael@0: save_errno = 0; michael@0: michael@0: #ifdef HASH_STATISTICS michael@0: (void)fprintf(stderr, "hdestroy: accesses %ld collisions %ld\n", michael@0: hash_accesses, hash_collisions); michael@0: (void)fprintf(stderr, "hdestroy: expansions %ld\n", michael@0: hash_expansions); michael@0: (void)fprintf(stderr, "hdestroy: overflows %ld\n", michael@0: hash_overflows); michael@0: (void)fprintf(stderr, "keys %ld maxp %d segmentcount %d\n", michael@0: hashp->NKEYS, hashp->MAX_BUCKET, hashp->nsegs); michael@0: michael@0: for (i = 0; i < NCACHED; i++) michael@0: (void)fprintf(stderr, michael@0: "spares[%d] = %d\n", i, hashp->SPARES[i]); michael@0: #endif michael@0: /* michael@0: * Call on buffer manager to free buffers, and if required, michael@0: * write them to disk. michael@0: */ michael@0: if (__buf_free(hashp, 1, hashp->save_file)) michael@0: save_errno = errno; michael@0: if (hashp->dir) { michael@0: free(*hashp->dir); /* Free initial segments */ michael@0: /* Free extra segments */ michael@0: while (hashp->exsegs--) michael@0: free(hashp->dir[--hashp->nsegs]); michael@0: free(hashp->dir); michael@0: } michael@0: if (flush_meta(hashp) && !save_errno) michael@0: save_errno = errno; michael@0: /* Free Bigmaps */ michael@0: for (i = 0; i < hashp->nmaps; i++) michael@0: if (hashp->mapp[i]) michael@0: free(hashp->mapp[i]); michael@0: michael@0: if (hashp->fp != -1) michael@0: (void)close(hashp->fp); michael@0: michael@0: if(hashp->filename) { michael@0: #if defined(_WIN32) || defined(_WINDOWS) || defined(XP_OS2) michael@0: if (hashp->is_temp) michael@0: (void)unlink(hashp->filename); michael@0: #endif michael@0: free(hashp->filename); michael@0: } michael@0: if (hashp->tmp_buf) michael@0: free(hashp->tmp_buf); michael@0: if (hashp->tmp_key) michael@0: free(hashp->tmp_key); michael@0: free(hashp); michael@0: if (save_errno) { michael@0: errno = save_errno; michael@0: return (DBM_ERROR); michael@0: } michael@0: return (SUCCESS); michael@0: } michael@0: michael@0: #if defined(_WIN32) || defined(_WINDOWS) michael@0: /* michael@0: * Close and reopen file to force file length update on windows. michael@0: * michael@0: * Returns: michael@0: * 0 == OK michael@0: * -1 DBM_ERROR michael@0: */ michael@0: static int michael@0: update_EOF(HTAB *hashp) michael@0: { michael@0: #if defined(DBM_REOPEN_ON_FLUSH) michael@0: char * file = hashp->filename; michael@0: off_t file_size; michael@0: int flags; michael@0: int mode = -1; michael@0: struct stat statbuf; michael@0: michael@0: memset(&statbuf, 0, sizeof statbuf); michael@0: michael@0: /* make sure we won't lose the file by closing it. */ michael@0: if (!file || (stat(file, &statbuf) && (errno == ENOENT))) { michael@0: /* pretend we did it. */ michael@0: return 0; michael@0: } michael@0: michael@0: (void)close(hashp->fp); michael@0: michael@0: flags = hashp->flags & ~(O_TRUNC | O_CREAT | O_EXCL); michael@0: michael@0: if ((hashp->fp = DBFILE_OPEN(file, flags | O_BINARY, mode)) == -1) michael@0: return -1; michael@0: file_size = lseek(hashp->fp, (off_t)0, SEEK_END); michael@0: if (file_size == -1) michael@0: return -1; michael@0: hashp->file_size = file_size; michael@0: return 0; michael@0: #else michael@0: int fd = hashp->fp; michael@0: off_t file_size = lseek(fd, (off_t)0, SEEK_END); michael@0: HANDLE handle = (HANDLE)_get_osfhandle(fd); michael@0: BOOL cool = FlushFileBuffers(handle); michael@0: #ifdef DEBUG3 michael@0: if (!cool) { michael@0: DWORD err = GetLastError(); michael@0: (void)fprintf(stderr, michael@0: "FlushFileBuffers failed, last error = %d, 0x%08x\n", michael@0: err, err); michael@0: } michael@0: #endif michael@0: if (file_size == -1) michael@0: return -1; michael@0: hashp->file_size = file_size; michael@0: return cool ? 0 : -1; michael@0: #endif michael@0: } michael@0: #endif michael@0: michael@0: /* michael@0: * Write modified pages to disk michael@0: * michael@0: * Returns: michael@0: * 0 == OK michael@0: * -1 DBM_ERROR michael@0: */ michael@0: static int michael@0: hash_sync(const DB *dbp, uint flags) michael@0: { michael@0: HTAB *hashp; michael@0: michael@0: if (flags != 0) { michael@0: errno = EINVAL; michael@0: return (DBM_ERROR); michael@0: } michael@0: michael@0: if (!dbp) michael@0: return (DBM_ERROR); michael@0: michael@0: hashp = (HTAB *)dbp->internal; michael@0: if(!hashp) michael@0: return (DBM_ERROR); michael@0: michael@0: if (!hashp->save_file) michael@0: return (0); michael@0: if (__buf_free(hashp, 0, 1) || flush_meta(hashp)) michael@0: return (DBM_ERROR); michael@0: #if defined(_WIN32) || defined(_WINDOWS) michael@0: if (hashp->updateEOF && hashp->filename && !hashp->is_temp) { michael@0: int status = update_EOF(hashp); michael@0: hashp->updateEOF = 0; michael@0: if (status) michael@0: return status; michael@0: } michael@0: #endif michael@0: hashp->new_file = 0; michael@0: return (0); michael@0: } michael@0: michael@0: /* michael@0: * Returns: michael@0: * 0 == OK michael@0: * -1 indicates that errno should be set michael@0: */ michael@0: static int michael@0: flush_meta(HTAB *hashp) michael@0: { michael@0: HASHHDR *whdrp; michael@0: #if BYTE_ORDER == LITTLE_ENDIAN michael@0: HASHHDR whdr; michael@0: #endif michael@0: int fp, i, wsize; michael@0: michael@0: if (!hashp->save_file) michael@0: return (0); michael@0: hashp->MAGIC = HASHMAGIC; michael@0: hashp->VERSION = HASHVERSION; michael@0: hashp->H_CHARKEY = hashp->hash(CHARKEY, sizeof(CHARKEY)); michael@0: michael@0: fp = hashp->fp; michael@0: whdrp = &hashp->hdr; michael@0: #if BYTE_ORDER == LITTLE_ENDIAN michael@0: whdrp = &whdr; michael@0: swap_header_copy(&hashp->hdr, whdrp); michael@0: #endif michael@0: if ((lseek(fp, (off_t)0, SEEK_SET) == -1) || michael@0: ((wsize = write(fp, (char*)whdrp, sizeof(HASHHDR))) == -1)) michael@0: return (-1); michael@0: else michael@0: if (wsize != sizeof(HASHHDR)) { michael@0: errno = EFTYPE; michael@0: hashp->dbmerrno = errno; michael@0: return (-1); michael@0: } michael@0: for (i = 0; i < NCACHED; i++) michael@0: if (hashp->mapp[i]) michael@0: if (__put_page(hashp, (char *)hashp->mapp[i], michael@0: hashp->BITMAPS[i], 0, 1)) michael@0: return (-1); michael@0: return (0); michael@0: } michael@0: michael@0: /*******************************SEARCH ROUTINES *****************************/ michael@0: /* michael@0: * All the access routines return michael@0: * michael@0: * Returns: michael@0: * 0 on SUCCESS michael@0: * 1 to indicate an external DBM_ERROR (i.e. key not found, etc) michael@0: * -1 to indicate an internal DBM_ERROR (i.e. out of memory, etc) michael@0: */ michael@0: static int michael@0: hash_get( michael@0: const DB *dbp, michael@0: const DBT *key, michael@0: DBT *data, michael@0: uint flag) michael@0: { michael@0: HTAB *hashp; michael@0: int rv; michael@0: michael@0: hashp = (HTAB *)dbp->internal; michael@0: if (!hashp) michael@0: return (DBM_ERROR); michael@0: michael@0: if (flag) { michael@0: hashp->dbmerrno = errno = EINVAL; michael@0: return (DBM_ERROR); michael@0: } michael@0: michael@0: rv = hash_access(hashp, HASH_GET, (DBT *)key, data); michael@0: michael@0: if(rv == DATABASE_CORRUPTED_ERROR) michael@0: { michael@0: #if defined(unix) && defined(DEBUG) michael@0: printf("\n\nDBM Database has been corrupted, tell Lou...\n\n"); michael@0: #endif michael@0: __remove_database((DB *)dbp); michael@0: } michael@0: michael@0: return(rv); michael@0: } michael@0: michael@0: static int michael@0: hash_put( michael@0: const DB *dbp, michael@0: DBT *key, michael@0: const DBT *data, michael@0: uint flag) michael@0: { michael@0: HTAB *hashp; michael@0: int rv; michael@0: michael@0: hashp = (HTAB *)dbp->internal; michael@0: if (!hashp) michael@0: return (DBM_ERROR); michael@0: michael@0: if (flag && flag != R_NOOVERWRITE) { michael@0: hashp->dbmerrno = errno = EINVAL; michael@0: return (DBM_ERROR); michael@0: } michael@0: if ((hashp->flags & O_ACCMODE) == O_RDONLY) { michael@0: hashp->dbmerrno = errno = EPERM; michael@0: return (DBM_ERROR); michael@0: } michael@0: michael@0: rv = hash_access(hashp, flag == R_NOOVERWRITE ? michael@0: HASH_PUTNEW : HASH_PUT, (DBT *)key, (DBT *)data); michael@0: michael@0: if(rv == DATABASE_CORRUPTED_ERROR) michael@0: { michael@0: #if defined(unix) && defined(DEBUG) michael@0: printf("\n\nDBM Database has been corrupted, tell Lou...\n\n"); michael@0: #endif michael@0: __remove_database((DB *)dbp); michael@0: } michael@0: michael@0: return(rv); michael@0: } michael@0: michael@0: static int michael@0: hash_delete( michael@0: const DB *dbp, michael@0: const DBT *key, michael@0: uint flag) /* Ignored */ michael@0: { michael@0: HTAB *hashp; michael@0: int rv; michael@0: michael@0: hashp = (HTAB *)dbp->internal; michael@0: if (!hashp) michael@0: return (DBM_ERROR); michael@0: michael@0: if (flag && flag != R_CURSOR) { michael@0: hashp->dbmerrno = errno = EINVAL; michael@0: return (DBM_ERROR); michael@0: } michael@0: if ((hashp->flags & O_ACCMODE) == O_RDONLY) { michael@0: hashp->dbmerrno = errno = EPERM; michael@0: return (DBM_ERROR); michael@0: } michael@0: rv = hash_access(hashp, HASH_DELETE, (DBT *)key, NULL); michael@0: michael@0: if(rv == DATABASE_CORRUPTED_ERROR) michael@0: { michael@0: #if defined(unix) && defined(DEBUG) michael@0: printf("\n\nDBM Database has been corrupted, tell Lou...\n\n"); michael@0: #endif michael@0: __remove_database((DB *)dbp); michael@0: } michael@0: michael@0: return(rv); michael@0: } michael@0: michael@0: #define MAX_OVERFLOW_HASH_ACCESS_LOOPS 2000 michael@0: /* michael@0: * Assume that hashp has been set in wrapper routine. michael@0: */ michael@0: static int michael@0: hash_access( michael@0: HTAB *hashp, michael@0: ACTION action, michael@0: DBT *key, DBT *val) michael@0: { michael@0: register BUFHEAD *rbufp; michael@0: BUFHEAD *bufp, *save_bufp; michael@0: register uint16 *bp; michael@0: register long n, ndx, off; michael@0: register size_t size; michael@0: register char *kp; michael@0: uint16 pageno; michael@0: uint32 ovfl_loop_count=0; michael@0: int32 last_overflow_page_no = -1; michael@0: michael@0: #ifdef HASH_STATISTICS michael@0: hash_accesses++; michael@0: #endif michael@0: michael@0: off = hashp->BSIZE; michael@0: size = key->size; michael@0: kp = (char *)key->data; michael@0: rbufp = __get_buf(hashp, __call_hash(hashp, kp, size), NULL, 0); michael@0: if (!rbufp) michael@0: return (DATABASE_CORRUPTED_ERROR); michael@0: save_bufp = rbufp; michael@0: michael@0: /* Pin the bucket chain */ michael@0: rbufp->flags |= BUF_PIN; michael@0: for (bp = (uint16 *)rbufp->page, n = *bp++, ndx = 1; ndx < n;) michael@0: { michael@0: michael@0: if (bp[1] >= REAL_KEY) { michael@0: /* Real key/data pair */ michael@0: if (size == (unsigned long)(off - *bp) && michael@0: memcmp(kp, rbufp->page + *bp, size) == 0) michael@0: goto found; michael@0: off = bp[1]; michael@0: #ifdef HASH_STATISTICS michael@0: hash_collisions++; michael@0: #endif michael@0: bp += 2; michael@0: ndx += 2; michael@0: } else if (bp[1] == OVFLPAGE) { michael@0: michael@0: /* database corruption: overflow loop detection */ michael@0: if(last_overflow_page_no == (int32)*bp) michael@0: return (DATABASE_CORRUPTED_ERROR); michael@0: michael@0: last_overflow_page_no = *bp; michael@0: michael@0: rbufp = __get_buf(hashp, *bp, rbufp, 0); michael@0: if (!rbufp) { michael@0: save_bufp->flags &= ~BUF_PIN; michael@0: return (DBM_ERROR); michael@0: } michael@0: michael@0: ovfl_loop_count++; michael@0: if(ovfl_loop_count > MAX_OVERFLOW_HASH_ACCESS_LOOPS) michael@0: return (DATABASE_CORRUPTED_ERROR); michael@0: michael@0: /* FOR LOOP INIT */ michael@0: bp = (uint16 *)rbufp->page; michael@0: n = *bp++; michael@0: ndx = 1; michael@0: off = hashp->BSIZE; michael@0: } else if (bp[1] < REAL_KEY) { michael@0: if ((ndx = michael@0: __find_bigpair(hashp, rbufp, ndx, kp, (int)size)) > 0) michael@0: goto found; michael@0: if (ndx == -2) { michael@0: bufp = rbufp; michael@0: if (!(pageno = michael@0: __find_last_page(hashp, &bufp))) { michael@0: ndx = 0; michael@0: rbufp = bufp; michael@0: break; /* FOR */ michael@0: } michael@0: rbufp = __get_buf(hashp, pageno, bufp, 0); michael@0: if (!rbufp) { michael@0: save_bufp->flags &= ~BUF_PIN; michael@0: return (DBM_ERROR); michael@0: } michael@0: /* FOR LOOP INIT */ michael@0: bp = (uint16 *)rbufp->page; michael@0: n = *bp++; michael@0: ndx = 1; michael@0: off = hashp->BSIZE; michael@0: } else { michael@0: save_bufp->flags &= ~BUF_PIN; michael@0: return (DBM_ERROR); michael@0: michael@0: } michael@0: } michael@0: } michael@0: michael@0: /* Not found */ michael@0: switch (action) { michael@0: case HASH_PUT: michael@0: case HASH_PUTNEW: michael@0: if (__addel(hashp, rbufp, key, val)) { michael@0: save_bufp->flags &= ~BUF_PIN; michael@0: return (DBM_ERROR); michael@0: } else { michael@0: save_bufp->flags &= ~BUF_PIN; michael@0: return (SUCCESS); michael@0: } michael@0: case HASH_GET: michael@0: case HASH_DELETE: michael@0: default: michael@0: save_bufp->flags &= ~BUF_PIN; michael@0: return (ABNORMAL); michael@0: } michael@0: michael@0: found: michael@0: switch (action) { michael@0: case HASH_PUTNEW: michael@0: save_bufp->flags &= ~BUF_PIN; michael@0: return (ABNORMAL); michael@0: case HASH_GET: michael@0: bp = (uint16 *)rbufp->page; michael@0: if (bp[ndx + 1] < REAL_KEY) { michael@0: if (__big_return(hashp, rbufp, ndx, val, 0)) michael@0: return (DBM_ERROR); michael@0: } else { michael@0: val->data = (uint8 *)rbufp->page + (int)bp[ndx + 1]; michael@0: val->size = bp[ndx] - bp[ndx + 1]; michael@0: } michael@0: break; michael@0: case HASH_PUT: michael@0: if ((__delpair(hashp, rbufp, ndx)) || michael@0: (__addel(hashp, rbufp, key, val))) { michael@0: save_bufp->flags &= ~BUF_PIN; michael@0: return (DBM_ERROR); michael@0: } michael@0: break; michael@0: case HASH_DELETE: michael@0: if (__delpair(hashp, rbufp, ndx)) michael@0: return (DBM_ERROR); michael@0: break; michael@0: default: michael@0: abort(); michael@0: } michael@0: save_bufp->flags &= ~BUF_PIN; michael@0: return (SUCCESS); michael@0: } michael@0: michael@0: static int michael@0: hash_seq( michael@0: const DB *dbp, michael@0: DBT *key, DBT *data, michael@0: uint flag) michael@0: { michael@0: register uint32 bucket; michael@0: register BUFHEAD *bufp; michael@0: HTAB *hashp; michael@0: uint16 *bp, ndx; michael@0: michael@0: hashp = (HTAB *)dbp->internal; michael@0: if (!hashp) michael@0: return (DBM_ERROR); michael@0: michael@0: if (flag && flag != R_FIRST && flag != R_NEXT) { michael@0: hashp->dbmerrno = errno = EINVAL; michael@0: return (DBM_ERROR); michael@0: } michael@0: #ifdef HASH_STATISTICS michael@0: hash_accesses++; michael@0: #endif michael@0: if ((hashp->cbucket < 0) || (flag == R_FIRST)) { michael@0: hashp->cbucket = 0; michael@0: hashp->cndx = 1; michael@0: hashp->cpage = NULL; michael@0: } michael@0: michael@0: for (bp = NULL; !bp || !bp[0]; ) { michael@0: if (!(bufp = hashp->cpage)) { michael@0: for (bucket = hashp->cbucket; michael@0: bucket <= (uint32)hashp->MAX_BUCKET; michael@0: bucket++, hashp->cndx = 1) { michael@0: bufp = __get_buf(hashp, bucket, NULL, 0); michael@0: if (!bufp) michael@0: return (DBM_ERROR); michael@0: hashp->cpage = bufp; michael@0: bp = (uint16 *)bufp->page; michael@0: if (bp[0]) michael@0: break; michael@0: } michael@0: hashp->cbucket = bucket; michael@0: if (hashp->cbucket > hashp->MAX_BUCKET) { michael@0: hashp->cbucket = -1; michael@0: return (ABNORMAL); michael@0: } michael@0: } else michael@0: bp = (uint16 *)hashp->cpage->page; michael@0: michael@0: #ifdef DEBUG michael@0: assert(bp); michael@0: assert(bufp); michael@0: #endif michael@0: while (bp[hashp->cndx + 1] == OVFLPAGE) { michael@0: bufp = hashp->cpage = michael@0: __get_buf(hashp, bp[hashp->cndx], bufp, 0); michael@0: if (!bufp) michael@0: return (DBM_ERROR); michael@0: bp = (uint16 *)(bufp->page); michael@0: hashp->cndx = 1; michael@0: } michael@0: if (!bp[0]) { michael@0: hashp->cpage = NULL; michael@0: ++hashp->cbucket; michael@0: } michael@0: } michael@0: ndx = hashp->cndx; michael@0: if (bp[ndx + 1] < REAL_KEY) { michael@0: if (__big_keydata(hashp, bufp, key, data, 1)) michael@0: return (DBM_ERROR); michael@0: } else { michael@0: key->data = (uint8 *)hashp->cpage->page + bp[ndx]; michael@0: key->size = (ndx > 1 ? bp[ndx - 1] : hashp->BSIZE) - bp[ndx]; michael@0: data->data = (uint8 *)hashp->cpage->page + bp[ndx + 1]; michael@0: data->size = bp[ndx] - bp[ndx + 1]; michael@0: ndx += 2; michael@0: if (ndx > bp[0]) { michael@0: hashp->cpage = NULL; michael@0: hashp->cbucket++; michael@0: hashp->cndx = 1; michael@0: } else michael@0: hashp->cndx = ndx; michael@0: } michael@0: return (SUCCESS); michael@0: } michael@0: michael@0: /********************************* UTILITIES ************************/ michael@0: michael@0: /* michael@0: * Returns: michael@0: * 0 ==> OK michael@0: * -1 ==> Error michael@0: */ michael@0: extern int michael@0: __expand_table(HTAB *hashp) michael@0: { michael@0: uint32 old_bucket, new_bucket; michael@0: int new_segnum, spare_ndx; michael@0: size_t dirsize; michael@0: michael@0: #ifdef HASH_STATISTICS michael@0: hash_expansions++; michael@0: #endif michael@0: new_bucket = ++hashp->MAX_BUCKET; michael@0: old_bucket = (hashp->MAX_BUCKET & hashp->LOW_MASK); michael@0: michael@0: new_segnum = new_bucket >> hashp->SSHIFT; michael@0: michael@0: /* Check if we need a new segment */ michael@0: if (new_segnum >= hashp->nsegs) { michael@0: /* Check if we need to expand directory */ michael@0: if (new_segnum >= hashp->DSIZE) { michael@0: /* Reallocate directory */ michael@0: dirsize = hashp->DSIZE * sizeof(SEGMENT *); michael@0: if (!hash_realloc(&hashp->dir, dirsize, dirsize << 1)) michael@0: return (-1); michael@0: hashp->DSIZE = dirsize << 1; michael@0: } michael@0: if ((hashp->dir[new_segnum] = michael@0: (SEGMENT)calloc((size_t)hashp->SGSIZE, sizeof(SEGMENT))) == NULL) michael@0: return (-1); michael@0: hashp->exsegs++; michael@0: hashp->nsegs++; michael@0: } michael@0: /* michael@0: * If the split point is increasing (MAX_BUCKET's log base 2 michael@0: * * increases), we need to copy the current contents of the spare michael@0: * split bucket to the next bucket. michael@0: */ michael@0: spare_ndx = __log2((uint32)(hashp->MAX_BUCKET + 1)); michael@0: if (spare_ndx > hashp->OVFL_POINT) { michael@0: hashp->SPARES[spare_ndx] = hashp->SPARES[hashp->OVFL_POINT]; michael@0: hashp->OVFL_POINT = spare_ndx; michael@0: } michael@0: michael@0: if (new_bucket > (uint32)hashp->HIGH_MASK) { michael@0: /* Starting a new doubling */ michael@0: hashp->LOW_MASK = hashp->HIGH_MASK; michael@0: hashp->HIGH_MASK = new_bucket | hashp->LOW_MASK; michael@0: } michael@0: /* Relocate records to the new bucket */ michael@0: return (__split_page(hashp, old_bucket, new_bucket)); michael@0: } michael@0: michael@0: /* michael@0: * If realloc guarantees that the pointer is not destroyed if the realloc michael@0: * fails, then this routine can go away. michael@0: */ michael@0: static void * michael@0: hash_realloc( michael@0: SEGMENT **p_ptr, michael@0: size_t oldsize, size_t newsize) michael@0: { michael@0: register void *p; michael@0: michael@0: if ((p = malloc(newsize))) { michael@0: memmove(p, *p_ptr, oldsize); michael@0: memset((char *)p + oldsize, 0, newsize - oldsize); michael@0: free(*p_ptr); michael@0: *p_ptr = (SEGMENT *)p; michael@0: } michael@0: return (p); michael@0: } michael@0: michael@0: extern uint32 michael@0: __call_hash(HTAB *hashp, char *k, size_t len) michael@0: { michael@0: uint32 n, bucket; michael@0: michael@0: n = hashp->hash(k, len); michael@0: bucket = n & hashp->HIGH_MASK; michael@0: if (bucket > (uint32)hashp->MAX_BUCKET) michael@0: bucket = bucket & hashp->LOW_MASK; michael@0: return (bucket); michael@0: } michael@0: michael@0: /* michael@0: * Allocate segment table. On error, set errno. michael@0: * michael@0: * Returns 0 on success michael@0: */ michael@0: static int michael@0: alloc_segs( michael@0: HTAB *hashp, michael@0: int nsegs) michael@0: { michael@0: register int i; michael@0: register SEGMENT store; michael@0: michael@0: if ((hashp->dir = michael@0: (SEGMENT *)calloc((size_t)hashp->DSIZE, sizeof(SEGMENT *))) == NULL) { michael@0: errno = ENOMEM; michael@0: return (-1); michael@0: } michael@0: /* Allocate segments */ michael@0: if ((store = michael@0: (SEGMENT)calloc((size_t)nsegs << hashp->SSHIFT, sizeof(SEGMENT))) == NULL) { michael@0: errno = ENOMEM; michael@0: return (-1); michael@0: } michael@0: for (i = 0; i < nsegs; i++, hashp->nsegs++) michael@0: hashp->dir[i] = &store[i << hashp->SSHIFT]; michael@0: return (0); michael@0: } michael@0: michael@0: #if BYTE_ORDER == LITTLE_ENDIAN michael@0: /* michael@0: * Hashp->hdr needs to be byteswapped. michael@0: */ michael@0: static void michael@0: swap_header_copy( michael@0: HASHHDR *srcp, HASHHDR *destp) michael@0: { michael@0: int i; michael@0: michael@0: P_32_COPY(srcp->magic, destp->magic); michael@0: P_32_COPY(srcp->version, destp->version); michael@0: P_32_COPY(srcp->lorder, destp->lorder); michael@0: P_32_COPY(srcp->bsize, destp->bsize); michael@0: P_32_COPY(srcp->bshift, destp->bshift); michael@0: P_32_COPY(srcp->dsize, destp->dsize); michael@0: P_32_COPY(srcp->ssize, destp->ssize); michael@0: P_32_COPY(srcp->sshift, destp->sshift); michael@0: P_32_COPY(srcp->ovfl_point, destp->ovfl_point); michael@0: P_32_COPY(srcp->last_freed, destp->last_freed); michael@0: P_32_COPY(srcp->max_bucket, destp->max_bucket); michael@0: P_32_COPY(srcp->high_mask, destp->high_mask); michael@0: P_32_COPY(srcp->low_mask, destp->low_mask); michael@0: P_32_COPY(srcp->ffactor, destp->ffactor); michael@0: P_32_COPY(srcp->nkeys, destp->nkeys); michael@0: P_32_COPY(srcp->hdrpages, destp->hdrpages); michael@0: P_32_COPY(srcp->h_charkey, destp->h_charkey); michael@0: for (i = 0; i < NCACHED; i++) { michael@0: P_32_COPY(srcp->spares[i], destp->spares[i]); michael@0: P_16_COPY(srcp->bitmaps[i], destp->bitmaps[i]); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: swap_header(HTAB *hashp) michael@0: { michael@0: HASHHDR *hdrp; michael@0: int i; michael@0: michael@0: hdrp = &hashp->hdr; michael@0: michael@0: M_32_SWAP(hdrp->magic); michael@0: M_32_SWAP(hdrp->version); michael@0: M_32_SWAP(hdrp->lorder); michael@0: M_32_SWAP(hdrp->bsize); michael@0: M_32_SWAP(hdrp->bshift); michael@0: M_32_SWAP(hdrp->dsize); michael@0: M_32_SWAP(hdrp->ssize); michael@0: M_32_SWAP(hdrp->sshift); michael@0: M_32_SWAP(hdrp->ovfl_point); michael@0: M_32_SWAP(hdrp->last_freed); michael@0: M_32_SWAP(hdrp->max_bucket); michael@0: M_32_SWAP(hdrp->high_mask); michael@0: M_32_SWAP(hdrp->low_mask); michael@0: M_32_SWAP(hdrp->ffactor); michael@0: M_32_SWAP(hdrp->nkeys); michael@0: M_32_SWAP(hdrp->hdrpages); michael@0: M_32_SWAP(hdrp->h_charkey); michael@0: for (i = 0; i < NCACHED; i++) { michael@0: M_32_SWAP(hdrp->spares[i]); michael@0: M_16_SWAP(hdrp->bitmaps[i]); michael@0: } michael@0: } michael@0: #endif