mirror of
				https://github.com/isar/libmdbx.git
				synced 2025-10-31 15:38:57 +08:00 
			
		
		
		
	
		
			
				
	
	
		
			482 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			482 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| /* mdbx_dump.c - memory-mapped database dump tool */
 | |
| 
 | |
| /*
 | |
|  * Copyright 2015-2020 Leonid Yuriev <leo@yuriev.ru>
 | |
|  * 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
 | |
|  * <http://www.OpenLDAP.org/license.html>. */
 | |
| 
 | |
| #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 <ctype.h>
 | |
| 
 | |
| #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;
 | |
| }
 |