2 * This is a data store backend for the Citadel server which uses Berkeley DB.
4 * Copyright (c) 1987-2011 by the citadel.org team
6 * This program is open source software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as published
8 * by the Free Software Foundation; either version 3 of the License, or
9 * (at your option) any later version.
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 /*****************************************************************************
23 Tunable configuration parameters for the Berkeley DB back end
24 *****************************************************************************/
26 /* Citadel will checkpoint the db at the end of every session, but only if
27 * the specified number of kilobytes has been written, or if the specified
28 * number of minutes has passed, since the last checkpoint.
30 #define MAX_CHECKPOINT_KBYTES 256
31 #define MAX_CHECKPOINT_MINUTES 15
33 /*****************************************************************************/
42 #include <sys/types.h>
49 #elif defined(HAVE_DB4_DB_H)
52 #error Neither <db.h> nor <db4/db.h> was found by configure. Install db4-devel.
56 #if DB_VERSION_MAJOR < 4 || DB_VERSION_MINOR < 1
57 #error Citadel requires Berkeley DB v4.1 or newer. Please upgrade.
61 #include <libcitadel.h>
64 #include "citserver.h"
67 #include "sysdep_decls.h"
72 #include "ctdl_module.h"
75 static DB *dbp[MAXCDB]; /* One DB handle for each Citadel database */
76 static DB_ENV *dbenv; /* The DB environment (global) */
84 /* Verbose logging callback */
85 void cdb_verbose_log(const DB_ENV *dbenv, const char *msg)
87 if (!IsEmptyStr(msg)) {
88 syslog(LOG_DEBUG, "DB: %s\n", msg);
93 /* Verbose logging callback */
94 void cdb_verbose_err(const DB_ENV *dbenv, const char *errpfx, const char *msg)
96 syslog(LOG_ALERT, "DB: %s\n", msg);
100 /* just a little helper function */
101 static void txabort(DB_TXN * tid)
105 ret = tid->abort(tid);
108 syslog(LOG_EMERG, "bdb(): txn_abort: %s\n", db_strerror(ret));
113 /* this one is even more helpful than the last. */
114 static void txcommit(DB_TXN * tid)
118 ret = tid->commit(tid, 0);
121 syslog(LOG_EMERG, "bdb(): txn_commit: %s\n", db_strerror(ret));
126 /* are you sensing a pattern yet? */
127 static void txbegin(DB_TXN ** tid)
131 ret = dbenv->txn_begin(dbenv, NULL, tid, 0);
134 syslog(LOG_EMERG, "bdb(): txn_begin: %s\n", db_strerror(ret));
139 static void dbpanic(DB_ENV * env, int errval)
141 syslog(LOG_EMERG, "bdb(): PANIC: %s\n", db_strerror(errval));
144 static void cclose(DBC * cursor)
148 if ((ret = cursor->c_close(cursor))) {
149 syslog(LOG_EMERG, "bdb(): c_close: %s\n", db_strerror(ret));
154 static void bailIfCursor(DBC ** cursors, const char *msg)
158 for (i = 0; i < MAXCDB; i++)
159 if (cursors[i] != NULL) {
161 "bdb(): cursor still in progress on cdb %02x: %s\n", i, msg);
166 void check_handles(void *arg)
169 ThreadTSD *tsd = (ThreadTSD *) arg;
171 bailIfCursor(tsd->cursors, "in check_handles");
173 if (tsd->tid != NULL) {
174 syslog(LOG_EMERG, "bdb(): transaction still in progress!");
180 void cdb_check_handles(void)
182 check_handles(pthread_getspecific(ThreadKey));
187 * Cull the database logs
189 static void cdb_cull_logs(void)
198 /* Get the list of names. */
199 if ((ret = dbenv->log_archive(dbenv, &list, flags)) != 0) {
200 syslog(LOG_ERR, "cdb_cull_logs: %s\n", db_strerror(ret));
204 /* Print the list of names. */
206 for (file = list; *file != NULL; ++file) {
207 syslog(LOG_DEBUG, "Deleting log: %s\n", *file);
210 snprintf(errmsg, sizeof(errmsg),
211 " ** ERROR **\n \n \n "
212 "Citadel was unable to delete the "
213 "database log file '%s' because of the "
214 "following error:\n \n %s\n \n"
215 " This log file is no longer in use "
216 "and may be safely deleted.\n",
217 *file, strerror(errno));
218 CtdlAideMessage(errmsg, "Database Warning Message");
226 * Manually initiate log file cull.
228 void cmd_cull(char *argbuf) {
229 if (CtdlAccessCheck(ac_internal)) return;
231 cprintf("%d Database log file cull completed.\n", CIT_OK);
236 * Request a checkpoint of the database. Called once per minute by the thread manager.
238 void cdb_checkpoint(void)
242 syslog(LOG_DEBUG, "-- db checkpoint --\n");
243 ret = dbenv->txn_checkpoint(dbenv, MAX_CHECKPOINT_KBYTES, MAX_CHECKPOINT_MINUTES, 0);
246 syslog(LOG_EMERG, "cdb_checkpoint: txn_checkpoint: %s\n", db_strerror(ret));
250 /* After a successful checkpoint, we can cull the unused logs */
251 if (config.c_auto_cull) {
259 * Open the various databases we'll be using. Any database which
260 * does not exist should be created. Note that we don't need a
261 * critical section here, because there aren't any active threads
262 * manipulating the database yet.
264 void open_databases(void)
270 int dbversion_major, dbversion_minor, dbversion_patch;
271 int current_dbversion = 0;
273 syslog(LOG_DEBUG, "bdb(): open_databases() starting\n");
274 syslog(LOG_DEBUG, "Compiled db: %s\n", DB_VERSION_STRING);
275 syslog(LOG_INFO, " Linked db: %s\n",
276 db_version(&dbversion_major, &dbversion_minor, &dbversion_patch));
278 current_dbversion = (dbversion_major * 1000000) + (dbversion_minor * 1000) + dbversion_patch;
280 syslog(LOG_DEBUG, "Calculated dbversion: %d\n", current_dbversion);
281 syslog(LOG_DEBUG, " Previous dbversion: %d\n", CitControl.MMdbversion);
283 if ( (getenv("SUPPRESS_DBVERSION_CHECK") == NULL)
284 && (CitControl.MMdbversion > current_dbversion) ) {
285 syslog(LOG_EMERG, "You are attempting to run the Citadel server using a version\n"
286 "of Berkeley DB that is older than that which last created or\n"
287 "updated the database. Because this would probably cause data\n"
288 "corruption or loss, the server is aborting execution now.\n");
292 CitControl.MMdbversion = current_dbversion;
296 syslog(LOG_INFO, "Linked zlib: %s\n", zlibVersion());
300 * Silently try to create the database subdirectory. If it's
301 * already there, no problem.
303 if ((mkdir(ctdl_data_dir, 0700) != 0) && (errno != EEXIST)){
305 "unable to create database directory [%s]: %s",
306 ctdl_data_dir, strerror(errno));
308 if (chmod(ctdl_data_dir, 0700) != 0){
310 "unable to set database directory accessrights [%s]: %s",
311 ctdl_data_dir, strerror(errno));
313 if (chown(ctdl_data_dir, CTDLUID, (-1)) != 0){
315 "unable to set the owner for [%s]: %s",
316 ctdl_data_dir, strerror(errno));
318 syslog(LOG_DEBUG, "bdb(): Setting up DB environment\n");
319 db_env_set_func_yield((int (*)(u_long, u_long))sched_yield);
320 ret = db_env_create(&dbenv, 0);
322 syslog(LOG_EMERG, "bdb(): db_env_create: %s\n", db_strerror(ret));
323 syslog(LOG_EMERG, "exit code %d\n", ret);
326 dbenv->set_errpfx(dbenv, "citserver");
327 dbenv->set_paniccall(dbenv, dbpanic);
328 dbenv->set_errcall(dbenv, cdb_verbose_err);
329 dbenv->set_errpfx(dbenv, "ctdl");
330 #if (DB_VERSION_MAJOR == 4) && (DB_VERSION_MINOR >= 3)
331 dbenv->set_msgcall(dbenv, cdb_verbose_log);
333 dbenv->set_verbose(dbenv, DB_VERB_DEADLOCK, 1);
334 dbenv->set_verbose(dbenv, DB_VERB_RECOVERY, 1);
337 * We want to specify the shared memory buffer pool cachesize,
338 * but everything else is the default.
340 ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
342 syslog(LOG_EMERG, "bdb(): set_cachesize: %s\n", db_strerror(ret));
343 dbenv->close(dbenv, 0);
344 syslog(LOG_EMERG, "exit code %d\n", ret);
348 if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT))) {
349 syslog(LOG_EMERG, "bdb(): set_lk_detect: %s\n", db_strerror(ret));
350 dbenv->close(dbenv, 0);
351 syslog(LOG_EMERG, "exit code %d\n", ret);
355 flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_TXN | DB_INIT_LOCK | DB_THREAD | DB_RECOVER;
356 syslog(LOG_DEBUG, "dbenv->open(dbenv, %s, %d, 0)\n", ctdl_data_dir, flags);
357 ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0);
358 if (ret == DB_RUNRECOVERY) {
359 syslog(LOG_ALERT, "dbenv->open: %s\n", db_strerror(ret));
360 syslog(LOG_ALERT, "Attempting recovery...\n");
362 ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0);
364 if (ret == DB_RUNRECOVERY) {
365 syslog(LOG_ALERT, "dbenv->open: %s\n", db_strerror(ret));
366 syslog(LOG_ALERT, "Attempting catastrophic recovery...\n");
367 flags &= ~DB_RECOVER;
368 flags |= DB_RECOVER_FATAL;
369 ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0);
372 syslog(LOG_EMERG, "dbenv->open: %s\n", db_strerror(ret));
373 dbenv->close(dbenv, 0);
374 syslog(LOG_EMERG, "exit code %d\n", ret);
378 syslog(LOG_INFO, "Starting up DB\n");
380 for (i = 0; i < MAXCDB; ++i) {
382 /* Create a database handle */
383 ret = db_create(&dbp[i], dbenv, 0);
385 syslog(LOG_EMERG, "db_create: %s\n", db_strerror(ret));
386 syslog(LOG_EMERG, "exit code %d\n", ret);
391 /* Arbitrary names for our tables -- we reference them by
392 * number, so we don't have string names for them.
394 snprintf(dbfilename, sizeof dbfilename, "cdb.%02x", i);
396 ret = dbp[i]->open(dbp[i],
401 DB_CREATE | DB_AUTO_COMMIT | DB_THREAD,
405 syslog(LOG_EMERG, "db_open[%02x]: %s\n", i, db_strerror(ret));
407 syslog(LOG_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");
409 syslog(LOG_EMERG, "exit code %d\n", ret);
417 /* Make sure we own all the files, because in a few milliseconds
418 * we're going to drop root privs.
420 void cdb_chmod_data(void) {
423 char filename[PATH_MAX];
425 dp = opendir(ctdl_data_dir);
427 while (d = readdir(dp), d != NULL) {
428 if (d->d_name[0] != '.') {
429 snprintf(filename, sizeof filename,
430 "%s/%s", ctdl_data_dir, d->d_name);
431 syslog(LOG_DEBUG, "chmod(%s, 0600) returned %d\n",
432 filename, chmod(filename, 0600)
434 syslog(LOG_DEBUG, "chown(%s, CTDLUID, -1) returned %d\n",
435 filename, chown(filename, CTDLUID, (-1))
442 syslog(LOG_DEBUG, "open_databases() finished\n");
443 CtdlRegisterProtoHook(cmd_cull, "CULL", "Cull database logs");
448 * Close all of the db database files we've opened. This can be done
449 * in a loop, since it's just a bunch of closes.
451 void close_databases(void)
456 ctdl_thread_internal_free_tsd();
458 if ((ret = dbenv->txn_checkpoint(dbenv, 0, 0, 0))) {
460 "txn_checkpoint: %s\n", db_strerror(ret));
463 /* print some statistics... */
465 dbenv->lock_stat_print(dbenv, DB_STAT_ALL);
468 /* close the tables */
469 for (a = 0; a < MAXCDB; ++a) {
470 syslog(LOG_INFO, "Closing database %02x\n", a);
471 ret = dbp[a]->close(dbp[a], 0);
473 syslog(LOG_EMERG, "db_close: %s\n", db_strerror(ret));
478 /* Close the handle. */
479 ret = dbenv->close(dbenv, 0);
481 syslog(LOG_EMERG, "DBENV->close: %s\n", db_strerror(ret));
487 * Compression functions only used if we have zlib
489 void cdb_decompress_if_necessary(struct cdbdata *cdb)
491 static int magic = COMPRESS_MAGIC;
494 (cdb->ptr == NULL) ||
495 (cdb->len < sizeof(magic)) ||
496 (memcmp(cdb->ptr, &magic, sizeof(magic))))
500 /* At this point we know we're looking at a compressed item. */
502 struct CtdlCompressHeader zheader;
503 char *uncompressed_data;
504 char *compressed_data;
505 uLongf destLen, sourceLen;
508 memset(&zheader, 0, sizeof(struct CtdlCompressHeader));
509 cplen = sizeof(struct CtdlCompressHeader);
510 if (sizeof(struct CtdlCompressHeader) > cdb->len)
512 memcpy(&zheader, cdb->ptr, cplen);
514 compressed_data = cdb->ptr;
515 compressed_data += sizeof(struct CtdlCompressHeader);
517 sourceLen = (uLongf) zheader.compressed_len;
518 destLen = (uLongf) zheader.uncompressed_len;
519 uncompressed_data = malloc(zheader.uncompressed_len);
521 if (uncompress((Bytef *) uncompressed_data,
522 (uLongf *) & destLen,
523 (const Bytef *) compressed_data,
524 (uLong) sourceLen) != Z_OK) {
525 syslog(LOG_EMERG, "uncompress() error\n");
530 cdb->len = (size_t) destLen;
531 cdb->ptr = uncompressed_data;
532 #else /* HAVE_ZLIB */
533 syslog(LOG_EMERG, "Database contains compressed data, but this citserver was built without compression support.\n");
535 #endif /* HAVE_ZLIB */
541 * Store a piece of data. Returns 0 if the operation was successful. If a
542 * key already exists it should be overwritten.
544 int cdb_store(int cdb, const void *ckey, int ckeylen, void *cdata, int cdatalen)
552 struct CtdlCompressHeader zheader;
553 char *compressed_data = NULL;
555 size_t buffer_len = 0;
559 memset(&dkey, 0, sizeof(DBT));
560 memset(&ddata, 0, sizeof(DBT));
563 ddata.size = cdatalen;
567 /* Only compress Visit records. Everything else is uncompressed. */
568 if (cdb == CDB_VISIT) {
570 zheader.magic = COMPRESS_MAGIC;
571 zheader.uncompressed_len = cdatalen;
572 buffer_len = ((cdatalen * 101) / 100) + 100
573 + sizeof(struct CtdlCompressHeader);
574 destLen = (uLongf) buffer_len;
575 compressed_data = malloc(buffer_len);
576 if (compress2((Bytef *) (compressed_data + sizeof(struct CtdlCompressHeader)),
577 &destLen, (Bytef *) cdata, (uLongf) cdatalen, 1) != Z_OK)
579 syslog(LOG_EMERG, "compress2() error\n");
582 zheader.compressed_len = (size_t) destLen;
583 memcpy(compressed_data, &zheader, sizeof(struct CtdlCompressHeader));
584 ddata.size = (size_t) (sizeof(struct CtdlCompressHeader) + zheader.compressed_len);
585 ddata.data = compressed_data;
590 ret = dbp[cdb]->put(dbp[cdb], /* db */
591 MYTID, /* transaction ID */
596 syslog(LOG_EMERG, "cdb_store(%d): %s\n", cdb, db_strerror(ret));
601 free(compressed_data);
606 bailIfCursor(MYCURSORS, "attempt to write during r/o cursor");
611 if ((ret = dbp[cdb]->put(dbp[cdb], /* db */
612 tid, /* transaction ID */
616 if (ret == DB_LOCK_DEADLOCK) {
620 syslog(LOG_EMERG, "cdb_store(%d): %s\n",
621 cdb, db_strerror(ret));
628 free(compressed_data);
638 * Delete a piece of data. Returns 0 if the operation was successful.
640 int cdb_delete(int cdb, void *key, int keylen)
647 memset(&dkey, 0, sizeof dkey);
652 ret = dbp[cdb]->del(dbp[cdb], MYTID, &dkey, 0);
654 syslog(LOG_EMERG, "cdb_delete(%d): %s\n", cdb, db_strerror(ret));
655 if (ret != DB_NOTFOUND) {
660 bailIfCursor(MYCURSORS, "attempt to delete during r/o cursor");
665 if ((ret = dbp[cdb]->del(dbp[cdb], tid, &dkey, 0))
666 && ret != DB_NOTFOUND) {
667 if (ret == DB_LOCK_DEADLOCK) {
671 syslog(LOG_EMERG, "cdb_delete(%d): %s\n",
672 cdb, db_strerror(ret));
682 static DBC *localcursor(int cdb)
687 if (MYCURSORS[cdb] == NULL)
688 ret = dbp[cdb]->cursor(dbp[cdb], MYTID, &curs, 0);
691 MYCURSORS[cdb]->c_dup(MYCURSORS[cdb], &curs,
695 syslog(LOG_EMERG, "localcursor: %s\n", db_strerror(ret));
704 * Fetch a piece of data. If not found, returns NULL. Otherwise, it returns
705 * a struct cdbdata which it is the caller's responsibility to free later on
706 * using the cdb_free() routine.
708 struct cdbdata *cdb_fetch(int cdb, const void *key, int keylen)
711 struct cdbdata *tempcdb;
715 memset(&dkey, 0, sizeof(DBT));
720 memset(&dret, 0, sizeof(DBT));
721 dret.flags = DB_DBT_MALLOC;
722 ret = dbp[cdb]->get(dbp[cdb], MYTID, &dkey, &dret, 0);
727 memset(&dret, 0, sizeof(DBT));
728 dret.flags = DB_DBT_MALLOC;
730 curs = localcursor(cdb);
732 ret = curs->c_get(curs, &dkey, &dret, DB_SET);
735 while (ret == DB_LOCK_DEADLOCK);
739 if ((ret != 0) && (ret != DB_NOTFOUND)) {
740 syslog(LOG_EMERG, "cdb_fetch(%d): %s\n", cdb, db_strerror(ret));
746 tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
748 if (tempcdb == NULL) {
749 syslog(LOG_EMERG, "cdb_fetch: Cannot allocate memory for tempcdb\n");
753 tempcdb->len = dret.size;
754 tempcdb->ptr = dret.data;
755 cdb_decompress_if_necessary(tempcdb);
761 * Free a cdbdata item.
763 * Note that we only free the 'ptr' portion if it is not NULL. This allows
764 * other code to assume ownership of that memory simply by storing the
765 * pointer elsewhere and then setting 'ptr' to NULL. cdb_free() will then
768 void cdb_free(struct cdbdata *cdb)
776 void cdb_close_cursor(int cdb)
778 if (MYCURSORS[cdb] != NULL) {
779 cclose(MYCURSORS[cdb]);
782 MYCURSORS[cdb] = NULL;
786 * Prepare for a sequential search of an entire database.
787 * (There is guaranteed to be no more than one traversal in
788 * progress per thread at any given time.)
790 void cdb_rewind(int cdb)
794 if (MYCURSORS[cdb] != NULL) {
796 "cdb_rewind: must close cursor on database %d before reopening.\n", cdb);
798 /* cclose(MYCURSORS[cdb]); */
802 * Now initialize the cursor
804 ret = dbp[cdb]->cursor(dbp[cdb], MYTID, &MYCURSORS[cdb], 0);
806 syslog(LOG_EMERG, "cdb_rewind: db_cursor: %s\n", db_strerror(ret));
813 * Fetch the next item in a sequential search. Returns a pointer to a
814 * cdbdata structure, or NULL if we've hit the end.
816 struct cdbdata *cdb_next_item(int cdb)
819 struct cdbdata *cdbret;
822 /* Initialize the key/data pair so the flags aren't set. */
823 memset(&key, 0, sizeof(key));
824 memset(&data, 0, sizeof(data));
825 data.flags = DB_DBT_MALLOC;
827 ret = MYCURSORS[cdb]->c_get(MYCURSORS[cdb], &key, &data, DB_NEXT);
830 if (ret != DB_NOTFOUND) {
831 syslog(LOG_EMERG, "cdb_next_item(%d): %s\n", cdb, db_strerror(ret));
834 cclose(MYCURSORS[cdb]);
835 MYCURSORS[cdb] = NULL;
836 return NULL; /* presumably, end of file */
839 cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
840 cdbret->len = data.size;
841 cdbret->ptr = data.data;
842 cdb_decompress_if_necessary(cdbret);
850 * Transaction-based stuff. I'm writing this as I bake cookies...
853 void cdb_begin_transaction(void)
856 bailIfCursor(MYCURSORS, "can't begin transaction during r/o cursor");
859 syslog(LOG_EMERG, "cdb_begin_transaction: ERROR: nested transaction\n");
866 void cdb_end_transaction(void)
870 for (i = 0; i < MAXCDB; i++)
871 if (MYCURSORS[i] != NULL) {
873 "cdb_end_transaction: WARNING: cursor %d still open at transaction end\n",
875 cclose(MYCURSORS[i]);
881 "cdb_end_transaction: ERROR: txcommit(NULL) !!\n");
891 * Truncate (delete every record)
893 void cdb_trunc(int cdb)
901 "cdb_trunc must not be called in a transaction.\n");
904 bailIfCursor(MYCURSORS, "attempt to write during r/o cursor");
909 if ((ret = dbp[cdb]->truncate(dbp[cdb], /* db */
910 NULL, /* transaction ID */
911 &count, /* #rows deleted */
913 if (ret == DB_LOCK_DEADLOCK) {
917 syslog(LOG_EMERG, "cdb_truncate(%d): %s\n", cdb, db_strerror(ret));
919 syslog(LOG_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");