4 * This is a data store backend for the Citadel server which uses Berkeley DB.
6 * Copyright (c) 1987-2009 by the citadel.org team
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 3 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 /*****************************************************************************
25 Tunable configuration parameters for the Berkeley DB back end
26 *****************************************************************************/
28 /* Citadel will checkpoint the db at the end of every session, but only if
29 * the specified number of kilobytes has been written, or if the specified
30 * number of minutes has passed, since the last checkpoint.
32 #define MAX_CHECKPOINT_KBYTES 256
33 #define MAX_CHECKPOINT_MINUTES 15
35 /*****************************************************************************/
44 #include <sys/types.h>
50 #elif defined(HAVE_DB4_DB_H)
53 #error Neither <db.h> nor <db4/db.h> was found by configure. Install db4-devel.
57 #if DB_VERSION_MAJOR < 4 || DB_VERSION_MINOR < 1
58 #error Citadel requires Berkeley DB v4.1 or newer. Please upgrade.
62 #include <libcitadel.h>
65 #include "citserver.h"
68 #include "sysdep_decls.h"
73 #include "ctdl_module.h"
76 static DB *dbp[MAXCDB]; /* One DB handle for each Citadel database */
77 static DB_ENV *dbenv; /* The DB environment (global) */
85 /* Verbose logging callback */
86 void cdb_verbose_log(const DB_ENV *dbenv, const char *msg)
88 if (!IsEmptyStr(msg)) {
89 CtdlLogPrintf(CTDL_DEBUG, "DB: %s\n", msg);
94 /* Verbose logging callback */
95 void cdb_verbose_err(const DB_ENV *dbenv, const char *errpfx, const char *msg)
97 CtdlLogPrintf(CTDL_ALERT, "DB: %s\n", msg);
101 /* just a little helper function */
102 static void txabort(DB_TXN * tid)
106 ret = tid->abort(tid);
109 CtdlLogPrintf(CTDL_EMERG, "bdb(): txn_abort: %s\n", db_strerror(ret));
114 /* this one is even more helpful than the last. */
115 static void txcommit(DB_TXN * tid)
119 ret = tid->commit(tid, 0);
122 CtdlLogPrintf(CTDL_EMERG, "bdb(): txn_commit: %s\n", db_strerror(ret));
127 /* are you sensing a pattern yet? */
128 static void txbegin(DB_TXN ** tid)
132 ret = dbenv->txn_begin(dbenv, NULL, tid, 0);
135 CtdlLogPrintf(CTDL_EMERG, "bdb(): txn_begin: %s\n", db_strerror(ret));
140 static void dbpanic(DB_ENV * env, int errval)
142 CtdlLogPrintf(CTDL_EMERG, "bdb(): PANIC: %s\n", db_strerror(errval));
145 static void cclose(DBC * cursor)
149 if ((ret = cursor->c_close(cursor))) {
150 CtdlLogPrintf(CTDL_EMERG, "bdb(): c_close: %s\n", db_strerror(ret));
155 static void bailIfCursor(DBC ** cursors, const char *msg)
159 for (i = 0; i < MAXCDB; i++)
160 if (cursors[i] != NULL) {
161 CtdlLogPrintf(CTDL_EMERG,
162 "bdb(): cursor still in progress on cdb %02x: %s\n", i, msg);
167 void check_handles(void *arg)
170 ThreadTSD *tsd = (ThreadTSD *) arg;
172 bailIfCursor(tsd->cursors, "in check_handles");
174 if (tsd->tid != NULL) {
175 CtdlLogPrintf(CTDL_EMERG, "bdb(): transaction still in progress!");
181 void cdb_check_handles(void)
183 check_handles(pthread_getspecific(ThreadKey));
188 * Cull the database logs
190 static void cdb_cull_logs(void)
199 /* Get the list of names. */
200 if ((ret = dbenv->log_archive(dbenv, &list, flags)) != 0) {
201 CtdlLogPrintf(CTDL_ERR, "cdb_cull_logs: %s\n", db_strerror(ret));
205 /* Print the list of names. */
207 for (file = list; *file != NULL; ++file) {
208 CtdlLogPrintf(CTDL_DEBUG, "Deleting log: %s\n", *file);
211 snprintf(errmsg, sizeof(errmsg),
212 " ** ERROR **\n \n \n "
213 "Citadel was unable to delete the "
214 "database log file '%s' because of the "
215 "following error:\n \n %s\n \n"
216 " This log file is no longer in use "
217 "and may be safely deleted.\n",
218 *file, strerror(errno));
219 CtdlAideMessage(errmsg, "Database Warning Message");
227 * Manually initiate log file cull.
229 void cmd_cull(char *argbuf) {
230 if (CtdlAccessCheck(ac_internal)) return;
232 cprintf("%d Database log file cull completed.\n", CIT_OK);
237 * Request a checkpoint of the database. Called once per minute by the thread manager.
239 void cdb_checkpoint(void)
243 CtdlLogPrintf(CTDL_DEBUG, "-- db checkpoint --\n");
244 ret = dbenv->txn_checkpoint(dbenv, MAX_CHECKPOINT_KBYTES, MAX_CHECKPOINT_MINUTES, 0);
247 CtdlLogPrintf(CTDL_EMERG, "cdb_checkpoint: txn_checkpoint: %s\n", db_strerror(ret));
251 /* After a successful checkpoint, we can cull the unused logs */
252 if (config.c_auto_cull) {
260 * Open the various databases we'll be using. Any database which
261 * does not exist should be created. Note that we don't need a
262 * critical section here, because there aren't any active threads
263 * manipulating the database yet.
265 void open_databases(void)
271 int dbversion_major, dbversion_minor, dbversion_patch;
272 int current_dbversion = 0;
274 CtdlLogPrintf(CTDL_DEBUG, "bdb(): open_databases() starting\n");
275 CtdlLogPrintf(CTDL_DEBUG, "Compiled db: %s\n", DB_VERSION_STRING);
276 CtdlLogPrintf(CTDL_INFO, " Linked db: %s\n",
277 db_version(&dbversion_major, &dbversion_minor, &dbversion_patch));
279 current_dbversion = (dbversion_major * 1000000) + (dbversion_minor * 1000) + dbversion_patch;
281 CtdlLogPrintf(CTDL_DEBUG, "Calculated dbversion: %d\n", current_dbversion);
282 CtdlLogPrintf(CTDL_DEBUG, " Previous dbversion: %d\n", CitControl.MMdbversion);
284 if ( (getenv("SUPPRESS_DBVERSION_CHECK") == NULL)
285 && (CitControl.MMdbversion > current_dbversion) ) {
286 CtdlLogPrintf(CTDL_EMERG, "You are attempting to run the Citadel server using a version\n"
287 "of Berkeley DB that is older than that which last created or\n"
288 "updated the database. Because this would probably cause data\n"
289 "corruption or loss, the server is aborting execution now.\n");
293 CitControl.MMdbversion = current_dbversion;
297 CtdlLogPrintf(CTDL_INFO, "Linked zlib: %s\n", zlibVersion());
301 * Silently try to create the database subdirectory. If it's
302 * already there, no problem.
304 if ((mkdir(ctdl_data_dir, 0700) != 0) && (errno != EEXIST)){
305 CtdlLogPrintf(CTDL_EMERG,
306 "unable to create database directory [%s]: %s",
307 ctdl_data_dir, strerror(errno));
309 if (chmod(ctdl_data_dir, 0700) != 0){
310 CtdlLogPrintf(CTDL_EMERG,
311 "unable to set database directory accessrights [%s]: %s",
312 ctdl_data_dir, strerror(errno));
314 if (chown(ctdl_data_dir, CTDLUID, (-1)) != 0){
315 CtdlLogPrintf(CTDL_EMERG,
316 "unable to set the owner for [%s]: %s",
317 ctdl_data_dir, strerror(errno));
319 CtdlLogPrintf(CTDL_DEBUG, "bdb(): Setting up DB environment\n");
320 db_env_set_func_yield(sched_yield);
321 ret = db_env_create(&dbenv, 0);
323 CtdlLogPrintf(CTDL_EMERG, "bdb(): db_env_create: %s\n", db_strerror(ret));
324 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
327 dbenv->set_errpfx(dbenv, "citserver");
328 dbenv->set_paniccall(dbenv, dbpanic);
329 dbenv->set_errcall(dbenv, cdb_verbose_err);
330 dbenv->set_errpfx(dbenv, "ctdl");
331 #if (DB_VERSION_MAJOR == 4) && (DB_VERSION_MINOR >= 3)
332 dbenv->set_msgcall(dbenv, cdb_verbose_log);
334 dbenv->set_verbose(dbenv, DB_VERB_DEADLOCK, 1);
335 dbenv->set_verbose(dbenv, DB_VERB_RECOVERY, 1);
338 * We want to specify the shared memory buffer pool cachesize,
339 * but everything else is the default.
341 ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
343 CtdlLogPrintf(CTDL_EMERG, "bdb(): set_cachesize: %s\n", db_strerror(ret));
344 dbenv->close(dbenv, 0);
345 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
349 if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT))) {
350 CtdlLogPrintf(CTDL_EMERG, "bdb(): set_lk_detect: %s\n", db_strerror(ret));
351 dbenv->close(dbenv, 0);
352 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
356 flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_TXN | DB_INIT_LOCK | DB_THREAD | DB_RECOVER;
357 CtdlLogPrintf(CTDL_DEBUG, "dbenv->open(dbenv, %s, %d, 0)\n", ctdl_data_dir, flags);
358 ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0);
359 if (ret == DB_RUNRECOVERY) {
360 CtdlLogPrintf(CTDL_ALERT, "dbenv->open: %s\n", db_strerror(ret));
361 CtdlLogPrintf(CTDL_ALERT, "Attempting recovery...\n");
363 ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0);
365 if (ret == DB_RUNRECOVERY) {
366 CtdlLogPrintf(CTDL_ALERT, "dbenv->open: %s\n", db_strerror(ret));
367 CtdlLogPrintf(CTDL_ALERT, "Attempting catastrophic recovery...\n");
368 flags &= ~DB_RECOVER;
369 flags |= DB_RECOVER_FATAL;
370 ret = dbenv->open(dbenv, ctdl_data_dir, flags, 0);
373 CtdlLogPrintf(CTDL_EMERG, "dbenv->open: %s\n", db_strerror(ret));
374 dbenv->close(dbenv, 0);
375 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
379 CtdlLogPrintf(CTDL_INFO, "Starting up DB\n");
381 for (i = 0; i < MAXCDB; ++i) {
383 /* Create a database handle */
384 ret = db_create(&dbp[i], dbenv, 0);
386 CtdlLogPrintf(CTDL_EMERG, "db_create: %s\n", db_strerror(ret));
387 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
392 /* Arbitrary names for our tables -- we reference them by
393 * number, so we don't have string names for them.
395 snprintf(dbfilename, sizeof dbfilename, "cdb.%02x", i);
397 ret = dbp[i]->open(dbp[i],
402 DB_CREATE | DB_AUTO_COMMIT | DB_THREAD,
406 CtdlLogPrintf(CTDL_EMERG, "db_open[%02x]: %s\n", i, db_strerror(ret));
408 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");
410 CtdlLogPrintf(CTDL_EMERG, "exit code %d\n", ret);
418 /* Make sure we own all the files, because in a few milliseconds
419 * we're going to drop root privs.
421 void cdb_chmod_data(void) {
424 char filename[PATH_MAX];
426 dp = opendir(ctdl_data_dir);
428 while (d = readdir(dp), d != NULL) {
429 if (d->d_name[0] != '.') {
430 snprintf(filename, sizeof filename,
431 "%s/%s", ctdl_data_dir, d->d_name);
432 CtdlLogPrintf(9, "chmod(%s, 0600) returned %d\n",
433 filename, chmod(filename, 0600)
435 CtdlLogPrintf(9, "chown(%s, CTDLUID, -1) returned %d\n",
436 filename, chown(filename, CTDLUID, (-1))
443 CtdlLogPrintf(CTDL_DEBUG, "open_databases() finished\n");
444 CtdlRegisterProtoHook(cmd_cull, "CULL", "Cull database logs");
449 * Close all of the db database files we've opened. This can be done
450 * in a loop, since it's just a bunch of closes.
452 void close_databases(void)
457 ctdl_thread_internal_free_tsd();
459 if ((ret = dbenv->txn_checkpoint(dbenv, 0, 0, 0))) {
460 CtdlLogPrintf(CTDL_EMERG,
461 "txn_checkpoint: %s\n", db_strerror(ret));
464 /* print some statistics... */
466 dbenv->lock_stat_print(dbenv, DB_STAT_ALL);
469 /* close the tables */
470 for (a = 0; a < MAXCDB; ++a) {
471 CtdlLogPrintf(CTDL_INFO, "Closing database %02x\n", a);
472 ret = dbp[a]->close(dbp[a], 0);
474 CtdlLogPrintf(CTDL_EMERG, "db_close: %s\n", db_strerror(ret));
479 /* Close the handle. */
480 ret = dbenv->close(dbenv, 0);
482 CtdlLogPrintf(CTDL_EMERG, "DBENV->close: %s\n", db_strerror(ret));
488 * Compression functions only used if we have zlib
490 void cdb_decompress_if_necessary(struct cdbdata *cdb)
492 static int magic = COMPRESS_MAGIC;
495 (cdb->ptr == NULL) ||
497 (memcmp(cdb->ptr, &magic, sizeof(magic))))
501 /* At this point we know we're looking at a compressed item. */
503 struct CtdlCompressHeader zheader;
504 char *uncompressed_data;
505 char *compressed_data;
506 uLongf destLen, sourceLen;
508 memcpy(&zheader, cdb->ptr, sizeof(struct CtdlCompressHeader));
510 compressed_data = cdb->ptr;
511 compressed_data += sizeof(struct CtdlCompressHeader);
513 sourceLen = (uLongf) zheader.compressed_len;
514 destLen = (uLongf) zheader.uncompressed_len;
515 uncompressed_data = malloc(zheader.uncompressed_len);
517 if (uncompress((Bytef *) uncompressed_data,
518 (uLongf *) & destLen,
519 (const Bytef *) compressed_data,
520 (uLong) sourceLen) != Z_OK) {
521 CtdlLogPrintf(CTDL_EMERG, "uncompress() error\n");
526 cdb->len = (size_t) destLen;
527 cdb->ptr = uncompressed_data;
528 #else /* HAVE_ZLIB */
529 CtdlLogPrintf(CTDL_EMERG, "Database contains compressed data, but this citserver was built without compression support.\n");
531 #endif /* HAVE_ZLIB */
537 * Store a piece of data. Returns 0 if the operation was successful. If a
538 * key already exists it should be overwritten.
540 int cdb_store(int cdb, void *ckey, int ckeylen, void *cdata, int cdatalen)
548 struct CtdlCompressHeader zheader;
549 char *compressed_data = NULL;
551 size_t buffer_len = 0;
555 memset(&dkey, 0, sizeof(DBT));
556 memset(&ddata, 0, sizeof(DBT));
559 ddata.size = cdatalen;
563 /* Only compress Visit records. Everything else is uncompressed. */
564 if (cdb == CDB_VISIT) {
566 zheader.magic = COMPRESS_MAGIC;
567 zheader.uncompressed_len = cdatalen;
568 buffer_len = ((cdatalen * 101) / 100) + 100
569 + sizeof(struct CtdlCompressHeader);
570 destLen = (uLongf) buffer_len;
571 compressed_data = malloc(buffer_len);
572 if (compress2((Bytef *) (compressed_data + sizeof(struct CtdlCompressHeader)),
573 &destLen, (Bytef *) cdata, (uLongf) cdatalen, 1) != Z_OK)
575 CtdlLogPrintf(CTDL_EMERG, "compress2() error\n");
578 zheader.compressed_len = (size_t) destLen;
579 memcpy(compressed_data, &zheader, sizeof(struct CtdlCompressHeader));
580 ddata.size = (size_t) (sizeof(struct CtdlCompressHeader) + zheader.compressed_len);
581 ddata.data = compressed_data;
586 ret = dbp[cdb]->put(dbp[cdb], /* db */
587 MYTID, /* transaction ID */
592 CtdlLogPrintf(CTDL_EMERG, "cdb_store(%d): %s\n", cdb, db_strerror(ret));
597 free(compressed_data);
602 bailIfCursor(MYCURSORS, "attempt to write during r/o cursor");
607 if ((ret = dbp[cdb]->put(dbp[cdb], /* db */
608 tid, /* transaction ID */
612 if (ret == DB_LOCK_DEADLOCK) {
616 CtdlLogPrintf(CTDL_EMERG, "cdb_store(%d): %s\n",
617 cdb, db_strerror(ret));
624 free(compressed_data);
634 * Delete a piece of data. Returns 0 if the operation was successful.
636 int cdb_delete(int cdb, void *key, int keylen)
643 memset(&dkey, 0, sizeof dkey);
648 ret = dbp[cdb]->del(dbp[cdb], MYTID, &dkey, 0);
650 CtdlLogPrintf(CTDL_EMERG, "cdb_delete(%d): %s\n", cdb, db_strerror(ret));
651 if (ret != DB_NOTFOUND) {
656 bailIfCursor(MYCURSORS, "attempt to delete during r/o cursor");
661 if ((ret = dbp[cdb]->del(dbp[cdb], tid, &dkey, 0))
662 && ret != DB_NOTFOUND) {
663 if (ret == DB_LOCK_DEADLOCK) {
667 CtdlLogPrintf(CTDL_EMERG, "cdb_delete(%d): %s\n",
668 cdb, db_strerror(ret));
678 static DBC *localcursor(int cdb)
683 if (MYCURSORS[cdb] == NULL)
684 ret = dbp[cdb]->cursor(dbp[cdb], MYTID, &curs, 0);
687 MYCURSORS[cdb]->c_dup(MYCURSORS[cdb], &curs,
691 CtdlLogPrintf(CTDL_EMERG, "localcursor: %s\n", db_strerror(ret));
700 * Fetch a piece of data. If not found, returns NULL. Otherwise, it returns
701 * a struct cdbdata which it is the caller's responsibility to free later on
702 * using the cdb_free() routine.
704 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen)
707 struct cdbdata *tempcdb;
711 memset(&dkey, 0, sizeof(DBT));
716 memset(&dret, 0, sizeof(DBT));
717 dret.flags = DB_DBT_MALLOC;
718 ret = dbp[cdb]->get(dbp[cdb], MYTID, &dkey, &dret, 0);
723 memset(&dret, 0, sizeof(DBT));
724 dret.flags = DB_DBT_MALLOC;
726 curs = localcursor(cdb);
728 ret = curs->c_get(curs, &dkey, &dret, DB_SET);
731 while (ret == DB_LOCK_DEADLOCK);
735 if ((ret != 0) && (ret != DB_NOTFOUND)) {
736 CtdlLogPrintf(CTDL_EMERG, "cdb_fetch(%d): %s\n", cdb, db_strerror(ret));
742 tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
744 if (tempcdb == NULL) {
745 CtdlLogPrintf(CTDL_EMERG, "cdb_fetch: Cannot allocate memory for tempcdb\n");
749 tempcdb->len = dret.size;
750 tempcdb->ptr = dret.data;
751 cdb_decompress_if_necessary(tempcdb);
757 * Free a cdbdata item.
759 * Note that we only free the 'ptr' portion if it is not NULL. This allows
760 * other code to assume ownership of that memory simply by storing the
761 * pointer elsewhere and then setting 'ptr' to NULL. cdb_free() will then
764 void cdb_free(struct cdbdata *cdb)
772 void cdb_close_cursor(int cdb)
774 if (MYCURSORS[cdb] != NULL) {
775 cclose(MYCURSORS[cdb]);
778 MYCURSORS[cdb] = NULL;
782 * Prepare for a sequential search of an entire database.
783 * (There is guaranteed to be no more than one traversal in
784 * progress per thread at any given time.)
786 void cdb_rewind(int cdb)
790 if (MYCURSORS[cdb] != NULL) {
791 CtdlLogPrintf(CTDL_EMERG,
792 "cdb_rewind: must close cursor on database %d before reopening.\n", cdb);
794 /* cclose(MYCURSORS[cdb]); */
798 * Now initialize the cursor
800 ret = dbp[cdb]->cursor(dbp[cdb], MYTID, &MYCURSORS[cdb], 0);
802 CtdlLogPrintf(CTDL_EMERG, "cdb_rewind: db_cursor: %s\n", db_strerror(ret));
809 * Fetch the next item in a sequential search. Returns a pointer to a
810 * cdbdata structure, or NULL if we've hit the end.
812 struct cdbdata *cdb_next_item(int cdb)
815 struct cdbdata *cdbret;
818 /* Initialize the key/data pair so the flags aren't set. */
819 memset(&key, 0, sizeof(key));
820 memset(&data, 0, sizeof(data));
821 data.flags = DB_DBT_MALLOC;
823 ret = MYCURSORS[cdb]->c_get(MYCURSORS[cdb], &key, &data, DB_NEXT);
826 if (ret != DB_NOTFOUND) {
827 CtdlLogPrintf(CTDL_EMERG, "cdb_next_item(%d): %s\n", cdb, db_strerror(ret));
830 cclose(MYCURSORS[cdb]);
831 MYCURSORS[cdb] = NULL;
832 return NULL; /* presumably, end of file */
835 cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
836 cdbret->len = data.size;
837 cdbret->ptr = data.data;
838 cdb_decompress_if_necessary(cdbret);
846 * Transaction-based stuff. I'm writing this as I bake cookies...
849 void cdb_begin_transaction(void)
852 bailIfCursor(MYCURSORS, "can't begin transaction during r/o cursor");
855 CtdlLogPrintf(CTDL_EMERG, "cdb_begin_transaction: ERROR: nested transaction\n");
862 void cdb_end_transaction(void)
866 for (i = 0; i < MAXCDB; i++)
867 if (MYCURSORS[i] != NULL) {
868 CtdlLogPrintf(CTDL_WARNING,
869 "cdb_end_transaction: WARNING: cursor %d still open at transaction end\n",
871 cclose(MYCURSORS[i]);
876 CtdlLogPrintf(CTDL_EMERG,
877 "cdb_end_transaction: ERROR: txcommit(NULL) !!\n");
887 * Truncate (delete every record)
889 void cdb_trunc(int cdb)
896 CtdlLogPrintf(CTDL_EMERG,
897 "cdb_trunc must not be called in a transaction.\n");
900 bailIfCursor(MYCURSORS, "attempt to write during r/o cursor");
905 if ((ret = dbp[cdb]->truncate(dbp[cdb], /* db */
906 NULL, /* transaction ID */
907 &count, /* #rows deleted */
909 if (ret == DB_LOCK_DEADLOCK) {
913 CtdlLogPrintf(CTDL_EMERG, "cdb_truncate(%d): %s\n", cdb, db_strerror(ret));
915 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");