/* * Copyright (c) 2003-2005 Sendmail, Inc. and its suppliers. * All rights reserved. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ #include "sm/generic.h" SM_RCSID("@(#)$Id: bdb.c,v 1.43 2006/10/05 04:27:38 ca Exp $") #include "sm/assert.h" #include "sm/error.h" #include "sm/memops.h" #include "sm/heap.h" #include "map.h" #include "sm/map.h" #include "sm/maps.h" #include "sm/mapc.h" #include "sm/mapclasses.h" #include "sm/bdb.h" /* ToDo: implementation of map for Berkeley DB. translate BDB error numbers into sm? or can we simply reserve the BDB error range? locking? */ #ifndef DBMMODE # define DBMMODE 0640 #endif /* static sm_map_open_F sm_bdb_open; */ /* static sm_map_close_F sm_bdb_close; */ static sm_map_alloc_F sm_bdb_alloc; static sm_map_free_F sm_bdb_free; static sm_map_locate_F sm_bdb_locate; static sm_map_first_F sm_bdb_first; static sm_map_next_F sm_bdb_next; /* ** These are types of databases. */ #define SMDB_TYPE_DEFAULT NULL #define SMDB_TYPE_HASH "hash" #define SMDB_TYPE_BTREE "btree" #define SMDB_TYPE_NDBM "dbm" /* ** BDB_TYPE2BDB -- Translates database type to BDB type. ** ** Parameters: ** type -- The type to translate. ** ** Returns: ** The BDB type that corresponds to type. */ static DBTYPE bdb_type2bdb2(const sm_cstr_P type) { if (type == SMDB_TYPE_DEFAULT) return DB_HASH; if (strncmp((const char*)sm_cstr_data(type), SMDB_TYPE_HASH, sm_cstr_getlen(type)) == 0) return DB_HASH; if (strncmp((const char*)sm_cstr_data(type), SMDB_TYPE_BTREE, sm_cstr_getlen(type)) == 0) return DB_BTREE; return DB_UNKNOWN; } /* ** SM_BDB_SETOPT -- set options for map ** ** Parameters: ** map -- map ** ap -- options ** ** Returns: ** usual sm_error code */ static sm_ret_T sm_bdb_setopt(sm_map_P map, va_list ap) { sm_ret_T ret; int k, v; DB *db; SM_IS_MAP(map); db = (DB *) map->sm_map_db; if (db == NULL) return sm_error_perm(SM_EM_MAP, ENOENT); ret = SM_SUCCESS; for (;;) { k = va_arg(ap, int); if (k == SMPO_END) break; switch (k) { case SMPO_BDB_DB_CACHE_SIZE: v = va_arg(ap, int); ret = db->set_cachesize(db, 0, v, 1); if (ret != 0) { ret = BDB_ERR2RET(ret); goto error; } break; case SMPO_BDB_DB_HASH_NELEM: v = va_arg(ap, int); ret = db->set_h_nelem(db, v); if (ret != 0) { ret = BDB_ERR2RET(ret); goto error; } default: /* silently ignore bogus options? */ break; } } error: return ret; } /* ** SM_BDB_GETOPT -- get options for map ** ** Parameters: ** map -- map ** which -- which option? ** valp -- pointer to place where result should be stored ** ** Returns: ** usual sm_error code */ static sm_ret_T sm_bdb_getopt(sm_map_P map, int which, void *valp) { sm_ret_T ret; DB *db; SM_IS_MAP(map); db = (DB *) map->sm_map_db; if (db == NULL) return sm_error_perm(SM_EM_MAP, ENOENT); /* ... */ ret = SM_SUCCESS; return ret; } /* ** SM_BDB_CLOSE -- close map ** more parameters??? ** ** Parameters: ** map -- map ** flags -- currently ignored ** ** Returns: ** usual sm_error code */ static sm_ret_T sm_bdb_close(sm_map_P map, uint32_t flags) { sm_ret_T ret; sm_mapc_P mapc; DB *db; SM_IS_MAP(map); mapc = map->sm_map_class; SM_IS_MAPC(mapc); ret = SM_SUCCESS; db = (DB *) map->sm_map_db; if (db == NULL) return sm_error_perm(SM_EM_MAP, ENOENT); /* close BDB map */ ret = db->close(db, SM_IS_FLAG(map->sm_map_openflags, SMAP_MODE_RDONLY) ? DB_NOSYNC : 0); map->sm_map_db = NULL; return ret; } /* ** SM_BDB_DESTROY -- destroy map ** XXX more parameters... ** ** Parameters: ** map -- map ** flags -- flags for map ** ** Returns: ** usual sm_error code */ static sm_ret_T sm_bdb_destroy(sm_map_P map, uint32_t flags) { sm_ret_T ret; sm_mapc_P mapc; DB *db; SM_IS_MAP(map); mapc = map->sm_map_class; SM_IS_MAPC(mapc); ret = SM_SUCCESS; db = (DB *) map->sm_map_db; if (db == NULL) return sm_error_perm(SM_EM_MAP, ENOENT); /* call db destroy? */ map->sm_map_db = NULL; return ret; } /* ** SM_BDB_CREATE -- create map ** ** Parameters: ** mapc -- map context ** type -- type of map ** flags -- flags for map ** map -- map ** ** Returns: ** usual sm_error code */ static sm_ret_T sm_bdb_create(sm_mapc_P mapc, const sm_cstr_P type, uint32_t flags, sm_map_P map) { sm_ret_T ret; DB *db; SM_IS_MAPC(mapc); SM_REQUIRE(map != NULL); db = NULL; /* create BDB map ... */ ret = db_create(&db, NULL, 0); if (ret == 0) { map->sm_map_db = db; map->sm_map_caps = SMMAP_CAPS_LTALL; } /* XXX map error code? */ return ret; } /* ** BDB_OPEN_FLAGS -- translate external (map) flags into internal flags ** ** Paramters: ** flags -- map flags ** ** Returns: ** Internal flag value matching user selected flags */ static uint32_t bdb_open_flags(uint32_t flags) { uint32_t ret; #if MTA_USE_PTHREADS ret = DB_THREAD; #else ret = 0; #endif if (SM_IS_FLAG(flags, SMAP_MODE_RDONLY)) ret |= DB_RDONLY; if (SM_IS_FLAG(flags, SMAP_MODE_CREATE)) ret |= DB_CREATE; return ret; } /* ** SM_BDB_OPEN -- open map ** ** Parameters: ** mapc -- map context ** type -- type of map ** flags -- flags for opening map ** path -- path of map ** mode -- open mode (currently ignored, DBMMODE is used) ** map -- map ** ap -- additional argument ** ** Returns: ** usual sm_error code */ static sm_ret_T sm_bdb_open(sm_mapc_P mapc, const sm_cstr_P type, uint32_t flags, const char *path, int mode, sm_map_P map, va_list ap) { sm_ret_T ret; DB *db; DBTYPE dbtype; SM_IS_MAPC(mapc); SM_REQUIRE(map != NULL); db = NULL; ret = SM_SUCCESS; dbtype = bdb_type2bdb2(type); db = map->sm_map_db; #ifdef DB_CACHE_SIZE ret = db->set_cachesize(db, 0, DB_CACHE_SIZE, 0); if (ret != 0) { ret = BDB_ERR2RET(ret); goto error; } #endif /* DB_CACHE_SIZE */ #ifdef DB_HASH_NELEM if (dbtype == DB_HASH) { ret = db->set_h_nelem(db, DB_HASH_NELEM); if (ret != 0) { ret = BDB_ERR2RET(ret); goto error; } } #endif /* DB_HASH_NELEM */ /* open BDB map ... */ ret = db->open(db, NULL, path, NULL, dbtype, bdb_open_flags(flags), DBMMODE); if (ret != 0) { ret = BDB_ERR2RET(ret); goto error; } return ret; error: if (db != NULL) { (void) db->close(db, 0); db = NULL; } return ret; } /* ** SM_BDB_LOOKUP -- lookup a key in BDB, return data if found ** ** Parameters: ** map -- map context ** flags -- flags ** key -- key ** data -- data (output) ** ** Returns: ** usual sm_error code */ static sm_ret_T sm_bdb_lookup(sm_map_P map, uint32_t flags, sm_map_key_P key, sm_map_data_P data) { sm_ret_T ret; #if MTA_USE_PTHREADS size_t l; #endif sm_mapc_P mapc; DB *db; DBT db_key, db_data; SM_IS_MAP(map); SM_IS_KEY(key); SM_IS_DATA(data); mapc = map->sm_map_class; SM_IS_MAPC(mapc); ret = SM_SUCCESS; db = (DB *) map->sm_map_db; if (db == NULL) return sm_error_perm(SM_EM_MAP, ENOENT); /* XXX */ if (!SMMAP_IS_FL(map, SMMAP_FL_OPEN)) { /* map closed but can be reopened? */ if (mapc->sm_mapc_reopenf != NULL) ret = mapc->sm_mapc_reopenf(map, 0); else ret = sm_error_perm(SM_EM_MAP, SM_E_CLOSEDMAP); if (sm_is_err(ret)) return ret; } sm_memzero(&db_key, sizeof(db_key)); sm_memzero(&db_data, sizeof(db_data)); /* XXX WARNING: changes key inplace! */ if (SM_IS_FLAG(flags, SMMAP_FL_LWR_KEY)) sm_str2lower(key); db_key.size = sm_str_getlen(key); db_key.data = sm_str_data(key); #if MTA_USE_PTHREADS db_data.flags = DB_DBT_USERMEM; db_data.data = sm_str_data(data); db_data.ulen = sm_str_getsize(data); ret = db->get(db, NULL, &db_key, &db_data, 0); l = db_data.size; if (ret == DB_BUFFER_SMALL && l < sm_str_getmax(data)) { if (!sm_is_err(sm_str_resize_data(data, l))) { db_data.data = sm_str_data(data); db_data.ulen = sm_str_getsize(data); ret = db->get(db, NULL, &db_key, &db_data, 0); l = db_data.size; } } if (ret == 0 && l < sm_str_getmax(data)) SM_STR_SETLEN(data, db_data.size); #else /* MTA_USE_PTHREADS */ ret = db->get(db, NULL, &db_key, &db_data, 0); if (ret == 0 && data != NULL) { ret = sm_str_scatn(data, (const char *) db_data.data, db_data.size); if (sm_is_err(ret)) return ret; } #endif /* MTA_USE_PTHREADS */ return BDB_ERR2RET(ret); } /* ** SM_BDB_ADD -- add key/data to BDB ** ** Parameters: ** map -- map context ** key -- key ** data -- data ** flags -- flags ** ** Returns: ** usual sm_error code */ static sm_ret_T sm_bdb_add(sm_map_P map, sm_map_key_P key, sm_map_data_P data, uint flags) { sm_ret_T ret; sm_mapc_P mapc; DB *db; DBT db_key, db_data; SM_IS_MAP(map); SM_REQUIRE(key != NULL); SM_REQUIRE(data != NULL); mapc = map->sm_map_class; SM_IS_MAPC(mapc); ret = SM_SUCCESS; /* this needs to be more sophisticated if more flag values are used! */ if (flags == SMMAP_AFL_UNIQUE) flags = DB_NOOVERWRITE; db = (DB *) map->sm_map_db; if (db == NULL) return sm_error_perm(SM_EM_MAP, ENOENT); /* XXX */ sm_memzero(&db_key, sizeof(db_key)); sm_memzero(&db_data, sizeof(db_data)); db_key.size = sm_str_getlen(key); db_key.data = sm_str_data(key); db_data.size = sm_str_getlen(data); db_data.data = sm_str_data(data); ret = db->put(db, NULL, &db_key, &db_data, flags); return BDB_ERR2RET(ret); } /* ** SM_BDB_RM -- remove key/data from BDB ** ** Parameters: ** map -- map context ** key -- key ** ** Returns: ** usual sm_error code */ static sm_ret_T sm_bdb_rm(sm_map_P map, sm_map_key_P key) { sm_ret_T ret; sm_mapc_P mapc; DB *db; DBT db_key; SM_IS_MAP(map); SM_REQUIRE(key != NULL); mapc = map->sm_map_class; SM_IS_MAPC(mapc); ret = SM_SUCCESS; db = (DB *) map->sm_map_db; if (db == NULL) return sm_error_perm(SM_EM_MAP, ENOENT); /* XXX */ sm_memzero(&db_key, sizeof(db_key)); db_key.size = sm_str_getlen(key); db_key.data = sm_str_data(key); ret = db->del(db, NULL, &db_key, 0); return BDB_ERR2RET(ret); } /* ** SM_BDB_CLASS_CREATE -- create BDB map class ** ** Parameters: ** maps -- map system context ** ** Returns: ** usual sm_error code */ sm_ret_T sm_bdb_class_create(sm_maps_P maps) { sm_ret_T ret; sm_mapc_P mapc; sm_cstr_P htype; #define BDB_HASH "hash" /* add btree? */ ret = SM_SUCCESS; mapc = NULL; htype = sm_cstr_scpyn0((const uchar *)BDB_HASH, strlen(BDB_HASH)); if (htype == NULL) goto error; ret = sm_mapc_create(maps, htype, SMMAPC_FL_ALLOC_K|SMMAPC_FL_ALLOC_D| SMMAPC_FL_FREE_K| SMMAPC_FL_FREE_D| SMMAPC_FL_GEN_REOPEN|SMMAPC_FL_FILE, sm_bdb_create, sm_bdb_open, sm_bdb_close, NULL, sm_bdb_destroy, sm_bdb_add, sm_bdb_rm, sm_bdb_alloc, sm_bdb_free, sm_bdb_lookup, sm_bdb_locate, sm_bdb_first, sm_bdb_next, sm_bdb_setopt, sm_bdb_getopt, &mapc); SM_CSTR_FREE(htype); return ret; error: if (ret == SM_SUCCESS) ret = sm_error_temp(SM_EM_MAP, ENOMEM); /* cleanup mapc? */ return ret; }