4 * This is a data store backend for the Citadel server which uses Berkeley DB.
8 /*****************************************************************************
9 Tunable configuration parameters for the Berkeley 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 256
17 #define MAX_CHECKPOINT_MINUTES 15
19 /*****************************************************************************/
28 #include <sys/types.h>
34 #elif defined(HAVE_DB4_DB_H)
37 #error Neither <db.h> nor <db4/db.h> was found by configure. Install db4-devel.
41 #if DB_VERSION_MAJOR < 4 || DB_VERSION_MINOR < 1
42 #error Citadel requires Berkeley DB v4.1 or newer. Please upgrade.
46 #include <libcitadel.h>
49 #include "citserver.h"
52 #include "sysdep_decls.h"
57 #include "ctdl_module.h"
60 static DB *dbp[MAXCDB]; /* One DB handle for each Citadel database */
61 static DB_ENV *dbenv; /* The DB environment (global) */
69 /* Verbose logging callback */
70 void cdb_verbose_log(const DB_ENV *dbenv, const char *msg)
72 if (!IsEmptyStr(msg)) {
73 CtdlLogPrintf(CTDL_DEBUG, "DB: %s\n", msg);
78 /* Verbose logging callback */
79 void cdb_verbose_err(const DB_ENV *dbenv, const char *errpfx, const char *msg)
81 CtdlLogPrintf(CTDL_ALERT, "DB: %s\n", msg);
85 /* just a little helper function */
86 static void txabort(DB_TXN * tid)
90 ret = tid->abort(tid);
93 CtdlLogPrintf(CTDL_EMERG, "cdb_*: txn_abort: %s\n",
99 /* this one is even more helpful than the last. */
100 static void txcommit(DB_TXN * tid)
104 ret = tid->commit(tid, 0);
107 CtdlLogPrintf(CTDL_EMERG, "cdb_*: txn_commit: %s\n",
113 /* are you sensing a pattern yet? */
114 static void txbegin(DB_TXN ** tid)
118 ret = dbenv->txn_begin(dbenv, NULL, tid, 0);
121 CtdlLogPrintf(CTDL_EMERG, "cdb_*: txn_begin: %s\n",
127 static void dbpanic(DB_ENV * env, int errval)
129 CtdlLogPrintf(CTDL_EMERG, "cdb_*: Berkeley DB panic: %d\n", errval);
132 static void cclose(DBC * cursor)
136 if ((ret = cursor->c_close(cursor))) {
137 CtdlLogPrintf(CTDL_EMERG, "cdb_*: c_close: %s\n",
143 static void bailIfCursor(DBC ** cursors, const char *msg)
147 for (i = 0; i < MAXCDB; i++)
148 if (cursors[i] != NULL) {
149 CtdlLogPrintf(CTDL_EMERG,
150 "cdb_*: cursor still in progress on cdb %02x: %s\n",
156 void check_handles(void *arg)
159 ThreadTSD *tsd = (ThreadTSD *) arg;
161 bailIfCursor(tsd->cursors, "in check_handles");
163 if (tsd->tid != NULL) {
164 CtdlLogPrintf(CTDL_EMERG,
165 "cdb_*: transaction still in progress!");
171 void cdb_check_handles(void)
173 check_handles(pthread_getspecific(ThreadKey));
178 * Cull the database logs
180 static void cdb_cull_logs(void)
189 /* Get the list of names. */
190 if ((ret = dbenv->log_archive(dbenv, &list, flags)) != 0) {
191 CtdlLogPrintf(CTDL_ERR, "cdb_cull_logs: %s\n", db_strerror(ret));
195 /* Print the list of names. */
197 for (file = list; *file != NULL; ++file) {
198 CtdlLogPrintf(CTDL_DEBUG, "Deleting log: %s\n", *file);
201 snprintf(errmsg, sizeof(errmsg),
202 " ** ERROR **\n \n \n "
203 "Citadel was unable to delete the "
204 "database log file '%s' because of the "
205 "following error:\n \n %s\n \n"
206 " This log file is no longer in use "
207 "and may be safely deleted.\n",
208 *file, strerror(errno));
209 aide_message(errmsg, "Database Warning Message");
217 * Manually initiate log file cull.
219 void cmd_cull(char *argbuf) {
220 if (CtdlAccessCheck(ac_internal)) return;
222 cprintf("%d Database log file cull completed.\n", CIT_OK);
227 * Request a checkpoint of the database. Called once per minute by the thread manager.
229 void cdb_checkpoint(void)
233 CtdlLogPrintf(CTDL_DEBUG, "-- db checkpoint --\n");
234 ret = dbenv->txn_checkpoint(dbenv,
235 MAX_CHECKPOINT_KBYTES,
236 MAX_CHECKPOINT_MINUTES, 0);
239 CtdlLogPrintf(CTDL_EMERG, "cdb_checkpoint: txn_checkpoint: %s\n",
244 /* After a successful checkpoint, we can cull the unused logs */
245 if (config.c_auto_cull) {
253 * Open the various databases we'll be using. Any database which
254 * does not exist should be created. Note that we don't need a
255 * critical section here, because there aren't any active threads
256 * manipulating the database yet.
258 void open_databases(void)
264 int dbversion_major, dbversion_minor, dbversion_patch;
265 int current_dbversion = 0;
267 CtdlLogPrintf(CTDL_DEBUG, "cdb_*: open_databases() starting\n");
268 CtdlLogPrintf(CTDL_DEBUG, "Compiled db: %s\n", DB_VERSION_STRING);
269 CtdlLogPrintf(CTDL_INFO, " Linked db: %s\n",
270 db_version(&dbversion_major, &dbversion_minor, &dbversion_patch));
272 current_dbversion = (dbversion_major * 1000000) + (dbversion_minor * 1000) + dbversion_patch;
274 CtdlLogPrintf(CTDL_DEBUG, "Calculated dbversion: %d\n", current_dbversion);
275 CtdlLogPrintf(CTDL_DEBUG, " Previous dbversion: %d\n", CitControl.MMdbversion);
277 if ( (getenv("SUPPRESS_DBVERSION_CHECK") == NULL)
278 && (CitControl.MMdbversion > current_dbversion) ) {
279 CtdlLogPrintf(CTDL_EMERG, "You are attempting to run the Citadel server using a version\n"
280 "of Berkeley DB that is older than that which last created or\n"
281 "updated the database. Because this would probably cause data\n"
282 "corruption or loss, the server is aborting execution now.\n");
286 CitControl.MMdbversion = current_dbversion;
290 CtdlLogPrintf(CTDL_INFO, "Linked zlib: %s\n", zlibVersion());
294 * Silently try to create the database subdirectory. If it's
295 * already there, no problem.
297 mkdir(ctdl_data_dir, 0700);
298 chmod(ctdl_data_dir, 0700);
299 chown(ctdl_data_dir, CTDLUID, (-1));
301 CtdlLogPrintf(CTDL_DEBUG, "cdb_*: Setting up DB environment\n");
302 db_env_set_func_yield(sched_yield);
303 ret = db_env_create(&dbenv, 0);
305 CtdlLogPrintf(CTDL_EMERG, "cdb_*: db_env_create: %s\n", db_strerror(ret));
306 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
309 dbenv->set_errpfx(dbenv, "citserver");
310 dbenv->set_paniccall(dbenv, dbpanic);
311 dbenv->set_errcall(dbenv, cdb_verbose_err);
312 dbenv->set_errpfx(dbenv, "ctdl");
313 #if (DB_VERSION_MAJOR == 4) && (DB_VERSION_MINOR >= 3)
314 dbenv->set_msgcall(dbenv, cdb_verbose_log);
316 dbenv->set_verbose(dbenv, DB_VERB_DEADLOCK, 1);
317 dbenv->set_verbose(dbenv, DB_VERB_RECOVERY, 1);
320 * We want to specify the shared memory buffer pool cachesize,
321 * but everything else is the default.
323 ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
325 CtdlLogPrintf(CTDL_EMERG, "cdb_*: set_cachesize: %s\n", db_strerror(ret));
326 dbenv->close(dbenv, 0);
327 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
331 if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT))) {
332 CtdlLogPrintf(CTDL_EMERG, "cdb_*: set_lk_detect: %s\n", db_strerror(ret));
333 dbenv->close(dbenv, 0);
334 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
338 flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_TXN | DB_INIT_LOCK | DB_THREAD | DB_RECOVER;
339 CtdlLogPrintf(CTDL_DEBUG, "dbenv->open(dbenv, %s, %d, 0)\n", ctdl_data_dir, flags);
340 ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0);
341 if (ret == DB_RUNRECOVERY) {
342 CtdlLogPrintf(CTDL_ALERT, "dbenv->open: %s\n", db_strerror(ret));
343 CtdlLogPrintf(CTDL_ALERT, "Attempting recovery...\n");
345 ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0);
347 if (ret == DB_RUNRECOVERY) {
348 CtdlLogPrintf(CTDL_ALERT, "dbenv->open: %s\n", db_strerror(ret));
349 CtdlLogPrintf(CTDL_ALERT, "Attempting catastrophic recovery...\n");
350 flags &= ~DB_RECOVER;
351 flags |= DB_RECOVER_FATAL;
352 ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0);
355 CtdlLogPrintf(CTDL_EMERG, "dbenv->open: %s\n", db_strerror(ret));
356 dbenv->close(dbenv, 0);
357 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
361 CtdlLogPrintf(CTDL_INFO, "Starting up DB\n");
363 for (i = 0; i < MAXCDB; ++i) {
365 /* Create a database handle */
366 ret = db_create(&dbp[i], dbenv, 0);
368 CtdlLogPrintf(CTDL_EMERG, "db_create: %s\n", db_strerror(ret));
369 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
374 /* Arbitrary names for our tables -- we reference them by
375 * number, so we don't have string names for them.
377 snprintf(dbfilename, sizeof dbfilename, "cdb.%02x", i);
379 ret = dbp[i]->open(dbp[i],
384 DB_CREATE | DB_AUTO_COMMIT | DB_THREAD,
387 CtdlLogPrintf(CTDL_EMERG, "db_open[%02x]: %s\n", i, db_strerror(ret));
389 CtdlLogPrintf(CTDL_EMERG, "You may need to tune your database; please read http://www.citadel.org/doku.php/faq:troubleshooting:out_of_lock_entries for more information.\n");
391 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
399 /* Make sure we own all the files, because in a few milliseconds
400 * we're going to drop root privs.
402 void cdb_chmod_data(void) {
405 char filename[PATH_MAX];
407 dp = opendir(ctdl_data_dir);
409 while (d = readdir(dp), d != NULL) {
410 if (d->d_name[0] != '.') {
411 snprintf(filename, sizeof filename,
412 "%s/%s", ctdl_data_dir, d->d_name);
413 CtdlLogPrintf(9, "chmod(%s, 0600) returned %d\n",
414 filename, chmod(filename, 0600)
416 CtdlLogPrintf(9, "chown(%s, CTDLUID, -1) returned %d\n",
417 filename, chown(filename, CTDLUID, (-1))
424 CtdlLogPrintf(CTDL_DEBUG, "open_databases() finished\n");
426 CtdlRegisterProtoHook(cmd_cull, "CULL", "Cull database logs");
431 * Close all of the db database files we've opened. This can be done
432 * in a loop, since it's just a bunch of closes.
434 void close_databases(void)
439 ctdl_thread_internal_free_tsd();
441 if ((ret = dbenv->txn_checkpoint(dbenv, 0, 0, 0))) {
442 CtdlLogPrintf(CTDL_EMERG,
443 "txn_checkpoint: %s\n", db_strerror(ret));
446 /* print some statistics... */
448 dbenv->lock_stat_print(dbenv, DB_STAT_ALL);
451 /* close the tables */
452 for (a = 0; a < MAXCDB; ++a) {
453 CtdlLogPrintf(CTDL_INFO, "Closing database %02x\n", a);
454 ret = dbp[a]->close(dbp[a], 0);
456 CtdlLogPrintf(CTDL_EMERG,
457 "db_close: %s\n", db_strerror(ret));
462 /* Close the handle. */
463 ret = dbenv->close(dbenv, 0);
465 CtdlLogPrintf(CTDL_EMERG,
466 "DBENV->close: %s\n", db_strerror(ret));
472 * Compression functions only used if we have zlib
476 void cdb_decompress_if_necessary(struct cdbdata *cdb)
478 static int magic = COMPRESS_MAGIC;
479 struct CtdlCompressHeader zheader;
480 char *uncompressed_data;
481 char *compressed_data;
482 uLongf destLen, sourceLen;
486 if (cdb->ptr == NULL)
488 if (memcmp(cdb->ptr, &magic, sizeof(magic)))
491 /* At this point we know we're looking at a compressed item. */
492 memcpy(&zheader, cdb->ptr, sizeof(struct CtdlCompressHeader));
494 compressed_data = cdb->ptr;
495 compressed_data += sizeof(struct CtdlCompressHeader);
497 sourceLen = (uLongf) zheader.compressed_len;
498 destLen = (uLongf) zheader.uncompressed_len;
499 uncompressed_data = malloc(zheader.uncompressed_len);
501 if (uncompress((Bytef *) uncompressed_data,
502 (uLongf *) & destLen,
503 (const Bytef *) compressed_data,
504 (uLong) sourceLen) != Z_OK) {
505 CtdlLogPrintf(CTDL_EMERG, "uncompress() error\n");
510 cdb->len = (size_t) destLen;
511 cdb->ptr = uncompressed_data;
514 #endif /* HAVE_ZLIB */
518 * Store a piece of data. Returns 0 if the operation was successful. If a
519 * key already exists it should be overwritten.
521 int cdb_store(int cdb, void *ckey, int ckeylen, void *cdata, int cdatalen)
529 struct CtdlCompressHeader zheader;
530 char *compressed_data = NULL;
532 size_t buffer_len = 0;
536 memset(&dkey, 0, sizeof(DBT));
537 memset(&ddata, 0, sizeof(DBT));
540 ddata.size = cdatalen;
544 /* Only compress Visit records. Everything else is uncompressed. */
545 if (cdb == CDB_VISIT) {
547 zheader.magic = COMPRESS_MAGIC;
548 zheader.uncompressed_len = cdatalen;
549 buffer_len = ((cdatalen * 101) / 100) + 100
550 + sizeof(struct CtdlCompressHeader);
551 destLen = (uLongf) buffer_len;
552 compressed_data = malloc(buffer_len);
553 if (compress2((Bytef *) (compressed_data +
555 CtdlCompressHeader)),
556 &destLen, (Bytef *) cdata, (uLongf) cdatalen,
558 CtdlLogPrintf(CTDL_EMERG, "compress2() error\n");
561 zheader.compressed_len = (size_t) destLen;
562 memcpy(compressed_data, &zheader,
563 sizeof(struct CtdlCompressHeader));
564 ddata.size = (size_t) (sizeof(struct CtdlCompressHeader) +
565 zheader.compressed_len);
566 ddata.data = compressed_data;
571 ret = dbp[cdb]->put(dbp[cdb], /* db */
572 MYTID, /* transaction ID */
577 CtdlLogPrintf(CTDL_EMERG, "cdb_store(%d): %s\n", cdb,
583 free(compressed_data);
588 bailIfCursor(MYCURSORS,
589 "attempt to write during r/o cursor");
594 if ((ret = dbp[cdb]->put(dbp[cdb], /* db */
595 tid, /* transaction ID */
599 if (ret == DB_LOCK_DEADLOCK) {
603 CtdlLogPrintf(CTDL_EMERG, "cdb_store(%d): %s\n",
604 cdb, db_strerror(ret));
611 free(compressed_data);
620 * Delete a piece of data. Returns 0 if the operation was successful.
622 int cdb_delete(int cdb, void *key, int keylen)
629 memset(&dkey, 0, sizeof dkey);
634 ret = dbp[cdb]->del(dbp[cdb], MYTID, &dkey, 0);
636 CtdlLogPrintf(CTDL_EMERG, "cdb_delete(%d): %s\n", cdb,
638 if (ret != DB_NOTFOUND)
642 bailIfCursor(MYCURSORS,
643 "attempt to delete during r/o cursor");
648 if ((ret = dbp[cdb]->del(dbp[cdb], tid, &dkey, 0))
649 && ret != DB_NOTFOUND) {
650 if (ret == DB_LOCK_DEADLOCK) {
654 CtdlLogPrintf(CTDL_EMERG, "cdb_delete(%d): %s\n",
655 cdb, db_strerror(ret));
665 static DBC *localcursor(int cdb)
670 if (MYCURSORS[cdb] == NULL)
671 ret = dbp[cdb]->cursor(dbp[cdb], MYTID, &curs, 0);
674 MYCURSORS[cdb]->c_dup(MYCURSORS[cdb], &curs,
678 CtdlLogPrintf(CTDL_EMERG, "localcursor: %s\n", db_strerror(ret));
687 * Fetch a piece of data. If not found, returns NULL. Otherwise, it returns
688 * a struct cdbdata which it is the caller's responsibility to free later on
689 * using the cdb_free() routine.
691 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen)
694 struct cdbdata *tempcdb;
698 memset(&dkey, 0, sizeof(DBT));
703 memset(&dret, 0, sizeof(DBT));
704 dret.flags = DB_DBT_MALLOC;
705 ret = dbp[cdb]->get(dbp[cdb], MYTID, &dkey, &dret, 0);
710 memset(&dret, 0, sizeof(DBT));
711 dret.flags = DB_DBT_MALLOC;
713 curs = localcursor(cdb);
715 ret = curs->c_get(curs, &dkey, &dret, DB_SET);
718 while (ret == DB_LOCK_DEADLOCK);
722 if ((ret != 0) && (ret != DB_NOTFOUND)) {
723 CtdlLogPrintf(CTDL_EMERG, "cdb_fetch(%d): %s\n", cdb,
730 tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
732 if (tempcdb == NULL) {
733 CtdlLogPrintf(CTDL_EMERG,
734 "cdb_fetch: Cannot allocate memory for tempcdb\n");
738 tempcdb->len = dret.size;
739 tempcdb->ptr = dret.data;
741 cdb_decompress_if_necessary(tempcdb);
748 * Free a cdbdata item.
750 * Note that we only free the 'ptr' portion if it is not NULL. This allows
751 * other code to assume ownership of that memory simply by storing the
752 * pointer elsewhere and then setting 'ptr' to NULL. cdb_free() will then
755 void cdb_free(struct cdbdata *cdb)
763 void cdb_close_cursor(int cdb)
765 if (MYCURSORS[cdb] != NULL)
766 cclose(MYCURSORS[cdb]);
768 MYCURSORS[cdb] = NULL;
772 * Prepare for a sequential search of an entire database.
773 * (There is guaranteed to be no more than one traversal in
774 * progress per thread at any given time.)
776 void cdb_rewind(int cdb)
780 if (MYCURSORS[cdb] != NULL) {
781 CtdlLogPrintf(CTDL_EMERG,
782 "cdb_rewind: must close cursor on database %d before reopening.\n",
785 /* cclose(MYCURSORS[cdb]); */
789 * Now initialize the cursor
791 ret = dbp[cdb]->cursor(dbp[cdb], MYTID, &MYCURSORS[cdb], 0);
793 CtdlLogPrintf(CTDL_EMERG, "cdb_rewind: db_cursor: %s\n",
801 * Fetch the next item in a sequential search. Returns a pointer to a
802 * cdbdata structure, or NULL if we've hit the end.
804 struct cdbdata *cdb_next_item(int cdb)
807 struct cdbdata *cdbret;
810 /* Initialize the key/data pair so the flags aren't set. */
811 memset(&key, 0, sizeof(key));
812 memset(&data, 0, sizeof(data));
813 data.flags = DB_DBT_MALLOC;
815 ret = MYCURSORS[cdb]->c_get(MYCURSORS[cdb], &key, &data, DB_NEXT);
818 if (ret != DB_NOTFOUND) {
819 CtdlLogPrintf(CTDL_EMERG, "cdb_next_item(%d): %s\n",
820 cdb, db_strerror(ret));
823 cclose(MYCURSORS[cdb]);
824 MYCURSORS[cdb] = NULL;
825 return NULL; /* presumably, end of file */
828 cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
829 cdbret->len = data.size;
830 cdbret->ptr = data.data;
832 cdb_decompress_if_necessary(cdbret);
841 * Transaction-based stuff. I'm writing this as I bake cookies...
844 void cdb_begin_transaction(void)
847 bailIfCursor(MYCURSORS,
848 "can't begin transaction during r/o cursor");
851 CtdlLogPrintf(CTDL_EMERG,
852 "cdb_begin_transaction: ERROR: nested transaction\n");
859 void cdb_end_transaction(void)
863 for (i = 0; i < MAXCDB; i++)
864 if (MYCURSORS[i] != NULL) {
865 CtdlLogPrintf(CTDL_WARNING,
866 "cdb_end_transaction: WARNING: cursor %d still open at transaction end\n",
868 cclose(MYCURSORS[i]);
873 CtdlLogPrintf(CTDL_EMERG,
874 "cdb_end_transaction: ERROR: txcommit(NULL) !!\n");
883 * Truncate (delete every record)
885 void cdb_trunc(int cdb)
892 CtdlLogPrintf(CTDL_EMERG,
893 "cdb_trunc must not be called in a transaction.\n");
896 bailIfCursor(MYCURSORS,
897 "attempt to write during r/o cursor");
902 if ((ret = dbp[cdb]->truncate(dbp[cdb], /* db */
903 NULL, /* transaction ID */
904 &count, /* #rows deleted */
906 if (ret == DB_LOCK_DEADLOCK) {
910 CtdlLogPrintf(CTDL_EMERG,
911 "cdb_truncate(%d): %s\n", cdb,