📄 tdb.c
字号:
/* * Database functions * Copyright (C) Andrew Tridgell 1999 * * Redistribution and use in source and binary forms are permitted * provided that the above copyright notice and this paragraph are * duplicated in all such forms AND provided that this software or * any derived work is only used as part of the PPP daemon (pppd) * and related utilities. * The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. * * Note: this software is also available under the Gnu Public License * version 2 or later. */#include <stdlib.h>#include <stdio.h>#include <fcntl.h>#include <unistd.h>#include <string.h>#include <fcntl.h>#include <errno.h>#include <sys/mman.h>#include <sys/stat.h>#include "tdb.h"#define TDB_VERSION (0x26011967 + 1)#define TDB_MAGIC (0x26011999U)#define TDB_FREE_MAGIC (~TDB_MAGIC)#define TDB_ALIGN 4#define MIN_REC_SIZE (2*sizeof(struct list_struct) + TDB_ALIGN)#define DEFAULT_HASH_SIZE 128#define TDB_PAGE_SIZE 0x2000#define TDB_LEN_MULTIPLIER 10#define FREELIST_TOP (sizeof(struct tdb_header))#define LOCK_SET 1#define LOCK_CLEAR 0/* lock offsets */#define GLOBAL_LOCK 0#define ACTIVE_LOCK 4#define LIST_LOCK_BASE 1024#define BUCKET(hash) ((hash) % tdb->header.hash_size)#ifndef MAP_FILE#define MAP_FILE 0#endif/* the body of the database is made of one list_struct for the free space plus a separate data list for each hash value */struct list_struct { tdb_len rec_len; /* total byte length of record */ tdb_off next; /* offset of the next record in the list */ tdb_len key_len; /* byte length of key */ tdb_len data_len; /* byte length of data */ unsigned full_hash; /* the full 32 bit hash of the key */ unsigned magic; /* try to catch errors */ /* the following union is implied union { char record[rec_len]; struct { char key[key_len]; char data[data_len]; } } */};/* a null data record - useful for error returns */static TDB_DATA null_data;/* a byte range locking function - return 0 on success this functions locks/unlocks 1 byte at the specified offset */static int tdb_brlock(TDB_CONTEXT *tdb, tdb_off offset, int set, int rw_type, int lck_type){#if NOLOCK return 0;#else struct flock fl; if (tdb->fd == -1) return 0; /* for in memory tdb */ if (tdb->read_only) return -1; fl.l_type = set==LOCK_SET?rw_type:F_UNLCK; fl.l_whence = SEEK_SET; fl.l_start = offset; fl.l_len = 1; fl.l_pid = 0; if (fcntl(tdb->fd, lck_type, &fl) != 0) {#if TDB_DEBUG if (lck_type == F_SETLKW) { printf("lock %d failed at %d (%s)\n", set, offset, strerror(errno)); }#endif tdb->ecode = TDB_ERR_LOCK; return -1; } return 0;#endif}/* lock a list in the database. list -1 is the alloc list */static int tdb_lock(TDB_CONTEXT *tdb, int list){ if (list < -1 || list >= (int)tdb->header.hash_size) {#if TDB_DEBUG printf("bad list %d\n", list);#endif return -1; } if (tdb->locked[list+1] == 0) { if (tdb_brlock(tdb, LIST_LOCK_BASE + 4*list, LOCK_SET, F_WRLCK, F_SETLKW) != 0) { return -1; } } tdb->locked[list+1]++; return 0;}/* unlock the database. */static int tdb_unlock(TDB_CONTEXT *tdb, int list){ if (list < -1 || list >= (int)tdb->header.hash_size) {#if TDB_DEBUG printf("bad unlock list %d\n", list);#endif return -1; } if (tdb->locked[list+1] == 0) {#if TDB_DEBUG printf("not locked %d\n", list);#endif tdb->ecode = TDB_ERR_LOCK; return -1; } if (tdb->locked[list+1] == 1) { if (tdb_brlock(tdb, LIST_LOCK_BASE + 4*list, LOCK_CLEAR, F_WRLCK, F_SETLKW) != 0) { return -1; } } tdb->locked[list+1]--; return 0;}/* the hash algorithm - turn a key into an integer This is based on the hash agorithm from gdbm */static unsigned tdb_hash(TDB_DATA *key){ unsigned value; /* Used to compute the hash value. */ unsigned i; /* Used to cycle through random values. */ /* Set the initial value from the key size. */ value = 0x238F13AF * key->dsize; for (i=0; i < key->dsize; i++) { value = (value + (key->dptr[i] << (i*5 % 24))); } value = (1103515243 * value + 12345); return value;}/* find the top of the hash chain for an open database */static tdb_off tdb_hash_top(TDB_CONTEXT *tdb, unsigned hash){ tdb_off ret; hash = BUCKET(hash); ret = FREELIST_TOP + (hash+1)*sizeof(tdb_off); return ret;}/* check for an out of bounds access - if it is out of bounds then see if the database has been expanded by someone else and expand if necessary */static int tdb_oob(TDB_CONTEXT *tdb, tdb_off offset){ struct stat st; if ((offset <= tdb->map_size) || (tdb->fd == -1)) return 0; fstat(tdb->fd, &st); if (st.st_size <= (ssize_t)offset) { tdb->ecode = TDB_ERR_IO; return -1; }#if HAVE_MMAP if (tdb->map_ptr) { munmap(tdb->map_ptr, tdb->map_size); tdb->map_ptr = NULL; }#endif tdb->map_size = st.st_size;#if HAVE_MMAP tdb->map_ptr = (void *)mmap(NULL, tdb->map_size, tdb->read_only?PROT_READ:PROT_READ|PROT_WRITE, MAP_SHARED | MAP_FILE, tdb->fd, 0);#endif return 0;}/* write a lump of data at a specified offset */static int tdb_write(TDB_CONTEXT *tdb, tdb_off offset, const char *buf, tdb_len len){ if (tdb_oob(tdb, offset + len) != 0) { /* oops - trying to write beyond the end of the database! */ return -1; } if (tdb->map_ptr) { memcpy(offset + (char *)tdb->map_ptr, buf, len); } else { if (lseek(tdb->fd, offset, SEEK_SET) != offset || write(tdb->fd, buf, len) != (ssize_t)len) { tdb->ecode = TDB_ERR_IO; return -1; } } return 0;}/* read a lump of data at a specified offset */static int tdb_read(TDB_CONTEXT *tdb, tdb_off offset, char *buf, tdb_len len){ if (tdb_oob(tdb, offset + len) != 0) { /* oops - trying to read beyond the end of the database! */ return -1; } if (tdb->map_ptr) { memcpy(buf, offset + (char *)tdb->map_ptr, len); } else { if (lseek(tdb->fd, offset, SEEK_SET) != offset || read(tdb->fd, buf, len) != (ssize_t)len) { tdb->ecode = TDB_ERR_IO; return -1; } } return 0;}/* read a lump of data, allocating the space for it */static char *tdb_alloc_read(TDB_CONTEXT *tdb, tdb_off offset, tdb_len len){ char *buf; buf = (char *)malloc(len); if (!buf) { tdb->ecode = TDB_ERR_OOM; return NULL; } if (tdb_read(tdb, offset, buf, len) == -1) { free(buf); return NULL; } return buf;}/* convenience routine for writing a record */static int rec_write(TDB_CONTEXT *tdb, tdb_off offset, struct list_struct *rec){ return tdb_write(tdb, offset, (char *)rec, sizeof(*rec));}/* convenience routine for writing a tdb_off */static int ofs_write(TDB_CONTEXT *tdb, tdb_off offset, tdb_off *d){ return tdb_write(tdb, offset, (char *)d, sizeof(*d));}/* read a tdb_off from the store */static int ofs_read(TDB_CONTEXT *tdb, tdb_off offset, tdb_off *d){ return tdb_read(tdb, offset, (char *)d, sizeof(*d));}/* read a record and check for simple errors */static int rec_read(TDB_CONTEXT *tdb, tdb_off offset, struct list_struct *rec){ if (tdb_read(tdb, offset, (char *)rec, sizeof(*rec)) == -1) return -1; if (rec->magic != TDB_MAGIC) {#if TDB_DEBUG printf("bad magic 0x%08x at offset %d\n", rec->magic, offset);#endif tdb->ecode = TDB_ERR_CORRUPT; return -1; } if (tdb_oob(tdb, rec->next) != 0) { return -1; } return 0;}/* expand the database at least length bytes by expanding the underlying file and doing the mmap again if necessary */static int tdb_expand(TDB_CONTEXT *tdb, tdb_off length){ struct list_struct rec; tdb_off offset, ptr; char b = 0; tdb_lock(tdb,-1); /* make sure we know about any previous expansions by another process */ tdb_oob(tdb,tdb->map_size + 1); /* always make room for at least 10 more records */ length *= TDB_LEN_MULTIPLIER; /* and round the database up to a multiple of TDB_PAGE_SIZE */ length = ((tdb->map_size + length + TDB_PAGE_SIZE) & ~(TDB_PAGE_SIZE - 1)) - tdb->map_size; /* expand the file itself */ if (tdb->fd != -1) { lseek(tdb->fd, tdb->map_size + length - 1, SEEK_SET); if (write(tdb->fd, &b, 1) != 1) goto fail; } /* form a new freelist record */ offset = FREELIST_TOP; rec.rec_len = length - sizeof(rec); rec.magic = TDB_FREE_MAGIC; if (ofs_read(tdb, offset, &rec.next) == -1) { goto fail; }#if HAVE_MMAP if (tdb->fd != -1 && tdb->map_ptr) { munmap(tdb->map_ptr, tdb->map_size); tdb->map_ptr = NULL; }#endif tdb->map_size += length; if (tdb->fd == -1) { tdb->map_ptr = realloc(tdb->map_ptr, tdb->map_size); } /* write it out */ if (rec_write(tdb, tdb->map_size - length, &rec) == -1) { goto fail; } /* link it into the free list */ ptr = tdb->map_size - length; if (ofs_write(tdb, offset, &ptr) == -1) goto fail;#if HAVE_MMAP if (tdb->fd != -1) { tdb->map_ptr = (void *)mmap(NULL, tdb->map_size, PROT_READ|PROT_WRITE, MAP_SHARED | MAP_FILE, tdb->fd, 0); }#endif tdb_unlock(tdb, -1); return 0; fail: tdb_unlock(tdb,-1); return -1;}/* allocate some space from the free list. The offset returned points to a unconnected list_struct within the database with room for at least length bytes of total data 0 is returned if the space could not be allocated */static tdb_off tdb_allocate(TDB_CONTEXT *tdb, tdb_len length){ tdb_off offset, rec_ptr, last_ptr; struct list_struct rec, lastrec, newrec; tdb_lock(tdb, -1); again: last_ptr = 0; offset = FREELIST_TOP; /* read in the freelist top */ if (ofs_read(tdb, offset, &rec_ptr) == -1) { goto fail; } /* keep looking until we find a freelist record that is big enough */ while (rec_ptr) { if (tdb_read(tdb, rec_ptr, (char *)&rec, sizeof(rec)) == -1) { goto fail; } if (rec.magic != TDB_FREE_MAGIC) {#if TDB_DEBUG printf("bad magic 0x%08x in free list\n", rec.magic);#endif goto fail; } if (rec.rec_len >= length) { /* found it - now possibly split it up */ if (rec.rec_len > length + MIN_REC_SIZE) { length = (length + TDB_ALIGN) & ~(TDB_ALIGN-1); newrec.rec_len = rec.rec_len - (sizeof(rec) + length); newrec.next = rec.next; newrec.magic = TDB_FREE_MAGIC; rec.rec_len = length; rec.next = rec_ptr + sizeof(rec) + rec.rec_len; if (rec_write(tdb, rec.next, &newrec) == -1) { goto fail; } if (rec_write(tdb, rec_ptr, &rec) == -1) { goto fail; } } /* remove it from the list */ if (last_ptr == 0) { offset = FREELIST_TOP; if (ofs_write(tdb, offset, &rec.next) == -1) { goto fail; } } else { lastrec.next = rec.next; if (rec_write(tdb, last_ptr, &lastrec) == -1) { goto fail; } } /* all done - return the new record offset */ tdb_unlock(tdb, -1); return rec_ptr; } /* move to the next record */ lastrec = rec; last_ptr = rec_ptr; rec_ptr = rec.next; } /* we didn't find enough space. See if we can expand the database and if we can then try again */ if (tdb_expand(tdb, length + sizeof(rec)) == 0) goto again; fail:#if TDB_DEBUG printf("tdb_allocate failed for size %u\n", length);#endif tdb_unlock(tdb, -1); return 0;}/* initialise a new database with a specified hash size */static int tdb_new_database(TDB_CONTEXT *tdb, int hash_size){ struct tdb_header header; tdb_off offset; int i, size = 0; tdb_off buf[16]; /* create the header */ memset(&header, 0, sizeof(header)); memcpy(header.magic_food, TDB_MAGIC_FOOD, strlen(TDB_MAGIC_FOOD)+1); header.version = TDB_VERSION; header.hash_size = hash_size; lseek(tdb->fd, 0, SEEK_SET); ftruncate(tdb->fd, 0); if (tdb->fd != -1 && write(tdb->fd, &header, sizeof(header)) != sizeof(header)) { tdb->ecode = TDB_ERR_IO; return -1; } else size += sizeof(header); /* the freelist and hash pointers */ offset = 0; memset(buf, 0, sizeof(buf)); for (i=0;(hash_size+1)-i >= 16; i += 16) { if (tdb->fd != -1 && write(tdb->fd, buf, sizeof(buf)) != sizeof(buf)) { tdb->ecode = TDB_ERR_IO; return -1; } else size += sizeof(buf); } for (;i<hash_size+1; i++) { if (tdb->fd != -1 && write(tdb->fd, buf, sizeof(tdb_off)) != sizeof(tdb_off)) { tdb->ecode = TDB_ERR_IO; return -1; } else size += sizeof(tdb_off); } if (tdb->fd == -1) { tdb->map_ptr = calloc(size, 1); tdb->map_size = size; if (tdb->map_ptr == NULL) { tdb->ecode = TDB_ERR_IO; return -1; } memcpy(&tdb->header, &header, sizeof(header)); }#if TDB_DEBUG printf("initialised database of hash_size %u\n", hash_size);#endif return 0;}/* Returns 0 on fail. On success, return offset of record, and fills in rec */static tdb_off tdb_find(TDB_CONTEXT *tdb, TDB_DATA key, unsigned int hash, struct list_struct *rec){ tdb_off offset, rec_ptr; /* find the top of the hash chain */ offset = tdb_hash_top(tdb, hash); /* read in the hash top */ if (ofs_read(tdb, offset, &rec_ptr) == -1) return 0; /* keep looking until we find the right record */ while (rec_ptr) { if (rec_read(tdb, rec_ptr, rec) == -1) return 0; if (hash == rec->full_hash && key.dsize == rec->key_len) { char *k; /* a very likely hit - read the key */ k = tdb_alloc_read(tdb, rec_ptr + sizeof(*rec), rec->key_len); if (!k) return 0; if (memcmp(key.dptr, k, key.dsize) == 0) { free(k); return rec_ptr; } free(k); } /* move to the next record */ rec_ptr = rec->next; } return 0;}/* return an error string for the last tdb error*/char *tdb_error(TDB_CONTEXT *tdb){ int i; static struct { enum TDB_ERROR ecode; char *estring; } emap[] = { {TDB_SUCCESS, "Success"}, {TDB_ERR_CORRUPT, "Corrupt database"}, {TDB_ERR_IO, "IO Error"}, {TDB_ERR_LOCK, "Locking error"}, {TDB_ERR_OOM, "Out of memory"}, {TDB_ERR_EXISTS, "Record exists"}, {-1, NULL}}; if (tdb != NULL) { for (i=0;emap[i].estring;i++) { if (tdb->ecode == emap[i].ecode) return emap[i].estring; } } else { return "Invalid tdb context"; } return "Invalid error code";}/* update an entry in place - this only works if the new data size is <= the old data size and the key exists. on failure return -1*/int tdb_update(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf){ unsigned hash; struct list_struct rec; tdb_off rec_ptr; int ret = -1; if (tdb == NULL) {#ifdef TDB_DEBUG printf("tdb_update() called with null context\n");#endif return -1; } /* find which hash bucket it is in */ hash = tdb_hash(&key); tdb_lock(tdb, BUCKET(hash)); rec_ptr = tdb_find(tdb, key, hash, &rec); if (!rec_ptr) goto out; /* must be long enough */ if (rec.rec_len < key.dsize + dbuf.dsize) goto out; if (tdb_write(tdb, rec_ptr + sizeof(rec) + rec.key_len, dbuf.dptr, dbuf.dsize) == -1) goto out;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -