4 * Sleepycat (Berkeley) DB driver for Citadel/UX
8 /*****************************************************************************
9 Tunable configuration parameters for the Sleepycat DB back end
10 *****************************************************************************/
12 /* Set to 1 for transaction-based database logging. This is recommended for
13 * safe recovery in the event of system or application failure.
15 #define TRANSACTION_BASED 1
17 /* Citadel will checkpoint the db at the end of every session, but only if
18 * the specified number of kilobytes has been written, or if the specified
19 * number of minutes has passed, since the last checkpoint.
21 #define MAX_CHECKPOINT_KBYTES 0
22 #define MAX_CHECKPOINT_MINUTES 15
24 /*****************************************************************************/
37 #include "citserver.h"
39 #include "sysdep_decls.h"
40 #include "dynloader.h"
42 DB *dbp[MAXCDB]; /* One DB handle for each Citadel database */
43 DB_ENV *dbenv; /* The DB environment (global) */
45 struct cdbssd { /* Session-specific DB stuff */
46 DBC *cursor; /* Cursor, for traversals... */
47 DB_TXN *tid; /* Transaction ID */
50 struct cdbssd *ssd_arr = NULL;
52 #define MYCURSOR ssd_arr[CC->cs_pid].cursor
53 #define MYTID ssd_arr[CC->cs_pid].tid
56 * Ensure that we have enough space for session-specific data. We don't
57 * put anything in here that Citadel cares about; this is just database
58 * related stuff like cursors and transactions.
60 void cdb_allocate_ssd(void) {
62 * Make sure we have a cursor allocated for this session
65 lprintf(9, "num_ssd before realloc = %d\n", num_ssd);
66 if (num_ssd <= CC->cs_pid) {
67 num_ssd = CC->cs_pid + 1;
68 if (ssd_arr == NULL) {
69 ssd_arr = (struct cdbssd *)
70 mallok((sizeof(struct cdbssd) * num_ssd));
72 ssd_arr = (struct cdbssd *)
73 reallok(ssd_arr, (sizeof(struct cdbssd) * num_ssd));
76 lprintf(9, "num_ssd after realloc = %d\n", num_ssd);
81 * Reclaim unused space in the databases. We need to do each one of
82 * these discretely, rather than in a loop.
84 * This is a stub function in the Sleepycat DB backend, because there is no
85 * such API call available.
87 void defrag_databases(void)
95 * Request a checkpoint of the database.
97 void cdb_checkpoint(void) {
100 lprintf(7, "DB checkpoint\n");
101 ret = txn_checkpoint(dbenv,
102 MAX_CHECKPOINT_KBYTES,
103 MAX_CHECKPOINT_MINUTES,
106 lprintf(1, "txn_checkpoint: %s\n", db_strerror(ret));
112 * Open the various databases we'll be using. Any database which
113 * does not exist should be created. Note that we don't need an S_DATABASE
114 * critical section here, because there aren't any active threads manipulating
115 * the database yet -- and besides, it causes problems on BSDI.
117 void open_databases(void)
121 char dbfilename[256];
125 * Silently try to create the database subdirectory. If it's
126 * already there, no problem.
128 system("exec mkdir data 2>/dev/null");
130 lprintf(9, "Setting up DB environment\n");
131 ret = db_env_create(&dbenv, 0);
133 lprintf(1, "db_env_create: %s\n", db_strerror(ret));
136 dbenv->set_errpfx(dbenv, "citserver");
139 * We want to specify the shared memory buffer pool cachesize,
140 * but everything else is the default.
142 ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
144 lprintf(1, "set_cachesize: %s\n", db_strerror(ret));
145 dbenv->close(dbenv, 0);
150 * We specify DB_PRIVATE but not DB_INIT_LOCK or DB_THREAD, even
151 * though this is a multithreaded application. Since Citadel does all
152 * database access in S_DATABASE critical sections, access to the db
153 * is serialized already, so don't bother the database manager with
154 * it. Besides, it locks up when we do it that way.
156 #ifdef TRANSACTION_BASED
157 flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_TXN;
159 flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE;
161 ret = dbenv->open(dbenv, "./data", flags, 0);
163 lprintf(1, "dbenv->open: %s\n", db_strerror(ret));
164 dbenv->close(dbenv, 0);
168 lprintf(7, "Starting up DB\n");
170 for (i = 0; i < MAXCDB; ++i) {
172 /* Create a database handle */
173 ret = db_create(&dbp[i], dbenv, 0);
175 lprintf(1, "db_create: %s\n", db_strerror(ret));
180 /* Arbitrary names for our tables -- we reference them by
181 * number, so we don't have string names for them.
183 sprintf(dbfilename, "cdb.%02x", i);
185 ret = dbp[i]->open(dbp[i],
192 lprintf(1, "db_open[%d]: %s\n", i, db_strerror(ret));
198 CtdlRegisterSessionHook(cdb_allocate_ssd, EVT_START);
199 #ifdef TRANSACTION_BASED
200 CtdlRegisterSessionHook(cdb_checkpoint, EVT_STOP);
206 * Close all of the gdbm database files we've opened. This can be done
207 * in a loop, since it's just a bunch of closes.
209 void close_databases(void)
214 begin_critical_section(S_DATABASE);
215 for (a = 0; a < MAXCDB; ++a) {
216 lprintf(7, "Closing database %d\n", a);
217 ret = dbp[a]->close(dbp[a], 0);
219 lprintf(1, "db_close: %s\n", db_strerror(ret));
226 /* Close the handle. */
227 ret = dbenv->close(dbenv, 0);
229 lprintf(1, "DBENV->close: %s\n", db_strerror(ret));
233 end_critical_section(S_DATABASE);
239 * Store a piece of data. Returns 0 if the operation was successful. If a
240 * key already exists it should be overwritten.
242 int cdb_store(int cdb,
243 void *ckey, int ckeylen,
244 void *cdata, int cdatalen)
250 memset(&dkey, 0, sizeof(DBT));
251 memset(&ddata, 0, sizeof(DBT));
254 ddata.size = cdatalen;
257 begin_critical_section(S_DATABASE);
258 ret = dbp[cdb]->put(dbp[cdb], /* db */
259 MYTID, /* transaction ID */
263 end_critical_section(S_DATABASE);
265 lprintf(1, "cdb_store: %s\n", db_strerror(ret));
273 * Delete a piece of data. Returns 0 if the operation was successful.
275 int cdb_delete(int cdb, void *key, int keylen)
284 begin_critical_section(S_DATABASE);
285 ret = dbp[cdb]->del(dbp[cdb], MYTID, &dkey, 0);
286 end_critical_section(S_DATABASE);
295 * Fetch a piece of data. If not found, returns NULL. Otherwise, it returns
296 * a struct cdbdata which it is the caller's responsibility to free later on
297 * using the cdb_free() routine.
299 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen)
302 struct cdbdata *tempcdb;
306 memset(&dkey, 0, sizeof(DBT));
307 memset(&dret, 0, sizeof(DBT));
310 dret.flags = DB_DBT_MALLOC;
312 begin_critical_section(S_DATABASE);
313 ret = dbp[cdb]->get(dbp[cdb], MYTID, &dkey, &dret, 0);
314 end_critical_section(S_DATABASE);
315 if ((ret != 0) && (ret != DB_NOTFOUND)) {
316 lprintf(1, "cdb_fetch: %s\n", db_strerror(ret));
318 if (ret != 0) return NULL;
319 tempcdb = (struct cdbdata *) mallok(sizeof(struct cdbdata));
320 if (tempcdb == NULL) {
321 lprintf(2, "Cannot allocate memory!\n");
323 tempcdb->len = dret.size;
324 tempcdb->ptr = dret.data;
330 * Free a cdbdata item (ok, this is really no big deal, but we might need to do
331 * more complex stuff with other database managers in the future).
333 void cdb_free(struct cdbdata *cdb)
341 * Prepare for a sequential search of an entire database.
342 * (There is guaranteed to be no more than one traversal in
343 * progress per session at any given time.)
345 void cdb_rewind(int cdb)
350 * Now initialize the cursor
352 begin_critical_section(S_DATABASE);
353 ret = dbp[cdb]->cursor(dbp[cdb], MYTID, &MYCURSOR, 0);
355 lprintf(1, "db_cursor: %s\n", db_strerror(ret));
357 end_critical_section(S_DATABASE);
362 * Fetch the next item in a sequential search. Returns a pointer to a
363 * cdbdata structure, or NULL if we've hit the end.
365 struct cdbdata *cdb_next_item(int cdb)
368 struct cdbdata *cdbret;
371 /* Initialize the key/data pair so the flags aren't set. */
372 memset(&key, 0, sizeof(key));
373 memset(&data, 0, sizeof(data));
374 data.flags = DB_DBT_MALLOC;
376 begin_critical_section(S_DATABASE);
377 ret = MYCURSOR->c_get(MYCURSOR,
378 &key, &data, DB_NEXT);
379 end_critical_section(S_DATABASE);
381 if (ret) return NULL; /* presumably, end of file */
383 cdbret = (struct cdbdata *) mallok(sizeof(struct cdbdata));
384 cdbret->len = data.size;
385 cdbret->ptr = data.data;
392 * Transaction-based stuff. I'm writing this as I bake cookies...
395 void cdb_begin_transaction(void) {
397 #ifdef TRANSACTION_BASED
398 txn_begin(dbenv, NULL, &MYTID, 0);
404 void cdb_end_transaction(void) {
405 #ifdef TRANSACTION_BASED
406 txn_commit(MYTID, 0);