0bb24e25dc1865930aa7749d67b99fb7bb2aa325
[citadel.git] / citadel / utils / ctdlload.c
1 // Load (restore) the Citadel database from a flat file created by ctdldump
2 //
3 // Copyright (c) 2023-2024 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 <syslog.h>
23 #include <libcitadel.h>
24 #include <zlib.h>
25 #include "../server/sysdep.h"
26 #include "../server/citadel_defs.h"
27 #include "../server/server.h"
28 #include "../server/makeuserkey.h"
29 #include "../server/citadel_dirs.h"
30 #include "../server/database.h"
31
32 uid_t ctdluid = 0;
33
34 // Wrapper for realloc() that crashes and burns if the call fails.
35 void *reallok(void *ptr, size_t size) {
36         void *p = realloc(ptr, size);
37         if (!p) {
38                 fprintf(stderr, "realloc() failed to resize %p to %ld bytes, error: %m\n", ptr, size);
39                 abort();
40         }
41         return p;
42 }
43
44
45 // Convert a "msgtext" record to a message on disk.   NOT THREADSAFE
46 // This also works for "bigmsg" records.
47 int import_msgtext(char *line, struct cdbkeyval *kv) {
48
49         static char *b64_decoded_msg = NULL;
50         static size_t b64_decoded_alloc = 0;
51         long msgnum;
52         char *token;
53
54         token = strtok(line, "|");
55         msgnum = atol(strtok(NULL, "|"));
56         token = strtok(NULL, "|");
57
58         // The record key will be the message number
59         kv->key.len = sizeof(long);
60         kv->key.ptr = reallok(kv->key.ptr, kv->key.len);
61         memcpy(kv->key.ptr, &msgnum, kv->key.len);
62
63         // The record data will be the decoded message text.
64         // We are allocating more memory than we need, but BDB will only write the number of bytes we tell it to.
65         kv->val.ptr = (char *) reallok(kv->val.ptr, strlen(token));
66         kv->val.len = CtdlDecodeBase64(kv->val.ptr, token, strlen(token));
67         return(1);
68 }
69
70
71 // Convert a "msgmeta" record to a message metadata record on disk.  NOT THREADSAFE
72 int import_msgmeta(char *line, struct cdbkeyval *kv) {
73         char *token;
74         struct MetaData *m;
75
76         m = malloc(sizeof(struct MetaData));
77         if (!m) {
78                 fprintf(stderr, "import_msgmeta: malloc: %s\n", strerror(errno));
79                 return(0);
80         }
81         memset(m, 0, sizeof(struct MetaData));
82         char *p = line;
83
84         for (int i=0; (token = strsep(&p, "|")); ++i) {
85                 switch(i) {
86                         case 1:
87                                 m->meta_msgnum = atol(token);
88                                 break;
89                         case 2:
90                                 m->meta_refcount = atoi(token);
91                                 break;
92                         case 3:
93                                 strncpy(m->meta_content_type, token, sizeof(m->meta_content_type));
94                                 break;
95                         case 4:
96                                 m->meta_rfc822_length = atol(token);
97                                 break;
98                 }
99         }
100
101         // metadata records are stored in the CDB_MSGMAIN table,
102         // but with the index being the *negative* of the message number.
103         long index = 0 - m->meta_msgnum;
104         kv->key.len = sizeof(long);
105         kv->key.ptr = reallok(NULL, kv->key.len);
106         memcpy(kv->key.ptr, &index, kv->key.len);
107
108         // data
109         kv->val.len = sizeof(struct MetaData);
110         kv->val.ptr = (char *) m;                               // out_data owns this memory now
111
112         return(1);
113 }
114
115
116 // Convert a "user" record to a record on disk.  (Source string is unusable after this function is called.)
117 int import_user(char *line, struct cdbkeyval *kv) {
118         char userkey[USERNAME_SIZE];
119         char *token;
120         struct ctdluser *u;
121
122         u = malloc(sizeof(struct ctdluser));
123         if (!u) {
124                 fprintf(stderr, "malloc failed\n");
125                 return(0);
126         }
127         memset(u, 0, sizeof(struct ctdluser));
128         char *p = line;
129
130         for (int i=0; (token = strsep(&p, "|")); ++i) {
131                 switch(i) {
132                         case 1:
133                                 u->version = atoi(token);
134                                 break;
135                         case 2:
136                                 u->uid = atoi(token);
137                                 break;
138                         case 3:
139                                 safestrncpy(u->password, token, sizeof(u->password));
140                                 break;
141                         case 4:
142                                 u->flags = atoi(token);
143                                 break;
144                         case 5:
145                                 u->axlevel = atoi(token);
146                                 break;
147                         case 6:
148                                 u->usernum = atol(token);
149                                 break;
150                         case 7:
151                                 u->lastcall = atol(token);
152                                 break;
153                         case 8:
154                                 u->USuserpurge = atoi(token);
155                                 break;
156                         case 9:
157                                 safestrncpy(u->fullname, token, sizeof(u->fullname));
158                                 break;
159                         case 10:
160                                 u->msgnum_bio = atol(token);
161                                 break;
162                         case 11:
163                                 u->msgnum_pic = atol(token);
164                                 break;
165                         case 12:
166                                 CtdlDecodeBase64(token, token, strlen(token));                  // Decode in place
167                                 safestrncpy(u->emailaddrs, token, sizeof(u->emailaddrs));
168                                 break;
169                         case 13:
170                                 u->msgnum_inboxrules = atol(token);
171                                 break;
172                         case 14:
173                                 u->lastproc_inboxrules = atol(token);
174                                 break;
175                 }
176         }
177
178         // reject any user records without names
179         if (strlen(u->fullname) == 0) {
180                 free(u);
181                 return(0);
182         }
183         
184         // ok, good to go
185         makeuserkey(userkey, u->fullname);
186         kv->key.len = strlen(userkey);
187         kv->key.ptr = strdup(userkey);
188         kv->val.len = sizeof(struct ctdluser);
189         kv->val.ptr = (char *) u;
190         return(1);
191 }
192
193
194 // Convert a "room" record to a record on disk.  (Source string is unusable after this function is called.)
195 int import_room(char *line, struct cdbkeyval *kv) {
196         char *token;
197         struct ctdlroom *r;
198
199         r = malloc(sizeof(struct ctdlroom));
200         if (!r) {
201                 fprintf(stderr, "import_room: malloc: %s\n", strerror(errno));
202                 return(0);
203         }
204         memset(r, 0, sizeof(struct ctdlroom));
205         char *p = line;
206
207         for (int i=0; (token = strsep(&p, "|")); ++i) {
208                 switch(i) {
209                         case 1:
210                                 strncpy(r->QRname, token, sizeof(r->QRname));
211                                 break;
212                         case 2:
213                                 strncpy(r->QRpasswd, token, sizeof (r->QRpasswd));
214                                 break;
215                         case 3:
216                                 r->QRroomaide = atol(token);
217                                 break;
218                         case 4:
219                                 r->QRhighest = atol(token);
220                                 break;
221                         case 5:
222                                 r->QRgen = atol(token);
223                                 break;
224                         case 6:
225                                 r->QRflags = atoi(token);
226                                 break;
227                         case 7:
228                                 strncpy(r->QRdirname, token, sizeof(r->QRdirname));
229                                 break;
230                         case 8:
231                                 r->msgnum_info = atol(token);
232                                 break;
233                         case 9:
234                                 r->QRfloor = atoi(token);
235                                 break;
236                         case 10:
237                                 r->QRmtime = atol(token);
238                                 break;
239                         case 11:
240                                 r->QRep.expire_mode = atoi(token);
241                                 break;
242                         case 12:
243                                 r->QRep.expire_value = atoi(token);
244                                 break;
245                         case 13:
246                                 r->QRnumber = atol(token);
247                                 break;
248                         case 14:
249                                 r->QRorder = atoi(token);
250                                 break;
251                         case 15:
252                                 r->QRflags2 = atoi(token);
253                                 break;
254                         case 16:
255                                 r->QRdefaultview = atoi(token);
256                                 break;
257                         case 17:
258                                 r->msgnum_pic = atol(token);
259                                 break;
260                 }
261         }
262
263         // The key is the room name in all lower case
264         kv->key.len = strlen(r->QRname);
265         kv->key.ptr = strdup(r->QRname);
266         char *k = (char *)kv->key.ptr;
267         for (int i=0; i<=kv->key.len; ++i) {
268                 k[i] = tolower(k[i]);
269         }
270
271         kv->val.len = sizeof(struct ctdlroom);
272         kv->val.ptr = (char *) r;
273         return(1);
274 }
275
276
277 // Convert a floor record to a record on disk.
278 int import_floor(char *line, struct cdbkeyval *kv) {
279         char *token;
280         struct floor *f;
281         int floor_num;
282
283         f = malloc(sizeof(struct floor));
284         if (!f) {
285                 fprintf(stderr, "import_floor: malloc: %s\n", strerror(errno));
286                 return(0);
287         }
288         memset(f, 0, sizeof(struct floor));
289         char *p = line;
290
291         for (int i=0; (token = strsep(&p, "|")); ++i) {
292                 switch(i) {
293                         case 1:
294                                 floor_num = atoi(token);
295                                 break;
296                         case 2:
297                                 f->f_flags = atoi(token);
298                                 break;
299                         case 3:
300                                 strncpy(f->f_name, token, sizeof(f->f_name));
301                                 break;
302                         case 4:
303                                 f->f_ref_count = atoi(token);
304                                 break;
305                         case 5:
306                                 f->f_ep.expire_mode = atoi(token);
307                                 break;
308                         case 6:
309                                 f->f_ep.expire_value = atoi(token);
310                                 break;
311                 }
312         }
313
314         kv->key.len = sizeof(int);
315         kv->key.ptr = malloc(kv->key.len);
316         memcpy(kv->key.ptr, &floor_num, kv->key.len);
317
318         kv->val.len = sizeof(struct floor);
319         kv->val.ptr = (char *) f;
320         return(1);
321 }
322
323
324 // Import a msglist record
325 // msglist|26|32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51|
326 int import_msglist(char *line, struct cdbkeyval *kv) {
327         long roomnum;
328         char *token, *mtoken;
329         char *p = line;
330         char *q = NULL;
331         int num_msgs = 0;
332         long *msglist = NULL;
333
334         for (int i=0; (token = strsep(&p, "|")); ++i) {
335                 switch(i) {
336                         case 1:
337                                 roomnum = atol(token);
338                                 break;
339                         case 2:
340                                 q = token;
341                                 for (int j=0; (mtoken = strsep(&q, ",")); ++j) {
342                                         msglist = realloc(msglist, (num_msgs+1) * sizeof(long));
343                                         msglist[num_msgs++] = atol(mtoken);
344                                 }
345                                 break;
346                 }
347         }
348
349         kv->key.len = sizeof(long);
350         kv->key.ptr = malloc(kv->key.len);
351         memcpy(kv->key.ptr, &roomnum, kv->key.len);
352
353         kv->val.len = num_msgs * sizeof(long);
354         kv->val.ptr = (char *) msglist;
355
356         return(1);
357 }
358
359
360 // Convert a "visit" record to a record on disk.
361 int import_visit(char *line, struct cdbkeyval *kv) {
362         char *token;
363         struct visit *v;
364
365         v = malloc(sizeof(struct visit));
366         if (!v) {
367                 fprintf(stderr, "import_visit: malloc: %s\n", strerror(errno));
368                 return(0);
369         }
370         memset(v, 0, sizeof(struct visit));
371         char *p = line;
372
373         for (int i=0; (token = strsep(&p, "|")); ++i) {
374                 switch(i) {
375                         case 1:
376                                 v->v_roomnum = atol(token);
377                                 break;
378                         case 2:
379                                 v->v_roomgen = atol(token);
380                                 break;
381                         case 3:
382                                 v->v_usernum = atol(token);
383                                 break;
384                         case 4:
385                                 v->v_lastseen = atoi(token);
386                                 break;
387                         case 5:
388                                 v->v_flags = atoi(token);
389                                 break;
390                         case 6:
391                                 strncpy(v->v_seen, token, sizeof(v->v_seen));
392                                 break;
393                         case 7:
394                                 strncpy(v->v_answered, token, sizeof(v->v_answered));
395                                 break;
396                         case 8:
397                                 v->v_view = atoi(token);
398                                 break;
399                 }
400         }
401
402         // The key is the same as the first three data members (3 x long)
403         kv->key.len = sizeof(long) * 3;
404         kv->key.ptr = reallok(NULL, kv->key.len);
405         memcpy(kv->key.ptr, v, kv->key.len);
406
407         kv->val.len = sizeof(struct visit);
408         kv->val.ptr = (char *) v;
409         return(1);
410 }
411
412
413 // Convert a "dir" record to a record on disk.
414 int import_dir(char *line, struct cdbkeyval *kv) {
415         char dirkey[SIZ];
416         char username[USERNAME_SIZE];
417         char *token;
418
419         char *p = line;
420         for (int i=0; (token = strsep(&p, "|")); ++i) {
421                 switch(i) {
422                         case 1:
423                                 strncpy(dirkey, token, sizeof(dirkey));
424                                 break;
425                         case 2:
426                                 strncpy(username, token, sizeof(username));
427                                 break;
428                 }
429         }
430
431         kv->key.len = strlen(dirkey);
432         kv->key.ptr = reallok(NULL, kv->key.len);
433         memcpy(kv->key.ptr, dirkey, strlen(dirkey));
434
435         kv->val.len = strlen(username) + 1;
436         kv->val.ptr = strdup(username);
437
438         return(1);
439 }
440
441
442 // Convert a "usetable" record to a record on disk.
443 int import_usetable(char *line, struct cdbkeyval *kv) {
444         char *token;
445         struct UseTable *u;
446
447         u = malloc(sizeof(struct UseTable));
448         if (!u) {
449                 fprintf(stderr, "import_usetable: malloc: %s\n", strerror(errno));
450                 return(0);
451         }
452         memset(u, 0, sizeof(struct UseTable));
453         char *p = line;
454
455         for (int i=0; (token = strsep(&p, "|")); ++i) {
456                 switch(i) {
457                         case 1:
458                                 u->hash = atoi(token);
459                                 break;
460                         case 2:
461                                 u->timestamp = atol(token);
462                                 break;
463                 }
464         }
465
466         // the key is just an int (the hash)
467         kv->key.len = sizeof(int);
468         kv->key.ptr = reallok(NULL, kv->key.len);
469         memcpy(kv->key.ptr, &u->hash, kv->key.len);
470
471         kv->val.len = sizeof(struct UseTable);
472         kv->val.ptr = (char *) u;
473         return(1);
474 }
475
476
477 // Import a full text search index record.
478 // It's just like a msglists record: a key and a list of message numbers, but the key is "int" instead of "long"
479 int import_fulltext(char *line, struct cdbkeyval *kv) {
480         int indexnum;
481         char *token, *mtoken;
482         char *p = line;
483         char *q = NULL;
484         int num_msgs = 0;
485         long *msglist = NULL;
486
487         for (int i=0; (token = strsep(&p, "|")); ++i) {
488                 switch(i) {
489                         case 1:
490                                 indexnum = atoi(token);
491                                 break;
492                         case 2:
493                                 q = token;
494                                 for (int j=0; (mtoken = strsep(&q, ",")); ++j) {
495                                         msglist = realloc(msglist, (num_msgs+1) * sizeof(long));
496                                         msglist[num_msgs++] = atol(mtoken);
497                                 }
498                                 break;
499                 }
500         }
501
502         kv->key.len = sizeof(int);
503         kv->key.ptr = malloc(kv->key.len);
504         memcpy(kv->key.ptr, &indexnum, kv->key.len);
505
506         kv->val.len = num_msgs * sizeof(long);
507         kv->val.ptr = (char *) msglist;
508
509         return(1);
510 }
511
512
513 // Import an EUID Index record
514 // euidindex|msgnum|roomnum|euid|
515 int import_euidindex(char *line, struct cdbkeyval *kv) {
516         char euid[SIZ];
517         long msgnum;
518         long roomnum;
519         char *token;
520
521         char *p = line;
522         for (int i=0; (token = strsep(&p, "|")); ++i) {
523                 switch(i) {
524                         case 1:
525                                 msgnum = atol(token);
526                                 break;
527                         case 2:
528                                 roomnum = atol(token);
529                                 break;
530                         case 3:
531                                 strncpy(euid, token, sizeof(euid));
532                                 break;
533                 }
534         }
535
536         // The structure of an euidindex record *key* is:
537         // |----room_number----|----------EUID-------------|
538         //    (sizeof long)       (actual length of euid)
539         kv->key.len = sizeof(long) + strlen(euid) + 1;
540         kv->key.ptr = reallok(NULL, kv->key.len);
541         memcpy(kv->key.ptr, &roomnum, sizeof(long));
542         strcpy(kv->key.ptr + sizeof(long), euid);
543
544         // The structure of an euidindex record *value* is:
545         // |-----msg_number----|----room_number----|----------EUID-------------|
546         //    (sizeof long)       (sizeof long)       (actual length of euid)
547         kv->val.len = sizeof(long) + sizeof(long) + strlen(euid) + 1;
548         kv->val.ptr = (char *) reallok(NULL, kv->val.len);
549         memcpy(kv->val.ptr, &msgnum, sizeof(long));
550         memcpy(kv->val.ptr + sizeof(long), &roomnum, sizeof(long));
551         strcpy(kv->val.ptr + sizeof(long) + sizeof(long), euid);
552
553         return(1);
554 }
555
556
557 // Import a "users by number" (secondary index) record
558 // The key is a "long"
559 int import_usersbynumber(char *line, struct cdbkeyval *kv) {
560         char *token;
561         long usernum;
562
563         char *p = line;
564         for (int i=0; (token = strsep(&p, "|")); ++i) {
565                 switch(i) {
566                         case 1:
567                                 usernum = atol(token);
568                                 break;
569                         case 2:
570                                 kv->key.len = sizeof(long);
571                                 kv->key.ptr = reallok(NULL, sizeof(long));
572                                 memcpy(kv->key.ptr, &usernum, kv->key.len);
573                                 kv->val.ptr = (char *) strdup(token);
574                                 kv->val.len = strlen(kv->val.ptr) + 1;
575                                 return(1);
576                 }
577         }
578
579         return(0);              // should never get here unless it's a bad record
580 }
581
582
583 // Import a config record
584 // The key is the config key
585 // The data is the config key, a null, the value, and another null
586 int import_config(char *line, struct cdbkeyval *kv) {
587         char *token;
588         char *p = line;
589         char *k, *v;
590
591         for (int i=0; (token = strsep(&p, "|")); ++i) {
592                 switch(i) {
593                         case 1:
594                                 k = token;
595                                 kv->key.len = strlen(token);
596                                 kv->key.ptr = strdup(token);
597                                 break;
598                         case 2:
599                                 v = token;
600                                 kv->val.len = strlen(k) + strlen(v) + 2;
601                                 kv->val.ptr = (char *) reallok(NULL, kv->val.len);
602                                 strcpy(kv->val.ptr, k);
603                                 strcpy(kv->val.ptr + strlen(k) + 1, v);
604                                 break;
605                 }
606         }
607
608         return(1);
609 }
610
611
612 // Ingest one line of dump data.  NOT REENTRANT
613 void ingest_one(char *line, struct cdbkeyval *kv) {
614
615         static int good_rows = 0;
616         static int bad_rows = 0;
617         static int current_cdb = -1 ;
618         char record_type[32];
619         int row_was_good;
620
621         // Clear out our record buffer
622         memset(kv, 0, sizeof(struct cdbkeyval));
623         row_was_good = 0;
624
625         // Identify the record type we are currently working with,
626         // then call the correct conversion function to load up our record buffer.
627         extract_token(record_type, line, 0, '|', sizeof record_type);
628         if (!strcasecmp(record_type, "msgtext")) {
629                 current_cdb = CDB_MSGMAIN;
630                 row_was_good = import_msgtext(line, kv);
631         }
632         else if (!strcasecmp(record_type, "msgmeta")) {
633                 current_cdb = CDB_MSGMAIN;
634                 row_was_good = import_msgmeta(line, kv);
635         }
636         else if (!strcasecmp(record_type, "user")) {
637                 current_cdb = CDB_USERS;
638                 row_was_good = import_user(line, kv);
639         }
640         else if (!strcasecmp(record_type, "room")) {
641                 current_cdb = CDB_ROOMS;
642                 row_was_good = import_room(line, kv);
643         }
644         else if (!strcasecmp(record_type, "floor")) {
645                 current_cdb = CDB_FLOORTAB;
646                 row_was_good = import_floor(line, kv);
647         }
648         else if (!strcasecmp(record_type, "msglist")) {
649                 current_cdb = CDB_MSGLISTS;
650                 row_was_good = import_msglist(line, kv);
651         }
652         else if (!strcasecmp(record_type, "visit")) {
653                 current_cdb = CDB_VISIT;
654                 row_was_good = import_visit(line, kv);
655         }
656         else if (!strcasecmp(record_type, "dir")) {
657                 current_cdb = CDB_DIRECTORY;
658                 row_was_good = import_dir(line, kv);
659         }
660         else if (!strcasecmp(record_type, "use")) {
661                 current_cdb = CDB_USETABLE;
662                 row_was_good = import_usetable(line, kv);
663         }
664         else if (!strcasecmp(record_type, "bigmsg")) {
665                 current_cdb = CDB_BIGMSGS;
666                 row_was_good = import_msgtext(line, kv);
667         }
668         else if (!strcasecmp(record_type, "fulltext")) {
669                 current_cdb = CDB_FULLTEXT;
670                 row_was_good = import_fulltext(line, kv);
671         }
672         else if (!strcasecmp(record_type, "euidindex")) {
673                 current_cdb = CDB_EUIDINDEX;
674                 row_was_good = import_euidindex(line, kv);
675         }
676         else if (!strcasecmp(record_type, "usersbynumber")) {
677                 current_cdb = CDB_USERSBYNUMBER;
678                 row_was_good = import_usersbynumber(line, kv);
679         }
680         else if (!strcasecmp(record_type, "config")) {
681                 current_cdb = CDB_CONFIG;
682                 row_was_good = import_config(line, kv);
683         }
684         else {
685                 current_cdb = -1 ;
686         }
687
688         // If the conversion function was successful, write the record to the database.
689         if ( (row_was_good) && (current_cdb >= 0) ) {
690                 cdb_store(current_cdb, kv->key.ptr, kv->key.len, kv->val.ptr, kv->val.len);
691                 ++good_rows;
692         }
693         else {
694                 ++bad_rows;
695         }
696
697         fprintf(stderr, "\r   \033[32m%9d\033[0m / \033[31m%8d\033[0m ", good_rows, bad_rows);
698         fflush(stderr);
699
700 }
701
702
703 // This is the loop that loads the dump data.  NOT REENTRANT
704 void ingest(void) {
705         static size_t line_alloc = 1;
706         static char *line;
707         static size_t line_len = 0;
708         int ch;
709         int begin_found = 0;
710         int end_found = 0;
711         struct cdbkeyval kv;
712
713         memset(&kv, 0, sizeof(struct cdbkeyval));
714
715         line = reallok(NULL, line_alloc);
716
717         do {
718                 line_len = 0;
719
720                 while (ch = getc(stdin), ((ch != '\n') && (ch > 0))) {
721                         if ((line_len+2) > line_alloc) {
722                                 line_alloc *= 2;
723                                 line = reallok(line, line_alloc);
724                         }
725                         line[line_len++] = ch;
726                         line[line_len] = 0;
727                 }
728         
729                 if (ch <= 0) {
730                         return;
731                 }
732
733                 if (line_len > 0) {
734                         if ( (begin_found) && (!end_found) ) {
735                                 ingest_one(line, &kv);
736                         }
737                         if (!strncasecmp(line, HKEY("begin|"))) {
738                                 begin_found = 1;
739                                 fprintf(stderr, "   good rows / bad rows:\n");
740                         }
741                         if (!strncasecmp(line, HKEY("end|"))) {
742                                 fprintf(stderr, "\n");
743                                 end_found = 1;
744                         }
745                 }
746
747         } while (ch >= 0);
748
749         if (!begin_found) {
750                 fprintf(stderr, "\033[31mWARNING: \"begin\" line was not found in the loaded data.\033[0m\n");
751         }
752         if (!end_found) {
753                 fprintf(stderr, "\033[31mWARNING: \"end\" line was not found in the loaded data.\033[0m\n");
754         }
755 }
756
757
758 // Main entry point
759 int main(int argc, char **argv) {
760         int confirmed = 0;
761         char *ctdldir = CTDLDIR;
762
763         // display the greeting
764         fprintf(stderr,
765                 "\033[44m\033[1m╔════════════════════════════════════════════════════════════════════════╗\033[0m\n"
766                 "\033[44m\033[1m║ DB Load utility for Citadel                                            ║\033[0m\n"
767                 "\033[44m\033[1m║ Copyright (c) 2023-2024 by citadel.org et al.                          ║\033[0m\n"
768                 "\033[44m\033[1m║ This program is open source software.  Use, duplication, or disclosure ║\033[0m\n"
769                 "\033[44m\033[1m║ is subject to the terms of the GNU General Public license v3.          ║\033[0m\n"
770                 "\033[44m\033[1m╚════════════════════════════════════════════════════════════════════════╝\033[0m\n"
771         );
772
773         // Parse command line
774         int a;
775         while ((a = getopt(argc, argv, "h:y")) != EOF) {
776                 switch (a) {
777                 case 'h':
778                         ctdldir = optarg;
779                         break;
780                 case 'y':
781                         confirmed = 1;
782                         break;
783                 default:
784                         fprintf(stderr, "%s: usage: %s -h citadel_dir [<dumpfile]\n", argv[0], argv[0]);
785                         exit(2);
786                 }
787         }
788
789         if (confirmed == 1) {
790                 fprintf(stderr, "ctdlload: You have specified the [-y] flag, so processing will continue.\n");
791         }
792         else {
793                 fprintf(stderr, "ctdlload: usage: ctdlload -y -h[citadel_dir] <[dump_file]\n");
794                 fprintf(stderr, "          -y : yes, I know this program can do damage and I want to run it anyway.\n");
795                 fprintf(stderr, "          -h : [citadel_dir] is your server directory, usually /usr/local/citadel\n");
796                 fprintf(stderr, "          Please read [ https://www.citadel.org/dump-and-load.html ] to learn how to proceed.\n");
797                 exit(1);
798         }
799
800         if (chdir(ctdldir) != 0) {
801                 fprintf(stderr, "ctdlload: unable to change directory to [%s]: %m", ctdldir);
802                 exit(2);
803         }
804
805         // backend modules use syslog -- redirect to stderr
806         openlog("ctdlload", LOG_PERROR , LOG_DAEMON);
807
808         // Remove any database that is already in the target directory (yes, delete it, be careful)
809         system("rm -fv ./data/*");
810
811         // initialize the database backend
812         cdb_init_backends();
813         cdb_open_databases();
814
815         // Load rows from the input source
816         ingest();
817
818         // close databases
819         cdb_close_databases();
820
821         fprintf(stderr, "ctdlload: \033[32m\033[1mfinished\033[0m\n");
822         exit(0);
823 }