ctdldump: use the backend API instead of BDB calls
authorArt Cancro <ajc@citadel.org>
Thu, 24 Aug 2023 18:44:37 +0000 (09:44 -0900)
committerArt Cancro <ajc@citadel.org>
Thu, 24 Aug 2023 18:44:37 +0000 (09:44 -0900)
This makes ctdldump backend agnostic, or at least as backend
agnostic as citserver itself is at this point.  We will probably
add some calling syntax across the board when we add more
backends, but at least this utility doesn't have to be rewritten
now.  This makes me a happy ig.  ctdlload will be next.

citadel/Makefile
citadel/server/backends/berkeley_db/berkeley_db.c
citadel/server/citserver.h
citadel/server/database.h
citadel/utils/ctdldump.c

index 5f62ba680a534f272442a87ed354d30b63962152..cf13c6eca27838e1e6961ea9109e5f94d85c7ea1 100644 (file)
@@ -53,8 +53,8 @@ chkpw: utils/chkpw.c utils/*.h server/*.h
 chkpwd: utils/chkpwd.c utils/auth.c utils/*.h server/*.h
        cc ${CFLAGS} ${LDFLAGS} utils/chkpwd.c utils/auth.c -lcrypt -o chkpwd
 
-ctdldump: utils/ctdldump.c utils/*.h server/*.h
-       cc ${CFLAGS} ${LDFLAGS} utils/ctdldump.c -lcitadel -lz ${BACKEND_LDFLAGS} -lpthread -o ctdldump
+ctdldump: utils/ctdldump.c utils/*.h server/*.h ${BACKEND_OBJECTS}
+       cc ${CFLAGS} ${LDFLAGS} utils/ctdldump.c ${BACKEND_OBJECTS} -lcitadel -lz ${BACKEND_LDFLAGS} -lpthread -o ctdldump
 
 ctdlload: utils/ctdlload.c server/makeuserkey.c utils/*.h server/*.h
        cc ${CFLAGS} ${LDFLAGS} utils/ctdlload.c server/makeuserkey.c -lcitadel -lz ${BACKEND_LDFLAGS} -lpthread -o ctdlload
index 2a8e2f14a819635ad0946e4135ab6c124808e5e3..e2c102cf6230381a42fcc3230c434c553b7dd267 100644 (file)
@@ -16,6 +16,7 @@
 #include <stdio.h>
 #include <zlib.h>
 #include <db.h>
+#include <assert.h>
 #include <libcitadel.h>
 #include "../../citserver.h"
 #include "../../config.h"
@@ -45,6 +46,7 @@ pthread_key_t bdb_thread_key;
 
 // Some functions in this backend need to store some per-thread data.
 // This returns the pointer to the current thread's per-thread data block, creating it if necessary.
+// (This will also work in a non-threaded program; it will return the same pointer every time.)
 struct bdb_tsd *bdb_get_tsd(void) {
 
         struct bdb_tsd *c = (struct bdb_tsd *) pthread_getspecific(bdb_thread_key) ;
@@ -264,8 +266,8 @@ void bdb_open_databases(void) {
                exit(CTDLEXIT_DB);
        }
 
-       syslog(LOG_INFO, "bdb: mounting databases");
        for (i = 0; i < MAXCDB; ++i) {
+               syslog(LOG_INFO, "bdb: mounting database %02x", i);
                ret = db_create(&bdb_table[i], bdb_env, 0);                             // Create a database handle
                if (ret) {
                        syslog(LOG_ERR, "bdb: db_create: %s", db_strerror(ret));
@@ -607,6 +609,7 @@ struct cdbkeyval bdb_next_item(int cdb) {
        TSD->dbkey[cdb].flags = DB_DBT_MALLOC;
        TSD->dbdata[cdb].flags = DB_DBT_MALLOC;
 
+       assert(TSD->cursors[cdb] != NULL);
        ret = TSD->cursors[cdb]->c_get(TSD->cursors[cdb], &TSD->dbkey[cdb], &TSD->dbdata[cdb], DB_NEXT);
 
        if (ret) {
index b92a06173dc97e397ddc8c9328cd4a878ef3b900..09412b0524364d09d502bbff7a13b5c607c8ed70 100644 (file)
@@ -25,7 +25,6 @@ struct UserProcList {
 
 #define CTDLUSERIP      (IsEmptyStr(CC->cs_addr) ?  CC->cs_clientinfo: CC->cs_addr)
 
-void cdb_init_backends(void);
 void master_startup (void);
 int master_cleanup (int exitcode);
 void set_wtmpsupp (char *newtext);
index ed72f55d131c3be66fc8200b831bc8927e0a7862..c8345b8fd6684ad6897f92fdbd2d89587d0473d7 100644 (file)
@@ -7,6 +7,7 @@
 #ifndef DATABASE_H
 #define DATABASE_H
 
+extern void            cdb_init_backends(void);
 extern void            cdb_chmod_data(void);
 
 extern void            (*cdb_open_databases)(void);
index 20858527166a560960c5cdf2c71530058c7617a0..a7a83b44852342546f5cb037f0910f7f41ff89b2 100644 (file)
 #include <errno.h>
 #include <stdarg.h>
 #include <limits.h>
+#include <syslog.h>
 #include <libcitadel.h>
 #include <zlib.h>
-#include <db.h>
 #include "../server/sysdep.h"
 #include "../server/citadel_defs.h"
 #include "../server/server.h"
 #include "../server/citadel_dirs.h"
+#include "../server/database.h"
 
 uid_t ctdluid = 0;
 
@@ -55,81 +56,15 @@ char *b64out(void *data, size_t len) {
 }
 
 
-// Open a database environment
-DB_ENV *open_dbenv(char *dirname) {
-
-       DB_ENV *dbenv = NULL;
-
-       int ret;
-       int i;
-       u_int32_t flags = 0;
-       int dbversion_major, dbversion_minor, dbversion_patch;
-
-       db_version(&dbversion_major, &dbversion_minor, &dbversion_patch);
-
-       // Create synthetic integer version numbers and compare them.
-       // Never run with a libdb other than the one with which it was compiled.
-       int compiled_db_version = ( (DB_VERSION_MAJOR * 1000000) + (DB_VERSION_MINOR * 1000) + (DB_VERSION_PATCH) );
-       int linked_db_version = ( (dbversion_major * 1000000) + (dbversion_minor * 1000) + (dbversion_patch) );
-       if (compiled_db_version != linked_db_version) {
-               fprintf(stderr, "ctdldump: ctdldump is running with a version of libdb other than the one with which it was compiled.\n"
-                       "ctdldump: This is an invalid configuration.  ctdldump will now exit to prevent data loss.");
-               exit(CTDLEXIT_DB);
-       }
-
-       ret = db_env_create(&dbenv, 0);
-       if (ret) {
-               fprintf(stderr, "ctdldump: db_env_create: %s\n", db_strerror(ret));
-               fprintf(stderr, "ctdldump: exit code %d\n", ret);
-               exit(CTDLEXIT_DB);
-       }
-
-       // We want to specify the shared memory buffer pool cachesize, but everything else is the default.
-       ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
-       if (ret) {
-               fprintf(stderr, "ctdldump: set_cachesize: %s\n", db_strerror(ret));
-               dbenv->close(dbenv, 0);
-               fprintf(stderr, "ctdldump: exit code %d\n", ret);
-               exit(CTDLEXIT_DB);
-       }
-
-       if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT))) {
-               fprintf(stderr, "ctdldump: set_lk_detect: %s\n", db_strerror(ret));
-               dbenv->close(dbenv, 0);
-               fprintf(stderr, "ctdldump: exit code %d\n", ret);
-               exit(CTDLEXIT_DB);
-       }
-
-       flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_LOG;
-       ret = dbenv->open(dbenv, dirname, flags, 0);
-       if (ret) {
-               fprintf(stderr, "ctdldump: dbenv->open: %s\n", db_strerror(ret));
-               dbenv->close(dbenv, 0);
-               fprintf(stderr, "ctdldump: exit code %d\n", ret);
-               exit(CTDLEXIT_DB);
-       }
-
-       return(dbenv);
-}
-
-
-void close_dbenv(DB_ENV *dbenv) {
-       int ret = dbenv->close(dbenv, 0);
-       if (ret) {
-               fprintf(stderr, "ctdldump: dbenv->close: %s\n", db_strerror(ret));
-       }
-}
-
-
 // export function for a message in msgmain
-void export_msgmain(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_msgmain(int which_cdb, struct cdbkeyval kv) {
        long in_msgnum;
 
-       memcpy(&in_msgnum, in_key->data, sizeof(in_msgnum));
+       memcpy(&in_msgnum, kv.key.ptr, sizeof(in_msgnum));
 
        // If the msgnum is negative, we are looking at METADATA
        if (in_msgnum < 0) {
-               struct MetaData *meta = (struct MetaData *)in_data->data;
+               struct MetaData *meta = (struct MetaData *)kv.val.ptr;
                printf("msgmeta|%ld|%d|%s|%ld|\n",
                        meta->meta_msgnum,
                        meta->meta_refcount,
@@ -140,7 +75,7 @@ void export_msgmain(int which_cdb, DBT *in_key, DBT *in_data) {
 
        // If the msgnum is positive, we are looking at a MESSAGE
        else if (in_msgnum > 0) {
-               printf("msgtext|%ld|%s|\n", in_msgnum, b64out(in_data->data, in_data->size));
+               printf("msgtext|%ld|%s|\n", in_msgnum, b64out(kv.val.ptr, kv.val.len));
        }
 
        // If the msgnum is 0 it's probably not a valid record.
@@ -148,9 +83,9 @@ void export_msgmain(int which_cdb, DBT *in_key, DBT *in_data) {
 
 
 // export function for a user record
-void export_user(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_user(int which_cdb, struct cdbkeyval kv) {
 
-       struct ctdluser *user = (struct ctdluser *)in_data->data;
+       struct ctdluser *user = (struct ctdluser *)kv.val.ptr;
 
        printf("user|%d|%d|%s|%u|%d|%ld|%ld|%d|%s|%ld|%ld|%s|%ld|%ld|\n",
                user->version,
@@ -172,9 +107,9 @@ void export_user(int which_cdb, DBT *in_key, DBT *in_data) {
 
 
 // export function for a room record
-void export_room(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_room(int which_cdb, struct cdbkeyval kv) {
 
-       struct ctdlroom *room = (struct ctdlroom *)in_data->data;
+       struct ctdlroom *room = (struct ctdlroom *)kv.val.ptr;
 
        printf("room|%s|%s|%ld|%ld|%ld|%u|%s|%ld|%d|%ld|%d|%d|%ld|%d|%u|%d|%ld|\n",
                room->QRname,
@@ -199,12 +134,12 @@ void export_room(int which_cdb, DBT *in_key, DBT *in_data) {
 
 
 // export function for a floor record
-void export_floor(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_floor(int which_cdb, struct cdbkeyval kv) {
 
        int floor_num;
-       memcpy(&floor_num, in_key->data, sizeof(int));
+       memcpy(&floor_num, kv.key.ptr, sizeof(int));
 
-       struct floor *floor = (struct floor *)in_data->data;
+       struct floor *floor = (struct floor *)kv.val.ptr;
 
        printf("floor|%d|%u|%s|%d|%d|%d|\n",
                floor_num,
@@ -219,21 +154,21 @@ void export_floor(int which_cdb, DBT *in_key, DBT *in_data) {
 
 // export function for a msglist
 // (indexed by a long and the data is arrays of longs)
-void export_msglist(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_msglist(int which_cdb, struct cdbkeyval kv) {
        int i;
        int num_msgs;
        long msg;
 
        // records are indexed by a single "long" and contains an array of zero or more "long"s
        long roomnum;
-       memcpy(&roomnum, in_key->data, sizeof(long));
+       memcpy(&roomnum, kv.key.ptr, sizeof(long));
 
        printf("msglist|%ld|", roomnum);
 
-       if (in_data->size > 0) {
-               num_msgs = in_data->size / sizeof(long);
+       if (kv.val.len > 0) {
+               num_msgs = kv.val.len / sizeof(long);
                for (i=0; i<num_msgs; ++i) {
-                       memcpy(&msg, (in_data->data + (i * sizeof(long))), sizeof(long));
+                       memcpy(&msg, (kv.val.ptr + (i * sizeof(long))), sizeof(long));
                        if (i != 0) {
                                printf(",");
                        }
@@ -246,21 +181,21 @@ void export_msglist(int which_cdb, DBT *in_key, DBT *in_data) {
 
 // export function for a full text search index record
 // (indexed by an int and the data is arrays of longs)
-void export_fulltext(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_fulltext(int which_cdb, struct cdbkeyval kv) {
        int i;
        int num_msgs;
        long msg;
 
        // records are indexed by a single "int" and contains an array of zero or more "long"s
        int indexnum;
-       memcpy(&indexnum, in_key->data, sizeof(int));
+       memcpy(&indexnum, kv.key.ptr, sizeof(int));
 
        printf("fulltext|%d|", indexnum);
 
-       if (in_data->size > 0) {
-               num_msgs = in_data->size / sizeof(long);
+       if (kv.val.len > 0) {
+               num_msgs = kv.val.len / sizeof(long);
                for (i=0; i<num_msgs; ++i) {
-                       memcpy(&msg, (in_data->data + (i * sizeof(long))), sizeof(long));
+                       memcpy(&msg, (kv.val.ptr + (i * sizeof(long))), sizeof(long));
                        if (i != 0) {
                                printf(",");
                        }
@@ -272,8 +207,8 @@ void export_fulltext(int which_cdb, DBT *in_key, DBT *in_data) {
 
 
 // export function for a visit record
-void export_visit(int which_cdb, DBT *in_key, DBT *in_data) {
-       struct visit *visit = (struct visit *)in_data->data;
+void export_visit(int which_cdb, struct cdbkeyval kv) {
+       struct visit *visit = (struct visit *)kv.val.ptr;
        int i, len;
 
        // If there is corrupt data in the "seen" array, cut that out before exporting
@@ -307,31 +242,31 @@ void export_visit(int which_cdb, DBT *in_key, DBT *in_data) {
 
 
 // export function for a directory record
-void export_dir(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_dir(int which_cdb, struct cdbkeyval kv) {
        printf("dir|");
-       fwrite(in_key->data, in_key->size, 1, stdout);
-       printf("|%s|\n", (char *)in_data->data);
+       fwrite(kv.key.ptr, kv.key.len, 1, stdout);
+       printf("|%s|\n", (char *)kv.val.ptr);
 }
 
 
 // export function for a use table record
-void export_usetable(int which_cdb, DBT *in_key, DBT *in_data) {
-       struct UseTable *u = (struct UseTable *)in_data->data;
+void export_usetable(int which_cdb, struct cdbkeyval kv) {
+       struct UseTable *u = (struct UseTable *)kv.val.ptr;
        printf("use|%d|%ld|\n", u->hash, u->timestamp);
 }
 
 
 // export function for large message texts
-void export_bigmsg(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_bigmsg(int which_cdb, struct cdbkeyval kv) {
        long msgnum;
 
-       memcpy(&msgnum, in_key->data, sizeof(msgnum));
-       printf("bigmsg|%ld|%s|\n", msgnum, b64out(in_data->data, in_data->size));
+       memcpy(&msgnum, kv.key.ptr, sizeof(msgnum));
+       printf("bigmsg|%ld|%s|\n", msgnum, b64out(kv.val.ptr, kv.val.len));
 }
 
 
 // export function for EUID Index records
-void export_euidindex(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_euidindex(int which_cdb, struct cdbkeyval kv) {
 
        // The structure of an euidindex record *key* is:
        // |----room_number----|----------EUID-------------|
@@ -344,9 +279,9 @@ void export_euidindex(int which_cdb, DBT *in_key, DBT *in_data) {
        long msgnum, roomnum;
        char *euid;
 
-       memcpy(&msgnum, in_data->data, sizeof(long));
-       memcpy(&roomnum, in_data->data+sizeof(long), sizeof(msgnum));
-       euid = in_data->data+(sizeof(long)*2);
+       memcpy(&msgnum, kv.val.ptr, sizeof(long));
+       memcpy(&roomnum, kv.val.ptr+sizeof(long), sizeof(msgnum));
+       euid = kv.val.ptr+(sizeof(long)*2);
 
        printf("euidindex|%ld|%ld|%s|\n", msgnum, roomnum, euid);
 }
@@ -354,35 +289,35 @@ void export_euidindex(int which_cdb, DBT *in_key, DBT *in_data) {
 
 // export users-by-number records
 // (This is a secondary index -- should we just regenerate the data after import?)
-void export_usersbynumber(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_usersbynumber(int which_cdb, struct cdbkeyval kv) {
 
        // key is a long
        long usernum;
-       memcpy(&usernum, in_key->data, sizeof(usernum));
+       memcpy(&usernum, kv.key.ptr, sizeof(usernum));
 
        // value is a string
-       printf("usersbynumber|%ld|%s|\n", usernum, (char *)in_data->data);
+       printf("usersbynumber|%ld|%s|\n", usernum, (char *)kv.val.ptr);
 }
 
 
 // export function for a config record
-void export_config(int which_cdb, DBT *in_key, DBT *in_data) {
+void export_config(int which_cdb, struct cdbkeyval kv) {
 
        printf("config|%s|%s|\n",
-               (char *)in_data->data,
-               (char *)in_data->data + strlen(in_data->data) + 1
+               (char *)kv.val.ptr,
+               (char *)kv.val.ptr + strlen(kv.val.ptr) + 1
        );
 
 }
 
 
 // For obsolete databases, zero all the output
-void zero_function(int which_cdb, DBT *in_key, DBT *in_data) {
+void zero_function(int which_cdb, struct cdbkeyval kv) {
        // do nothing
 }
 
 
-void (*export_functions[])(int which_cdb, DBT *in_key, DBT *in_data) = {
+void (*export_functions[])(int which_cdb, struct cdbkeyval kv) = {
        export_msgmain,         // 00 CDB_MSGMAIN       
        export_user,            // 01 CDB_USERS
        export_room,            // 02 CDB_ROOMS
@@ -400,99 +335,21 @@ void (*export_functions[])(int which_cdb, DBT *in_key, DBT *in_data) = {
 };
 
 
-void export_table(int which_cdb, DB_ENV *src_dbenv) {
+void export_table(int which_cdb) {
        int ret;
-       int compressed;
-       char dbfilename[32];
-       uLongf destLen = 0;
-
-       // shamelessly swiped from https://docs.oracle.com/database/bdb181/html/programmer_reference/am_cursor.html
-       DB *src_dbp;
-       DBC *src_dbcp;
-       DBT in_key, in_data, uncomp_data;
+       struct cdbkeyval ckv;
+
        int num_good_rows = 0;
        int num_bad_rows = 0;
 
-       snprintf(dbfilename, sizeof dbfilename, "cdb.%02x", which_cdb);
-
-       // create a database handle for the source table
-       ret = db_create(&src_dbp, src_dbenv, 0);
-       if (ret) {
-               fprintf(stderr, "ctdldump: db_create: %s\n", db_strerror(ret));
-               fprintf(stderr, "ctdldump: exit code %d\n", ret);
-               exit(CTDLEXIT_DB);
-       }
-
-       // open the file containing the source table
-       ret = src_dbp->open(src_dbp, NULL, dbfilename, NULL, DB_BTREE, 0, 0600);
-       if (ret) {
-               fprintf(stderr, "ctdldump: db_open(%s): %s (skipping empty or missing table)\n", dbfilename, db_strerror(ret));
+       cdb_rewind(which_cdb);
+       while (ckv = cdb_next_item(which_cdb), ckv.val.ptr!=NULL) {
+               // Call the export function registered to this table
+               export_functions[which_cdb](which_cdb, ckv);
        }
-       else {
-
-               // Acquire a cursor to read the source table
-               if ((ret = src_dbp->cursor(src_dbp, NULL, &src_dbcp, 0)) != 0) {
-                       fprintf(stderr, "ctdldump: db_cursor: %s\n", db_strerror(ret));
-                       fprintf(stderr, "ctdldump: exit code %d\n", ret);
-                       exit(CTDLEXIT_DB);
-               }
-       
-               // Zero out these database keys
-               memset(&in_key,         0, sizeof(DBT));        // input
-               memset(&in_data,        0, sizeof(DBT));
-               memset(&uncomp_data,    0, sizeof(DBT));        // decompressed input (the key doesn't change)
-       
-               // Walk through the database, calling export functions as we go and clearing buffers before each call.
-               while (ret = src_dbcp->get(src_dbcp, &in_key, &in_data, DB_NEXT) == 0) {
-               
-                       // If either the key or data are zero length, skip this record
-                       if ((in_key.size == 0) || (in_data.size == 0)) {
-                               ++num_bad_rows;
-                       }
-       
-                       else {  // Both key and data are >0 length so we're good to go
-       
-                               // Do we need to decompress?
-                               static int32_t magic = COMPRESS_MAGIC;
-                               compressed = 0;
-                               if ((in_data.size >= sizeof(struct CtdlCompressHeader)) && (!memcmp(in_data.data, &magic, sizeof(magic)))) {
-               
-                                       // yes, we need to decompress
-                                       compressed = 1;
-                                       struct CtdlCompressHeader comp;
-                                       memcpy(&comp, in_data.data, sizeof(struct CtdlCompressHeader));
-                                       uncomp_data.size = comp.uncompressed_len;
-                                       uncomp_data.data = reallok(uncomp_data.data, uncomp_data.size);
-                                       destLen = (uLongf)comp.uncompressed_len;
-               
-                                       ret = uncompress((Bytef *)uncomp_data.data, (uLongf *)&destLen,
-                                                       (const Bytef *)in_data.data+sizeof(struct CtdlCompressHeader),
-                                                       (uLong)comp.compressed_len);
-                                       if (ret != Z_OK) {
-                                               fprintf(stderr, "ctdldump: uncompress() error %d\n", ret);
-                                               exit(CTDLEXIT_DB);
-                                       }
-                               }
-               
-                               // Call the export function registered to this table
-                               export_functions[which_cdb](which_cdb, &in_key, (compressed ? &uncomp_data : &in_data));
-               
-                               // Knowing the total number of rows isn't critical to the program.  It's just for the user to know.
-                               fflush(stdout);
-                       }
-               }
-       }
-
-       // free any leftover out_data pointers
-       free(uncomp_data.data);
-
-       // ...and close the database (table)
-       ret = src_dbp->close(src_dbp, 0);
-       if (ret) {
-               fprintf(stderr, "ctdldump: db_close: %s\n", db_strerror(ret));
-       }
-
 
+       // Knowing the total number of rows isn't critical to the program.  It's just for the user to know.
+       fflush(stdout);
 }
 
 
@@ -500,7 +357,6 @@ int main(int argc, char **argv) {
        int i = 0;
        char src_dir[PATH_MAX];
        int confirmed = 0;
-       static DB_ENV *src_dbenv;               // Source DB environment (global)
 
        // display the greeting
        fprintf(stderr, "\033[44m\033[33m\033[1m \033[K\033[0m\n"
@@ -544,13 +400,22 @@ int main(int argc, char **argv) {
                exit(1);
        }
 
-       src_dbenv = open_dbenv(src_dir);
+       // backend modules use syslog -- redirect to stderr
+       openlog("ctdldump", LOG_PERROR , LOG_DAEMON);
+
+       // initialize the database backend
+       cdb_init_backends();
+       cdb_open_databases();
+
        printf("begin|\n");
        for (i = 0; i < MAXCDB; ++i) {
-               export_table(i, src_dbenv);
+               export_table(i);
        }
-       close_dbenv(src_dbenv);
        printf("end|\n");
+       fflush(stdout);
+
+       // close databases
+       cdb_close_databases();
 
        fprintf(stderr, "ctdldump: \033[32m\033[1mfinished\033[0m\n");
        exit(0);