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