1999-12-21 06:04:37 +03:00
/*
Unix SMB / Netbios implementation .
Version 3.0
Samba database functions
Copyright ( C ) Andrew Tridgell 1999
This program is free software ; you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation ; either version 2 of the License , or
( at your option ) any later version .
This program is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
You should have received a copy of the GNU General Public License
along with this program ; if not , write to the Free Software
Foundation , Inc . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# if STANDALONE
# include <stdlib.h>
# include <stdio.h>
# include <fcntl.h>
# include <unistd.h>
# include <string.h>
# include <fcntl.h>
1999-12-23 04:14:20 +03:00
# include <errno.h>
1999-12-21 06:04:37 +03:00
# include <sys/mman.h>
# include <sys/stat.h>
# include "tdb.h"
# else
# include "includes.h"
# endif
1999-12-22 04:22:14 +03:00
# define TDB_VERSION (0x26011967 + 1)
1999-12-24 11:45:02 +03:00
# define TDB_MAGIC (0x26011999U)
# define TDB_FREE_MAGIC (~TDB_MAGIC)
2000-01-03 03:52:14 +03:00
# define TDB_ALIGN 4
1999-12-21 06:04:37 +03:00
# define MIN_REC_SIZE (2*sizeof(struct list_struct) + TDB_ALIGN)
2000-01-05 04:50:06 +03:00
# define DEFAULT_HASH_SIZE 128
1999-12-24 11:45:02 +03:00
# define TDB_PAGE_SIZE 0x2000
# define TDB_LEN_MULTIPLIER 10
1999-12-21 06:04:37 +03:00
# define FREELIST_TOP (sizeof(struct tdb_header))
2000-01-03 02:00:27 +03:00
# define LOCK_SET 1
# define LOCK_CLEAR 0
/* lock offsets */
# define GLOBAL_LOCK 0
# define ACTIVE_LOCK 4
# define LIST_LOCK_BASE 1024
1999-12-23 04:14:20 +03:00
# define BUCKET(hash) ((hash) % tdb->header.hash_size)
1999-12-21 06:04:37 +03:00
/* 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 */
1999-12-22 04:22:14 +03:00
unsigned magic ; /* try to catch errors */
1999-12-21 06:04:37 +03:00
/*
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 ;
1999-12-21 12:25:59 +03:00
/* a byte range locking function - return 0 on success
this functions locks / unlocks 1 byte at the specified offset */
2000-01-03 02:00:27 +03:00
static int tdb_brlock ( TDB_CONTEXT * tdb , tdb_off offset ,
int set , int rw_type , int lck_type )
1999-12-21 12:25:59 +03:00
{
1999-12-23 04:14:20 +03:00
# if NOLOCK
return 0 ;
# else
1999-12-21 12:25:59 +03:00
struct flock fl ;
1999-12-23 04:14:20 +03:00
if ( tdb - > read_only ) return - 1 ;
2000-01-03 02:00:27 +03:00
fl . l_type = set = = LOCK_SET ? rw_type : F_UNLCK ;
1999-12-21 12:25:59 +03:00
fl . l_whence = SEEK_SET ;
fl . l_start = offset ;
fl . l_len = 1 ;
fl . l_pid = 0 ;
2000-01-07 06:01:55 +03:00
if ( fcntl ( tdb - > fd , lck_type , & fl ) ! = 0 ) {
1999-12-23 04:14:20 +03:00
# if TDB_DEBUG
2000-01-07 06:01:55 +03:00
if ( lck_type = = F_SETLKW ) {
printf ( " lock %d failed at %d (%s) \n " ,
set , offset , strerror ( errno ) ) ;
}
1999-12-21 12:25:59 +03:00
# endif
1999-12-23 04:14:20 +03:00
return - 1 ;
}
1999-12-21 12:25:59 +03:00
return 0 ;
1999-12-23 04:14:20 +03:00
# endif
1999-12-21 12:25:59 +03:00
}
1999-12-23 04:14:20 +03:00
/* 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 ) {
2000-01-03 02:00:27 +03:00
if ( tdb_brlock ( tdb , LIST_LOCK_BASE + 4 * list , LOCK_SET ,
F_WRLCK , F_SETLKW ) ! = 0 ) {
1999-12-23 04:14:20 +03:00
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
return - 1 ;
}
if ( tdb - > locked [ list + 1 ] = = 1 ) {
2000-01-03 02:00:27 +03:00
if ( tdb_brlock ( tdb , LIST_LOCK_BASE + 4 * list , LOCK_CLEAR ,
F_WRLCK , F_SETLKW ) ! = 0 ) {
1999-12-23 04:14:20 +03:00
return - 1 ;
}
}
tdb - > locked [ list + 1 ] - - ;
return 0 ;
}
1999-12-21 12:25:59 +03:00
1999-12-21 06:04:37 +03:00
/* 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 ;
1999-12-23 04:14:20 +03:00
hash = BUCKET ( hash ) ;
1999-12-21 06:04:37 +03:00
ret = FREELIST_TOP + ( hash + 1 ) * sizeof ( tdb_off ) ;
return ret ;
}
1999-12-21 07:54:30 +03:00
/* 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 ;
1999-12-22 04:22:14 +03:00
if ( offset < = tdb - > map_size ) return 0 ;
1999-12-21 07:54:30 +03:00
fstat ( tdb - > fd , & st ) ;
1999-12-24 11:45:02 +03:00
if ( st . st_size < = ( ssize_t ) tdb - > map_size ) return - 1 ;
1999-12-21 07:54:30 +03:00
# 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 ;
}
1999-12-21 06:04:37 +03:00
/* write a lump of data at a specified offset */
static int tdb_write ( TDB_CONTEXT * tdb , tdb_off offset , char * buf , tdb_len len )
{
1999-12-21 07:54:30 +03:00
if ( tdb_oob ( tdb , offset + len ) ! = 0 ) {
1999-12-21 06:04:37 +03:00
/* oops - trying to write beyond the end of the database! */
# if TDB_DEBUG
printf ( " write error of length %u at offset %u (max %u) \n " ,
len , offset , tdb - > map_size ) ;
# endif
return - 1 ;
}
if ( tdb - > map_ptr ) {
memcpy ( offset + ( char * ) tdb - > map_ptr , buf , len ) ;
} else {
lseek ( tdb - > fd , offset , SEEK_SET ) ;
if ( write ( tdb - > fd , buf , len ) ! = ( ssize_t ) len ) {
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 )
{
1999-12-21 07:54:30 +03:00
if ( tdb_oob ( tdb , offset + len ) ! = 0 ) {
1999-12-21 06:04:37 +03:00
/* oops - trying to read beyond the end of the database! */
# if TDB_DEBUG
printf ( " read error of length %u at offset %u (max %u) \n " ,
len , offset , tdb - > map_size ) ;
# endif
return - 1 ;
}
if ( tdb - > map_ptr ) {
memcpy ( buf , offset + ( char * ) tdb - > map_ptr , len ) ;
} else {
lseek ( tdb - > fd , offset , SEEK_SET ) ;
if ( read ( tdb - > fd , buf , len ) ! = ( ssize_t ) len ) {
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 ) ;
2000-01-03 02:00:27 +03:00
if ( ! buf ) return NULL ;
1999-12-21 06:04:37 +03:00
if ( tdb_read ( tdb , offset , buf , len ) = = - 1 ) {
free ( buf ) ;
return NULL ;
}
return buf ;
}
1999-12-23 04:14:20 +03:00
/* 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 ) ) ;
}
1999-12-24 11:45:02 +03:00
/* 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 ) ) ;
}
1999-12-22 04:22:14 +03:00
/* 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
return - 1 ;
}
if ( tdb_oob ( tdb , rec - > next ) ! = 0 ) {
# if TDB_DEBUG
printf ( " bad next %d at offset %d \n " ,
rec - > next , offset ) ;
# endif
return - 1 ;
}
return 0 ;
}
1999-12-21 06:04:37 +03:00
/* 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 ;
1999-12-23 04:14:20 +03:00
tdb_lock ( tdb , - 1 ) ;
1999-12-24 11:45:02 +03:00
/* make sure we know about any previous expansions by another
process */
tdb_oob ( tdb , tdb - > map_size + 1 ) ;
1999-12-21 06:04:37 +03:00
/* always make room for at least 10 more records */
1999-12-24 11:45:02 +03:00
length * = TDB_LEN_MULTIPLIER ;
1999-12-21 06:04:37 +03:00
/* 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 */
lseek ( tdb - > fd , tdb - > map_size + length - 1 , SEEK_SET ) ;
1999-12-23 04:14:20 +03:00
if ( write ( tdb - > fd , & b , 1 ) ! = 1 ) goto fail ;
1999-12-21 06:04:37 +03:00
/* form a new freelist record */
offset = FREELIST_TOP ;
rec . rec_len = length - sizeof ( rec ) ;
1999-12-22 04:22:14 +03:00
rec . magic = TDB_FREE_MAGIC ;
1999-12-24 11:45:02 +03:00
if ( ofs_read ( tdb , offset , & rec . next ) = = - 1 ) {
1999-12-23 04:14:20 +03:00
goto fail ;
}
1999-12-21 06:04:37 +03:00
# if HAVE_MMAP
if ( tdb - > map_ptr ) {
munmap ( tdb - > map_ptr , tdb - > map_size ) ;
tdb - > map_ptr = NULL ;
}
# endif
tdb - > map_size + = length ;
/* write it out */
1999-12-23 04:14:20 +03:00
if ( rec_write ( tdb , tdb - > map_size - length , & rec ) = = - 1 ) {
goto fail ;
}
1999-12-21 06:04:37 +03:00
/* link it into the free list */
ptr = tdb - > map_size - length ;
1999-12-24 11:45:02 +03:00
if ( ofs_write ( tdb , offset , & ptr ) = = - 1 ) goto fail ;
1999-12-21 06:04:37 +03:00
# if HAVE_MMAP
tdb - > map_ptr = ( void * ) mmap ( NULL , tdb - > map_size ,
PROT_READ | PROT_WRITE ,
MAP_SHARED | MAP_FILE , tdb - > fd , 0 ) ;
# endif
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , - 1 ) ;
1999-12-21 06:04:37 +03:00
return 0 ;
1999-12-23 04:14:20 +03:00
fail :
tdb_unlock ( tdb , - 1 ) ;
return - 1 ;
1999-12-21 06:04:37 +03:00
}
/* 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 ;
1999-12-23 04:14:20 +03:00
tdb_lock ( tdb , - 1 ) ;
1999-12-21 06:04:37 +03:00
again :
last_ptr = 0 ;
offset = FREELIST_TOP ;
/* read in the freelist top */
1999-12-24 11:45:02 +03:00
if ( ofs_read ( tdb , offset , & rec_ptr ) = = - 1 ) {
1999-12-21 06:04:37 +03:00
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 ;
}
1999-12-24 11:45:02 +03:00
if ( rec . magic ! = TDB_FREE_MAGIC ) {
# if TDB_DEBUG
printf ( " bad magic 0x%08x in free list \n " , rec . magic ) ;
# endif
goto fail ;
}
1999-12-22 04:22:14 +03:00
1999-12-21 06:04:37 +03:00
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 ;
1999-12-22 04:22:14 +03:00
newrec . magic = TDB_FREE_MAGIC ;
1999-12-21 06:04:37 +03:00
rec . rec_len = length ;
rec . next = rec_ptr + sizeof ( rec ) + rec . rec_len ;
1999-12-23 04:14:20 +03:00
if ( rec_write ( tdb , rec . next , & newrec ) = = - 1 ) {
1999-12-21 06:04:37 +03:00
goto fail ;
}
1999-12-23 04:14:20 +03:00
if ( rec_write ( tdb , rec_ptr , & rec ) = = - 1 ) {
1999-12-21 06:04:37 +03:00
goto fail ;
}
}
/* remove it from the list */
if ( last_ptr = = 0 ) {
offset = FREELIST_TOP ;
1999-12-24 11:45:02 +03:00
if ( ofs_write ( tdb , offset , & rec . next ) = = - 1 ) {
1999-12-21 06:04:37 +03:00
goto fail ;
}
} else {
lastrec . next = rec . next ;
1999-12-23 04:14:20 +03:00
if ( rec_write ( tdb , last_ptr , & lastrec ) = = - 1 ) {
1999-12-21 06:04:37 +03:00
goto fail ;
}
}
/* all done - return the new record offset */
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , - 1 ) ;
1999-12-21 06:04:37 +03:00
return rec_ptr ;
}
/* move to the next record */
lastrec = rec ;
last_ptr = rec_ptr ;
rec_ptr = rec . next ;
}
1999-12-24 11:45:02 +03:00
/* we didn't find enough space. See if we can expand the
database and if we can then try again */
1999-12-21 06:04:37 +03:00
if ( tdb_expand ( tdb , length + sizeof ( rec ) ) = = 0 ) goto again ;
fail :
# if TDB_DEBUG
printf ( " tdb_allocate failed for size %u \n " , length ) ;
# endif
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , - 1 ) ;
1999-12-21 06:04:37 +03:00
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 ;
/* create the header */
2000-01-07 09:14:43 +03:00
memset ( & header , 0 , sizeof ( header ) ) ;
memcpy ( header . magic_food , TDB_MAGIC_FOOD , strlen ( TDB_MAGIC_FOOD ) + 1 ) ;
1999-12-21 06:04:37 +03:00
header . version = TDB_VERSION ;
header . hash_size = hash_size ;
1999-12-22 04:31:09 +03:00
lseek ( tdb - > fd , 0 , SEEK_SET ) ;
ftruncate ( tdb - > fd , 0 ) ;
1999-12-21 06:04:37 +03:00
if ( write ( tdb - > fd , & header , sizeof ( header ) ) ! = sizeof ( header ) ) return - 1 ;
/* the freelist and hash pointers */
offset = 0 ;
for ( i = 0 ; i < hash_size + 1 ; i + + ) {
if ( write ( tdb - > fd , & offset , sizeof ( tdb_off ) ) ! = sizeof ( tdb_off ) ) return - 1 ;
}
# if TDB_DEBUG
1999-12-22 04:22:14 +03:00
printf ( " initialised database of hash_size %u \n " ,
hash_size ) ;
1999-12-21 06:04:37 +03:00
# endif
return 0 ;
}
2000-01-07 06:01:55 +03:00
/* 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 )
1999-12-21 06:04:37 +03:00
{
tdb_off offset , rec_ptr ;
2000-01-07 06:01:55 +03:00
1999-12-21 06:04:37 +03:00
/* find the top of the hash chain */
offset = tdb_hash_top ( tdb , hash ) ;
/* read in the hash top */
2000-01-07 06:01:55 +03:00
if ( ofs_read ( tdb , offset , & rec_ptr ) = = - 1 )
return 0 ;
1999-12-21 06:04:37 +03:00
/* keep looking until we find the right record */
while ( rec_ptr ) {
2000-01-07 06:01:55 +03:00
if ( rec_read ( tdb , rec_ptr , rec ) = = - 1 )
return 0 ;
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
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 ) ;
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
if ( ! k )
1999-12-21 06:04:37 +03:00
return 0 ;
2000-01-07 06:01:55 +03:00
if ( memcmp ( key . dptr , k , key . dsize ) = = 0 ) {
free ( k ) ;
return rec_ptr ;
}
free ( k ) ;
1999-12-21 06:04:37 +03:00
}
/* move to the next record */
2000-01-07 06:01:55 +03:00
rec_ptr = rec - > next ;
1999-12-21 06:04:37 +03:00
}
2000-01-07 06:01:55 +03:00
return 0 ;
1999-12-21 06:04:37 +03:00
}
2000-01-07 06:01:55 +03:00
/* 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 )
1999-12-21 06:04:37 +03:00
{
unsigned hash ;
struct list_struct rec ;
2000-01-07 06:01:55 +03:00
tdb_off rec_ptr ;
int ret = - 1 ;
1999-12-21 06:04:37 +03:00
/* find which hash bucket it is in */
hash = tdb_hash ( & key ) ;
1999-12-23 04:14:20 +03:00
tdb_lock ( tdb , BUCKET ( hash ) ) ;
2000-01-07 06:01:55 +03:00
rec_ptr = tdb_find ( tdb , key , hash , & rec ) ;
1999-12-23 04:14:20 +03:00
2000-01-07 06:01:55 +03:00
if ( ! rec_ptr )
goto out ;
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
/* must be long enough */
if ( rec . rec_len < key . dsize + dbuf . dsize )
goto out ;
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
if ( tdb_write ( tdb , rec_ptr + sizeof ( rec ) + rec . key_len ,
dbuf . dptr , dbuf . dsize ) = = - 1 )
goto out ;
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
if ( dbuf . dsize ! = rec . data_len ) {
/* update size */
rec . data_len = dbuf . dsize ;
ret = rec_write ( tdb , rec_ptr , & rec ) ;
} else
ret = 0 ;
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
out :
tdb_unlock ( tdb , BUCKET ( hash ) ) ;
return ret ;
}
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
/* find an entry in the database given a key */
TDB_DATA tdb_fetch ( TDB_CONTEXT * tdb , TDB_DATA key )
{
unsigned hash ;
tdb_off rec_ptr ;
struct list_struct rec ;
TDB_DATA ret = null_data ;
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
/* find which hash bucket it is in */
hash = tdb_hash ( & key ) ;
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
tdb_lock ( tdb , BUCKET ( hash ) ) ;
rec_ptr = tdb_find ( tdb , key , hash , & rec ) ;
if ( rec_ptr ) {
ret . dptr = tdb_alloc_read ( tdb ,
rec_ptr + sizeof ( rec ) + rec . key_len ,
rec . data_len ) ;
ret . dsize = rec . data_len ;
}
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , BUCKET ( hash ) ) ;
2000-01-07 06:01:55 +03:00
return ret ;
1999-12-21 06:04:37 +03:00
}
/* 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
*/
int tdb_exists ( TDB_CONTEXT * tdb , TDB_DATA key )
{
unsigned hash ;
2000-01-07 06:01:55 +03:00
tdb_off rec_ptr ;
1999-12-21 06:04:37 +03:00
struct list_struct rec ;
2000-01-07 06:01:55 +03:00
1999-12-21 06:04:37 +03:00
/* find which hash bucket it is in */
hash = tdb_hash ( & key ) ;
1999-12-23 04:14:20 +03:00
tdb_lock ( tdb , BUCKET ( hash ) ) ;
2000-01-07 06:01:55 +03:00
rec_ptr = tdb_find ( tdb , key , hash , & rec ) ;
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , BUCKET ( hash ) ) ;
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
return rec_ptr ! = 0 ;
}
1999-12-21 06:04:37 +03:00
/* traverse the entire database - calling fn(tdb, key, data) on each element.
return - 1 on error or the record count traversed
1999-12-21 07:54:30 +03:00
if fn is NULL then it is not called
1999-12-21 06:04:37 +03:00
a non - zero return value from fn ( ) indicates that the traversal should stop
*/
int tdb_traverse ( TDB_CONTEXT * tdb , int ( * fn ) ( TDB_CONTEXT * tdb , TDB_DATA key , TDB_DATA dbuf ) )
{
int count = 0 ;
unsigned h ;
tdb_off offset , rec_ptr ;
struct list_struct rec ;
char * data ;
TDB_DATA key , dbuf ;
/* loop over all hash chains */
for ( h = 0 ; h < tdb - > header . hash_size ; h + + ) {
1999-12-23 04:14:20 +03:00
tdb_lock ( tdb , BUCKET ( h ) ) ;
1999-12-21 06:04:37 +03:00
/* read in the hash top */
offset = tdb_hash_top ( tdb , h ) ;
1999-12-24 11:45:02 +03:00
if ( ofs_read ( tdb , offset , & rec_ptr ) = = - 1 ) {
1999-12-23 04:14:20 +03:00
goto fail ;
1999-12-21 06:04:37 +03:00
}
/* traverse all records for this hash */
while ( rec_ptr ) {
1999-12-22 04:22:14 +03:00
if ( rec_read ( tdb , rec_ptr , & rec ) = = - 1 ) {
1999-12-23 04:14:20 +03:00
goto fail ;
1999-12-21 06:04:37 +03:00
}
/* now read the full record */
data = tdb_alloc_read ( tdb , rec_ptr + sizeof ( rec ) ,
rec . key_len + rec . data_len ) ;
if ( ! data ) {
1999-12-23 04:14:20 +03:00
goto fail ;
1999-12-21 06:04:37 +03:00
}
key . dptr = data ;
key . dsize = rec . key_len ;
dbuf . dptr = data + rec . key_len ;
dbuf . dsize = rec . data_len ;
count + + ;
1999-12-21 07:54:30 +03:00
if ( fn & & fn ( tdb , key , dbuf ) ! = 0 ) {
1999-12-21 06:04:37 +03:00
/* they want us to stop traversing */
free ( data ) ;
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , BUCKET ( h ) ) ;
1999-12-21 06:04:37 +03:00
return count ;
}
/* a miss - drat */
free ( data ) ;
/* move to the next record */
rec_ptr = rec . next ;
}
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , BUCKET ( h ) ) ;
1999-12-21 06:04:37 +03:00
}
/* return the number traversed */
return count ;
1999-12-23 04:14:20 +03:00
fail :
tdb_unlock ( tdb , BUCKET ( h ) ) ;
return - 1 ;
1999-12-21 06:04:37 +03:00
}
/* find the first entry in the database and return its key */
TDB_DATA tdb_firstkey ( TDB_CONTEXT * tdb )
{
tdb_off offset , rec_ptr ;
struct list_struct rec ;
unsigned hash ;
TDB_DATA ret ;
/* look for a non-empty hash chain */
for ( hash = 0 , rec_ptr = 0 ;
1999-12-23 04:14:20 +03:00
hash < tdb - > header . hash_size ;
1999-12-21 06:04:37 +03:00
hash + + ) {
/* find the top of the hash chain */
offset = tdb_hash_top ( tdb , hash ) ;
1999-12-23 04:14:20 +03:00
tdb_lock ( tdb , BUCKET ( hash ) ) ;
1999-12-21 06:04:37 +03:00
/* read in the hash top */
1999-12-24 11:45:02 +03:00
if ( ofs_read ( tdb , offset , & rec_ptr ) = = - 1 ) {
1999-12-23 04:14:20 +03:00
goto fail ;
1999-12-21 06:04:37 +03:00
}
1999-12-23 04:14:20 +03:00
if ( rec_ptr ) break ;
tdb_unlock ( tdb , BUCKET ( hash ) ) ;
1999-12-21 06:04:37 +03:00
}
if ( rec_ptr = = 0 ) return null_data ;
/* we've found a non-empty chain, now read the record */
1999-12-22 04:22:14 +03:00
if ( rec_read ( tdb , rec_ptr , & rec ) = = - 1 ) {
1999-12-23 04:14:20 +03:00
goto fail ;
1999-12-21 06:04:37 +03:00
}
/* allocate and read the key space */
ret . dptr = tdb_alloc_read ( tdb , rec_ptr + sizeof ( rec ) , rec . key_len ) ;
ret . dsize = rec . key_len ;
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , BUCKET ( hash ) ) ;
1999-12-21 06:04:37 +03:00
return ret ;
1999-12-23 04:14:20 +03:00
fail :
tdb_unlock ( tdb , BUCKET ( hash ) ) ;
return null_data ;
1999-12-21 06:04:37 +03:00
}
/* find the next entry in the database, returning its key */
TDB_DATA tdb_nextkey ( TDB_CONTEXT * tdb , TDB_DATA key )
{
2000-01-07 06:01:55 +03:00
unsigned hash , hbucket ;
tdb_off rec_ptr , offset ;
1999-12-21 06:04:37 +03:00
struct list_struct rec ;
TDB_DATA ret ;
/* find which hash bucket it is in */
hash = tdb_hash ( & key ) ;
2000-01-07 06:01:55 +03:00
hbucket = BUCKET ( hash ) ;
tdb_lock ( tdb , hbucket ) ;
rec_ptr = tdb_find ( tdb , key , hash , & rec ) ;
if ( rec_ptr ) {
/* we want the next record after this one */
rec_ptr = rec . next ;
1999-12-21 06:04:37 +03:00
}
2000-01-07 06:01:55 +03:00
/* not found or last in hash: look for next non-empty hash chain */
while ( rec_ptr = = 0 ) {
tdb_unlock ( tdb , hbucket ) ;
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
if ( + + hbucket > = tdb - > header . hash_size - 1 )
return null_data ;
1999-12-21 06:04:37 +03:00
2000-01-07 06:01:55 +03:00
offset = tdb_hash_top ( tdb , hbucket ) ;
tdb_lock ( tdb , hbucket ) ;
1999-12-21 06:04:37 +03:00
/* read in the hash top */
1999-12-24 11:45:02 +03:00
if ( ofs_read ( tdb , offset , & rec_ptr ) = = - 1 ) {
2000-01-07 06:01:55 +03:00
tdb_unlock ( tdb , hbucket ) ;
return null_data ;
1999-12-21 06:04:37 +03:00
}
}
2000-01-07 06:01:55 +03:00
/* Read the record. */
if ( rec_read ( tdb , rec_ptr , & rec ) = = 0 ) {
tdb_unlock ( tdb , hbucket ) ;
return null_data ;
1999-12-21 06:04:37 +03:00
}
2000-01-07 06:01:55 +03:00
/* allocate and read the key */
1999-12-21 06:04:37 +03:00
ret . dptr = tdb_alloc_read ( tdb , rec_ptr + sizeof ( rec ) , rec . key_len ) ;
ret . dsize = rec . key_len ;
2000-01-07 06:01:55 +03:00
tdb_unlock ( tdb , hbucket ) ;
1999-12-23 04:14:20 +03:00
2000-01-07 06:01:55 +03:00
return ret ;
1999-12-21 06:04:37 +03:00
}
/* delete an entry in the database given a key */
int tdb_delete ( TDB_CONTEXT * tdb , TDB_DATA key )
{
unsigned hash ;
tdb_off offset , rec_ptr , last_ptr ;
struct list_struct rec , lastrec ;
2000-01-02 04:40:35 +03:00
char * data = NULL ;
1999-12-21 06:04:37 +03:00
/* find which hash bucket it is in */
hash = tdb_hash ( & key ) ;
1999-12-23 04:14:20 +03:00
tdb_lock ( tdb , BUCKET ( hash ) ) ;
1999-12-21 06:04:37 +03:00
/* find the top of the hash chain */
offset = tdb_hash_top ( tdb , hash ) ;
/* read in the hash top */
1999-12-24 11:45:02 +03:00
if ( ofs_read ( tdb , offset , & rec_ptr ) = = - 1 ) {
1999-12-21 06:04:37 +03:00
goto fail ;
}
last_ptr = 0 ;
/* keep looking until we find the right record */
while ( rec_ptr ) {
1999-12-22 04:22:14 +03:00
if ( rec_read ( tdb , rec_ptr , & rec ) = = - 1 ) {
1999-12-21 06:04:37 +03:00
goto fail ;
}
if ( hash = = rec . full_hash & & key . dsize = = rec . key_len ) {
/* a very likely hit - read the record and full key */
data = tdb_alloc_read ( tdb , rec_ptr + sizeof ( rec ) ,
rec . key_len ) ;
if ( ! data ) {
goto fail ;
}
if ( memcmp ( key . dptr , data , key . dsize ) = = 0 ) {
2000-01-07 06:01:55 +03:00
/* a definite match - delete it */
1999-12-21 06:04:37 +03:00
if ( last_ptr = = 0 ) {
offset = tdb_hash_top ( tdb , hash ) ;
1999-12-24 11:45:02 +03:00
if ( ofs_write ( tdb , offset , & rec . next ) = = - 1 ) {
1999-12-21 06:04:37 +03:00
goto fail ;
}
} else {
lastrec . next = rec . next ;
1999-12-23 04:14:20 +03:00
if ( rec_write ( tdb , last_ptr , & lastrec ) = = - 1 ) {
1999-12-21 06:04:37 +03:00
goto fail ;
}
}
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , BUCKET ( hash ) ) ;
tdb_lock ( tdb , - 1 ) ;
1999-12-21 06:04:37 +03:00
/* and recover the space */
offset = FREELIST_TOP ;
1999-12-24 11:45:02 +03:00
if ( ofs_read ( tdb , offset , & rec . next ) = = - 1 ) {
1999-12-23 04:14:20 +03:00
goto fail2 ;
1999-12-21 06:04:37 +03:00
}
1999-12-22 04:22:14 +03:00
rec . magic = TDB_FREE_MAGIC ;
1999-12-23 04:14:20 +03:00
if ( rec_write ( tdb , rec_ptr , & rec ) = = - 1 ) {
goto fail2 ;
1999-12-21 06:04:37 +03:00
}
1999-12-24 11:45:02 +03:00
if ( ofs_write ( tdb , offset , & rec_ptr ) = = - 1 ) {
1999-12-23 04:14:20 +03:00
goto fail2 ;
1999-12-21 06:04:37 +03:00
}
/* yipee - all done */
free ( data ) ;
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , - 1 ) ;
1999-12-21 06:04:37 +03:00
return 0 ;
}
/* a miss - drat */
free ( data ) ;
1999-12-24 11:45:02 +03:00
data = NULL ;
1999-12-21 06:04:37 +03:00
}
/* move to the next record */
last_ptr = rec_ptr ;
lastrec = rec ;
rec_ptr = rec . next ;
}
fail :
1999-12-24 11:45:02 +03:00
if ( data ) free ( data ) ;
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , BUCKET ( hash ) ) ;
return - 1 ;
fail2 :
1999-12-24 11:45:02 +03:00
if ( data ) free ( data ) ;
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , - 1 ) ;
1999-12-21 06:04:37 +03:00
return - 1 ;
}
/* 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 ;
unsigned hash ;
tdb_off rec_ptr , offset ;
2000-01-02 04:40:35 +03:00
char * p = NULL ;
1999-12-21 06:04:37 +03:00
1999-12-23 04:14:20 +03:00
/* find which hash bucket it is in */
hash = tdb_hash ( & key ) ;
1999-12-21 06:04:37 +03:00
/* check for it existing */
if ( flag = = TDB_INSERT & & tdb_exists ( tdb , key ) ) {
return - 1 ;
}
/* first try in-place update */
if ( flag ! = TDB_INSERT & & tdb_update ( tdb , key , dbuf ) = = 0 ) {
return 0 ;
}
rec_ptr = tdb_allocate ( tdb , key . dsize + dbuf . dsize ) ;
if ( rec_ptr = = 0 ) {
1999-12-23 04:14:20 +03:00
return - 1 ;
1999-12-21 06:04:37 +03:00
}
1999-12-23 04:14:20 +03:00
tdb_lock ( tdb , BUCKET ( hash ) ) ;
1999-12-24 11:45:02 +03:00
/* delete any existing record - if it doesn't exist we don't care */
if ( flag ! = TDB_INSERT ) {
tdb_delete ( tdb , key ) ;
}
1999-12-21 06:04:37 +03:00
/* read the newly created record */
if ( tdb_read ( tdb , rec_ptr , ( char * ) & rec , sizeof ( rec ) ) = = - 1 ) {
goto fail ;
}
1999-12-22 04:22:14 +03:00
if ( rec . magic ! = TDB_FREE_MAGIC ) goto fail ;
1999-12-21 06:04:37 +03:00
/* find the top of the hash chain */
offset = tdb_hash_top ( tdb , hash ) ;
/* read in the hash top diretcly into our next pointer */
1999-12-24 11:45:02 +03:00
if ( ofs_read ( tdb , offset , & rec . next ) = = - 1 ) {
1999-12-21 06:04:37 +03:00
goto fail ;
}
rec . key_len = key . dsize ;
rec . data_len = dbuf . dsize ;
rec . full_hash = hash ;
1999-12-22 04:22:14 +03:00
rec . magic = TDB_MAGIC ;
1999-12-21 06:04:37 +03:00
2000-01-02 04:40:35 +03:00
p = ( char * ) malloc ( sizeof ( rec ) + key . dsize + dbuf . dsize ) ;
if ( ! p ) goto fail ;
memcpy ( p , & rec , sizeof ( rec ) ) ;
memcpy ( p + sizeof ( rec ) , key . dptr , key . dsize ) ;
memcpy ( p + sizeof ( rec ) + key . dsize , dbuf . dptr , dbuf . dsize ) ;
if ( tdb_write ( tdb , rec_ptr , p , sizeof ( rec ) + key . dsize + dbuf . dsize ) = = - 1 )
goto fail ;
free ( p ) ;
p = NULL ;
1999-12-21 06:04:37 +03:00
/* and point the top of the hash chain at it */
1999-12-24 11:45:02 +03:00
if ( ofs_write ( tdb , offset , & rec_ptr ) = = - 1 ) goto fail ;
1999-12-21 06:04:37 +03:00
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , BUCKET ( hash ) ) ;
1999-12-21 06:04:37 +03:00
return 0 ;
fail :
# if TDB_DEBUG
1999-12-23 04:14:20 +03:00
printf ( " store failed for hash 0x%08x in bucket %u \n " , hash , BUCKET ( hash ) ) ;
1999-12-21 06:04:37 +03:00
# endif
2000-01-02 04:40:35 +03:00
if ( p ) free ( p ) ;
1999-12-23 04:14:20 +03:00
tdb_unlock ( tdb , BUCKET ( hash ) ) ;
1999-12-21 06:04:37 +03:00
return - 1 ;
}
/* open the database, creating it if necessary
2000-01-03 02:03:32 +03:00
The open_flags and mode are passed straight to the open call on the database
1999-12-21 06:04:37 +03:00
file . A flags value of O_WRONLY is invalid
The hash size is advisory , use zero for a default value .
return is NULL on error
*/
2000-01-03 02:00:27 +03:00
TDB_CONTEXT * tdb_open ( char * name , int hash_size , int tdb_flags ,
int open_flags , mode_t mode )
1999-12-21 06:04:37 +03:00
{
TDB_CONTEXT tdb , * ret ;
struct stat st ;
tdb . fd = - 1 ;
tdb . name = NULL ;
tdb . map_ptr = NULL ;
2000-01-03 02:00:27 +03:00
if ( ( open_flags & O_ACCMODE ) = = O_WRONLY ) goto fail ;
1999-12-21 06:04:37 +03:00
if ( hash_size = = 0 ) hash_size = DEFAULT_HASH_SIZE ;
1999-12-24 11:45:02 +03:00
memset ( & tdb , 0 , sizeof ( tdb ) ) ;
2000-01-03 02:00:27 +03:00
tdb . read_only = ( ( open_flags & O_ACCMODE ) = = O_RDONLY ) ;
tdb . fd = open ( name , open_flags , mode ) ;
1999-12-21 06:04:37 +03:00
if ( tdb . fd = = - 1 ) goto fail ;
2000-01-03 02:00:27 +03:00
/* ensure there is only one process initialising at once */
tdb_brlock ( & tdb , GLOBAL_LOCK , LOCK_SET , F_WRLCK , F_SETLKW ) ;
if ( tdb_flags & TDB_CLEAR_IF_FIRST ) {
/* we need to zero the database if we are the only
one with it open */
if ( tdb_brlock ( & tdb , ACTIVE_LOCK , LOCK_SET , F_WRLCK , F_SETLK ) = = 0 ) {
ftruncate ( tdb . fd , 0 ) ;
tdb_brlock ( & tdb , ACTIVE_LOCK , LOCK_CLEAR , F_WRLCK , F_SETLK ) ;
}
}
/* leave this lock in place */
tdb_brlock ( & tdb , ACTIVE_LOCK , LOCK_SET , F_RDLCK , F_SETLKW ) ;
1999-12-23 04:14:20 +03:00
1999-12-24 11:45:02 +03:00
if ( read ( tdb . fd , & tdb . header , sizeof ( tdb . header ) ) ! = sizeof ( tdb . header ) | |
2000-01-07 06:01:55 +03:00
strcmp ( tdb . header . magic_food , TDB_MAGIC_FOOD ) ! = 0 | |
1999-12-24 11:45:02 +03:00
tdb . header . version ! = TDB_VERSION ) {
1999-12-21 06:04:37 +03:00
/* its not a valid database - possibly initialise it */
2000-01-03 02:00:27 +03:00
if ( ! ( open_flags & O_CREAT ) ) {
1999-12-21 06:04:37 +03:00
goto fail ;
}
if ( tdb_new_database ( & tdb , hash_size ) = = - 1 ) goto fail ;
lseek ( tdb . fd , 0 , SEEK_SET ) ;
1999-12-24 11:45:02 +03:00
if ( read ( tdb . fd , & tdb . header , sizeof ( tdb . header ) ) ! = sizeof ( tdb . header ) ) goto fail ;
1999-12-21 06:04:37 +03:00
}
fstat ( tdb . fd , & st ) ;
/* map the database and fill in the return structure */
tdb . name = ( char * ) strdup ( name ) ;
1999-12-23 04:14:20 +03:00
tdb . locked = ( int * ) calloc ( tdb . header . hash_size + 1 ,
sizeof ( tdb . locked [ 0 ] ) ) ;
if ( ! tdb . locked ) goto fail ;
1999-12-21 06:04:37 +03:00
tdb . map_size = st . st_size ;
# if HAVE_MMAP
tdb . map_ptr = ( void * ) mmap ( NULL , st . st_size ,
1999-12-21 07:54:30 +03:00
tdb . read_only ? PROT_READ : PROT_READ | PROT_WRITE ,
1999-12-21 06:04:37 +03:00
MAP_SHARED | MAP_FILE , tdb . fd , 0 ) ;
# endif
ret = ( TDB_CONTEXT * ) malloc ( sizeof ( tdb ) ) ;
if ( ! ret ) goto fail ;
* ret = tdb ;
# if TDB_DEBUG
printf ( " mapped database of hash_size %u map_size=%u \n " ,
1999-12-22 04:22:14 +03:00
hash_size , tdb . map_size ) ;
1999-12-21 06:04:37 +03:00
# endif
2000-01-03 02:00:27 +03:00
tdb_brlock ( & tdb , GLOBAL_LOCK , LOCK_CLEAR , F_WRLCK , F_SETLKW ) ;
1999-12-21 06:04:37 +03:00
return ret ;
fail :
if ( tdb . name ) free ( tdb . name ) ;
if ( tdb . fd ! = - 1 ) close ( tdb . fd ) ;
if ( tdb . map_ptr ) munmap ( tdb . map_ptr , tdb . map_size ) ;
return NULL ;
}
/* close a database */
int tdb_close ( TDB_CONTEXT * tdb )
{
if ( ! tdb ) return - 1 ;
if ( tdb - > name ) free ( tdb - > name ) ;
if ( tdb - > fd ! = - 1 ) close ( tdb - > fd ) ;
if ( tdb - > map_ptr ) munmap ( tdb - > map_ptr , tdb - > map_size ) ;
1999-12-23 04:14:20 +03:00
if ( tdb - > locked ) free ( tdb - > locked ) ;
1999-12-21 06:04:37 +03:00
memset ( tdb , 0 , sizeof ( * tdb ) ) ;
free ( tdb ) ;
return 0 ;
}
/* lock the database. If we already have it locked then don't do anything */
int tdb_writelock ( TDB_CONTEXT * tdb )
{
1999-12-23 04:14:20 +03:00
return tdb_lock ( tdb , - 1 ) ;
1999-12-21 06:04:37 +03:00
}
1999-12-21 12:25:59 +03:00
/* unlock the database. */
1999-12-21 06:04:37 +03:00
int tdb_writeunlock ( TDB_CONTEXT * tdb )
{
1999-12-23 04:14:20 +03:00
return tdb_unlock ( tdb , - 1 ) ;
1999-12-21 12:25:59 +03:00
}
1999-12-21 06:04:37 +03:00
1999-12-21 12:25:59 +03:00
/* lock one hash chain. This is meant to be used to reduce locking
contention - it cannot guarantee how many records will be locked */
int tdb_lockchain ( TDB_CONTEXT * tdb , TDB_DATA key )
{
1999-12-23 04:14:20 +03:00
return tdb_lock ( tdb , BUCKET ( tdb_hash ( & key ) ) ) ;
1999-12-21 12:25:59 +03:00
}
1999-12-21 06:04:37 +03:00
1999-12-21 12:25:59 +03:00
/* unlock one hash chain */
int tdb_unlockchain ( TDB_CONTEXT * tdb , TDB_DATA key )
{
1999-12-23 04:14:20 +03:00
return tdb_unlock ( tdb , BUCKET ( tdb_hash ( & key ) ) ) ;
1999-12-21 06:04:37 +03:00
}