4 * Sleepycat (Berkeley) DB driver for Citadel/UX
8 /*****************************************************************************
9 Tunable configuration parameters for the Sleepycat DB back end
10 *****************************************************************************/
12 /* Citadel will checkpoint the db at the end of every session, but only if
13 * the specified number of kilobytes has been written, or if the specified
14 * number of minutes has passed, since the last checkpoint.
16 #define MAX_CHECKPOINT_KBYTES 0
17 #define MAX_CHECKPOINT_MINUTES 15
19 /*****************************************************************************/
29 #include <sys/types.h>
34 #include "citserver.h"
36 #include "sysdep_decls.h"
37 #include "dynloader.h"
39 DB *dbp[MAXCDB]; /* One DB handle for each Citadel database */
40 DB_ENV *dbenv; /* The DB environment (global) */
43 struct cdbssd { /* Session-specific DB stuff */
44 DBC *cursor; /* Cursor, for traversals... */
47 struct cdbssd *ssd_arr = NULL;
50 #define MYCURSOR ssd_arr[CC->cs_pid].cursor
53 * Ensure that we have enough space for session-specific data. We don't
54 * put anything in here that Citadel cares about; this is just database
55 * related stuff like cursors and transactions.
57 void cdb_allocate_ssd(void) {
59 * Make sure we have a cursor allocated for this session
62 lprintf(9, "num_ssd before realloc = %d\n", num_ssd);
63 if (num_ssd <= CC->cs_pid) {
64 num_ssd = CC->cs_pid + 1;
65 if (ssd_arr == NULL) {
66 ssd_arr = (struct cdbssd *)
67 mallok((sizeof(struct cdbssd) * num_ssd));
69 ssd_arr = (struct cdbssd *)
70 reallok(ssd_arr, (sizeof(struct cdbssd) * num_ssd));
73 lprintf(9, "num_ssd after realloc = %d\n", num_ssd);
78 * Reclaim unused space in the databases. We need to do each one of
79 * these discretely, rather than in a loop.
81 * This is a stub function in the Sleepycat DB backend, because there is no
82 * such API call available.
84 void defrag_databases(void)
92 * Request a checkpoint of the database.
94 void cdb_checkpoint(void) {
97 ret = txn_checkpoint(dbenv,
98 MAX_CHECKPOINT_KBYTES,
99 MAX_CHECKPOINT_MINUTES,
102 lprintf(1, "txn_checkpoint: %s\n", db_strerror(ret));
108 * Open the various databases we'll be using. Any database which
109 * does not exist should be created. Note that we don't need an S_DATABASE
110 * critical section here, because there aren't any active threads manipulating
111 * the database yet -- and besides, it causes problems on BSDI.
113 void open_databases(void)
117 char dbfilename[256];
120 lprintf(9, "open_databases() starting\n");
122 * Silently try to create the database subdirectory. If it's
123 * already there, no problem.
125 system("exec mkdir data 2>/dev/null");
127 lprintf(9, "Setting up DB environment\n");
128 ret = db_env_create(&dbenv, 0);
130 lprintf(1, "db_env_create: %s\n", db_strerror(ret));
133 dbenv->set_errpfx(dbenv, "citserver");
136 * We want to specify the shared memory buffer pool cachesize,
137 * but everything else is the default.
139 ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
141 lprintf(1, "set_cachesize: %s\n", db_strerror(ret));
142 dbenv->close(dbenv, 0);
147 * We specify DB_PRIVATE but not DB_INIT_LOCK or DB_THREAD, even
148 * though this is a multithreaded application. Since Citadel does all
149 * database access in S_DATABASE critical sections, access to the db
150 * is serialized already, so don't bother the database manager with
151 * it. Besides, it locks up when we do it that way.
153 flags = DB_CREATE|DB_RECOVER|DB_INIT_MPOOL|DB_PRIVATE|DB_INIT_TXN;
154 /* flags |= DB_INIT_LOCK | DB_THREAD; */
155 ret = dbenv->open(dbenv, "./data", flags, 0);
157 lprintf(1, "dbenv->open: %s\n", db_strerror(ret));
158 dbenv->close(dbenv, 0);
162 lprintf(7, "Starting up DB\n");
164 for (i = 0; i < MAXCDB; ++i) {
166 /* Create a database handle */
167 ret = db_create(&dbp[i], dbenv, 0);
169 lprintf(1, "db_create: %s\n", db_strerror(ret));
174 /* Arbitrary names for our tables -- we reference them by
175 * number, so we don't have string names for them.
177 sprintf(dbfilename, "cdb.%02x", i);
179 ret = dbp[i]->open(dbp[i],
186 lprintf(1, "db_open[%d]: %s\n", i, db_strerror(ret));
192 CtdlRegisterSessionHook(cdb_allocate_ssd, EVT_START);
193 CtdlRegisterSessionHook(cdb_checkpoint, EVT_TIMER);
194 lprintf(9, "open_databases() finished\n");
199 * Close all of the gdbm database files we've opened. This can be done
200 * in a loop, since it's just a bunch of closes.
202 void close_databases(void)
207 begin_critical_section(S_DATABASE);
208 for (a = 0; a < MAXCDB; ++a) {
209 lprintf(7, "Closing database %d\n", a);
210 ret = dbp[a]->close(dbp[a], 0);
212 lprintf(1, "db_close: %s\n", db_strerror(ret));
219 /* Close the handle. */
220 ret = dbenv->close(dbenv, 0);
222 lprintf(1, "DBENV->close: %s\n", db_strerror(ret));
226 end_critical_section(S_DATABASE);
232 * Store a piece of data. Returns 0 if the operation was successful. If a
233 * key already exists it should be overwritten.
235 int cdb_store(int cdb,
236 void *ckey, int ckeylen,
237 void *cdata, int cdatalen)
243 memset(&dkey, 0, sizeof(DBT));
244 memset(&ddata, 0, sizeof(DBT));
247 ddata.size = cdatalen;
250 begin_critical_section(S_DATABASE);
251 ret = dbp[cdb]->put(dbp[cdb], /* db */
252 MYTID, /* transaction ID */
256 end_critical_section(S_DATABASE);
258 lprintf(1, "cdb_store(%d): %s\n", cdb, db_strerror(ret));
266 * Delete a piece of data. Returns 0 if the operation was successful.
268 int cdb_delete(int cdb, void *key, int keylen)
277 begin_critical_section(S_DATABASE);
278 ret = dbp[cdb]->del(dbp[cdb], MYTID, &dkey, 0);
279 end_critical_section(S_DATABASE);
288 * Fetch a piece of data. If not found, returns NULL. Otherwise, it returns
289 * a struct cdbdata which it is the caller's responsibility to free later on
290 * using the cdb_free() routine.
292 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen)
295 struct cdbdata *tempcdb;
299 memset(&dkey, 0, sizeof(DBT));
300 memset(&dret, 0, sizeof(DBT));
303 dret.flags = DB_DBT_MALLOC;
305 begin_critical_section(S_DATABASE);
306 ret = dbp[cdb]->get(dbp[cdb], MYTID, &dkey, &dret, 0);
307 end_critical_section(S_DATABASE);
308 if ((ret != 0) && (ret != DB_NOTFOUND)) {
309 lprintf(1, "cdb_fetch: %s\n", db_strerror(ret));
311 if (ret != 0) return NULL;
312 tempcdb = (struct cdbdata *) mallok(sizeof(struct cdbdata));
313 if (tempcdb == NULL) {
314 lprintf(2, "Cannot allocate memory!\n");
316 tempcdb->len = dret.size;
317 tempcdb->ptr = dret.data;
323 * Free a cdbdata item (ok, this is really no big deal, but we might need to do
324 * more complex stuff with other database managers in the future).
326 void cdb_free(struct cdbdata *cdb)
334 * Prepare for a sequential search of an entire database.
335 * (There is guaranteed to be no more than one traversal in
336 * progress per session at any given time.)
338 void cdb_rewind(int cdb)
343 * Now initialize the cursor
345 begin_critical_section(S_DATABASE);
346 ret = dbp[cdb]->cursor(dbp[cdb], MYTID, &MYCURSOR, 0);
348 lprintf(1, "db_cursor: %s\n", db_strerror(ret));
350 end_critical_section(S_DATABASE);
355 * Fetch the next item in a sequential search. Returns a pointer to a
356 * cdbdata structure, or NULL if we've hit the end.
358 struct cdbdata *cdb_next_item(int cdb)
361 struct cdbdata *cdbret;
364 /* Initialize the key/data pair so the flags aren't set. */
365 memset(&key, 0, sizeof(key));
366 memset(&data, 0, sizeof(data));
367 data.flags = DB_DBT_MALLOC;
369 begin_critical_section(S_DATABASE);
370 ret = MYCURSOR->c_get(MYCURSOR,
371 &key, &data, DB_NEXT);
372 end_critical_section(S_DATABASE);
374 if (ret) return NULL; /* presumably, end of file */
376 cdbret = (struct cdbdata *) mallok(sizeof(struct cdbdata));
377 cdbret->len = data.size;
378 cdbret->ptr = data.data;
385 * Transaction-based stuff. I'm writing this as I bake cookies...
388 void cdb_begin_transaction(void) {
390 begin_critical_section(S_DATABASE);
392 if (MYTID != NULL) { /* FIXME this slows it down, take it out */
393 lprintf(1, "ERROR: thread %d is opening a new transaction with one already open!\n", getpid());
396 txn_begin(dbenv, NULL, &MYTID, 0);
398 end_critical_section(S_DATABASE);
401 void cdb_end_transaction(void) {
402 begin_critical_section(S_DATABASE);
403 if (MYTID == NULL) lprintf(1, "ERROR: txn_commit(NULL) !!\n");
404 else txn_commit(MYTID, 0);
405 MYTID = NULL; /* FIXME take out */
406 end_critical_section(S_DATABASE);