]> code.citadel.org Git - citadel.git/blob - citadel/utils/ctdlload.c
ctdldump.c: skeletonize
[citadel.git] / citadel / utils / ctdlload.c
1 // Don't run this.  It doesn't work and if you try to run it you will immediately die.
2 //
3 // Copyright (c) 2023 by Art Cancro citadel.org
4 //
5 // This program is open source software.  Use, duplication, or disclosure
6 // is subject to the terms of the GNU General Public License, version 3.
7
8 #include <stdlib.h>
9 #include <unistd.h>
10 #include <ctype.h>
11 #include <stdio.h>
12 #include <signal.h>
13 #include <sys/types.h>
14 #include <sys/socket.h>
15 #include <sys/un.h>
16 #include <netdb.h>
17 #include <string.h>
18 #include <pwd.h>
19 #include <errno.h>
20 #include <stdarg.h>
21 #include <limits.h>
22 #include <libcitadel.h>
23 #include <zlib.h>
24 #include <db.h>
25 #include "../server/sysdep.h"
26 #include "../server/citadel_defs.h"
27 #include "../server/server.h"
28 #include "../server/citadel_dirs.h"
29 #include "ctdl3264_structs.h"
30
31
32 // Wrapper for realloc() that crashes and burns if the call fails.
33 void *reallok(void *ptr, size_t size) {
34         void *p = realloc(ptr, size);
35         if (!p) {
36                 fprintf(stderr, "realloc() failed to resize %p to %ld bytes, error: %m\n", ptr, size);
37                 exit(1);
38         }
39         return p;
40 }
41 #define realloc reallok
42
43
44 // Open a database environment
45 DB_ENV *open_dbenv(char *dirname) {
46
47         DB_ENV *dbenv = NULL;
48
49         int ret;
50         int i;
51         u_int32_t flags = 0;
52         int dbversion_major, dbversion_minor, dbversion_patch;
53
54         printf( "db: open_dbenv() starting\n"
55                 "db:    Linked zlib: %s\n"
56                 "db: Compiled libdb: %s\n"
57                 "db:   Linked libdb: %s\n",
58                 zlibVersion(),
59                 DB_VERSION_STRING,
60                 db_version(&dbversion_major, &dbversion_minor, &dbversion_patch)
61         );
62
63         // Create synthetic integer version numbers and compare them.
64         // Never run with a libdb older than the one with which it was compiled.
65         int compiled_db_version = ( (DB_VERSION_MAJOR * 1000000) + (DB_VERSION_MINOR * 1000) + (DB_VERSION_PATCH) );
66         int linked_db_version = ( (dbversion_major * 1000000) + (dbversion_minor * 1000) + (dbversion_patch) );
67         if (compiled_db_version > linked_db_version) {
68                 printf( "db: ctdl3264 is running with a version of libdb older than the one with which it was compiled.\n"
69                         "db: This is an invalid configuration.  ctdl3264 will now exit to prevent data loss.");
70                 exit(CTDLEXIT_DB);
71         }
72
73         printf("db: Setting up DB environment\n");
74         ret = db_env_create(&dbenv, 0);
75         if (ret) {
76                 printf("db: db_env_create: %s\n", db_strerror(ret));
77                 printf("db: exit code %d\n", ret);
78                 exit(CTDLEXIT_DB);
79         }
80
81         // We want to specify the shared memory buffer pool cachesize, but everything else is the default.
82         ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
83         if (ret) {
84                 printf("db: set_cachesize: %s\n", db_strerror(ret));
85                 dbenv->close(dbenv, 0);
86                 printf("db: exit code %d\n", ret);
87                 exit(CTDLEXIT_DB);
88         }
89
90         if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT))) {
91                 printf("db: set_lk_detect: %s\n", db_strerror(ret));
92                 dbenv->close(dbenv, 0);
93                 printf("db: exit code %d\n", ret);
94                 exit(CTDLEXIT_DB);
95         }
96
97         flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_LOG;
98         printf("db: dbenv open(dir=%s, flags=%d)\n", dirname, flags);
99         ret = dbenv->open(dbenv, dirname, flags, 0);
100         if (ret) {
101                 printf("db: dbenv->open: %s\n", db_strerror(ret));
102                 dbenv->close(dbenv, 0);
103                 printf("db: exit code %d\n", ret);
104                 exit(CTDLEXIT_DB);
105         }
106
107         return(dbenv);
108 }
109
110
111 void close_dbenv(DB_ENV *dbenv) {
112         printf("db: closing dbenv\n");
113         int ret = dbenv->close(dbenv, 0);
114         if (ret) {
115                 printf("db: dbenv->close: %s\n", db_strerror(ret));
116         }
117 }
118
119
120 // convert function for a message in msgmain
121 void convert_msgmain(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
122         int32_t in_msgnum;
123         long out_msgnum;
124         memcpy(&in_msgnum, in_key->data, sizeof(in_msgnum));
125         out_msgnum = (long)in_msgnum;
126
127         if (in_key->size != 4) {
128                 fprintf(stderr, "\033[31m\033[1m *** SOURCE DATABASE IS NOT 32-BIT *** ABORTING *** \033[0m\n");
129                 abort();
130         }
131
132         // If the msgnum is negative, we are looking at METADATA
133         if (in_msgnum < 0) {
134                 struct MetaData_32 *meta32 = (struct MetaData_32 *)in_data->data;
135
136                 // printf("\033[32m\033[1mMetadata: msgnum=%d , refcount=%d , content_type=\"%s\" , rfc822len=%d\033[0m\n", meta32->meta_msgnum, meta32->meta_refcount, meta32->meta_content_type, meta32->meta_rfc822_length);
137
138                 out_key->size = sizeof(long);
139                 out_key->data = realloc(out_key->data, out_key->size);
140                 memcpy(out_key->data, &out_msgnum, sizeof(long));
141
142                 out_data->size = sizeof(struct MetaData);
143                 out_data->data = realloc(out_data->data, out_data->size);
144                 struct MetaData *meta64 = (struct MetaData *)out_data->data;
145                 memset(meta64, 0, sizeof(struct MetaData));
146                 meta64->meta_msgnum             = (long)        meta32->meta_msgnum;
147                 meta64->meta_refcount           = (int)         meta32->meta_refcount;
148                 strcpy(meta64->meta_content_type,               meta32->meta_content_type);
149                 meta64->meta_rfc822_length      = (long)        meta32->meta_rfc822_length;
150         }
151
152         // If the msgnum is positive, we are looking at a MESSAGE
153         else if (in_msgnum > 0) {
154                 out_key->size = sizeof(long);
155                 out_key->data = realloc(out_key->data, out_key->size);
156                 memcpy(out_key->data, &out_msgnum, sizeof(long));
157
158                 // copy the message verbatim
159                 out_data->size = in_data->size;
160                 out_data->data = realloc(out_data->data, out_data->size);
161                 memcpy(out_data->data, in_data->data, out_data->size);
162                 // printf("\033[32m\033[1mMessage: %ld\033[0m\n", out_msgnum);
163         }
164
165         // If the msgnum is 0 it's probably not a valid record.
166         else {
167                 // printf("\033[31mmsgmain: message number 0 is impossible, skipping this record\033[0m\n");
168         }
169 }
170
171
172 // convert function for a user record
173 void convert_users(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
174
175         // The key is a string so we can just copy it over
176         out_key->size = in_key->size;
177         out_key->data = realloc(out_key->data, out_key->size);
178         memcpy(out_key->data, in_key->data, in_key->size);
179
180         struct ctdluser_32 *user32 = (struct ctdluser_32 *)in_data->data;
181
182         out_data->size = sizeof(struct ctdluser);
183         out_data->data = realloc(out_data->data, out_data->size);
184         struct ctdluser *user64 = (struct ctdluser *)out_data->data;
185
186         user64->version                 = (int)         user32->version;
187         user64->uid                     = (uid_t)       user32->uid;
188         strcpy(user64->password,                        user32->password);
189         user64->flags                   = (unsigned)    user32->flags;
190         user64->axlevel                 = (cit_uint8_t) user32->axlevel;
191         user64->usernum                 = (long)        user32->usernum;
192         user64->lastcall                = (time_t)      user32->lastcall;
193         user64->USuserpurge             = (int)         user32->USuserpurge;
194         strcpy(user64->fullname,                        user32->fullname);
195         user64->msgnum_bio              = (long)        user32->msgnum_bio;
196         user64->msgnum_pic              = (long)        user32->msgnum_pic;
197         strcpy(user64->emailaddrs,                      user32->emailaddrs);
198         user64->msgnum_inboxrules       = (long)        user32->msgnum_inboxrules;
199         user64->lastproc_inboxrules     = (long)        user32->lastproc_inboxrules;
200
201         // printf("\033[32m\033[1mUser: %s\033[0m\n", user64->fullname);
202 }
203
204
205 // convert function for a room record
206 void convert_rooms(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
207
208         // The key is a string so we can just copy it over
209         out_key->size = in_key->size;
210         out_key->data = realloc(out_key->data, out_key->size);
211         memcpy(out_key->data, in_key->data, in_key->size);
212
213         // data
214         struct ctdlroom_32 *room32 = (struct ctdlroom_32 *)in_data->data;
215         out_data->size = sizeof(struct ctdlroom);
216         out_data->data = realloc(out_data->data, out_data->size);
217         struct ctdlroom *room64 = (struct ctdlroom *)out_data->data;
218
219         strcpy(room64->QRname,                          room32->QRname);
220         strcpy(room64->QRpasswd,                        room32->QRpasswd);
221         room64->QRroomaide              = (long)        room32->QRroomaide;
222         room64->QRhighest               = (long)        room32->QRhighest;
223         room64->QRgen                   = (time_t)      room32->QRgen;
224         room64->QRflags                 = (unsigned)    room32->QRflags;
225         strcpy(room64->QRdirname,                       room32->QRdirname);
226         room64->msgnum_info             = (long)        room32->msgnum_info;
227         room64->QRfloor                 = (char)        room32->QRfloor;
228         room64->QRmtime                 = (time_t)      room32->QRmtime;
229         room64->QRep.expire_mode        = (int)         room32->QRep.expire_mode;
230         room64->QRep.expire_value       = (int)         room32->QRep.expire_value;
231         room64->QRnumber                = (long)        room32->QRnumber;
232         room64->QRorder                 = (char)        room32->QRorder;
233         room64->QRflags2                = (unsigned)    room32->QRflags2;
234         room64->QRdefaultview           = (int)         room32->QRdefaultview;
235         room64->msgnum_pic              = (long)        room32->msgnum_pic;
236
237         // printf("\033[32m\033[1mRoom: %s\033[0m\n", room64->QRname);
238 }
239
240
241 // convert function for a floor record
242 void convert_floors(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
243
244         // the key is an "int", and "int" is 32-bits on both 32 and 64 bit platforms.
245         out_key->size = in_key->size;
246         out_key->data = realloc(out_key->data, out_key->size);
247         memcpy(out_key->data, in_key->data, in_key->size);
248
249         // data
250         struct floor_32 *floor32 = (struct floor_32 *)in_data->data;
251         out_data->size = sizeof(struct floor);
252         out_data->data = realloc(out_data->data, out_data->size);
253         struct floor *floor64 = (struct floor *)out_data->data;
254
255         // these are probably bit-for-bit identical, actually ... but we do it the "right" way anyway
256         floor64->f_flags                = (unsigned short)      floor32->f_flags;
257         strcpy(floor64->f_name,                                 floor32->f_name);
258         floor64->f_ref_count            = (int)                 floor32->f_ref_count;
259         floor64->f_ep.expire_mode       = (int)                 floor32->f_ep.expire_mode;
260         floor64->f_ep.expire_value      = (int)                 floor32->f_ep.expire_value;
261
262         // printf("\033[32m\033[1mFloor: %s\033[0m\n", floor64->f_name);
263 }
264
265
266 // convert function for a msglist or a fulltext index record
267 // (both are indexed by a long and the data is arrays of longs)
268 void convert_msglists(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
269         int i;
270
271         char *table = (which_cdb == CDB_FULLTEXT) ? "FullText" : "Msglist";
272
273         // records are indexed by a single "long" and contains an array of zero or more "long"s
274         // and remember ... "long" is int32_t on the source system
275         int32_t in_roomnum;
276         long out_roomnum;
277         memcpy(&in_roomnum, in_key->data, sizeof(in_roomnum));
278         out_roomnum = (long) in_roomnum;
279
280         if (in_key->size != 4) {
281                 fprintf(stderr, "\033[31m\033[1m *** SOURCE DATABASE IS NOT 32-BIT *** ABORTING *** \033[0m\n");
282                 abort();
283         }
284
285         int num_msgs = in_data->size / sizeof(int32_t);
286         // printf("\033[32m\033[1m%s: key %ld (%d messages)\033[0m\n", table, out_roomnum, num_msgs);
287
288         // the key is a "long"
289         out_key->size = sizeof(out_roomnum);
290         out_key->data = realloc(out_key->data, out_key->size);
291         memcpy(out_key->data, &out_roomnum, sizeof(out_roomnum));
292
293         // the data is another array, but a wider type
294         out_data->size = sizeof(long) * num_msgs;
295         out_data->data = realloc(out_data->data, out_data->size);
296
297         int32_t in_msg = 0;
298         long out_msg = 0;
299         for (i=0; i<num_msgs; ++i) {
300                 memcpy(&in_msg, (in_data->data + (i * sizeof(int32_t))), sizeof(int32_t));
301                 out_msg = (long) in_msg;
302                 memcpy((out_data->data + (i * sizeof(long))), &out_msg, sizeof(long));
303                 // printf("msg#%ld\n", out_msg);
304         }
305 }
306
307
308 // convert function for a visit record
309 void convert_visits(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
310
311         // data
312         struct visit_32 *visit32 = (struct visit_32 *)in_data->data;
313         out_data->size = sizeof(struct visit);
314         out_data->data = realloc(out_data->data, out_data->size);
315         struct visit *visit64 = (struct visit *)out_data->data;
316
317         //  the data (zero it out so it will compress well)
318         memset(visit64, 0, sizeof(struct visit));
319         visit64->v_roomnum              = (long)        visit32->v_roomnum;
320         visit64->v_roomgen              = (long)        visit32->v_roomgen;
321         visit64->v_usernum              = (long)        visit32->v_usernum;
322         visit64->v_lastseen             = (long)        visit32->v_lastseen;
323         visit64->v_flags                = (unsigned)    visit32->v_flags;
324         strcpy(visit64->v_seen,                         visit32->v_seen);
325         strcpy(visit64->v_answered,                     visit32->v_answered);
326         visit64->v_view                 = (int)         visit32->v_view;
327
328         // printf("\033[32m\033[1mVisit: room %10ld, gen %10ld, user %10ld\033[0m\n", visit64->v_roomnum, visit64->v_roomgen, visit64->v_usernum);
329
330         // create the key (which is based on the data, so there is no need to convert the old key)
331         out_key->size = sizeof(struct visit_index);
332         out_key->data = realloc(out_key->data, out_key->size);
333         struct visit_index *newvisitindex = (struct visit_index *) out_key->data;
334         newvisitindex->iRoomID          =               visit64->v_roomnum;
335         newvisitindex->iRoomGen         =               visit64->v_roomgen;
336         newvisitindex->iUserID          =               visit64->v_usernum;
337 }
338
339
340 // convert function for a directory record
341 void convert_dir(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
342
343         // the key is a string
344         out_key->size = in_key->size;
345         out_key->data = realloc(out_key->data, out_key->size + 1);
346         memcpy(out_key->data, in_key->data, in_key->size);
347         char *k = (char *)out_key->data;
348         k[out_key->size] = 0;
349
350         // the data is also a string
351         out_data->size = in_data->size;
352         out_data->data = realloc(out_data->data, out_data->size + 1);
353         memcpy(out_data->data, in_data->data, in_data->size);
354         char *d = (char *)out_data->data;
355         d[out_data->size] = 0;
356
357         // please excuse my friend, he isn't null terminated
358         // printf("\033[32m\033[1mDirectory entry: %s -> %s\033[0m\n", (char *)out_key->data, (char *)out_data->data);
359 }
360
361
362 // convert function for a use table record
363 void convert_usetable(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
364
365         // the key is an int, which is the same size (32 bits) on both 32 and 64 bit systems
366         out_key->size = in_key->size;
367         out_key->data = realloc(out_key->data, out_key->size);
368         memcpy(out_key->data, in_key->data, in_key->size);
369
370         // the data is a "struct UseTable"
371         struct UseTable_32 *use32 = (struct UseTable_32 *)in_data->data;
372         out_data->size = sizeof(struct UseTable);
373         out_data->data = realloc(out_data->data, out_data->size);
374         memset(out_data->data, 0, out_data->size);
375         struct UseTable *use64 = (struct UseTable *)out_data->data;
376
377         //  the data
378         use64->hash                     =               use32->hash;
379         use64->timestamp                = (time_t)      use32->timestamp;
380
381         // printf("\033[32m\033[1muse table: %d , %s\033[0m\n", use64->hash, asctime(localtime(&use64->timestamp)));
382 }
383
384
385 // convert function for large message texts
386 void convert_bigmsgs(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
387
388         // The key is a packed long
389         int32_t in_msgnum;
390         long out_msgnum;
391         memcpy(&in_msgnum, in_key->data, sizeof(in_msgnum));
392         out_msgnum = (long)in_msgnum;
393
394         if (in_key->size != 4) {
395                 fprintf(stderr, "\033[31m\033[1m *** SOURCE DATABASE IS NOT 32-BIT *** ABORTING *** \033[0m\n");
396                 abort();
397         }
398
399         out_key->size = sizeof(long);
400         out_key->data = realloc(out_key->data, out_key->size);
401         memcpy(out_key->data, &out_msgnum, sizeof(long));
402
403         // the data is binary-ish but has no packed integers
404         out_data->size = in_data->size;
405         out_data->data = realloc(out_data->data, out_data->size);
406         memcpy(out_data->data, in_data->data, in_data->size);
407
408         // printf("\033[32m\033[1mBigmsg %ld , length %d\033[0m\n", out_msgnum, out_data->size);
409 }
410
411
412 // convert function for EUID Index records
413 void convert_euidindex(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
414
415         // The structure of an euidindex record *key* is:
416         // |----room_number----|----------EUID-------------|
417         //    (sizeof long)       (actual length of euid)
418
419         // The structure of an euidindex record *value* is:
420         // |-----msg_number----|----room_number----|----------EUID-------------|
421         //    (sizeof long)       (sizeof long)       (actual length of euid)
422
423         int32_t in_msgnum = 0;
424         int32_t in_roomnum = 0;
425         char euid[SIZ];
426         long out_msgnum = 0;
427         long out_roomnum = 0;
428
429         memcpy(&in_msgnum, in_data->data, sizeof(in_msgnum));
430         memcpy(&in_roomnum, in_data->data+sizeof(int32_t), sizeof(in_msgnum));
431         strcpy(euid, in_data->data+(sizeof(int32_t)*2));
432
433         out_msgnum = (long) in_msgnum;
434         out_roomnum = (long) in_roomnum;
435         // printf("euidindex: msgnum=%ld, roomnum=%ld, euid=\"%s\"\n", out_msgnum, out_roomnum, euid);
436
437         out_key->size = sizeof(long) + strlen(euid) + 1;
438         out_key->data = realloc(out_key->data, out_key->size);
439         memcpy(out_key->data, &out_roomnum, sizeof(out_roomnum));
440         strcpy(out_key->data+sizeof(out_roomnum), euid);
441
442         out_data->size = sizeof(long) + sizeof(long) + strlen(euid) + 1;
443         out_data->data = realloc(out_data->data, out_data->size);
444         memcpy(out_data->data, &out_msgnum, sizeof(out_msgnum));
445         memcpy(out_data->data+sizeof(out_msgnum), &out_roomnum, sizeof(out_roomnum));
446         strcpy(out_data->data+sizeof(out_msgnum)+sizeof(out_roomnum), euid);
447
448
449         //int i;
450         //char ch;
451 //
452         //printf("  in_key:             ");
453         //for (i=0; i<in_key->size; ++i) {
454                 //ch = 0;
455                 //memcpy(&ch, in_key->data+i, 1);
456                 //printf("%02X ", (int) ch);
457         //}
458         //printf("\n");
459 //
460         //printf(" out_key: ");
461         //for (i=0; i<out_key->size; ++i) {
462                 //ch = 0;
463                 //memcpy(&ch, out_key->data+i, 1);
464                 //printf("%02X ", (int) ch);
465         //}
466         //printf("\n");
467 //
468         //printf(" in_data:                         ");
469         //for (i=0; i<in_data->size; ++i) {
470                 //ch = 0;
471                 //memcpy(&ch, in_data->data+i, 1);
472         //      printf("%02X ", (int) ch);
473         //}
474         //printf("\n");
475 //
476         //printf("out_data: ");
477         //for (i=0; i<out_data->size; ++i) {
478                 //ch = 0;
479                 //memcpy(&ch, out_data->data+i, 1);
480                 //printf("%02X ", (int) ch);
481         //}
482         //printf("\n");
483
484
485 }
486
487
488 // convert users-by-number records
489 void convert_usersbynumber(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
490
491         // key is a long
492         // and remember ... "long" is int32_t on the source system
493         int32_t in_usernum;
494         long out_usernum;
495         memcpy(&in_usernum, in_key->data, sizeof(in_usernum));
496         out_usernum = (long) in_usernum;
497
498         if (in_key->size != 4) {
499                 fprintf(stderr, "\033[31m\033[1m *** SOURCE DATABASE IS NOT 32-BIT *** ABORTING *** \033[0m\n");
500                 abort();
501         }
502
503         out_key->size = sizeof(out_usernum);
504         out_key->data = realloc(out_key->data, out_key->size);
505         memcpy(out_key->data, &out_usernum, sizeof(out_usernum));
506
507         // value is a string
508         out_data->size = in_data->size;
509         out_data->data = realloc(out_data->data, out_data->size);
510         memcpy(out_data->data, in_data->data, in_data->size);
511
512         // printf("usersbynumber: %ld --> %s\n", out_usernum, (char *)out_data->data);
513 }
514
515
516 // convert function for a config record
517 void convert_config(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
518
519         // the key is a string
520         out_key->size = in_key->size;
521         out_key->data = realloc(out_key->data, out_key->size + 1);
522         memcpy(out_key->data, in_key->data, in_key->size);
523         char *k = (char *)out_key->data;
524         k[out_key->size] = 0;
525
526         // the data is a pair of strings
527         out_data->size = in_data->size;
528         out_data->data = realloc(out_data->data, out_data->size + 1);
529         memcpy(out_data->data, in_data->data, in_data->size);
530         char *d = (char *)out_data->data;
531         d[out_data->size] = 0;
532
533         // please excuse my friend, he isn't null terminated
534         // printf("\033[32m\033[1mConfig entry: %s -> %s\033[0m\n", (char *)out_key->data, (char *)out_data->data+strlen(out_data->data)+1);
535 }
536
537
538 // For obsolete databases, zero all the output
539 void zero_function(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) {
540         out_key->size = 0;
541         out_data->size = 0;
542 }
543
544
545 void (*convert_functions[])(int which_cdb, DBT *in_key, DBT *in_data, DBT *out_key, DBT *out_data) = {
546         convert_msgmain,        // CDB_MSGMAIN
547         convert_users,          // CDB_USERS
548         convert_rooms,          // CDB_ROOMS
549         convert_floors,         // CDB_FLOORTAB
550         convert_msglists,       // CDB_MSGLISTS
551         convert_visits,         // CDB_VISIT
552         convert_dir,            // CDB_DIRECTORY
553         convert_usetable,       // CDB_USETABLE
554         convert_bigmsgs,        // CDB_BIGMSGS
555         convert_msglists,       // CDB_FULLTEXT
556         convert_euidindex,      // CDB_EUIDINDEX
557         convert_usersbynumber,  // CDB_USERSBYNUMBER
558         zero_function,          // CDB_UNUSED1 (obsolete)
559         convert_config          // CDB_CONFIG
560 };
561
562
563 void convert_table(int which_cdb, DB_ENV *src_dbenv, DB_ENV *dst_dbenv) {
564         int ret;
565         int compressed;
566         char dbfilename[32];
567         uLongf destLen = 0;
568
569         // shamelessly swiped from https://docs.oracle.com/database/bdb181/html/programmer_reference/am_cursor.html
570         DB *src_dbp, *dst_dbp;
571         DBC *src_dbcp;
572         DBT in_key, in_data, out_key, out_data, uncomp_data;
573         int num_good_rows = 0;
574         int num_bad_rows = 0;
575
576         snprintf(dbfilename, sizeof dbfilename, "cdb.%02x", which_cdb);
577
578         // create a database handle for the source table
579         ret = db_create(&src_dbp, src_dbenv, 0);
580         if (ret) {
581                 printf("db: db_create: %s\n", db_strerror(ret));
582                 printf("db: exit code %d\n", ret);
583                 exit(CTDLEXIT_DB);
584         }
585
586         // open the file containing the source table
587         // printf("\033[33m\033[1mdb: opening source %s\033[0m\n", dbfilename);
588         ret = src_dbp->open(src_dbp, NULL, dbfilename, NULL, DB_BTREE, 0, 0600);
589         if (ret) {
590                 printf("db: db_open: %s\n", db_strerror(ret));
591                 printf("db: exit code %d\n", ret);
592                 exit(CTDLEXIT_DB);
593         }
594
595         // create a database handle for the destination table
596         ret = db_create(&dst_dbp, dst_dbenv, 0);
597         if (ret) {
598                 printf("db: db_create: %s\n", db_strerror(ret));
599                 printf("db: exit code %d\n", ret);
600                 exit(CTDLEXIT_DB);
601         }
602
603         // open the file containing the destination table
604         // printf("\033[33m\033[1mdb: opening destination %s\033[0m\n", dbfilename);
605         ret = dst_dbp->open(dst_dbp, NULL, dbfilename, NULL, DB_BTREE, (DB_CREATE | DB_TRUNCATE), 0600);
606         if (ret) {
607                 printf("db: db_open: %s\n", db_strerror(ret));
608                 printf("db: exit code %d\n", ret);
609                 exit(CTDLEXIT_DB);
610         }
611
612         // Acquire a cursor to read the source table
613         // printf("\033[33m\033[1mdb: acquiring cursor\033[0m\n");
614         if ((ret = src_dbp->cursor(src_dbp, NULL, &src_dbcp, 0)) != 0) {
615                 printf("db: db_cursor: %s\n", db_strerror(ret));
616                 printf("db: exit code %d\n", ret);
617                 exit(CTDLEXIT_DB);
618         }
619
620         // Zero out these database keys
621         memset(&in_key,         0, sizeof(DBT));        // input
622         memset(&in_data,        0, sizeof(DBT));
623         memset(&out_key,        0, sizeof(DBT));        // output
624         memset(&out_data,       0, sizeof(DBT));
625         memset(&uncomp_data,    0, sizeof(DBT));        // decompressed input (the key doesn't change)
626
627         // Walk through the database, calling convert functions as we go and clearing buffers before each call.
628         while (out_key.size = 0, out_data.size = 0, (ret = src_dbcp->get(src_dbcp, &in_key, &in_data, DB_NEXT)) == 0) {
629         
630                 // If either the key or data are zero length, skip this record
631                 if ((in_key.size == 0) || (in_data.size == 0)) {
632                         ++num_bad_rows;
633                         // printf("\033[31minput keylen=%d , datalen=%d , skipping record\033[0m\n", in_key.size, in_data.size);
634                 }
635
636                 else {  // Both key and data are >0 length so we're good to go
637
638                         // Do we need to decompress?
639                         static int32_t magic = COMPRESS_MAGIC;
640                         compressed = 0;
641                         if ( (in_data.size >= sizeof(struct CtdlCompressHeader_32)) && (!memcmp(in_data.data, &magic, sizeof(magic))) ) {
642         
643                                 // yes, we need to decompress
644                                 compressed = 1;
645                                 struct CtdlCompressHeader_32 comp32;
646                                 memcpy(&comp32, in_data.data, sizeof(struct CtdlCompressHeader_32));
647                                 uncomp_data.size = comp32.uncompressed_len;
648                                 uncomp_data.data = realloc(uncomp_data.data, uncomp_data.size);
649                                 destLen = (uLongf)comp32.uncompressed_len;
650         
651                                 ret = uncompress((Bytef *)uncomp_data.data, (uLongf *)&destLen, (const Bytef *)in_data.data+sizeof(struct CtdlCompressHeader_32), (uLong)comp32.compressed_len);
652                                 if (ret != Z_OK) {
653                                         printf("db: uncompress() error %d\n", ret);
654                                         exit(CTDLEXIT_DB);
655                                 }
656                                 // printf("DB: %02x ,  in_keylen: %-3d ,  in_datalen: %-10d , dataptr: %012lx \033[31m(decompressed)\033[0m\n", which_cdb, (int)in_key.size, comp32.uncompressed_len, (long unsigned int)uncomp_data.data);
657                         }
658                         else {
659                                 // printf("DB: %02x ,  in_keylen: %-3d ,  in_datalen: %-10d , dataptr: %012lx\n", which_cdb, (int)in_key.size, (int)in_data.size, (long unsigned int)in_data.data);
660                         }
661         
662                         // Call the convert function registered to this table
663                         convert_functions[which_cdb](which_cdb, &in_key, (compressed ? &uncomp_data : &in_data), &out_key, &out_data);
664
665                         // write the converted record to the target database
666                         if (out_key.size > 0) {
667         
668                                 // printf("DB: %02x , out_keylen: %-3d , out_datalen: %-10d , dataptr: %012lx\n", which_cdb, (int)out_key.size, (int)out_data.size, (long unsigned int)out_data.data);
669                                 ret = dst_dbp->put(dst_dbp, NULL, &out_key, &out_data, 0);
670         
671                                 if (ret) {
672                                         printf("db: cdb_put(%d): %s", which_cdb, db_strerror(ret));
673                                         exit(CTDLEXIT_DB);
674                                 }
675                                 ++num_good_rows;
676                         }
677                         else {
678                                 ++num_bad_rows;
679                                 // printf("\033[31moutput keylen=%d , skipping record\033[0m\n", out_key.size);
680                         }
681
682                 // Knowing the total number of rows isn't critical to the program.  It's just for the user to know.
683                 printf("\033[33m%5d\033[37m  \033[32m%9d\033[37m  \033[31m%8d\033[0m\r", which_cdb, num_good_rows, num_bad_rows);
684                 fflush(stdout);
685                 }
686         }
687
688         if (ret != DB_NOTFOUND) {
689                 printf("db: db_get: %s\n", db_strerror(ret));
690                 printf("db: exit code %d\n", ret);
691                 exit(CTDLEXIT_DB);
692         }
693
694         printf("\n");
695
696         // free any leftover out_data pointers
697         free(out_key.data);
698         free(out_data.data);
699         free(uncomp_data.data);
700
701         // ...and close the database (table)
702         // printf("\033[33m\033[1mdb: closing source %02x\033[0m\n", which_cdb);
703         ret = src_dbp->close(src_dbp, 0);
704         if (ret) {
705                 printf("db: db_close: %s\n", db_strerror(ret));
706         }
707
708         // printf("\033[33m\033[1mdb: closing destination %02x\033[0m\n", which_cdb);
709         ret = dst_dbp->close(dst_dbp, 0);
710         if (ret) {
711                 printf("db: db_close: %s\n", db_strerror(ret));
712         }
713
714         // printf("\n");
715
716 }
717
718
719 int main(int argc, char **argv) {
720         char *src_dir = NULL;
721         char *dst_dir = NULL;
722         int confirmed = 0;
723         static DB_ENV *src_dbenv;               // Source DB environment (global)
724         static DB_ENV *dst_dbenv;               // Source DB environment (global)
725
726         // Check to make sure we're running on the target 64-bit system
727         if (sizeof(void *) != 8) {
728                 fprintf(stderr, "%s: this is a %ld-bit system.\n", argv[0], sizeof(void *)*8);
729                 fprintf(stderr, "%s: you must run this on a 64-bit system, onto which a 32-bit database has been copied.\n", argv[0]);
730                 exit(1);
731         }
732
733         // Parse command line
734         int a;
735         while ((a = getopt(argc, argv, "h:d:y")) != EOF) {
736                 switch (a) {
737                 case 'h':
738                         src_dir = optarg;
739                         break;
740                 case 'd':
741                         dst_dir = optarg;
742                         break;
743                 case 'y':
744                         confirmed = 1;
745                         break;
746                 default:
747                         fprintf(stderr, "%s: usage: %s -s source_dir -d dest_dir\n", argv[0], argv[0]);
748                         exit(2);
749                 }
750         }
751
752         // Warn the user
753         printf("------------------------------------------------------------------------\n");
754         printf("ctdl3264 converts a Citadel database written on a 32-bit system to one  \n");
755         printf("that can be run on a 64-bit system.  It is intended to be run OFFLINE.  \n");
756         printf("Neither the source nor the target data directories should be mounted by \n");
757         printf("a running Citadel server.  We \033[1mguarantee\033[0m data corruption if you do not   \n");
758         printf("observe this warning!  The source [-s] directory should contain a copy  \n");
759         printf("of the database from your 32-bit system.  The destination [-d] directory\n");
760         printf("should be empty and will receive your 64-bit database.                  \n");
761         printf("------------------------------------------------------------------------\n");
762         printf("     Source 32-bit directory: %s\n", src_dir);
763         printf("Destination 64-bit directory: %s\n", dst_dir);
764         printf("------------------------------------------------------------------------\n");
765
766         if (confirmed == 1) {
767                 printf("You have specified the [-y] flag, so processing will continue.\n");
768         }
769         else {
770                 printf("Please read [ https://www.citadel.org/ctdl3264.html ] to learn how to proceed.\n");
771                 exit(0);
772         }
773
774
775         src_dbenv = open_dbenv(src_dir);
776         dst_dbenv = open_dbenv(dst_dir);
777         printf("table  good rows  bad rows\n");
778         printf("-----  ---------  --------\n");
779         for (int i = 0; i < MAXCDB; ++i) {
780                 convert_table(i, src_dbenv, dst_dbenv);
781         }
782         close_dbenv(src_dbenv);
783         close_dbenv(dst_dbenv);
784
785         exit(0);
786 }