📄 tdb.c
字号:
if (!(rec_ptr = tdb_find(tdb, key, hash, rec))) tdb_unlock(tdb, BUCKET(hash), locktype); return rec_ptr;}enum TDB_ERROR tdb_error(TDB_CONTEXT *tdb){ return tdb->ecode;}static struct tdb_errname { enum TDB_ERROR ecode; const 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"}, {TDB_ERR_NOLOCK, "Lock exists on other keys"}, {TDB_ERR_NOEXIST, "Record does not exist"} };/* Error string for the last tdb error */const char *tdb_errorstr(TDB_CONTEXT *tdb){ u32 i; for (i = 0; i < sizeof(emap) / sizeof(struct tdb_errname); i++) if (tdb->ecode == emap[i].ecode) return emap[i].estring; 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.*/static int tdb_update_hash(TDB_CONTEXT *tdb, TDB_DATA key, u32 hash, TDB_DATA dbuf){ struct list_struct rec; tdb_off rec_ptr; /* find entry */ if (!(rec_ptr = tdb_find(tdb, key, hash, &rec))) return -1; /* must be long enough key, data and tailer */ if (rec.rec_len < key.dsize + dbuf.dsize + sizeof(tdb_off)) { tdb->ecode = TDB_SUCCESS; /* Not really an error */ return -1; } if (tdb_write(tdb, rec_ptr + sizeof(rec) + rec.key_len, dbuf.dptr, dbuf.dsize) == -1) return -1; if (dbuf.dsize != rec.data_len) { /* update size */ rec.data_len = dbuf.dsize; return rec_write(tdb, rec_ptr, &rec); } return 0;}/* find an entry in the database given a key *//* If an entry doesn't exist tdb_err will be set to * TDB_ERR_NOEXIST. If a key has no data attached * tdb_err will not be set. Both will return a * zero pptr and zero dsize. */TDB_DATA tdb_fetch(TDB_CONTEXT *tdb, TDB_DATA key){ tdb_off rec_ptr; struct list_struct rec; TDB_DATA ret; u32 hash; /* find which hash bucket it is in */ hash = tdb->hash_fn(&key); if (!(rec_ptr = tdb_find_lock_hash(tdb,key,hash,F_RDLCK,&rec))) return tdb_null; if (rec.data_len) ret.dptr = tdb_alloc_read(tdb, rec_ptr + sizeof(rec) + rec.key_len, rec.data_len); else ret.dptr = NULL; ret.dsize = rec.data_len; tdb_unlock(tdb, BUCKET(rec.full_hash), F_RDLCK); return ret;}/* check if an entry in the database exists note that 1 is returned if the key is found and 0 is returned if not found this doesn't match the conventions in the rest of this module, but is compatible with gdbm*/static int tdb_exists_hash(TDB_CONTEXT *tdb, TDB_DATA key, u32 hash){ struct list_struct rec; if (tdb_find_lock_hash(tdb, key, hash, F_RDLCK, &rec) == 0) return 0; tdb_unlock(tdb, BUCKET(rec.full_hash), F_RDLCK); return 1;}int tdb_exists(TDB_CONTEXT *tdb, TDB_DATA key){ u32 hash = tdb->hash_fn(&key); return tdb_exists_hash(tdb, key, hash);}/* record lock stops delete underneath */static int lock_record(TDB_CONTEXT *tdb, tdb_off off){ return off ? tdb_brlock(tdb, off, F_RDLCK, F_SETLKW, 0) : 0;}/* Write locks override our own fcntl readlocks, so check it here. Note this is meant to be F_SETLK, *not* F_SETLKW, as it's not an error to fail to get the lock here.*/ static int write_lock_record(TDB_CONTEXT *tdb, tdb_off off){ struct tdb_traverse_lock *i; for (i = &tdb->travlocks; i; i = i->next) if (i->off == off) return -1; return tdb_brlock(tdb, off, F_WRLCK, F_SETLK, 1);}/* Note this is meant to be F_SETLK, *not* F_SETLKW, as it's not an error to fail to get the lock here.*/static int write_unlock_record(TDB_CONTEXT *tdb, tdb_off off){ return tdb_brlock(tdb, off, F_UNLCK, F_SETLK, 0);}/* fcntl locks don't stack: avoid unlocking someone else's */static int unlock_record(TDB_CONTEXT *tdb, tdb_off off){ struct tdb_traverse_lock *i; u32 count = 0; if (off == 0) return 0; for (i = &tdb->travlocks; i; i = i->next) if (i->off == off) count++; return (count == 1 ? tdb_brlock(tdb, off, F_UNLCK, F_SETLKW, 0) : 0);}/* actually delete an entry in the database given the offset */static int do_delete(TDB_CONTEXT *tdb, tdb_off rec_ptr, struct list_struct*rec){ tdb_off last_ptr, i; struct list_struct lastrec; if (tdb->read_only) return -1; if (write_lock_record(tdb, rec_ptr) == -1) { /* Someone traversing here: mark it as dead */ rec->magic = TDB_DEAD_MAGIC; return rec_write(tdb, rec_ptr, rec); } if (write_unlock_record(tdb, rec_ptr) != 0) return -1; /* find previous record in hash chain */ if (ofs_read(tdb, TDB_HASH_TOP(rec->full_hash), &i) == -1) return -1; for (last_ptr = 0; i != rec_ptr; last_ptr = i, i = lastrec.next) if (rec_read(tdb, i, &lastrec) == -1) return -1; /* unlink it: next ptr is at start of record. */ if (last_ptr == 0) last_ptr = TDB_HASH_TOP(rec->full_hash); if (ofs_write(tdb, last_ptr, &rec->next) == -1) return -1; /* recover the space */ if (tdb_free(tdb, rec_ptr, rec) == -1) return -1; return 0;}/* Uses traverse lock: 0 = finish, -1 = error, other = record offset */static int tdb_next_lock(TDB_CONTEXT *tdb, struct tdb_traverse_lock *tlock, struct list_struct *rec){ int want_next = (tlock->off != 0); /* Lock each chain from the start one. */ for (; tlock->hash < tdb->header.hash_size; tlock->hash++) { if (tdb_lock(tdb, tlock->hash, F_WRLCK) == -1) return -1; /* No previous record? Start at top of chain. */ if (!tlock->off) { if (ofs_read(tdb, TDB_HASH_TOP(tlock->hash), &tlock->off) == -1) goto fail; } else { /* Otherwise unlock the previous record. */ if (unlock_record(tdb, tlock->off) != 0) goto fail; } if (want_next) { /* We have offset of old record: grab next */ if (rec_read(tdb, tlock->off, rec) == -1) goto fail; tlock->off = rec->next; } /* Iterate through chain */ while( tlock->off) { tdb_off current; if (rec_read(tdb, tlock->off, rec) == -1) goto fail; if (!TDB_DEAD(rec)) { /* Woohoo: we found one! */ if (lock_record(tdb, tlock->off) != 0) goto fail; return tlock->off; } /* Try to clean dead ones from old traverses */ current = tlock->off; tlock->off = rec->next; if (!tdb->read_only && do_delete(tdb, current, rec) != 0) goto fail; } tdb_unlock(tdb, tlock->hash, F_WRLCK); want_next = 0; } /* We finished iteration without finding anything */ return TDB_ERRCODE(TDB_SUCCESS, 0); fail: tlock->off = 0; if (tdb_unlock(tdb, tlock->hash, F_WRLCK) != 0) TDB_LOG((tdb, 0, "tdb_next_lock: On error unlock failed!\n")); return -1;}/* traverse the entire database - calling fn(tdb, key, data) on each element. return -1 on error or the record count traversed if fn is NULL then it is not called a non-zero return value from fn() indicates that the traversal should stop */int tdb_traverse(TDB_CONTEXT *tdb, tdb_traverse_func fn, void *private){ TDB_DATA key, dbuf; struct list_struct rec; struct tdb_traverse_lock tl = { NULL, 0, 0 }; int ret, count = 0; /* This was in the initializaton, above, but the IRIX compiler * did not like it. crh */ tl.next = tdb->travlocks.next; /* fcntl locks don't stack: beware traverse inside traverse */ tdb->travlocks.next = &tl; /* tdb_next_lock places locks on the record returned, and its chain */ while ((ret = tdb_next_lock(tdb, &tl, &rec)) > 0) { count++; /* now read the full record */ key.dptr = tdb_alloc_read(tdb, tl.off + sizeof(rec), rec.key_len + rec.data_len); if (!key.dptr) { ret = -1; if (tdb_unlock(tdb, tl.hash, F_WRLCK) != 0) goto out; if (unlock_record(tdb, tl.off) != 0) TDB_LOG((tdb, 0, "tdb_traverse: key.dptr == NULL and unlock_record failed!\n")); goto out; } key.dsize = rec.key_len; dbuf.dptr = key.dptr + rec.key_len; dbuf.dsize = rec.data_len; /* Drop chain lock, call out */ if (tdb_unlock(tdb, tl.hash, F_WRLCK) != 0) { ret = -1; goto out; } if (fn && fn(tdb, key, dbuf, private)) { /* They want us to terminate traversal */ ret = count; if (unlock_record(tdb, tl.off) != 0) { TDB_LOG((tdb, 0, "tdb_traverse: unlock_record failed!\n"));; ret = -1; } tdb->travlocks.next = tl.next; SAFE_FREE(key.dptr); return count; } SAFE_FREE(key.dptr); }out: tdb->travlocks.next = tl.next; if (ret < 0) return -1; else return count;}/* find the first entry in the database and return its key */TDB_DATA tdb_firstkey(TDB_CONTEXT *tdb){ TDB_DATA key; struct list_struct rec; /* release any old lock */ if (unlock_record(tdb, tdb->travlocks.off) != 0) return tdb_null; tdb->travlocks.off = tdb->travlocks.hash = 0; if (tdb_next_lock(tdb, &tdb->travlocks, &rec) <= 0) return tdb_null; /* now read the key */ key.dsize = rec.key_len; key.dptr =tdb_alloc_read(tdb,tdb->travlocks.off+sizeof(rec),key.dsize); if (tdb_unlock(tdb, BUCKET(tdb->travlocks.hash), F_WRLCK) != 0) TDB_LOG((tdb, 0, "tdb_firstkey: error occurred while tdb_unlocking!\n")); return key;}/* find the next entry in the database, returning its key */TDB_DATA tdb_nextkey(TDB_CONTEXT *tdb, TDB_DATA oldkey){ u32 oldhash; TDB_DATA key = tdb_null; struct list_struct rec; char *k = NULL; /* Is locked key the old key? If so, traverse will be reliable. */ if (tdb->travlocks.off) { if (tdb_lock(tdb,tdb->travlocks.hash,F_WRLCK)) return tdb_null; if (rec_read(tdb, tdb->travlocks.off, &rec) == -1 || !(k = tdb_alloc_read(tdb,tdb->travlocks.off+sizeof(rec), rec.key_len)) || memcmp(k, oldkey.dptr, oldkey.dsize) != 0) { /* No, it wasn't: unlock it and start from scratch */ if (unlock_record(tdb, tdb->travlocks.off) != 0) return tdb_null; if (tdb_unlock(tdb, tdb->travlocks.hash, F_WRLCK) != 0) return tdb_null; tdb->travlocks.off = 0; } SAFE_FREE(k); } if (!tdb->travlocks.off) { /* No previous element: do normal find, and lock record */ tdb->travlocks.off = tdb_find_lock_hash(tdb, oldkey, tdb->hash_fn(&oldkey), F_WRLCK, &rec); if (!tdb->travlocks.off) return tdb_null; tdb->travlocks.hash = BUCKET(rec.full_hash); if (lock_record(tdb, tdb->travlocks.off) != 0) { TDB_LOG((tdb, 0, "tdb_nextkey: lock_record failed (%s)!\n", strerror(errno))); return tdb_null; } } oldhash = tdb->travlocks.hash; /* Grab next record: locks chain and returned record, unlocks old record */ if (tdb_next_lock(tdb, &tdb->travlocks, &rec) > 0) { key.dsize = rec.key_len; key.dptr = tdb_alloc_read(tdb, tdb->travlocks.off+sizeof(rec), key.dsize); /* Unlock the chain of this new record */ if (tdb_unlock(tdb, tdb->travlocks.hash, F_WRLCK) != 0) TDB_LOG((tdb, 0, "tdb_nextkey: WARNING tdb_unlock failed!\n")); } /* Unlock the chain of old record */ if (tdb_unlock(tdb, BUCKET(oldhash), F_WRLCK) != 0) TDB_LOG((tdb, 0, "tdb_nextkey: WARNING tdb_unlock failed!\n")); return key;}/* delete an entry in the database given a key */static int tdb_delete_hash(TDB_CONTEXT *tdb, TDB_DATA key, u32 hash){ tdb_off rec_ptr; struct list_struct rec; int ret; if (!(rec_ptr = tdb_find_lock_hash(tdb, key, hash, F_WRLCK, &rec))) return -1; ret = do_delete(tdb, rec_ptr, &rec); if (tdb_unlock(tdb, BUCKET(rec.full_hash), F_WRLCK) != 0) TDB_LOG((tdb, 0, "tdb_delete: WARNING tdb_unlock failed!\n")); return ret;}int tdb_delete(TDB_CONTEXT *tdb, TDB_DATA key){ u32 hash = tdb->hash_fn(&key); return tdb_delete_hash(tdb, key, hash);}/* store an element in the database, replacing any existing element with the same key return 0 on success, -1 on failure*/int tdb_store(TDB_CONTEXT *tdb, TDB_DATA key, TDB_DATA dbuf, int flag){ struct list_struct rec; u32 hash; tdb_off rec_ptr; char *p = NULL; int ret = 0; /* find which hash bucket it is in */ hash = tdb->hash_fn(&key); if (tdb_lock(tdb, BUCKET(hash), F_WRLCK) == -1) return -1; /* check for it existing, on insert. */ if (flag == TDB_INSERT) { if (tdb_exists_hash(tdb, key, hash)) { tdb->ecode = TDB_ERR_EXISTS; goto fail; } } else { /* first try in-place update, on modify or replace. */ if (tdb_update_hash(tdb, key, hash, dbuf) == 0) goto out; if (tdb->ecode == TDB_ERR_NOEXIST && flag == TDB_MODIFY) { /* if the record doesn't exist and we are in TDB_MODIFY mode then we should fail the store */ goto fail; } } /* reset the error code potentially set by the tdb_update() */ tdb->ecode = TDB_SUCCESS; /* delete any existing record - if it doesn't exist we don't care. Doing this first reduces fragmentation, and avoids coalescing with `allocated' block before it's updated. */ if (flag != TDB_INSERT) tdb_delete_hash(tdb, key, hash); /* Copy key+value *before* allocating free space in case malloc fails and we are left with a dead spot in the tdb. */ if (!(p = (char *)malloc(key.dsize + dbuf.dsize))) { tdb->ecode = TDB_ERR_OOM; goto fail; } memcpy(p, key.dptr, key.dsize); if (dbuf.dsize) memcpy(p+key.dsize, dbuf.dptr, dbuf.dsize); /* we have to allocate some space */ if (!(rec_ptr = tdb_allocate(tdb, key.dsize + dbuf.dsize, &rec))) goto fail; /* Read hash top into next ptr */ if (ofs_read(tdb, TDB_HASH_TOP(hash), &rec.next) == -1) goto fail; rec.key_len = key.dsize; rec.data_len = dbuf.dsize; rec.full_hash = hash; rec.magic = TDB_MAGIC; /* write out and point the top of the hash chain at it */ if (rec_write(tdb, rec_ptr, &rec) == -1 || tdb_write(tdb, rec_ptr+sizeof(rec), p, key.dsize+dbuf.dsize)==-1 || ofs_write(tdb, TDB_HASH_TOP(hash), &rec_ptr) == -1) { /* Need to tdb_unallocate() here */ goto fail; } out: SAFE_FREE(p); tdb_unlock(tdb, BUCKET(hash), F_WRLCK); return ret;fail: ret = -1; goto out;}/* Attempt to append data to 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. Record must be locked before calling.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -