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