/* mdbx_dump.c - memory-mapped database dump tool */ /* * Copyright 2015-2020 Leonid Yuriev * and other libmdbx authors: please see AUTHORS file. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted only as authorized by the OpenLDAP * Public License. * * A copy of this license is available in the file LICENSE in the * top-level directory of the distribution or, alternatively, at * . */ #ifdef _MSC_VER #if _MSC_VER > 1800 #pragma warning(disable : 4464) /* relative include path contains '..' */ #endif #pragma warning(disable : 4996) /* The POSIX name is deprecated... */ #endif /* _MSC_VER (warnings) */ #define MDBX_TOOLS /* Avoid using internal mdbx_assert() */ #include "internals.h" #include #define PRINT 1 #define GLOBAL 2 static int mode = GLOBAL; typedef struct flagbit { int bit; char *name; } flagbit; flagbit dbflags[] = {{MDBX_REVERSEKEY, "reversekey"}, {MDBX_DUPSORT, "dupsort"}, {MDBX_INTEGERKEY, "integerkey"}, {MDBX_DUPFIXED, "dupfixed"}, {MDBX_INTEGERDUP, "integerdup"}, {MDBX_REVERSEDUP, "reversedup"}, {0, nullptr}}; #if defined(_WIN32) || defined(_WIN64) #include "wingetopt.h" static volatile BOOL user_break; static BOOL WINAPI ConsoleBreakHandlerRoutine(DWORD dwCtrlType) { (void)dwCtrlType; user_break = true; return true; } #else /* WINDOWS */ static volatile sig_atomic_t user_break; static void signal_handler(int sig) { (void)sig; user_break = 1; } #endif /* !WINDOWS */ static const char hexc[] = "0123456789abcdef"; static void dumpbyte(unsigned char c) { putchar(hexc[c >> 4]); putchar(hexc[c & 0xf]); } static void text(MDBX_val *v) { unsigned char *c, *end; putchar(' '); c = v->iov_base; end = c + v->iov_len; while (c < end) { if (isprint(*c) && *c != '\\') { putchar(*c); } else { putchar('\\'); dumpbyte(*c); } c++; } putchar('\n'); } static void dumpval(MDBX_val *v) { unsigned char *c, *end; putchar(' '); c = v->iov_base; end = c + v->iov_len; while (c < end) dumpbyte(*c++); putchar('\n'); } bool quiet = false, rescue = false; const char *prog; static void error(const char *func, int rc) { fprintf(stderr, "%s: %s() error %d %s\n", prog, func, rc, mdbx_strerror(rc)); } /* Dump in BDB-compatible format */ static int dump_sdb(MDBX_txn *txn, MDBX_dbi dbi, char *name) { unsigned int flags; int rc = mdbx_dbi_flags(txn, dbi, &flags); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_dbi_flags", rc); return rc; } MDBX_stat ms; rc = mdbx_dbi_stat(txn, dbi, &ms, sizeof(ms)); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_dbi_stat", rc); return rc; } MDBX_envinfo info; rc = mdbx_env_info_ex(mdbx_txn_env(txn), txn, &info, sizeof(info)); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_env_info_ex", rc); return rc; } printf("VERSION=3\n"); if (mode & GLOBAL) { mode -= GLOBAL; if (info.mi_geo.upper != info.mi_geo.lower) printf("geometry=l%" PRIu64 ",c%" PRIu64 ",u%" PRIu64 ",s%" PRIu64 ",g%" PRIu64 "\n", info.mi_geo.lower, info.mi_geo.current, info.mi_geo.upper, info.mi_geo.shrink, info.mi_geo.grow); printf("mapsize=%" PRIu64 "\n", info.mi_geo.upper); printf("maxreaders=%u\n", info.mi_maxreaders); mdbx_canary canary; rc = mdbx_canary_get(txn, &canary); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_canary_get", rc); return rc; } if (canary.v) printf("canary=v%" PRIu64 ",x%" PRIu64 ",y%" PRIu64 ",z%" PRIu64 "\n", canary.v, canary.x, canary.y, canary.z); } printf("format=%s\n", mode & PRINT ? "print" : "bytevalue"); if (name) printf("database=%s\n", name); printf("type=btree\n"); printf("db_pagesize=%u\n", ms.ms_psize); /* if (ms.ms_mod_txnid) printf("txnid=%" PRIaTXN "\n", ms.ms_mod_txnid); else if (!name) printf("txnid=%" PRIaTXN "\n", mdbx_txn_id(txn)); */ printf("duplicates=%d\n", (flags & (MDBX_DUPSORT | MDBX_DUPFIXED | MDBX_INTEGERDUP | MDBX_REVERSEDUP)) ? 1 : 0); for (int i = 0; dbflags[i].bit; i++) if (flags & dbflags[i].bit) printf("%s=1\n", dbflags[i].name); uint64_t sequence; rc = mdbx_dbi_sequence(txn, dbi, &sequence, 0); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_dbi_sequence", rc); return rc; } if (sequence) printf("sequence=%" PRIu64 "\n", sequence); printf("HEADER=END\n"); /*-------------------------------------------------*/ MDBX_cursor *cursor; MDBX_val key, data; rc = mdbx_cursor_open(txn, dbi, &cursor); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_cursor_open", rc); return rc; } if (MDBX_DEBUG > 0 && rescue) { cursor->mc_flags |= C_SKIPORD; if (cursor->mc_xcursor) cursor->mc_xcursor->mx_cursor.mc_flags |= C_SKIPORD; } while ((rc = mdbx_cursor_get(cursor, &key, &data, MDBX_NEXT)) == MDBX_SUCCESS) { if (user_break) { rc = MDBX_EINTR; break; } if (mode & PRINT) { text(&key); text(&data); } else { dumpval(&key); dumpval(&data); } } printf("DATA=END\n"); if (rc == MDBX_NOTFOUND) rc = MDBX_SUCCESS; if (unlikely(rc != MDBX_SUCCESS)) error("mdbx_cursor_get", rc); return rc; } static void usage(void) { fprintf(stderr, "usage: %s [-V] [-q] [-f file] [-l] [-p] [-a|-s subdb] [-r] " "dbpath\n" " -V\t\tprint version and exit\n" " -q\t\tbe quiet\n" " -f\t\twrite to file instead of stdout\n" " -l\t\tlist subDBs and exit\n" " -p\t\tuse printable characters\n" " -a\t\tdump main DB and all subDBs,\n" " \t\tby default dump only the main DB\n" " -s\t\tdump only the named subDB\n" " -r\t\trescure mode (ignore errors to dump corrupted DB)\n", prog); exit(EXIT_FAILURE); } static int equal_or_greater(const MDBX_val *a, const MDBX_val *b) { return (a->iov_len == b->iov_len && memcmp(a->iov_base, b->iov_base, a->iov_len) == 0) ? 0 : 1; } int main(int argc, char *argv[]) { int i, rc; MDBX_env *env; MDBX_txn *txn; MDBX_dbi dbi; prog = argv[0]; char *envname; char *subname = nullptr; unsigned envflags = 0; bool alldbs = false, list = false; if (argc < 2) usage(); while ((i = getopt(argc, argv, "af:lnps:Vrq")) != EOF) { switch (i) { case 'V': printf("mdbx_dump version %d.%d.%d.%d\n" " - source: %s %s, commit %s, tree %s\n" " - anchor: %s\n" " - build: %s for %s by %s\n" " - flags: %s\n" " - options: %s\n", mdbx_version.major, mdbx_version.minor, mdbx_version.release, mdbx_version.revision, mdbx_version.git.describe, mdbx_version.git.datetime, mdbx_version.git.commit, mdbx_version.git.tree, mdbx_sourcery_anchor, mdbx_build.datetime, mdbx_build.target, mdbx_build.compiler, mdbx_build.flags, mdbx_build.options); return EXIT_SUCCESS; case 'l': list = true; /*FALLTHROUGH*/; __fallthrough; case 'a': if (subname) usage(); alldbs = true; break; case 'f': if (freopen(optarg, "w", stdout) == nullptr) { fprintf(stderr, "%s: %s: reopen: %s\n", prog, optarg, mdbx_strerror(errno)); exit(EXIT_FAILURE); } break; case 'n': envflags |= MDBX_NOSUBDIR; break; case 'p': mode |= PRINT; break; case 's': if (alldbs) usage(); subname = optarg; break; case 'q': quiet = true; break; case 'r': rescue = true; break; default: usage(); } } if (optind != argc - 1) usage(); #if defined(_WIN32) || defined(_WIN64) SetConsoleCtrlHandler(ConsoleBreakHandlerRoutine, true); #else #ifdef SIGPIPE signal(SIGPIPE, signal_handler); #endif #ifdef SIGHUP signal(SIGHUP, signal_handler); #endif signal(SIGINT, signal_handler); signal(SIGTERM, signal_handler); #endif /* !WINDOWS */ envname = argv[optind]; if (!quiet) { fprintf(stderr, "mdbx_dump %s (%s, T-%s)\nRunning for %s...\n", mdbx_version.git.describe, mdbx_version.git.datetime, mdbx_version.git.tree, envname); fflush(nullptr); } rc = mdbx_env_create(&env); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_env_create", rc); return EXIT_FAILURE; } if (alldbs || subname) { rc = mdbx_env_set_maxdbs(env, 2); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_env_set_maxdbs", rc); goto env_close; } } rc = mdbx_env_open( env, envname, envflags | (rescue ? MDBX_RDONLY | MDBX_EXCLUSIVE : MDBX_RDONLY), 0); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_env_open", rc); goto env_close; } rc = mdbx_txn_begin(env, nullptr, MDBX_RDONLY, &txn); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_txn_begin", rc); goto env_close; } rc = mdbx_dbi_open(txn, subname, MDBX_ACCEDE, &dbi); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_dbi_open", rc); goto txn_abort; } if (alldbs) { assert(dbi == MAIN_DBI); MDBX_cursor *cursor; rc = mdbx_cursor_open(txn, MAIN_DBI, &cursor); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_cursor_open", rc); goto txn_abort; } if (MDBX_DEBUG > 0 && rescue) { cursor->mc_flags |= C_SKIPORD; if (cursor->mc_xcursor) cursor->mc_xcursor->mx_cursor.mc_flags |= C_SKIPORD; } bool have_raw = false; int count = 0; MDBX_val key; while (MDBX_SUCCESS == (rc = mdbx_cursor_get(cursor, &key, nullptr, MDBX_NEXT_NODUP))) { if (user_break) { rc = MDBX_EINTR; break; } if (memchr(key.iov_base, '\0', key.iov_len)) continue; subname = mdbx_malloc(key.iov_len + 1); memcpy(subname, key.iov_base, key.iov_len); subname[key.iov_len] = '\0'; MDBX_dbi sub_dbi; rc = mdbx_dbi_open_ex(txn, subname, MDBX_ACCEDE, &sub_dbi, rescue ? equal_or_greater : nullptr, rescue ? equal_or_greater : nullptr); if (unlikely(rc != MDBX_SUCCESS)) { if (rc == MDBX_INCOMPATIBLE) { have_raw = true; continue; } error("mdbx_dbi_open", rc); if (!rescue) break; } else { count++; if (list) { printf("%s\n", subname); } else { rc = dump_sdb(txn, sub_dbi, subname); if (unlikely(rc != MDBX_SUCCESS)) { if (!rescue) break; fprintf(stderr, "%s: %s: ignore %s for `%s` and continue\n", prog, envname, mdbx_strerror(rc), subname); /* Here is a hack for rescue mode, don't do that: * - we should restart transaction in case error due * database corruption; * - but we won't close cursor, reopen and re-positioning it * for new a transaction; * - this is possible since DB is opened in read-only exclusive * mode and transaction is the same, i.e. has the same address * and so on. */ rc = mdbx_txn_reset(txn); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_txn_reset", rc); goto env_close; } rc = mdbx_txn_renew(txn); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_txn_renew", rc); goto env_close; } } } rc = mdbx_dbi_close(env, sub_dbi); if (unlikely(rc != MDBX_SUCCESS)) { error("mdbx_dbi_close", rc); break; } } mdbx_free(subname); } mdbx_cursor_close(cursor); cursor = nullptr; if (have_raw && (!count /* || rescue */)) rc = dump_sdb(txn, MAIN_DBI, nullptr); else if (!count) { fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, envname); rc = MDBX_NOTFOUND; } } else { rc = dump_sdb(txn, dbi, subname); } switch (rc) { case MDBX_NOTFOUND: rc = MDBX_SUCCESS; case MDBX_SUCCESS: break; case MDBX_EINTR: fprintf(stderr, "Interrupted by signal/user\n"); break; default: if (unlikely(rc != MDBX_SUCCESS)) error("mdbx_cursor_get", rc); } mdbx_dbi_close(env, dbi); txn_abort: mdbx_txn_abort(txn); env_close: mdbx_env_close(env); return rc ? EXIT_FAILURE : EXIT_SUCCESS; }