]> code.citadel.org Git - citadel.git/blob - citadel/utils/ctdlload.c
ctdldump/ctdlload: fixed on Raspberry Pi, reintroduced to build
[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/makeuserkey.h"
29 #include "../server/citadel_dirs.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                 abort();
38         }
39         return p;
40 }
41
42
43 // Open a database environment
44 DB_ENV *open_dbenv(char *dirname) {
45
46         DB_ENV *dbenv = NULL;
47
48         int ret;
49         int i;
50         u_int32_t flags = 0;
51         int dbversion_major, dbversion_minor, dbversion_patch;
52         db_version(&dbversion_major, &dbversion_minor, &dbversion_patch);
53
54         // Create synthetic integer version numbers and compare them.
55         // Never run with a libdb other than the one with which it was compiled.
56         int compiled_db_version = ( (DB_VERSION_MAJOR * 1000000) + (DB_VERSION_MINOR * 1000) + (DB_VERSION_PATCH) );
57         int linked_db_version = ( (dbversion_major * 1000000) + (dbversion_minor * 1000) + (dbversion_patch) );
58         if (compiled_db_version != linked_db_version) {
59                 fprintf(stderr,
60                         "db: ctdlload is running with a version of libdb other than the one with which it was compiled.\n"
61                         "db: compiled: %d\n"
62                         "db:   linked: %d\n"
63                         "db: This is an invalid configuration.  ctdlload will now exit to prevent data loss.",
64                         compiled_db_version,
65                         linked_db_version
66                 );
67                 exit(CTDLEXIT_DB);
68         }
69
70         ret = db_env_create(&dbenv, 0);
71         if (ret) {
72                 fprintf(stderr,"db: db_env_create: %s\n", db_strerror(ret));
73                 fprintf(stderr,"db: exit code %d\n", ret);
74                 exit(CTDLEXIT_DB);
75         }
76
77         // We want to specify the shared memory buffer pool cachesize, but everything else is the default.
78         ret = dbenv->set_cachesize(dbenv, 0, 64 * 1024, 0);
79         if (ret) {
80                 fprintf(stderr,"db: set_cachesize: %s\n", db_strerror(ret));
81                 dbenv->close(dbenv, 0);
82                 fprintf(stderr,"db: exit code %d\n", ret);
83                 exit(CTDLEXIT_DB);
84         }
85
86         if ((ret = dbenv->set_lk_detect(dbenv, DB_LOCK_DEFAULT))) {
87                 fprintf(stderr,"db: set_lk_detect: %s\n", db_strerror(ret));
88                 dbenv->close(dbenv, 0);
89                 fprintf(stderr,"db: exit code %d\n", ret);
90                 exit(CTDLEXIT_DB);
91         }
92
93         flags = DB_CREATE | DB_INIT_MPOOL | DB_PRIVATE | DB_INIT_LOG;
94         ret = dbenv->open(dbenv, dirname, flags, 0);
95         if (ret) {
96                 fprintf(stderr,"db: dbenv->open: %s\n", db_strerror(ret));
97                 dbenv->close(dbenv, 0);
98                 fprintf(stderr,"db: exit code %d\n", ret);
99                 exit(CTDLEXIT_DB);
100         }
101
102         return(dbenv);
103 }
104
105
106 void close_dbenv(DB_ENV *dbenv) {
107         int ret = dbenv->close(dbenv, 0);
108         if (ret) {
109                 fprintf(stderr,"db: dbenv->close: %s\n", db_strerror(ret));
110         }
111 }
112
113
114 // Convert a "msgtext" record to a message on disk.   NOT THREADSAFE
115 // This also works for "bigmsg" records.
116 int convert_msgtext(char *line, DBT *out_key, DBT *out_data) {
117
118         static char *b64_decoded_msg = NULL;
119         static size_t b64_decoded_alloc = 0;
120         long msgnum;
121         char *token;
122
123         token = strtok(line, "|");
124         msgnum = atol(strtok(NULL, "|"));
125         token = strtok(NULL, "|");
126
127         // The record key will be the message number
128         out_key->size = sizeof(long);
129         out_key->data = reallok(out_key->data, out_key->size);
130         memcpy(out_key->data, &msgnum, out_key->size);
131
132         // The record data will be the decoded message text.
133         // We are allocating more memory than we need, but BDB will only write the number of bytes we tell it to.
134         out_data->data = reallok(out_data->data, strlen(token));
135         out_data->size = CtdlDecodeBase64(out_data->data, token, strlen(token));
136         return(1);
137 }
138
139
140 // Convert a "msgmeta" record to a message metadata record on disk.  NOT THREADSAFE
141 int convert_msgmeta(char *line, DBT *out_key, DBT *out_data) {
142         char *token;
143         struct MetaData *m = malloc(sizeof(struct MetaData));
144         token = strtok(line, "|");
145         m->meta_msgnum = atol(strtok(NULL, "|"));
146         m->meta_refcount = atoi(strtok(NULL, "|"));
147         strncpy(m->meta_content_type, strtok(NULL, "|"), sizeof(m->meta_content_type));
148         m->meta_rfc822_length = atol(strtok(NULL, "|"));
149
150         // metadata records are stored in the CDB_MSGMAIN table,
151         // but with the index being the *negative* of the message number.
152         long index = 0 - m->meta_msgnum;
153         out_key->size = sizeof(long);
154         out_key->data = reallok(NULL, out_key->size);
155         memcpy(out_key->data, &index, out_key->size);
156
157         // data
158         out_data->size = sizeof(struct MetaData);
159         out_data->data = m;                             // out_data owns this memory now
160
161         return(1);
162 }
163
164
165 // Convert a "user" record to a record on disk.  (Source string is unusable after this function is called.)
166 int convert_user(char *line, DBT *out_key, DBT *out_data) {
167         char userkey[USERNAME_SIZE];
168         char *token;
169         struct ctdluser *u = malloc(sizeof(struct ctdluser));
170
171         memset(u, 0, sizeof(struct ctdluser));
172         char *p = line;
173
174         for (int i=0; (token = strsep(&p, "|")); ++i) {
175                 switch(i) {
176                         case 1:
177                                 u->version = atoi(token);
178                                 break;
179                         case 2:
180                                 u->uid = atoi(token);
181                                 break;
182                         case 3:
183                                 strncpy(u->password, token, sizeof(u->password));
184                                 break;
185                         case 4:
186                                 u->flags = atoi(token);
187                                 break;
188                         case 5:
189                                 u->axlevel = atoi(token);
190                                 break;
191                         case 6:
192                                 u->usernum = atol(token);
193                                 break;
194                         case 7:
195                                 u->lastcall = atol(token);
196                                 break;
197                         case 8:
198                                 u->USuserpurge = atoi(token);
199                                 break;
200                         case 9:
201                                 strncpy(u->fullname, token, sizeof(u->fullname));
202                                 break;
203                         case 10:
204                                 u->msgnum_bio = atol(token);
205                                 break;
206                         case 11:
207                                 u->msgnum_pic = atol(token);
208                                 break;
209                         case 12:
210                                 CtdlDecodeBase64(u->emailaddrs, token, strlen(token));
211                                 break;
212                         case 13:
213                                 u->msgnum_inboxrules = atol(token);
214                                 break;
215                         case 14:
216                                 u->lastproc_inboxrules = atol(token);
217                                 break;
218                 }
219         }
220         
221         makeuserkey(userkey, u->fullname);
222         out_key->size = strlen(userkey);
223         out_key->data = strdup(userkey);
224         out_data->size = sizeof(struct ctdluser);
225         out_data->data = u;
226         return(1);
227 }
228
229
230 // Convert a "room" record to a record on disk.  (Source string is unusable after this function is called.)
231 int convert_room(char *line, DBT *out_key, DBT *out_data) {
232         char *token;
233         struct ctdlroom *r = malloc(sizeof(struct ctdlroom));
234
235         memset(r, 0, sizeof(struct ctdlroom));
236         char *p = line;
237
238         for (int i=0; (token = strsep(&p, "|")); ++i) {
239                 switch(i) {
240                         case 1:
241                                 strncpy(r->QRname, token, sizeof(r->QRname));
242                                 break;
243                         case 2:
244                                 strncpy(r->QRpasswd, token, sizeof (r->QRpasswd));
245                                 break;
246                         case 3:
247                                 r->QRroomaide = atol(token);
248                                 break;
249                         case 4:
250                                 r->QRhighest = atol(token);
251                                 break;
252                         case 5:
253                                 r->QRgen = atol(token);
254                                 break;
255                         case 6:
256                                 r->QRflags = atoi(token);
257                                 break;
258                         case 7:
259                                 strncpy(r->QRdirname, token, sizeof(r->QRdirname));
260                                 break;
261                         case 8:
262                                 r->msgnum_info = atol(token);
263                                 break;
264                         case 9:
265                                 r->QRfloor = atoi(token);
266                                 break;
267                         case 10:
268                                 r->QRmtime = atol(token);
269                                 break;
270                         case 11:
271                                 r->QRep.expire_mode = atoi(token);
272                                 break;
273                         case 12:
274                                 r->QRep.expire_value = atoi(token);
275                                 break;
276                         case 13:
277                                 r->QRnumber = atol(token);
278                                 break;
279                         case 14:
280                                 r->QRorder = atoi(token);
281                                 break;
282                         case 15:
283                                 r->QRflags2 = atoi(token);
284                                 break;
285                         case 16:
286                                 r->QRdefaultview = atoi(token);
287                                 break;
288                         case 17:
289                                 r->msgnum_pic = atol(token);
290                                 break;
291                 }
292         }
293
294         // The key is the room name in all lower case
295         out_key->size = strlen(r->QRname);
296         out_key->data = strdup(r->QRname);
297         char *k = (char *)out_key->data;
298         for (int i=0; i<=out_key->size; ++i) {
299                 k[i] = tolower(k[i]);
300         }
301
302         out_data->size = sizeof(struct ctdlroom);
303         out_data->data = r;
304         return(1);
305 }
306
307
308 // Convert a floor record to a record on disk.
309 int convert_floor(char *line, DBT *out_key, DBT *out_data) {
310         char *token;
311         struct floor *f = malloc(sizeof(struct floor));
312         int floor_num;
313
314         memset(f, 0, sizeof(struct floor));
315         char *p = line;
316
317         for (int i=0; (token = strsep(&p, "|")); ++i) {
318                 switch(i) {
319                         case 1:
320                                 floor_num = atoi(token);
321                                 break;
322                         case 2:
323                                 f->f_flags = atoi(token);
324                                 break;
325                         case 3:
326                                 strncpy(f->f_name, token, sizeof(f->f_name));
327                                 break;
328                         case 4:
329                                 f->f_ref_count = atoi(token);
330                                 break;
331                         case 5:
332                                 f->f_ep.expire_mode = atoi(token);
333                                 break;
334                         case 6:
335                                 f->f_ep.expire_value = atoi(token);
336                                 break;
337                 }
338         }
339
340         out_key->size = sizeof(int);
341         out_key->data = malloc(out_key->size);
342         memcpy(out_key->data, &floor_num, out_key->size);
343
344         out_data->size = sizeof(struct floor);
345         out_data->data = f;
346         return(1);
347 }
348
349
350 // Import a msglist record
351 // msglist|26|32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51|
352 int convert_msglist(char *line, DBT *out_key, DBT *out_data) {
353         long roomnum;
354         char *token, *mtoken;
355         char *p = line;
356         char *q = NULL;
357         int num_msgs = 0;
358         long *msglist = NULL;
359
360         for (int i=0; (token = strsep(&p, "|")); ++i) {
361                 switch(i) {
362                         case 1:
363                                 roomnum = atol(token);
364                                 break;
365                         case 2:
366                                 q = token;
367                                 for (int j=0; (mtoken = strsep(&q, ",")); ++j) {
368                                         msglist = realloc(msglist, (num_msgs+1) * sizeof(long));
369                                         msglist[num_msgs++] = atol(mtoken);
370                                 }
371                                 break;
372                 }
373         }
374
375         out_key->size = sizeof(long);
376         out_key->data = malloc(out_key->size);
377         memcpy(out_key->data, &roomnum, out_key->size);
378
379         out_data->size = num_msgs * sizeof(long);
380         out_data->data = msglist;
381
382         return(1);
383 }
384
385
386 // Convert a "visit" record to a record on disk.
387 int convert_visit(char *line, DBT *out_key, DBT *out_data) {
388         char *token;
389         struct visit *v = malloc(sizeof(struct visit));
390
391         memset(v, 0, sizeof(struct visit));
392         char *p = line;
393
394         for (int i=0; (token = strsep(&p, "|")); ++i) {
395                 switch(i) {
396                         case 1:
397                                 v->v_roomnum = atol(token);
398                                 break;
399                         case 2:
400                                 v->v_roomgen = atol(token);
401                                 break;
402                         case 3:
403                                 v->v_usernum = atol(token);
404                                 break;
405                         case 4:
406                                 v->v_lastseen = atoi(token);
407                                 break;
408                         case 5:
409                                 v->v_flags = atoi(token);
410                                 break;
411                         case 6:
412                                 strncpy(v->v_seen, token, sizeof(v->v_seen));
413                                 break;
414                         case 7:
415                                 strncpy(v->v_answered, token, sizeof(v->v_answered));
416                                 break;
417                         case 8:
418                                 v->v_view = atoi(token);
419                                 break;
420                 }
421         }
422
423         // The key is the same as the first three data members (3 x long)
424         out_key->size = sizeof(long) * 3;
425         out_key->data = reallok(NULL, out_key->size);
426         memcpy(out_key->data, v, out_key->size);
427
428         out_data->size = sizeof(struct visit);
429         out_data->data = v;
430         return(1);
431 }
432
433
434 // Convert a "usetable" record to a record on disk.
435 int convert_usetable(char *line, DBT *out_key, DBT *out_data) {
436         char *token;
437         struct UseTable *u = malloc(sizeof(struct UseTable));
438
439         memset(u, 0, sizeof(struct UseTable));
440         char *p = line;
441
442         for (int i=0; (token = strsep(&p, "|")); ++i) {
443                 switch(i) {
444                         case 1:
445                                 u->hash = atoi(token);
446                                 break;
447                         case 2:
448                                 u->timestamp = atol(token);
449                                 break;
450                 }
451         }
452
453         // the key is just an int (the hash)
454         out_key->size = sizeof(int);
455         out_key->data = reallok(NULL, out_key->size);
456         memcpy(out_key->data, &u->hash, out_key->size);
457
458         out_data->size = sizeof(struct UseTable);
459         out_data->data = u;
460         return(1);
461 }
462
463
464 // Import a config record
465 // The key is the config key
466 // The data is the config key, a null, the value, and another null
467 int convert_config(char *line, DBT *out_key, DBT *out_data) {
468         char *token;
469         char *p = line;
470         char *k, *v;
471
472         for (int i=0; (token = strsep(&p, "|")); ++i) {
473                 switch(i) {
474                         case 1:
475                                 k = token;
476                                 out_key->size = strlen(token);
477                                 out_key->data = strdup(token);
478                                 break;
479                         case 2:
480                                 v = token;
481                                 out_data->size = strlen(k) + strlen(v) + 2;
482                                 out_data->data = reallok(NULL, out_data->size);
483                                 strcpy(out_data->data, k);
484                                 strcpy(out_data->data + strlen(k) + 1, v);
485                                 break;
486                 }
487         }
488
489         return(1);
490 }
491
492
493 // Ingest one line of dump data.  NOT REENTRANT
494 void ingest_one(char *line, DB_ENV *dst_dbenv) {
495
496         static int good_rows = 0;
497         static int bad_rows = 0;
498         static int previous_cdb = -1 ;
499         static int current_cdb = -1 ;
500         static DB *dst_dbp;
501         char record_type[32];
502         int ret;
503         char dbfilename[32];
504         int row_was_good;
505         DBT out_key, out_data;
506
507         // We are assuming that the lines of the dump file will generally be sorted by table.
508         // By remembering the last table we worked with, we can do close/open if the table changes.
509
510         if (current_cdb >= 0) {
511                 fprintf(stderr, "   \033[33m%02x \033[32m%9d \033[31m%8d\033[0m\r", current_cdb, good_rows, bad_rows);
512                 fflush(stderr);
513         }
514
515         // Identify the record type we are currently working with
516         extract_token(record_type, line, 0, '|', sizeof record_type);
517         if (!strcasecmp(record_type, "msgtext"))                current_cdb = CDB_MSGMAIN;
518         else if (!strcasecmp(record_type, "msgmeta"))           current_cdb = CDB_MSGMAIN;
519         else if (!strcasecmp(record_type, "user"))              current_cdb = CDB_USERS;
520         else if (!strcasecmp(record_type, "room"))              current_cdb = CDB_ROOMS;
521         else if (!strcasecmp(record_type, "floor"))             current_cdb = CDB_FLOORTAB;
522         else if (!strcasecmp(record_type, "msglist"))           current_cdb = CDB_MSGLISTS;
523         else if (!strcasecmp(record_type, "visit"))             current_cdb = CDB_VISIT;
524         else if (!strcasecmp(record_type, "dir"))               current_cdb = CDB_DIRECTORY;
525         else if (!strcasecmp(record_type, "use"))               current_cdb = CDB_USETABLE;
526         else if (!strcasecmp(record_type, "bigmsg"))            current_cdb = CDB_BIGMSGS;
527         else if (!strcasecmp(record_type, "euidindex"))         current_cdb = CDB_EUIDINDEX;
528         else if (!strcasecmp(record_type, "usersbynumber"))     current_cdb = CDB_USERSBYNUMBER;
529         else if (!strcasecmp(record_type, "config"))            current_cdb = CDB_CONFIG;
530         else                                                    current_cdb = -1 ;
531
532         if (current_cdb != previous_cdb) {
533                 if (previous_cdb >= 0) {
534                         fprintf(stderr, "\n");
535                 }
536                 if (previous_cdb >= 0) {
537                         ret = dst_dbp->close(dst_dbp, 0);
538                         if (ret) {
539                                 fprintf(stderr, "db: db_close: %s\n", db_strerror(ret));
540                         }
541                 }
542
543                 if (current_cdb >= 0) {
544                         good_rows = 0;
545                         bad_rows = 0;
546                         snprintf(dbfilename, sizeof dbfilename, "cdb.%02x", current_cdb);
547
548                         // create a database handle for the destination table
549                         ret = db_create(&dst_dbp, dst_dbenv, 0);
550                         if (ret) {
551                                 fprintf(stderr, "db: db_create: %s\n", db_strerror(ret));
552                                 fprintf(stderr, "db: exit code %d\n", ret);
553                                 exit(CTDLEXIT_DB);
554                         }
555                 
556                         // open the file containing the destination table
557                         ret = dst_dbp->open(dst_dbp, NULL, dbfilename, NULL, DB_BTREE, (DB_CREATE | DB_TRUNCATE), 0600);
558                         if (ret) {
559                                 fprintf(stderr, "db: db_open: %s\n", db_strerror(ret));
560                                 fprintf(stderr, "db: exit code %d\n", ret);
561                                 exit(CTDLEXIT_DB);
562                         }
563                 }
564
565                 previous_cdb = current_cdb;
566         }
567
568         // If we have a valid record type and a target database open, dispatch the correct record type handler.
569         memset(&out_key, 0, sizeof(DBT));
570         memset(&out_data, 0, sizeof(DBT));
571         row_was_good = 0;
572         if      (!strcasecmp(record_type, "msgtext"))           row_was_good = convert_msgtext(line, &out_key, &out_data);
573         else if (!strcasecmp(record_type, "msgmeta"))           row_was_good = convert_msgmeta(line, &out_key, &out_data);
574         else if (!strcasecmp(record_type, "user"))              row_was_good = convert_user(line, &out_key, &out_data);
575         else if (!strcasecmp(record_type, "room"))              row_was_good = convert_room(line, &out_key, &out_data);
576         else if (!strcasecmp(record_type, "floor"))             row_was_good = convert_floor(line, &out_key, &out_data);
577         else if (!strcasecmp(record_type, "msglist"))           row_was_good = convert_msglist(line, &out_key, &out_data);
578         else if (!strcasecmp(record_type, "visit"))             row_was_good = convert_visit(line, &out_key, &out_data);
579         else if (!strcasecmp(record_type, "use"))               row_was_good = convert_usetable(line, &out_key, &out_data);
580         else if (!strcasecmp(record_type, "bigmsg"))            row_was_good = convert_msgtext(line, &out_key, &out_data);
581         else if (!strcasecmp(record_type, "config"))            row_was_good = convert_config(line, &out_key, &out_data);
582         else                                                    row_was_good = 0;
583
584         if (row_was_good) {
585                 ++good_rows;
586                 ret = dst_dbp->put(dst_dbp, NULL, &out_key, &out_data, 0);
587                 if (ret) {
588                         fprintf(stderr, "db: cdb_put(%x): %s", current_cdb, db_strerror(ret));
589                         exit(CTDLEXIT_DB);
590                 }
591         }
592         else {
593                 ++bad_rows;
594         }
595
596         free(out_key.data);
597         free(out_data.data);
598 }
599
600
601 // This is the loop that loads the dump data.  NOT REENTRANT
602 void ingest(DB_ENV *dst_dbenv) {
603         static size_t line_alloc = 1;
604         static char *line;
605         static size_t line_len = 0;
606         int ch;
607
608         fprintf(stderr, "\033[7mtable\033[0m \033[7mgood_rows\033[0m \033[7mbad_rows\033[0m\n");
609         line = reallok(NULL, line_alloc);
610
611         do {
612                 line_len = 0;
613
614                 while (ch = getc(stdin), ((ch != '\n') && (ch > 0))) {
615                         if ((line_len+2) > line_alloc) {
616                                 line_alloc *= 2;
617                                 line = reallok(line, line_alloc);
618                         }
619                         line[line_len++] = ch;
620                         line[line_len] = 0;
621                 }
622         
623                 if (ch <= 0) {
624                         return;
625                 }
626
627                 if (line_len > 0) {
628                         ingest_one(line, dst_dbenv);
629                 }
630
631         } while (ch >= 0);
632 }
633
634
635 // Main entry point
636 int main(int argc, char **argv) {
637         char *dst_dir = NULL;
638         int confirmed = 0;
639         static DB_ENV *dst_dbenv;               // Source DB environment (global)
640
641         // display the greeting
642         fprintf(stderr, "\033[42m\033[30m \033[K\033[0m\n"
643                         "\033[42m\033[30m DB Load utility for Citadel \033[K\033[0m\n"
644                         "\033[42m\033[30m Copyright (c) 2023 by citadel.org et al.  \033[K\033[0m\n"
645                         "\033[42m\033[30m This program is open source software.  Use, duplication, or disclosure \033[K\033[0m\n"
646                         "\033[42m\033[30m is subject to the terms of the GNU General Public license v3. \033[K\033[0m\n"
647                         "\033[42m\033[30m \033[K\033[0m\n");
648
649         // Parse command line
650         int a;
651         while ((a = getopt(argc, argv, "h:y")) != EOF) {
652                 switch (a) {
653                 case 'h':
654                         dst_dir = optarg;
655                         break;
656                 case 'y':
657                         confirmed = 1;
658                         break;
659                 default:
660                         fprintf(stderr, "%s: usage: %s -h dest_dir [<dumpfile]\n", argv[0], argv[0]);
661                         exit(2);
662                 }
663         }
664
665         if (confirmed == 1) {
666                 fprintf(stderr, "ctdlload: You have specified the [-y] flag, so processing will continue.\n");
667         }
668         else {
669                 fprintf(stderr, "ctdlload: Please read [ https://www.citadel.org/dump-and-load.html ] to learn how to proceed.\n");
670                 exit(0);
671         }
672
673         char cmd[1024];
674         snprintf(cmd, sizeof cmd, "rm -fv %s/cdb.* %s/log.*", dst_dir, dst_dir);
675         system(cmd);
676
677         dst_dbenv = open_dbenv(dst_dir);
678         ingest(dst_dbenv);
679         close_dbenv(dst_dbenv);
680
681         exit(0);
682 }