1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 // vim: ft=cpp:expandtab:ts=8:sw=4:softtabstop=4:
3 #ident "$Id$"
4 /*======
5 This file is part of PerconaFT.
6 
7 
8 Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved.
9 
10     PerconaFT is free software: you can redistribute it and/or modify
11     it under the terms of the GNU General Public License, version 2,
12     as published by the Free Software Foundation.
13 
14     PerconaFT is distributed in the hope that it will be useful,
15     but WITHOUT ANY WARRANTY; without even the implied warranty of
16     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17     GNU General Public License for more details.
18 
19     You should have received a copy of the GNU General Public License
20     along with PerconaFT.  If not, see <http://www.gnu.org/licenses/>.
21 
22 ----------------------------------------
23 
24     PerconaFT is free software: you can redistribute it and/or modify
25     it under the terms of the GNU Affero General Public License, version 3,
26     as published by the Free Software Foundation.
27 
28     PerconaFT is distributed in the hope that it will be useful,
29     but WITHOUT ANY WARRANTY; without even the implied warranty of
30     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
31     GNU Affero General Public License for more details.
32 
33     You should have received a copy of the GNU Affero General Public License
34     along with PerconaFT.  If not, see <http://www.gnu.org/licenses/>.
35 ======= */
36 
37 #ident "Copyright (c) 2006, 2015, Percona and/or its affiliates. All rights reserved."
38 
39 #include <db.h>
40 
41 #include <toku_stdlib.h>
42 #include <toku_stdint.h>
43 #include <toku_portability.h>
44 #include <toku_assert.h>
45 #include <stdio.h>
46 #include <sys/types.h>
47 #include <unistd.h>
48 #include <string.h>
49 #include <ctype.h>
50 #include <errno.h>
51 #include <getopt.h>
52 #include <signal.h>
53 #include <memory.h>
54 
55 typedef struct {
56    bool     leadingspace;
57    bool     plaintext;
58    bool     header;
59    bool     footer;
60    bool     is_private;
61    bool     recovery_and_txn;
62    char*    progname;
63    char*    homedir;
64    char*    database;
65    char*    subdatabase;
66    int      exitcode;
67    int      recover_flags;
68    DBTYPE   dbtype;
69    DBTYPE   opened_dbtype;
70    DB*      db;
71    DB_ENV*  dbenv;
72 } dump_globals;
73 
74 dump_globals g;
75 
76 #define SET_BITS(bitvector, bits)      ((bitvector) |= (bits))
77 #define REMOVE_BITS(bitvector, bits)   ((bitvector) &= ~(bits))
78 #define IS_SET_ANY(bitvector, bits)    ((bitvector) & (bits))
79 #define IS_SET_ALL(bitvector, bits)    (((bitvector) & (bits)) == (bits))
80 
81 #define IS_POWER_OF_2(num)             ((num) > 0 && ((num) & ((num) - 1)) == 0)
82 
83 //DB_ENV->err disabled since it does not use db_strerror
84 #define PRINT_ERROR(retval, ...)                                  \
85 do {                                                              \
86 if (0) g.dbenv->err(g.dbenv, retval, __VA_ARGS__);                \
87 else {                                                            \
88    fprintf(stderr, "\tIn %s:%d %s()\n", __FILE__, __LINE__, __FUNCTION__); \
89    fprintf(stderr, "%s: %s:", g.progname, db_strerror(retval));   \
90    fprintf(stderr, __VA_ARGS__);                                  \
91    fprintf(stderr, "\n");                                         \
92    fflush(stderr);                                                \
93 }                                                                 \
94 } while (0)
95 
96 //DB_ENV->err disabled since it does not use db_strerror, errx does not exist.
97 #define PRINT_ERRORX(...)                                               \
98 do {                                                              \
99 if (0) g.dbenv->err(g.dbenv, 0, __VA_ARGS__);                     \
100 else {                                                            \
101    fprintf(stderr, "\tIn %s:%d %s()\n", __FILE__, __LINE__, __FUNCTION__); \
102    fprintf(stderr, "%s: ", g.progname);                           \
103    fprintf(stderr, __VA_ARGS__);                                  \
104    fprintf(stderr, "\n");                                         \
105    fflush(stderr);                                                \
106 }                                                                 \
107 } while (0)
108 
109 int   strtoint32  (char* str,  int32_t* num,  int32_t min,  int32_t max, int base);
110 int   strtouint32 (char* str, uint32_t* num, uint32_t min, uint32_t max, int base);
111 int   strtoint64  (char* str,  int64_t* num,  int64_t min,  int64_t max, int base);
112 int   strtouint64 (char* str, uint64_t* num, uint64_t min, uint64_t max, int base);
113 
114 /*
115  * Convert a string to an integer of type "type".
116  *
117  *
118  * Sets errno and returns:
119  *    EINVAL: str == NULL, num == NULL, or string not of the form [ \t]*[+-]?[0-9]+
120  *    ERANGE: value out of range specified. (Range of [min, max])
121  *
122  * *num is unchanged on error.
123  * Returns:
124  *
125  */
126 #define DEF_STR_TO(name, type, bigtype, strtofunc, frmt)       \
127 int name(char* str, type* num, type min, type max, int base)   \
128 {                                                              \
129    char* test;                                                 \
130    bigtype value;                                              \
131                                                                \
132    assert(str);                                                \
133    assert(num);                                                \
134    assert(min <= max);                                         \
135    assert(g.dbenv || g.progname);                              \
136    assert(base == 0 || (base >= 2 && base <= 36));             \
137                                                                \
138    errno = 0;                                                  \
139    while (isspace(*str)) str++;                                \
140    value = strtofunc(str, &test, base);                        \
141    if ((*test != '\0' && *test != '\n') || test == str) {      \
142       PRINT_ERRORX("%s: Invalid numeric argument\n", str);           \
143       errno = EINVAL;                                          \
144       goto error;                                              \
145    }                                                           \
146    if (errno != 0) {                                           \
147       PRINT_ERROR(errno, "%s\n", str);                               \
148    }                                                           \
149    if (value < min) {                                          \
150       PRINT_ERRORX("%s: Less than minimum value (%" frmt ")\n", str, min); \
151       goto error;                                              \
152    }                                                           \
153    if (value > max) {                                          \
154       PRINT_ERRORX("%s: Greater than maximum value (%" frmt ")\n", str, max); \
155       goto error;                                              \
156    }                                                           \
157    *num = value;                                               \
158    return EXIT_SUCCESS;                                        \
159 error:                                                         \
160    return errno;                                               \
161 }
162 
DEF_STR_TO(strtoint32,int32_t,int64_t,strtoll,PRId32)163 DEF_STR_TO(strtoint32,  int32_t,  int64_t,  strtoll,  PRId32)
164 DEF_STR_TO(strtouint32, uint32_t, uint64_t, strtoull, PRIu32)
165 DEF_STR_TO(strtoint64,  int64_t,  int64_t,  strtoll,  PRId64)
166 DEF_STR_TO(strtouint64, uint64_t, uint64_t, strtoull, PRIu64)
167 
168 static inline void
169 outputbyte(uint8_t ch)
170 {
171    if (g.plaintext) {
172       if (ch == '\\')         printf("\\\\");
173       else if (isprint(ch))   printf("%c", ch);
174       else                    printf("\\%02x", ch);
175    }
176    else printf("%02x", ch);
177 }
178 
179 static inline void
outputstring(char * str)180 outputstring(char* str)
181 {
182    char* p;
183 
184    for (p = str; *p != '\0'; p++) {
185       outputbyte((uint8_t)*p);
186    }
187 }
188 
189 static inline void
outputplaintextstring(char * str)190 outputplaintextstring(char* str)
191 {
192    bool old_plaintext = g.plaintext;
193    g.plaintext = true;
194    outputstring(str);
195    g.plaintext = old_plaintext;
196 }
197 
198 static inline int
verify_library_version(void)199 verify_library_version(void)
200 {
201    int major;
202    int minor;
203 
204    db_version(&major, &minor, NULL);
205    if (major != DB_VERSION_MAJOR || minor != DB_VERSION_MINOR) {
206       PRINT_ERRORX("version %d.%d doesn't match library version %d.%d\n",
207              DB_VERSION_MAJOR, DB_VERSION_MINOR, major, minor);
208       return EXIT_FAILURE;
209    }
210    return EXIT_SUCCESS;
211 }
212 
213 static int last_caught = 0;
214 
catch_signal(int which_signal)215 static void catch_signal(int which_signal) {
216     last_caught = which_signal;
217     if (last_caught == 0) last_caught = SIGINT;
218 }
219 
220 static inline void
init_catch_signals(void)221 init_catch_signals(void) {
222     signal(SIGINT, catch_signal);
223     signal(SIGTERM, catch_signal);
224 #ifdef SIGHUP
225     signal(SIGHUP, catch_signal);
226 #endif
227 #ifdef SIGPIPE
228     signal(SIGPIPE, catch_signal);
229 #endif
230 }
231 
232 static inline int
caught_any_signals(void)233 caught_any_signals(void) {
234     return last_caught != 0;
235 }
236 
237 static inline void
resend_signals(void)238 resend_signals(void) {
239     if (last_caught) {
240         signal(last_caught, SIG_DFL);
241         raise(last_caught);
242     }
243 }
244 
245 static int   usage          (void);
246 static int   create_init_env(void);
247 static int   dump_database  (void);
248 static int   open_database  (void);
249 static int   dump_pairs     (void);
250 static int   dump_footer    (void);
251 static int   dump_header    (void);
252 static int   close_database (void);
253 
main(int argc,char * const argv[])254 int main(int argc, char *const argv[]) {
255    int ch;
256    int retval;
257 
258    /* Set up the globals. */
259    memset(&g, 0, sizeof(g));
260    g.leadingspace   = true;
261    //TODO: Uncomment when DB_UNKNOWN + db->get_type are implemented.
262    g.dbtype         = DB_UNKNOWN;
263    //g.dbtype         = DB_BTREE;
264    g.progname       = argv[0];
265    g.header         = true;
266    g.footer         = true;
267    g.recovery_and_txn = true;
268 
269    if (verify_library_version() != 0) goto error;
270 
271    while ((ch = getopt(argc, argv, "d:f:h:klNP:ps:RrVTx")) != EOF) {
272       switch (ch) {
273          case ('d'): {
274             PRINT_ERRORX("-%c option not supported.\n", ch);
275             goto error;
276          }
277          case ('f'): {
278             if (freopen(optarg, "w", stdout) == NULL) {
279                fprintf(stderr,
280                        "%s: %s: reopen: %s\n",
281                        g.progname, optarg, strerror(errno));
282                goto error;
283             }
284             break;
285          }
286          case ('h'): {
287             g.homedir = optarg;
288             break;
289          }
290          case ('k'): {
291             PRINT_ERRORX("-%c option not supported.\n", ch);
292             goto error;
293          }
294          case ('l'): {
295             //TODO: Implement (Requires master database support)
296             PRINT_ERRORX("-%c option not supported.\n", ch); //YET!
297             goto error;
298          }
299          case ('N'): {
300             PRINT_ERRORX("-%c option not supported.\n", ch);
301             goto error;
302          }
303          case ('P'): {
304             /* Clear password. */
305             memset(optarg, 0, strlen(optarg));
306             PRINT_ERRORX("-%c option not supported.\n", ch);
307             goto error;
308          }
309          case ('p'): {
310             g.plaintext = true;
311             break;
312          }
313          case ('R'): {
314             //TODO: Uncomment when DB_SALVAGE,DB_AGGRESSIVE are implemented.
315             /*g.recover_flags |= DB_SALVAGE | DB_AGGRESSIVE;*/
316 
317             //TODO: Implement aggressive recovery (requires db->verify())
318             PRINT_ERRORX("-%c option not supported.\n", ch);
319             goto error;
320          }
321          case ('r'): {
322             //TODO: Uncomment when DB_SALVAGE,DB_AGGRESSIVE are implemented.
323             /*g.recover_flags |= DB_SALVAGE;*/
324 
325             //TODO: Implement recovery (requires db->verify())
326             PRINT_ERRORX("-%c option not supported.\n", ch);
327             goto error;
328          }
329          case ('s'): {
330             g.subdatabase = optarg;
331             break;
332          }
333          case ('V'): {
334             printf("%s\n", db_version(NULL, NULL, NULL));
335             goto cleanup;
336          }
337          case ('T'): {
338             g.plaintext    = true;
339             g.leadingspace = false;
340             g.header       = false;
341             g.footer       = false;
342             break;
343          }
344          case ('x'): {
345 	    g.recovery_and_txn = false;
346 	    break;
347 	 }
348          case ('?'):
349          default: {
350             g.exitcode = usage();
351             goto cleanup;
352          }
353       }
354    }
355    argc -= optind;
356    argv += optind;
357 
358    //TODO: Uncomment when DB_SALVAGE,DB_AGGRESSIVE,DB_PRINTABLE,db->verify are implemented.
359    /*
360    if (g.plaintext) g.recover_flags |= DB_PRINTABLE;
361 
362    if (g.subdatabase != NULL && IS_SET_ALL(g.recover_flags, DB_SALVAGE)) {
363       if (IS_SET_ALL(g.recover_flags, DB_AGGRESSIVE)) {
364          PRINT_ERRORX("The -s and -R options may not both be specified.\n");
365          goto error;
366       }
367       PRINT_ERRORX("The -s and -r options may not both be specified.\n");
368       goto error;
369 
370    }
371    */
372 
373    if (argc != 1) {
374       g.exitcode = usage();
375       goto cleanup;
376    }
377 
378    init_catch_signals();
379 
380    g.database = argv[0];
381    if (caught_any_signals()) goto cleanup;
382    if (create_init_env() != 0) goto error;
383    if (caught_any_signals()) goto cleanup;
384    if (dump_database() != 0) goto error;
385    if (false) {
386 error:
387       g.exitcode = EXIT_FAILURE;
388       fprintf(stderr, "%s: Quitting out due to errors.\n", g.progname);
389    }
390 cleanup:
391    if (g.dbenv && (retval = g.dbenv->close(g.dbenv, 0)) != 0) {
392       g.exitcode = EXIT_FAILURE;
393       fprintf(stderr, "%s: %s: dbenv->close\n", g.progname, db_strerror(retval));
394    }
395    //   if (g.subdatabase)      free(g.subdatabase);
396    resend_signals();
397 
398    return g.exitcode;
399 }
400 
dump_database()401 int dump_database()
402 {
403    int retval;
404 
405    /* Create a database handle. */
406    retval = db_create(&g.db, g.dbenv, 0);
407    if (retval != 0) {
408       PRINT_ERROR(retval, "db_create");
409       return EXIT_FAILURE;
410    }
411 
412    /*
413    TODO: If/when supporting encryption
414    if (g.password && (retval = db->set_flags(db, DB_ENCRYPT))) {
415       PRINT_ERROR(ret, "DB->set_flags: DB_ENCRYPT");
416       goto error;
417    }
418    */
419    if (open_database() != 0) goto error;
420    if (caught_any_signals()) goto cleanup;
421    if (g.header && dump_header() != 0) goto error;
422    if (caught_any_signals()) goto cleanup;
423    if (dump_pairs() != 0) goto error;
424    if (caught_any_signals()) goto cleanup;
425    if (g.footer && dump_footer() != 0) goto error;
426 
427    if (false) {
428 error:
429       g.exitcode = EXIT_FAILURE;
430    }
431 cleanup:
432 
433    if (close_database() != 0) g.exitcode = EXIT_FAILURE;
434 
435    return g.exitcode;
436 }
437 
usage()438 int usage()
439 {
440    fprintf(stderr,
441            "usage: %s [-pVT] [-x] [-f output] [-h home] [-s database] db_file\n",
442            g.progname);
443    return EXIT_FAILURE;
444 }
445 
create_init_env()446 int create_init_env()
447 {
448    int retval;
449    DB_ENV* dbenv;
450    int flags;
451    //TODO: Experiments to determine right cache size for tokudb, or maybe command line argument.
452 
453    retval = db_env_create(&dbenv, 0);
454    if (retval) {
455       fprintf(stderr, "%s: db_dbenv_create: %s\n", g.progname, db_strerror(retval));
456       goto error;
457    }
458    ///TODO: UNCOMMENT/IMPLEMENT dbenv->set_errfile(dbenv, stderr);
459    dbenv->set_errpfx(dbenv, g.progname);
460    /*
461    TODO: Anything for encryption?
462    */
463 
464    /* Open the dbenvironment. */
465    g.is_private = false;
466    //flags = DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL | DB_USE_ENVIRON;
467    flags = DB_INIT_LOCK | DB_INIT_MPOOL; ///TODO: UNCOMMENT/IMPLEMENT | DB_USE_ENVIRON;
468    if (g.recovery_and_txn) {
469       SET_BITS(flags, DB_INIT_LOG | DB_INIT_TXN | DB_RECOVER);
470    }
471 
472    /*
473    ///TODO: UNCOMMENT/IMPLEMENT  Notes:  We require DB_PRIVATE
474    if (!dbenv->open(dbenv, g.homedir, flags, 0)) goto success;
475    */
476 
477    /*
478    ///TODO: UNCOMMENT/IMPLEMENT
479    retval = dbenv->set_cachesize(dbenv, 0, cache, 1);
480    if (retval) {
481       PRINT_ERROR(retval, "DB_ENV->set_cachesize");
482       goto error;
483    }
484    */
485    g.is_private = true;
486    //TODO: Do we want to support transactions even in single-process mode?
487    //Logging is not necessary.. this is read-only.
488    //However, do we need to use DB_INIT_LOG to join a logging environment?
489    //REMOVE_BITS(flags, DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_TXN);
490    SET_BITS(flags, DB_CREATE | DB_PRIVATE);
491 
492    retval = dbenv->open(dbenv, g.homedir, flags, 0);
493    if (retval) {
494       PRINT_ERROR(retval, "DB_ENV->open");
495       goto error;
496    }
497    g.dbenv = dbenv;
498    return EXIT_SUCCESS;
499 
500 error:
501    return EXIT_FAILURE;
502 }
503 
504 #define DUMP_FLAG(bit, dump)    if (IS_SET_ALL(flags, bit)) printf(dump);
505 
506 #define DUMP_IGNORED_FLAG(bit, dump)
507 
508 
dump_header()509 int dump_header()
510 {
511    uint32_t flags;
512    int retval;
513    DB* db = g.db;
514 
515    assert(g.header);
516    printf("VERSION=3\n");
517    printf("format=%s\n", g.plaintext ? "print" : "bytevalue");
518    //TODO: Uncomment when DB_UNKNOWN + db->get_type are implemented.
519    /*assert(g.dbtype == DB_BTREE || (g.dbtype == DB_UNKNOWN && g.opened_dbtype == DB_BTREE));*/
520    printf("type=btree\n");
521    //TODO: Get page size from db.  Currently tokudb does not support db->get_pagesize.
522    //Don't print this out //printf("db_pagesize=4096\n");
523    if (g.subdatabase) {
524       printf("subdatabase=");
525       outputplaintextstring(g.subdatabase);
526       printf("\n");
527    }
528    //TODO: Uncomment when db->get_flags is implemented
529    if ((retval = db->get_flags(db, &flags)) != 0) {
530       PRINT_ERROR(retval, "DB->get_flags");
531       goto error;
532    }
533    DUMP_IGNORED_FLAG(DB_CHKSUM,    "chksum=1\n");
534    DUMP_IGNORED_FLAG(DB_RECNUM,    "recnum=1\n");
535    printf("HEADER=END\n");
536 
537    if (ferror(stdout)) goto error;
538    return EXIT_SUCCESS;
539 
540 error:
541    return EXIT_FAILURE;
542 }
543 
dump_footer()544 int dump_footer()
545 {
546    printf("DATA=END\n");
547    if (ferror(stdout)) goto error;
548 
549    return EXIT_SUCCESS;
550 error:
551    return EXIT_FAILURE;
552 }
553 
open_database()554 int open_database()
555 {
556    DB* db = g.db;
557    int retval;
558 
559    int open_flags = 0;//|DB_RDONLY;
560    //TODO: Transaction auto commit stuff
561    SET_BITS(open_flags, DB_AUTO_COMMIT);
562 
563    retval = db->open(db, NULL, g.database, g.subdatabase, g.dbtype, open_flags, 0666);
564    if (retval != 0) {
565       PRINT_ERROR(retval, "DB->open: %s", g.database);
566       goto error;
567    }
568    //TODO: Uncomment when DB_UNKNOWN + db->get_type are implemented.
569    /*
570    retval = db->get_type(db, &g.opened_dbtype);
571    if (retval != 0) {
572       PRINT_ERROR(retval, "DB->get_type");
573       goto error;
574    }
575    if (g.opened_dbtype != DB_BTREE) {
576       PRINT_ERRORX("Unsupported db type %d\n", g.opened_dbtype);
577       goto error;
578    }
579    if (g.dbtype != DB_UNKNOWN && g.opened_dbtype != g.dbtype) {
580       PRINT_ERRORX("DBTYPE %d does not match opened DBTYPE %d.\n", g.dbtype, g.opened_dbtype);
581       goto error;
582    }*/
583    return EXIT_SUCCESS;
584 error:
585    fprintf(stderr, "Quitting out due to errors.\n");
586    return EXIT_FAILURE;
587 }
588 
dump_dbt(DBT * dbt)589 static int dump_dbt(DBT* dbt)
590 {
591    char* str;
592    uint32_t idx;
593 
594    assert(dbt);
595    str = (char*)dbt->data;
596    if (g.leadingspace) printf(" ");
597    if (dbt->size > 0) {
598       assert(dbt->data);
599       for (idx = 0; idx < dbt->size; idx++) {
600          outputbyte(str[idx]);
601          if (ferror(stdout)) {
602             perror("stdout");
603             goto error;
604          }
605       }
606    }
607    printf("\n");
608    if (false) {
609 error:
610       g.exitcode = EXIT_FAILURE;
611    }
612    return g.exitcode;
613 }
614 
dump_pairs()615 int dump_pairs()
616 {
617    int retval;
618    DBT key;
619    DBT data;
620    DB* db = g.db;
621    DBC* dbc = NULL;
622 
623    memset(&key, 0, sizeof(key));
624    memset(&data, 0, sizeof(data));
625 
626    DB_TXN* txn = NULL;
627    if (g.recovery_and_txn) {
628       retval = g.dbenv->txn_begin(g.dbenv, NULL, &txn, 0);
629       if (retval) {
630 	 PRINT_ERROR(retval, "DB_ENV->txn_begin");
631 	 goto error;
632       }
633    }
634 
635    if ((retval = db->cursor(db, txn, &dbc, 0)) != 0) {
636       PRINT_ERROR(retval, "DB->cursor");
637       goto error;
638    }
639    while ((retval = dbc->c_get(dbc, &key, &data, DB_NEXT)) == 0) {
640       if (caught_any_signals()) goto cleanup;
641       if (dump_dbt(&key) != 0) goto error;
642       if (dump_dbt(&data) != 0) goto error;
643    }
644    if (retval != DB_NOTFOUND) {
645       PRINT_ERROR(retval, "DBC->c_get");
646       goto error;
647    }
648 
649 
650    if (false) {
651 error:
652       g.exitcode = EXIT_FAILURE;
653    }
654 cleanup:
655    if (dbc && (retval = dbc->c_close(dbc)) != 0) {
656       PRINT_ERROR(retval, "DBC->c_close");
657       g.exitcode = EXIT_FAILURE;
658    }
659    if (txn) {
660        if (retval) {
661            int r2 = txn->abort(txn);
662            if (r2) PRINT_ERROR(r2, "DB_TXN->abort");
663        }
664        else {
665            retval = txn->commit(txn, 0);
666            if (retval) PRINT_ERROR(retval, "DB_TXN->abort");
667        }
668    }
669    return g.exitcode;
670 }
671 
close_database()672 int close_database()
673 {
674    DB* db = g.db;
675    int retval;
676 
677    assert(db);
678    if ((retval = db->close(db, 0)) != 0) {
679       PRINT_ERROR(retval, "DB->close");
680       goto error;
681    }
682    return EXIT_SUCCESS;
683 error:
684    return EXIT_FAILURE;
685 }
686