}
-// Wrapper for txn_begin() that logs/aborts on error. Not part of the backend API.
-static void bdb_txbegin(DB_TXN **tid) {
- int ret;
-
- ret = bdb_env->txn_begin(bdb_env, NULL, tid, 0);
-
- if (ret) {
- syslog(LOG_ERR, "bdb: txn_begin: %s", db_strerror(ret));
- bdb_abort();
- }
-}
-
-
// Panic callback for Berkeley DB. Not part of the backend API.
static void bdb_dbpanic(DB_ENV *env, int errval) {
syslog(LOG_ERR, "bdb: PANIC: %s", db_strerror(errval));
}
+// Transaction-based stuff. I'm writing this as I bake cookies...
+void bdb_begin_transaction(void) {
+ int ret;
+ bdb_bailIfCursor(TSD->cursors, "can't begin transaction during r/o cursor");
+
+ if (TSD->tid != NULL) {
+ syslog(LOG_ERR, "bdb: bdb_begin_transaction: ERROR: nested transaction");
+ bdb_abort();
+ }
+
+ ret = bdb_env->txn_begin(bdb_env, NULL, &TSD->tid, 0);
+ if (ret) {
+ syslog(LOG_ERR, "bdb: bdb_begin_transaction: %s", db_strerror(ret));
+ bdb_abort();
+ }
+}
+
+
+// ...and the cookies are cursed.
+void bdb_end_transaction(void) {
+ int i;
+
+ for (i = 0; i < MAXCDB; i++) {
+ if (TSD->cursors[i] != NULL) {
+ syslog(LOG_WARNING, "bdb: bdb_end_transaction: WARNING: cursor %d still open at transaction end", i);
+ bdb_cclose(TSD->cursors[i]);
+ TSD->cursors[i] = NULL;
+ }
+ }
+
+ if (TSD->tid == NULL) {
+ syslog(LOG_ERR, "bdb: bdb_end_transaction: ERROR: bdb_txcommit(NULL) !!");
+ bdb_abort();
+ }
+ else {
+ bdb_txcommit(TSD->tid);
+ }
+
+ TSD->tid = NULL;
+}
+
+
// Request a checkpoint of the database. Called once per minute by the thread manager.
void bdb_checkpoint(void) {
int ret;
bdb_env->set_verbose(bdb_env, DB_VERB_DEADLOCK, 1);
bdb_env->set_verbose(bdb_env, DB_VERB_RECOVERY, 1);
- // We want to specify the shared memory buffer pool cachesize, but everything else is the default.
- // 2023aug21 ajc: the third argument is zero, so this never did anything
- // ret = bdb_env->set_cachesize(bdb_env, 0, 64 * 1024, 0);
- // if (ret) {
- // syslog(LOG_ERR, "bdb: set_cachesize: %s", db_strerror(ret));
- // bdb_env->close(bdb_env, 0);
- // syslog(LOG_ERR, "bdb: exit code %d", ret);
- // exit(CTDLEXIT_DB);
- // }
-
- // This appears to do nothing over and above the default
- //if ((ret = bdb_env->set_lk_detect(bdb_env, DB_LOCK_DEFAULT))) {
- //syslog(LOG_ERR, "bdb: set_lk_detect: %s", db_strerror(ret));
- //bdb_env->close(bdb_env, 0);
- //syslog(LOG_ERR, "bdb: exit code %d", ret);
- //exit(CTDLEXIT_DB);
- //}
-
- flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_TXN | /*DB_INIT_LOCK |*/ DB_THREAD | DB_INIT_LOG;
+ flags = DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_INIT_TXN | DB_RECOVER | DB_THREAD;
syslog(LOG_DEBUG, "bdb: bdb_env->open(bdb_env, %s, %d, 0)", ctdl_db_dir, flags);
ret = bdb_env->open(bdb_env, ctdl_db_dir, flags, 0); // try opening the database cleanly
- if (ret == DB_RUNRECOVERY) {
- syslog(LOG_ERR, "bdb: bdb_env->open: %s", db_strerror(ret));
- syslog(LOG_ERR, "bdb: attempting recovery...");
- flags |= DB_RECOVER;
- ret = bdb_env->open(bdb_env, ctdl_db_dir, flags, 0); // try recovery
- }
- if (ret == DB_RUNRECOVERY) {
- syslog(LOG_ERR, "bdb: bdb_env->open: %s", db_strerror(ret));
- syslog(LOG_ERR, "bdb: attempting catastrophic recovery...");
- flags &= ~DB_RECOVER;
- flags |= DB_RECOVER_FATAL;
- ret = bdb_env->open(bdb_env, ctdl_db_dir, flags, 0); // try catastrophic recovery
- }
if (ret) {
- syslog(LOG_ERR, "bdb: bdb_env->open: %s", db_strerror(ret));
+ syslog(LOG_ERR, "bdb: bdb_env->open: %s: %s", ctdl_db_dir, db_strerror(ret));
bdb_env->close(bdb_env, 0);
syslog(LOG_ERR, "bdb: exit code %d", ret);
exit(CTDLEXIT_DB);
int compressing = 0;
size_t buffer_len = 0;
uLongf destLen = 0;
+ int existing_txn = 0; // set to nonzero if we are already inside a transaction
memset(&dkey, 0, sizeof(DBT));
memset(&ddata, 0, sizeof(DBT));
}
if (TSD->tid != NULL) {
- ret = bdb_table[cdb]->put(bdb_table[cdb], // db
- TSD->tid, // transaction ID
- &dkey, // key
- &ddata, // data
- 0 // flags
- );
- if (ret) {
- syslog(LOG_ERR, "bdb: bdb_store(%d): %s", cdb, db_strerror(ret));
- bdb_abort();
- }
- if (compressing) {
- free(compressed_data);
- }
- return ret;
+ existing_txn = 1;
}
- else {
- bdb_bailIfCursor(TSD->cursors, "attempt to write during r/o cursor");
-
- retry:
- bdb_txbegin(&tid);
-
- if ((ret = bdb_table[cdb]->put(bdb_table[cdb], // db
- tid, // transaction ID
- &dkey, // key
- &ddata, // data
- 0))) { // flags
- if (ret == DB_LOCK_DEADLOCK) {
- bdb_txabort(tid);
- goto retry;
- }
- else {
- syslog(LOG_ERR, "bdb: bdb_store(%d): %s", cdb, db_strerror(ret));
- bdb_abort();
- }
- }
- else {
- bdb_txcommit(tid);
- if (compressing) {
- free(compressed_data);
- }
- return ret;
- }
+
+ if (!existing_txn) { // If we're not already inside a transaction,
+ bdb_begin_transaction(); // create our own for this operation.
+ }
+
+ ret = bdb_table[cdb]->put(bdb_table[cdb], // db
+ TSD->tid, // transaction ID
+ &dkey, // key
+ &ddata, // data
+ 0 // flags
+ );
+
+ if (ret) {
+ syslog(LOG_ERR, "bdb: bdb_store(%02x): %s", cdb, db_strerror(ret));
+ bdb_abort();
+ }
+
+ if (!existing_txn) {
+ bdb_end_transaction();
+ }
+
+ if (compressing) {
+ free(compressed_data);
}
+
return ret;
}
// Delete a piece of data. Returns 0 if the operation was successful.
int bdb_delete(int cdb, void *key, int keylen) {
DBT dkey;
- DB_TXN *tid;
int ret;
+ int existing_txn = 0; // set to nonzero if we are already inside a transaction
memset(&dkey, 0, sizeof dkey);
dkey.size = keylen;
dkey.data = key;
if (TSD->tid != NULL) {
- ret = bdb_table[cdb]->del(bdb_table[cdb], TSD->tid, &dkey, 0);
- if (ret) {
- syslog(LOG_ERR, "bdb: bdb_delete(%d): %s", cdb, db_strerror(ret));
- if (ret != DB_NOTFOUND) {
- bdb_abort();
- }
- }
+ existing_txn = 1;
}
- else {
- bdb_bailIfCursor(TSD->cursors, "attempt to delete during r/o cursor");
- retry:
- bdb_txbegin(&tid);
-
- if ((ret = bdb_table[cdb]->del(bdb_table[cdb], tid, &dkey, 0)) && ret != DB_NOTFOUND) {
- if (ret == DB_LOCK_DEADLOCK) {
- bdb_txabort(tid);
- goto retry;
- }
- else {
- syslog(LOG_ERR, "bdb: bdb_delete(%d): %s", cdb, db_strerror(ret));
- bdb_abort();
- }
- }
- else {
- bdb_txcommit(tid);
- }
+ if (!existing_txn) { // If we're not already inside a transaction,
+ bdb_begin_transaction(); // create our own for this operation.
}
- return ret;
-}
-
-static DBC *bdb_localcursor(int cdb) {
- int ret;
- DBC *curs;
-
- if (TSD->cursors[cdb] == NULL) {
- ret = bdb_table[cdb]->cursor(bdb_table[cdb], TSD->tid, &curs, 0);
- }
- else {
- ret = TSD->cursors[cdb]->c_dup(TSD->cursors[cdb], &curs, DB_POSITION);
+ ret = bdb_table[cdb]->del(bdb_table[cdb], TSD->tid, &dkey, 0);
+ if (ret) {
+ if (ret != DB_NOTFOUND) {
+ syslog(LOG_ERR, "bdb: bdb_delete(%02x): %s", cdb, db_strerror(ret));
+ bdb_abort();
+ }
}
- if (ret) {
- syslog(LOG_ERR, "bdb: bdb_localcursor: %s", db_strerror(ret));
- bdb_abort();
+ if (!existing_txn) {
+ bdb_end_transaction(); // Only end the transaction if we began it.
}
- return curs;
+ return ret;
}
// Fetch a piece of data. Returns a "struct cdbdata"
-// cdbdata.len will be 0 if the item is not found.
+// If the item is not found, the pointer will be NULL.
struct cdbdata bdb_fetch(int cdb, const void *key, int keylen) {
struct cdbdata returned_data;
dkey.size = keylen;
dkey.data = (void *) key;
- if (TSD->tid != NULL) {
- TSD->dbdata[cdb].flags = DB_DBT_REALLOC;
- ret = bdb_table[cdb]->get(bdb_table[cdb], TSD->tid, &dkey, &TSD->dbdata[cdb], 0);
- }
- else {
- DBC *curs;
-
- do {
- TSD->dbdata[cdb].flags = DB_DBT_REALLOC;
- curs = bdb_localcursor(cdb);
- ret = curs->c_get(curs, &dkey, &TSD->dbdata[cdb], DB_SET);
- bdb_cclose(curs);
- } while (ret == DB_LOCK_DEADLOCK);
- }
+ TSD->dbdata[cdb].flags = DB_DBT_REALLOC;
+ ret = bdb_table[cdb]->get(bdb_table[cdb], TSD->tid, &dkey, &TSD->dbdata[cdb], 0);
if ((ret != 0) && (ret != DB_NOTFOUND)) {
syslog(LOG_ERR, "bdb: bdb_fetch(%d): %s", cdb, db_strerror(ret));
}
-// Transaction-based stuff. I'm writing this as I bake cookies...
-void bdb_begin_transaction(void) {
- bdb_bailIfCursor(TSD->cursors, "can't begin transaction during r/o cursor");
-
- if (TSD->tid != NULL) {
- syslog(LOG_ERR, "bdb: bdb_begin_transaction: ERROR: nested transaction");
- bdb_abort();
- }
-
- bdb_txbegin(&TSD->tid);
-}
-
-
-void bdb_end_transaction(void) {
- int i;
-
- for (i = 0; i < MAXCDB; i++) {
- if (TSD->cursors[i] != NULL) {
- syslog(LOG_WARNING, "bdb: bdb_end_transaction: WARNING: cursor %d still open at transaction end", i);
- bdb_cclose(TSD->cursors[i]);
- TSD->cursors[i] = NULL;
- }
- }
-
- if (TSD->tid == NULL) {
- syslog(LOG_ERR, "bdb: bdb_end_transaction: ERROR: bdb_txcommit(NULL) !!");
- bdb_abort();
- }
- else {
- bdb_txcommit(TSD->tid);
- }
-
- TSD->tid = NULL;
-}
-
-
// Truncate (delete every record)
void bdb_trunc(int cdb) {
int ret;
syslog(LOG_ERR, "bdb: bdb_trunc must not be called in a transaction.");
bdb_abort();
}
- else {
- bdb_bailIfCursor(TSD->cursors, "attempt to write during r/o cursor");
-
- retry:
- if ((ret = bdb_table[cdb]->truncate(bdb_table[cdb], // db
- NULL, // transaction ID
- &count, // #rows deleted
- 0))) { // flags
- if (ret == DB_LOCK_DEADLOCK) {
- goto retry;
- }
- else {
- syslog(LOG_ERR, "bdb: bdb_truncate(%d): %s", cdb, db_strerror(ret));
- if (ret == ENOMEM) {
- syslog(LOG_ERR, "bdb: You may need to tune your database; please read http://www.citadel.org for more information.");
- }
- exit(CTDLEXIT_DB);
- }
+ bdb_begin_transaction(); // create our own transaction for this operation.
+ ret = bdb_table[cdb]->truncate(bdb_table[cdb], // db
+ NULL, // transaction ID
+ &count, // #rows deleted
+ 0); // flags
+ //
+ if (ret) {
+ syslog(LOG_ERR, "bdb: bdb_truncate(%d): %s", cdb, db_strerror(ret));
+ if (ret == ENOMEM) {
+ syslog(LOG_ERR, "bdb: You may need to tune your database; please read http://www.citadel.org for more information.");
}
+ exit(CTDLEXIT_DB);
}
+ bdb_end_transaction();
}
return 0; // No messages at all? No further action.
}
- /*
- * Now begin the traversal.
- */
+ // Now begin the traversal.
if (num_msgs > 0) for (a = 0; a < num_msgs; ++a) {
- /* If the caller is looking for a specific MIME type, filter
- * out all messages which are not of the type requested.
- */
+ // If the caller is looking for a specific MIME type, filter
+ // out all messages which are not of the type requested.
if ((content_type != NULL) && (!IsEmptyStr(content_type))) {
- /* This call to GetMetaData() sits inside this loop
- * so that we only do the extra database read per msg
- * if we need to. Doing the extra read all the time
- * really kills the server. If we ever need to use
- * metadata for another search criterion, we need to
- * move the read somewhere else -- but still be smart
- * enough to only do the read if the caller has
- * specified something that will need it.
- */
+ // This call to GetMetaData() sits inside this loop
+ // so that we only do the extra database read per msg
+ // if we need to. Doing the extra read all the time
+ // really kills the server. If we ever need to use
+ // metadata for another search criterion, we need to
+ // move the read somewhere else -- but still be smart
+ // enough to only do the read if the caller has
+ // specified something that will need it.
if (server_shutting_down) {
if (need_to_free_re) regfree(&re);
free(msglist);
}
GetMetaData(&smi, msglist[a]);
- /* if (strcasecmp(smi.meta_content_type, content_type)) { old non-regex way */
if (regexec(&re, smi.meta_content_type, 1, &pm, 0) != 0) {
msglist[a] = 0L;
}
num_msgs = sort_msglist(msglist, num_msgs);
- /* If a template was supplied, filter out the messages which
- * don't match. (This could induce some delays!)
- */
+ // If a template was supplied, filter out the messages which don't match. (This could induce some delays!)
if (num_msgs > 0) {
if (compare != NULL) {
for (a = 0; a < num_msgs; ++a) {
}
-/*
- * Convenience function to process a big block of AdjRefCount() operations
- */
-void AdjRefCountList(long *msgnum, long nmsg, int incr)
-{
+// Convenience function to process a big block of AdjRefCount() operations
+void AdjRefCountList(long *msgnum, long nmsg, int incr) {
long i;
for (i = 0; i < nmsg; i++) {
}
-/*
- * AdjRefCount - adjust the reference count for a message. We need to delete from disk any message whose reference count reaches zero.
- */
-void AdjRefCount(long msgnum, int incr)
-{
+// AdjRefCount - adjust the reference count for a message.
+// We need to delete from disk any message whose reference count reaches zero.
+void AdjRefCount(long msgnum, int incr) {
struct MetaData smi;
long delnum;
- /* This is a *tight* critical section; please keep it that way, as
- * it may get called while nested in other critical sections.
- * Complicating this any further will surely cause deadlock!
- */
+ // This is a *tight* critical section; please keep it that way, as
+ // it may get called while nested in other critical sections.
+ // Complicating this any further will surely cause deadlock!
begin_critical_section(S_SUPPMSGMAIN);
GetMetaData(&smi, msgnum);
smi.meta_refcount += incr;
end_critical_section(S_SUPPMSGMAIN);
syslog(LOG_DEBUG, "msgbase: AdjRefCount() msg %ld ref count delta %+d, is now %d", msgnum, incr, smi.meta_refcount);
- /* If the reference count is now zero, delete both the message and its metadata record.
- */
+ // If the reference count is now zero, delete both the message and its metadata record.
if (smi.meta_refcount == 0) {
syslog(LOG_DEBUG, "msgbase: deleting message <%ld>", msgnum);
- /* Call delete hooks with NULL room to show it has gone altogether */
+ // Call delete hooks with NULL room to show it has gone altogether
PerformDeleteHooks(NULL, msgnum);
- /* Remove from message base */
+ // Remove from message base
delnum = msgnum;
cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
- cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long));
+ cdb_delete(CDB_BIGMSGS, &delnum, (int)sizeof(long)); // There might not be a bigmsgs. Not an error.
- /* Remove metadata record */
+ // Remove metadata record
delnum = (0L - msgnum);
cdb_delete(CDB_MSGMAIN, &delnum, (int)sizeof(long));
}
}
-/*
- * Write a generic object to this room
- *
- * Returns the message number of the written object, in case you need it.
- */
-long CtdlWriteObject(char *req_room, /* Room to stuff it in */
- char *content_type, /* MIME type of this object */
- char *raw_message, /* Data to be written */
- off_t raw_length, /* Size of raw_message */
- struct ctdluser *is_mailbox, /* Mailbox room? */
- int is_binary, /* Is encoding necessary? */
- unsigned int flags /* Internal save flags */
+// Write a generic object to this room
+// Returns the message number of the written object, in case you need it.
+long CtdlWriteObject(char *req_room, // Room to stuff it in
+ char *content_type, // MIME type of this object
+ char *raw_message, // Data to be written
+ off_t raw_length, // Size of raw_message
+ struct ctdluser *is_mailbox, // Mailbox room?
+ int is_binary, // Is encoding necessary?
+ unsigned int flags // Internal save flags
) {
struct ctdlroom qrbuf;
char roomname[ROOMNAMELEN];