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/*======
5This file is part of PerconaFT.
6
7
8Copyright (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
55typedef 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
74dump_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, ...) \
85do { \
86if (0) g.dbenv->err(g.dbenv, retval, __VA_ARGS__); \
87else { \
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(...) \
98do { \
99if (0) g.dbenv->err(g.dbenv, 0, __VA_ARGS__); \
100else { \
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
109int strtoint32 (char* str, int32_t* num, int32_t min, int32_t max, int base);
110int strtouint32 (char* str, uint32_t* num, uint32_t min, uint32_t max, int base);
111int strtoint64 (char* str, int64_t* num, int64_t min, int64_t max, int base);
112int 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) \
127int 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; \
159error: \
160 return errno; \
161}
162
163DEF_STR_TO(strtoint32, int32_t, int64_t, strtoll, PRId32)
164DEF_STR_TO(strtouint32, uint32_t, uint64_t, strtoull, PRIu32)
165DEF_STR_TO(strtoint64, int64_t, int64_t, strtoll, PRId64)
166DEF_STR_TO(strtouint64, uint64_t, uint64_t, strtoull, PRIu64)
167
168static inline void
169outputbyte(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
179static inline void
180outputstring(char* str)
181{
182 char* p;
183
184 for (p = str; *p != '\0'; p++) {
185 outputbyte((uint8_t)*p);
186 }
187}
188
189static inline void
190outputplaintextstring(char* str)
191{
192 bool old_plaintext = g.plaintext;
193 g.plaintext = true;
194 outputstring(str);
195 g.plaintext = old_plaintext;
196}
197
198static inline int
199verify_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
213static int last_caught = 0;
214
215static void catch_signal(int which_signal) {
216 last_caught = which_signal;
217 if (last_caught == 0) last_caught = SIGINT;
218}
219
220static inline void
221init_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
232static inline int
233caught_any_signals(void) {
234 return last_caught != 0;
235}
236
237static inline void
238resend_signals(void) {
239 if (last_caught) {
240 signal(last_caught, SIG_DFL);
241 raise(last_caught);
242 }
243}
244
245static int usage (void);
246static int create_init_env(void);
247static int dump_database (void);
248static int open_database (void);
249static int dump_pairs (void);
250static int dump_footer (void);
251static int dump_header (void);
252static int close_database (void);
253
254int 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) {
386error:
387 g.exitcode = EXIT_FAILURE;
388 fprintf(stderr, "%s: Quitting out due to errors.\n", g.progname);
389 }
390cleanup:
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
401int 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) {
428error:
429 g.exitcode = EXIT_FAILURE;
430 }
431cleanup:
432
433 if (close_database() != 0) g.exitcode = EXIT_FAILURE;
434
435 return g.exitcode;
436}
437
438int 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
446int 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
500error:
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
509int 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
540error:
541 return EXIT_FAILURE;
542}
543
544int dump_footer()
545{
546 printf("DATA=END\n");
547 if (ferror(stdout)) goto error;
548
549 return EXIT_SUCCESS;
550error:
551 return EXIT_FAILURE;
552}
553
554int 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;
584error:
585 fprintf(stderr, "Quitting out due to errors.\n");
586 return EXIT_FAILURE;
587}
588
589static 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) {
609error:
610 g.exitcode = EXIT_FAILURE;
611 }
612 return g.exitcode;
613}
614
615int 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) {
651error:
652 g.exitcode = EXIT_FAILURE;
653 }
654cleanup:
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
672int 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;
683error:
684 return EXIT_FAILURE;
685}
686