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) ||
496 (cdb->len < sizeof(magic)) ||
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;
509 memset(&zheader, 0, sizeof(struct CtdlCompressHeader));
510 cplen = sizeof(struct CtdlCompressHeader);
511 if (sizeof(struct CtdlCompressHeader) > cdb->len)
513 memcpy(&zheader, cdb->ptr, cplen);
515 compressed_data = cdb->ptr;
516 compressed_data += sizeof(struct CtdlCompressHeader);
518 sourceLen = (uLongf) zheader.compressed_len;
519 destLen = (uLongf) zheader.uncompressed_len;
520 uncompressed_data = malloc(zheader.uncompressed_len);
522 if (uncompress((Bytef *) uncompressed_data,
523 (uLongf *) & destLen,
524 (const Bytef *) compressed_data,
525 (uLong) sourceLen) != Z_OK) {
526 CtdlLogPrintf(CTDL_EMERG, "uncompress() error\n");
531 cdb->len = (size_t) destLen;
532 cdb->ptr = uncompressed_data;
533 #else /* HAVE_ZLIB */
534 CtdlLogPrintf(CTDL_EMERG, "Database contains compressed data, but this citserver was built without compression support.\n");
536 #endif /* HAVE_ZLIB */
542 * Store a piece of data. Returns 0 if the operation was successful. If a
543 * key already exists it should be overwritten.
545 int cdb_store(int cdb, void *ckey, int ckeylen, void *cdata, int cdatalen)
553 struct CtdlCompressHeader zheader;
554 char *compressed_data = NULL;
556 size_t buffer_len = 0;
560 memset(&dkey, 0, sizeof(DBT));
561 memset(&ddata, 0, sizeof(DBT));
564 ddata.size = cdatalen;
568 /* Only compress Visit records. Everything else is uncompressed. */
569 if (cdb == CDB_VISIT) {
571 zheader.magic = COMPRESS_MAGIC;
572 zheader.uncompressed_len = cdatalen;
573 buffer_len = ((cdatalen * 101) / 100) + 100
574 + sizeof(struct CtdlCompressHeader);
575 destLen = (uLongf) buffer_len;
576 compressed_data = malloc(buffer_len);
577 if (compress2((Bytef *) (compressed_data + sizeof(struct CtdlCompressHeader)),
578 &destLen, (Bytef *) cdata, (uLongf) cdatalen, 1) != Z_OK)
580 CtdlLogPrintf(CTDL_EMERG, "compress2() error\n");
583 zheader.compressed_len = (size_t) destLen;
584 memcpy(compressed_data, &zheader, sizeof(struct CtdlCompressHeader));
585 ddata.size = (size_t) (sizeof(struct CtdlCompressHeader) + zheader.compressed_len);
586 ddata.data = compressed_data;
591 ret = dbp[cdb]->put(dbp[cdb], /* db */
592 MYTID, /* transaction ID */
597 CtdlLogPrintf(CTDL_EMERG, "cdb_store(%d): %s\n", cdb, db_strerror(ret));
602 free(compressed_data);
607 bailIfCursor(MYCURSORS, "attempt to write during r/o cursor");
612 if ((ret = dbp[cdb]->put(dbp[cdb], /* db */
613 tid, /* transaction ID */
617 if (ret == DB_LOCK_DEADLOCK) {
621 CtdlLogPrintf(CTDL_EMERG, "cdb_store(%d): %s\n",
622 cdb, db_strerror(ret));
629 free(compressed_data);
639 * Delete a piece of data. Returns 0 if the operation was successful.
641 int cdb_delete(int cdb, void *key, int keylen)
648 memset(&dkey, 0, sizeof dkey);
653 ret = dbp[cdb]->del(dbp[cdb], MYTID, &dkey, 0);
655 CtdlLogPrintf(CTDL_EMERG, "cdb_delete(%d): %s\n", cdb, db_strerror(ret));
656 if (ret != DB_NOTFOUND) {
661 bailIfCursor(MYCURSORS, "attempt to delete during r/o cursor");
666 if ((ret = dbp[cdb]->del(dbp[cdb], tid, &dkey, 0))
667 && ret != DB_NOTFOUND) {
668 if (ret == DB_LOCK_DEADLOCK) {
672 CtdlLogPrintf(CTDL_EMERG, "cdb_delete(%d): %s\n",
673 cdb, db_strerror(ret));
683 static DBC *localcursor(int cdb)
688 if (MYCURSORS[cdb] == NULL)
689 ret = dbp[cdb]->cursor(dbp[cdb], MYTID, &curs, 0);
692 MYCURSORS[cdb]->c_dup(MYCURSORS[cdb], &curs,
696 CtdlLogPrintf(CTDL_EMERG, "localcursor: %s\n", db_strerror(ret));
705 * Fetch a piece of data. If not found, returns NULL. Otherwise, it returns
706 * a struct cdbdata which it is the caller's responsibility to free later on
707 * using the cdb_free() routine.
709 struct cdbdata *cdb_fetch(int cdb, void *key, int keylen)
712 struct cdbdata *tempcdb;
716 memset(&dkey, 0, sizeof(DBT));
721 memset(&dret, 0, sizeof(DBT));
722 dret.flags = DB_DBT_MALLOC;
723 ret = dbp[cdb]->get(dbp[cdb], MYTID, &dkey, &dret, 0);
728 memset(&dret, 0, sizeof(DBT));
729 dret.flags = DB_DBT_MALLOC;
731 curs = localcursor(cdb);
733 ret = curs->c_get(curs, &dkey, &dret, DB_SET);
736 while (ret == DB_LOCK_DEADLOCK);
740 if ((ret != 0) && (ret != DB_NOTFOUND)) {
741 CtdlLogPrintf(CTDL_EMERG, "cdb_fetch(%d): %s\n", cdb, db_strerror(ret));
747 tempcdb = (struct cdbdata *) malloc(sizeof(struct cdbdata));
749 if (tempcdb == NULL) {
750 CtdlLogPrintf(CTDL_EMERG, "cdb_fetch: Cannot allocate memory for tempcdb\n");
754 tempcdb->len = dret.size;
755 tempcdb->ptr = dret.data;
756 cdb_decompress_if_necessary(tempcdb);
762 * Free a cdbdata item.
764 * Note that we only free the 'ptr' portion if it is not NULL. This allows
765 * other code to assume ownership of that memory simply by storing the
766 * pointer elsewhere and then setting 'ptr' to NULL. cdb_free() will then
769 void cdb_free(struct cdbdata *cdb)
777 void cdb_close_cursor(int cdb)
779 if (MYCURSORS[cdb] != NULL) {
780 cclose(MYCURSORS[cdb]);
783 MYCURSORS[cdb] = NULL;
787 * Prepare for a sequential search of an entire database.
788 * (There is guaranteed to be no more than one traversal in
789 * progress per thread at any given time.)
791 void cdb_rewind(int cdb)
795 if (MYCURSORS[cdb] != NULL) {
796 CtdlLogPrintf(CTDL_EMERG,
797 "cdb_rewind: must close cursor on database %d before reopening.\n", cdb);
799 /* cclose(MYCURSORS[cdb]); */
803 * Now initialize the cursor
805 ret = dbp[cdb]->cursor(dbp[cdb], MYTID, &MYCURSORS[cdb], 0);
807 CtdlLogPrintf(CTDL_EMERG, "cdb_rewind: db_cursor: %s\n", db_strerror(ret));
814 * Fetch the next item in a sequential search. Returns a pointer to a
815 * cdbdata structure, or NULL if we've hit the end.
817 struct cdbdata *cdb_next_item(int cdb)
820 struct cdbdata *cdbret;
823 /* Initialize the key/data pair so the flags aren't set. */
824 memset(&key, 0, sizeof(key));
825 memset(&data, 0, sizeof(data));
826 data.flags = DB_DBT_MALLOC;
828 ret = MYCURSORS[cdb]->c_get(MYCURSORS[cdb], &key, &data, DB_NEXT);
831 if (ret != DB_NOTFOUND) {
832 CtdlLogPrintf(CTDL_EMERG, "cdb_next_item(%d): %s\n", cdb, db_strerror(ret));
835 cclose(MYCURSORS[cdb]);
836 MYCURSORS[cdb] = NULL;
837 return NULL; /* presumably, end of file */
840 cdbret = (struct cdbdata *) malloc(sizeof(struct cdbdata));
841 cdbret->len = data.size;
842 cdbret->ptr = data.data;
843 cdb_decompress_if_necessary(cdbret);
851 * Transaction-based stuff. I'm writing this as I bake cookies...
854 void cdb_begin_transaction(void)
857 bailIfCursor(MYCURSORS, "can't begin transaction during r/o cursor");
860 CtdlLogPrintf(CTDL_EMERG, "cdb_begin_transaction: ERROR: nested transaction\n");
867 void cdb_end_transaction(void)
871 for (i = 0; i < MAXCDB; i++)
872 if (MYCURSORS[i] != NULL) {
873 CtdlLogPrintf(CTDL_WARNING,
874 "cdb_end_transaction: WARNING: cursor %d still open at transaction end\n",
876 cclose(MYCURSORS[i]);
881 CtdlLogPrintf(CTDL_EMERG,
882 "cdb_end_transaction: ERROR: txcommit(NULL) !!\n");
892 * Truncate (delete every record)
894 void cdb_trunc(int cdb)
901 CtdlLogPrintf(CTDL_EMERG,
902 "cdb_trunc must not be called in a transaction.\n");
905 bailIfCursor(MYCURSORS, "attempt to write during r/o cursor");
910 if ((ret = dbp[cdb]->truncate(dbp[cdb], /* db */
911 NULL, /* transaction ID */
912 &count, /* #rows deleted */
914 if (ret == DB_LOCK_DEADLOCK) {
918 CtdlLogPrintf(CTDL_EMERG, "cdb_truncate(%d): %s\n", cdb, db_strerror(ret));
920 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");