mdbx: subdirs.

Change-Id: Iea70b29ed39f55ee363729300f6ce54127b4e880
This commit is contained in:
Leo Yuriev
2017-03-16 17:27:05 +03:00
parent c2fda6be5a
commit 95e606606a
28 changed files with 39 additions and 85 deletions

156
src/barriers.h Normal file
View File

@@ -0,0 +1,156 @@
/*
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
* Copyright 2015,2016 Peter-Service R&D LLC.
* 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>.
*/
/*****************************************************************************
* Properly compiler/memory/coherence barriers
* in the most portable way for libmdbx project.
*
* Feedback and comments are welcome.
* https://gist.github.com/leo-yuriev/ba186a6bf5cf3a27bae7 */
#pragma once
/* *INDENT-OFF* */
/* clang-format off */
#if defined(__mips) && defined(__linux)
/* Only MIPS has explicit cache control */
# include <asm/cachectl.h>
#endif
#if defined(__GNUC__) || defined(__clang__)
# define MDBX_INLINE __inline
#elif defined(__INTEL_COMPILER) /* LY: Intel Compiler may mimic GCC and MSC */
# include <intrin.h>
# if defined(__ia64__) || defined(__ia64) || defined(_M_IA64)
# pragma intrinsic(__mf)
# elif defined(__i386__) || defined(__x86_64__)
# pragma intrinsic(_mm_mfence)
# endif
# define MDBX_INLINE __inline
#elif defined(__SUNPRO_C) || defined(__sun) || defined(sun)
# include <mbarrier.h>
# define MDBX_INLINE inline
#elif (defined(_HPUX_SOURCE) || defined(__hpux) || defined(__HP_aCC)) \
&& (defined(HP_IA64) || defined(__ia64))
# include <machine/sys/inline.h>
# define MDBX_INLINE
#elif defined(__IBMC__) && defined(__powerpc)
# include <atomic.h>
# define MDBX_INLINE
#elif defined(_AIX)
# include <builtins.h>
# include <sys/atomic_op.h>
# define MDBX_INLINE
#elif (defined(__osf__) && defined(__DECC)) || defined(__alpha)
# include <machine/builtins.h>
# include <c_asm.h>
# define MDBX_INLINE
#elif defined(__MWERKS__)
/* CodeWarrior - troubles ? */
# pragma gcc_extensions
# define MDBX_INLINE
#elif defined(__SNC__)
/* Sony PS3 - troubles ? */
# define MDBX_INLINE
#else
# define MDBX_INLINE
#endif
#if defined(__i386__) || defined(__x86_64__) \
|| defined(_M_AMD64) || defined(_M_IX86) \
|| defined(__i386) || defined(__amd64) \
|| defined(i386) || defined(__x86_64) \
|| defined(_AMD64_) || defined(_M_X64)
# define MDB_CACHE_IS_COHERENT 1
#elif defined(__hppa) || defined(__hppa__)
# define MDB_CACHE_IS_COHERENT 1
#endif
#ifndef MDB_CACHE_IS_COHERENT
# define MDB_CACHE_IS_COHERENT 0
#endif
#define MDBX_BARRIER_COMPILER 0
#define MDBX_BARRIER_MEMORY 1
static MDBX_INLINE void mdbx_barrier(int type) {
#if defined(__clang__)
__asm__ __volatile__ ("" ::: "memory");
if (type > MDBX_BARRIER_COMPILER)
# if __has_extension(c_atomic) || __has_extension(cxx_atomic)
__c11_atomic_thread_fence(__ATOMIC_SEQ_CST);
# else
__sync_synchronize();
# endif
#elif defined(__GNUC__)
__asm__ __volatile__ ("" ::: "memory");
if (type > MDBX_BARRIER_COMPILER)
# if defined(__ATOMIC_SEQ_CST)
__atomic_thread_fence(__ATOMIC_SEQ_CST);
# else
__sync_synchronize();
# endif
#elif defined(__INTEL_COMPILER) /* LY: Intel Compiler may mimic GCC and MSC */
__memory_barrier();
if (type > MDBX_BARRIER_COMPILER)
# if defined(__ia64__) || defined(__ia64) || defined(_M_IA64)
__mf();
# elif defined(__i386__) || defined(__x86_64__)
_mm_mfence();
# else
# error "Unknown target for Intel Compiler, please report to us."
# endif
#elif defined(__SUNPRO_C) || defined(__sun) || defined(sun)
__compiler_barrier();
if (type > MDBX_BARRIER_COMPILER)
__machine_rw_barrier();
#elif (defined(_HPUX_SOURCE) || defined(__hpux) || defined(__HP_aCC)) \
&& (defined(HP_IA64) || defined(__ia64))
_Asm_sched_fence(/* LY: no-arg meaning 'all expect ALU', e.g. 0x3D3D */);
if (type > MDBX_BARRIER_COMPILER)
_Asm_mf();
#elif defined(_AIX) || defined(__ppc__) || defined(__powerpc__) \
|| defined(__ppc64__) || defined(__powerpc64__)
__fence();
if (type > MDBX_BARRIER_COMPILER)
__lwsync();
#elif (defined(__osf__) && defined(__DECC)) || defined(__alpha)
__PAL_DRAINA(); /* LY: excessive ? */
__MB();
#else
# error "Could not guess the kind of compiler, please report to us."
#endif
}
#define mdbx_compiler_barrier() \
mdbx_barrier(MDBX_BARRIER_COMPILER)
#define mdbx_memory_barrier() \
mdbx_barrier(MDBX_BARRIER_MEMORY)
#define mdbx_coherent_barrier() \
mdbx_barrier(MDB_CACHE_IS_COHERENT ? MDBX_BARRIER_COMPILER : MDBX_BARRIER_MEMORY)
static MDBX_INLINE void mdbx_invalidate_cache(void *addr, int nbytes) {
mdbx_coherent_barrier();
#if defined(__mips) && defined(__linux)
/* MIPS has cache coherency issues.
* Note: for any nbytes >= on-chip cache size, entire is flushed. */
cacheflush(addr, nbytes, DCACHE);
#elif defined(_M_MRX000) || defined(_MIPS_)
# error "Sorry, cacheflush() for MIPS not implemented"
#else
/* LY: assume no mmap/dcache issues. */
(void) addr;
(void) nbytes;
#endif
}

11542
src/mdbx.c Normal file

File diff suppressed because it is too large Load Diff

1656
src/mdbx.h Normal file

File diff suppressed because it is too large Load Diff

979
src/mdbx_chk.c Normal file
View File

@@ -0,0 +1,979 @@
/* mdbx_chk.c - memory-mapped database check tool */
/*
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
* Copyright 2015,2016 Peter-Service R&D LLC.
*
* This file is part of libmdbx.
*
* libmdbx is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* libmdbx is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include <ctype.h>
#include <errno.h>
#include <malloc.h>
#include <signal.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include "mdbx.h"
#include "midl.h"
typedef struct flagbit {
int bit;
char *name;
} flagbit;
flagbit dbflags[] = {{MDB_DUPSORT, "dupsort"},
{MDB_INTEGERKEY, "integerkey"},
{MDB_REVERSEKEY, "reversekey"},
{MDB_DUPFIXED, "dupfixed"},
{MDB_REVERSEDUP, "reversedup"},
{MDB_INTEGERDUP, "integerdup"},
{0, NULL}};
static volatile sig_atomic_t gotsignal;
static void signal_handler(int sig) {
(void)sig;
gotsignal = 1;
}
#define MAX_DBI 32768
#define EXIT_INTERRUPTED (EXIT_FAILURE + 4)
#define EXIT_FAILURE_SYS (EXIT_FAILURE + 3)
#define EXIT_FAILURE_MDB (EXIT_FAILURE + 2)
#define EXIT_FAILURE_CHECK_MAJOR (EXIT_FAILURE + 1)
#define EXIT_FAILURE_CHECK_MINOR EXIT_FAILURE
struct {
const char *dbi_names[MAX_DBI];
size_t dbi_pages[MAX_DBI];
size_t dbi_empty_pages[MAX_DBI];
size_t dbi_payload_bytes[MAX_DBI];
size_t dbi_lost_bytes[MAX_DBI];
short *pagemap;
size_t total_payload_bytes;
size_t pgcount;
} walk;
static __attribute__((constructor)) void init_walk(void) {
walk.dbi_names[0] = "@gc";
}
size_t total_unused_bytes;
int exclusive = 2;
MDB_env *env;
MDB_txn *txn, *locktxn;
MDBX_envinfo info;
MDBX_stat stat;
size_t maxkeysize, reclaimable_pages, freedb_pages, lastpgno;
size_t userdb_count, skipped_subdb;
unsigned verbose, quiet;
const char *only_subdb;
struct problem {
struct problem *pr_next;
size_t count;
const char *caption;
};
struct problem *problems_list;
size_t total_problems;
static void __attribute__((format(printf, 1, 2))) print(const char *msg, ...) {
if (!quiet) {
va_list args;
fflush(stderr);
va_start(args, msg);
vfprintf(stdout, msg, args);
va_end(args);
}
}
static void __attribute__((format(printf, 1, 2))) error(const char *msg, ...) {
total_problems++;
if (!quiet) {
va_list args;
fflush(stdout);
va_start(args, msg);
vfprintf(stderr, msg, args);
va_end(args);
fflush(NULL);
}
}
static void pagemap_cleanup(void) {
int i;
for (i = 1; i < MAX_DBI; ++i) {
if (walk.dbi_names[i]) {
free((void *)walk.dbi_names[i]);
walk.dbi_names[i] = NULL;
}
}
free(walk.pagemap);
walk.pagemap = NULL;
}
static int pagemap_lookup_dbi(const char *dbi) {
static int last;
int i;
if (last > 0 && strcmp(walk.dbi_names[last], dbi) == 0)
return last;
for (i = 1; walk.dbi_names[i] && last < MAX_DBI; ++i)
if (strcmp(walk.dbi_names[i], dbi) == 0)
return last = i;
if (i == MAX_DBI)
return -1;
walk.dbi_names[i] = strdup(dbi);
if (verbose > 1) {
print(" - found '%s' area\n", dbi);
fflush(NULL);
}
return last = i;
}
static void problem_add(const char *object, size_t entry_number,
const char *msg, const char *extra, ...) {
total_problems++;
if (!quiet) {
int need_fflush = 0;
struct problem *p;
for (p = problems_list; p; p = p->pr_next)
if (p->caption == msg)
break;
if (!p) {
p = calloc(1, sizeof(*p));
p->caption = msg;
p->pr_next = problems_list;
problems_list = p;
need_fflush = 1;
}
p->count++;
if (verbose > 1) {
print(" %s #%zu: %s", object, entry_number, msg);
if (extra) {
va_list args;
printf(" (");
va_start(args, extra);
vfprintf(stdout, extra, args);
va_end(args);
printf(")");
}
printf("\n");
if (need_fflush)
fflush(NULL);
}
}
}
static struct problem *problems_push() {
struct problem *p = problems_list;
problems_list = NULL;
return p;
}
static size_t problems_pop(struct problem *list) {
size_t count = 0;
if (problems_list) {
int i;
print(" - problems: ");
for (i = 0; problems_list; ++i) {
struct problem *p = problems_list->pr_next;
count += problems_list->count;
print("%s%s (%zu)", i ? ", " : "", problems_list->caption,
problems_list->count);
free(problems_list);
problems_list = p;
}
print("\n");
fflush(NULL);
}
problems_list = list;
return count;
}
static int pgvisitor(size_t pgno, unsigned pgnumber, void *ctx, const char *dbi,
const char *type, int nentries, int payload_bytes,
int header_bytes, int unused_bytes) {
(void)ctx;
if (type) {
size_t page_bytes = payload_bytes + header_bytes + unused_bytes;
size_t page_size = pgnumber * stat.ms_psize;
int index = pagemap_lookup_dbi(dbi);
if (index < 0)
return ENOMEM;
if (verbose > 2 && (!only_subdb || strcmp(only_subdb, dbi) == 0)) {
if (pgnumber == 1)
print(" %s-page %zu", type, pgno);
else
print(" %s-span %zu[%u]", type, pgno, pgnumber);
print(" of %s: header %i, payload %i, unused %i\n", dbi, header_bytes,
payload_bytes, unused_bytes);
}
walk.pgcount += pgnumber;
if (unused_bytes < 0 || (size_t)unused_bytes > page_size)
problem_add("page", pgno, "illegal unused-bytes", "%zu < %i < %zu", 0,
unused_bytes, stat.ms_psize);
if (header_bytes < (int)sizeof(long) ||
(size_t)header_bytes >= stat.ms_psize - sizeof(long))
problem_add("page", pgno, "illegal header-length", "%zu < %i < %zu",
sizeof(long), header_bytes, stat.ms_psize - sizeof(long));
if (payload_bytes < 1) {
if (nentries > 1) {
problem_add("page", pgno, "zero size-of-entry",
"payload %i bytes, %i entries", payload_bytes, nentries);
if ((size_t)header_bytes + unused_bytes < page_size) {
/* LY: hush a misuse error */
page_bytes = page_size;
}
} else {
problem_add("page", pgno, "empty", "payload %i bytes, %i entries",
payload_bytes, nentries);
walk.dbi_empty_pages[index] += 1;
}
}
if (page_bytes != page_size) {
problem_add("page", pgno, "misused", "%zu != %zu (%ih + %ip + %iu)",
page_size, page_bytes, header_bytes, payload_bytes,
unused_bytes);
if (page_size > page_bytes)
walk.dbi_lost_bytes[index] += page_size - page_bytes;
} else {
walk.dbi_payload_bytes[index] += payload_bytes + header_bytes;
walk.total_payload_bytes += payload_bytes + header_bytes;
}
if (pgnumber) {
do {
if (pgno >= lastpgno)
problem_add("page", pgno, "wrong page-no", "%zu > %zi", pgno,
lastpgno);
else if (walk.pagemap[pgno])
problem_add("page", pgno, "already used", "in %s",
walk.dbi_names[walk.pagemap[pgno]]);
else {
walk.pagemap[pgno] = index;
walk.dbi_pages[index] += 1;
}
++pgno;
} while (--pgnumber);
}
}
return gotsignal ? EINTR : MDB_SUCCESS;
}
typedef int(visitor)(size_t record_number, MDB_val *key, MDB_val *data);
static int process_db(MDB_dbi dbi, char *name, visitor *handler, int silent);
static int handle_userdb(size_t record_number, MDB_val *key, MDB_val *data) {
(void)record_number;
(void)key;
(void)data;
return MDB_SUCCESS;
}
static int handle_freedb(size_t record_number, MDB_val *key, MDB_val *data) {
char *bad = "";
size_t pg, prev;
ssize_t i, number, span = 0;
size_t *iptr = data->mv_data, txnid = *(size_t *)key->mv_data;
if (key->mv_size != sizeof(txnid))
problem_add("entry", record_number, "wrong txn-id size", "key-size %zi",
key->mv_size);
else if (txnid < 1 || txnid > info.me_last_txnid)
problem_add("entry", record_number, "wrong txn-id", "%zu", txnid);
if (data->mv_size < sizeof(size_t) || data->mv_size % sizeof(size_t))
problem_add("entry", record_number, "wrong idl size", "%zu", data->mv_size);
else {
number = *iptr++;
if (number >= MDB_IDL_UM_MAX)
problem_add("entry", record_number, "wrong idl length", "%zi", number);
else if ((number + 1) * sizeof(size_t) != data->mv_size)
problem_add("entry", record_number, "mismatch idl length", "%zi != %zu",
number * sizeof(size_t), data->mv_size);
else {
freedb_pages += number;
if (info.me_tail_txnid > txnid)
reclaimable_pages += number;
for (i = number, prev = 1; --i >= 0;) {
pg = iptr[i];
if (pg < 2 /* META_PAGE */ || pg > info.me_last_pgno)
problem_add("entry", record_number, "wrong idl entry",
"2 < %zi < %zi", pg, info.me_last_pgno);
else if (pg <= prev) {
bad = " [bad sequence]";
problem_add("entry", record_number, "bad sequence", "%zi <= %zi", pg,
prev);
}
prev = pg;
pg += span;
for (; i >= span && iptr[i - span] == pg; span++, pg++)
;
}
if (verbose > 2 && !only_subdb) {
print(" transaction %zu, %zd pages, maxspan %zd%s\n",
*(size_t *)key->mv_data, number, span, bad);
if (verbose > 3) {
int j = number - 1;
while (j >= 0) {
pg = iptr[j];
for (span = 1; --j >= 0 && iptr[j] == pg + span; span++)
;
if (span > 1)
print(" %9zu[%zd]\n", pg, span);
else
print(" %9zu\n", pg);
}
}
}
}
}
return MDB_SUCCESS;
}
static int handle_maindb(size_t record_number, MDB_val *key, MDB_val *data) {
char *name;
int rc;
size_t i;
name = key->mv_data;
for (i = 0; i < key->mv_size; ++i) {
if (name[i] < ' ')
return handle_userdb(record_number, key, data);
}
name = malloc(key->mv_size + 1);
memcpy(name, key->mv_data, key->mv_size);
name[key->mv_size] = '\0';
userdb_count++;
rc = process_db(-1, name, handle_userdb, 0);
free(name);
if (rc != MDB_INCOMPATIBLE)
return rc;
return handle_userdb(record_number, key, data);
}
static int process_db(MDB_dbi dbi, char *name, visitor *handler, int silent) {
MDB_cursor *mc;
MDBX_stat ms;
MDB_val key, data;
MDB_val prev_key, prev_data;
unsigned flags;
int rc, i;
struct problem *saved_list;
size_t problems_count;
unsigned record_count = 0, dups = 0;
size_t key_bytes = 0, data_bytes = 0;
if (0 > (int)dbi) {
rc = mdbx_dbi_open(txn, name, 0, &dbi);
if (rc) {
if (!name ||
rc !=
MDB_INCOMPATIBLE) /* LY: mainDB's record is not a user's DB. */ {
error(" - mdbx_open '%s' failed, error %d %s\n", name ? name : "main",
rc, mdbx_strerror(rc));
}
return rc;
}
}
if (dbi >= 2 /* CORE_DBS */ && name && only_subdb &&
strcmp(only_subdb, name)) {
if (verbose) {
print("Skip processing '%s'...\n", name);
fflush(NULL);
}
skipped_subdb++;
return MDB_SUCCESS;
}
if (!silent && verbose) {
print("Processing '%s'...\n", name ? name : "main");
fflush(NULL);
}
rc = mdbx_dbi_flags(txn, dbi, &flags);
if (rc) {
error(" - mdbx_dbi_flags failed, error %d %s\n", rc, mdbx_strerror(rc));
return rc;
}
rc = mdbx_stat(txn, dbi, &ms, sizeof(ms));
if (rc) {
error(" - mdbx_stat failed, error %d %s\n", rc, mdbx_strerror(rc));
return rc;
}
if (!silent && verbose) {
print(" - dbi-id %d, flags:", dbi);
if (!flags)
print(" none");
else {
for (i = 0; dbflags[i].bit; i++)
if (flags & dbflags[i].bit)
print(" %s", dbflags[i].name);
}
print(" (0x%02X)\n", flags);
if (verbose > 1) {
print(" - page size %u, entries %zu\n", ms.ms_psize, ms.ms_entries);
print(" - b-tree depth %u, pages: branch %zu, leaf %zu, overflow %zu\n",
ms.ms_depth, ms.ms_branch_pages, ms.ms_leaf_pages,
ms.ms_overflow_pages);
}
}
rc = mdbx_cursor_open(txn, dbi, &mc);
if (rc) {
error(" - mdbx_cursor_open failed, error %d %s\n", rc, mdbx_strerror(rc));
return rc;
}
saved_list = problems_push();
prev_key.mv_data = NULL;
prev_data.mv_size = 0;
rc = mdbx_cursor_get(mc, &key, &data, MDB_FIRST);
while (rc == MDB_SUCCESS) {
if (gotsignal) {
print(" - interrupted by signal\n");
fflush(NULL);
rc = EINTR;
goto bailout;
}
if (key.mv_size > maxkeysize) {
problem_add("entry", record_count, "key length exceeds max-key-size",
"%zu > %zu", key.mv_size, maxkeysize);
} else if ((flags & MDB_INTEGERKEY) && key.mv_size != sizeof(size_t) &&
key.mv_size != sizeof(int)) {
problem_add("entry", record_count, "wrong key length", "%zu != %zu",
key.mv_size, sizeof(size_t));
}
if ((flags & MDB_INTEGERDUP) && data.mv_size != sizeof(size_t) &&
data.mv_size != sizeof(int)) {
problem_add("entry", record_count, "wrong data length", "%zu != %zu",
data.mv_size, sizeof(size_t));
}
if (prev_key.mv_data) {
if ((flags & MDB_DUPFIXED) && prev_data.mv_size != data.mv_size) {
problem_add("entry", record_count, "different data length",
"%zu != %zu", prev_data.mv_size, data.mv_size);
}
int cmp = mdbx_cmp(txn, dbi, &prev_key, &key);
if (cmp > 0) {
problem_add("entry", record_count, "broken ordering of entries", NULL);
} else if (cmp == 0) {
++dups;
if (!(flags & MDB_DUPSORT))
problem_add("entry", record_count, "duplicated entries", NULL);
else if (flags & MDB_INTEGERDUP) {
cmp = mdbx_dcmp(txn, dbi, &prev_data, &data);
if (cmp > 0)
problem_add("entry", record_count,
"broken ordering of multi-values", NULL);
}
}
} else if (verbose) {
if (flags & MDB_INTEGERKEY)
print(" - fixed key-size %zu\n", key.mv_size);
if (flags & (MDB_INTEGERDUP | MDB_DUPFIXED))
print(" - fixed data-size %zu\n", data.mv_size);
}
if (handler) {
rc = handler(record_count, &key, &data);
if (rc)
goto bailout;
}
record_count++;
key_bytes += key.mv_size;
data_bytes += data.mv_size;
prev_key = key;
prev_data = data;
rc = mdbx_cursor_get(mc, &key, &data, MDB_NEXT);
}
if (rc != MDB_NOTFOUND)
error(" - mdbx_cursor_get failed, error %d %s\n", rc, mdbx_strerror(rc));
else
rc = 0;
if (record_count != ms.ms_entries)
problem_add("entry", record_count, "differentent number of entries",
"%zu != %zu", record_count, ms.ms_entries);
bailout:
problems_count = problems_pop(saved_list);
if (!silent && verbose) {
print(" - summary: %u records, %u dups, %zu key's bytes, %zu data's "
"bytes, %zu problems\n",
record_count, dups, key_bytes, data_bytes, problems_count);
fflush(NULL);
}
mdbx_cursor_close(mc);
return rc || problems_count;
}
static void usage(char *prog) {
fprintf(stderr,
"usage: %s dbpath [-V] [-v] [-n] [-q] [-w] [-c] [-d] [-s subdb]\n"
" -V\t\tshow version\n"
" -v\t\tmore verbose, could be used multiple times\n"
" -n\t\tNOSUBDIR mode for open\n"
" -q\t\tbe quiet\n"
" -w\t\tlock DB for writing while checking\n"
" -d\t\tdisable page-by-page traversal of b-tree\n"
" -s subdb\tprocess a specific subdatabase only\n"
" -c\t\tforce cooperative mode (don't try exclusive)\n",
prog);
exit(EXIT_INTERRUPTED);
}
const char *meta_synctype(size_t sign) {
switch (sign) {
case 0:
return "no-sync/legacy";
case 1:
return "weak";
default:
return "steady";
}
}
int meta_lt(size_t txn1, size_t sign1, size_t txn2, size_t sign2) {
return ((sign1 > 1) == (sign2 > 1)) ? txn1 < txn2 : txn2 && sign2 > 1;
}
int main(int argc, char *argv[]) {
int i, rc;
char *prog = argv[0];
char *envname;
int envflags = MDB_RDONLY;
int problems_maindb = 0, problems_freedb = 0, problems_meta = 0;
int dont_traversal = 0;
size_t n;
struct timespec timestamp_start, timestamp_finish;
double elapsed;
atexit(pagemap_cleanup);
if (clock_gettime(CLOCK_MONOTONIC, &timestamp_start)) {
rc = errno;
error("clock_gettime failed, error %d %s\n", rc, mdbx_strerror(rc));
return EXIT_FAILURE_SYS;
}
if (argc < 2) {
usage(prog);
}
while ((i = getopt(argc, argv, "Vvqnwcds:")) != EOF) {
switch (i) {
case 'V':
printf("%s\n", MDB_VERSION_STRING);
exit(EXIT_SUCCESS);
break;
case 'v':
verbose++;
break;
case 'q':
quiet = 1;
break;
case 'n':
envflags |= MDB_NOSUBDIR;
break;
case 'w':
envflags &= ~MDB_RDONLY;
break;
case 'c':
exclusive = 0;
break;
case 'd':
dont_traversal = 1;
break;
case 's':
if (only_subdb && strcmp(only_subdb, optarg))
usage(prog);
only_subdb = optarg;
break;
default:
usage(prog);
}
}
if (optind != argc - 1)
usage(prog);
#ifdef SIGPIPE
signal(SIGPIPE, signal_handler);
#endif
#ifdef SIGHUP
signal(SIGHUP, signal_handler);
#endif
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);
envname = argv[optind];
print("Running mdbx_chk for '%s' in %s mode...\n", envname,
(envflags & MDB_RDONLY) ? "read-only" : "write-lock");
fflush(NULL);
rc = mdbx_env_create(&env);
if (rc) {
error("mdbx_env_create failed, error %d %s\n", rc, mdbx_strerror(rc));
return rc < 0 ? EXIT_FAILURE_MDB : EXIT_FAILURE_SYS;
}
rc = mdbx_env_get_maxkeysize(env);
if (rc < 0) {
error("mdbx_env_get_maxkeysize failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto bailout;
}
maxkeysize = rc;
rc = mdbx_env_set_maxdbs(env, MAX_DBI);
if (rc < 0) {
error("mdbx_env_set_maxdbs failed, error %d %s\n", rc, mdbx_strerror(rc));
goto bailout;
}
rc = mdbx_env_open_ex(env, envname, envflags, 0664, &exclusive);
if (rc) {
error("mdbx_env_open failed, error %d %s\n", rc, mdbx_strerror(rc));
goto bailout;
}
if (verbose)
print(" - %s mode\n", exclusive ? "monopolistic" : "cooperative");
if (!(envflags & MDB_RDONLY)) {
rc = mdbx_txn_begin(env, NULL, 0, &locktxn);
if (rc) {
error("mdbx_txn_begin(lock-write) failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto bailout;
}
}
rc = mdbx_txn_begin(env, NULL, MDB_RDONLY, &txn);
if (rc) {
error("mdbx_txn_begin(read-only) failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto bailout;
}
rc = mdbx_env_info(env, &info, sizeof(info));
if (rc) {
error("mdbx_env_info failed, error %d %s\n", rc, mdbx_strerror(rc));
goto bailout;
}
rc = mdbx_env_stat(env, &stat, sizeof(stat));
if (rc) {
error("mdbx_env_stat failed, error %d %s\n", rc, mdbx_strerror(rc));
goto bailout;
}
lastpgno = info.me_last_pgno + 1;
errno = 0;
if (verbose) {
double k = 1024.0;
const char sf[] =
"KMGTPEZY"; /* LY: Kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta! */
for (i = 0; sf[i + 1] && info.me_mapsize / k > 1000.0; ++i)
k *= 1024;
print(" - map size %zu (%.2f %cb)\n", info.me_mapsize, info.me_mapsize / k,
sf[i]);
if (info.me_mapaddr)
print(" - mapaddr %p\n", info.me_mapaddr);
print(" - pagesize %u, max keysize %zu (%s), max readers %u\n",
stat.ms_psize, maxkeysize,
(maxkeysize == 511) ? "default" : (maxkeysize == 0) ? "devel"
: "custom",
info.me_maxreaders);
print(" - transactions: last %zu, bottom %zu, lag reading %zi\n",
info.me_last_txnid, info.me_tail_txnid,
info.me_last_txnid - info.me_tail_txnid);
print(" - meta-1: %s %zu, %s", meta_synctype(info.me_meta1_sign),
info.me_meta1_txnid, meta_lt(info.me_meta1_txnid, info.me_meta1_sign,
info.me_meta2_txnid, info.me_meta2_sign)
? "tail"
: "head");
if (info.me_meta1_txnid > info.me_last_txnid)
print(", rolled-back %zu (%zu >>> %zu)",
info.me_meta1_txnid - info.me_last_txnid, info.me_meta1_txnid,
info.me_last_txnid);
print("\n");
print(" - meta-2: %s %zu, %s", meta_synctype(info.me_meta2_sign),
info.me_meta2_txnid, meta_lt(info.me_meta2_txnid, info.me_meta2_sign,
info.me_meta1_txnid, info.me_meta1_sign)
? "tail"
: "head");
if (info.me_meta2_txnid > info.me_last_txnid)
print(", rolled-back %zu (%zu >>> %zu)",
info.me_meta2_txnid - info.me_last_txnid, info.me_meta2_txnid,
info.me_last_txnid);
print("\n");
}
if (exclusive > 1) {
if (verbose)
print(" - perform full check last-txn-id with meta-pages\n");
if (!meta_lt(info.me_meta1_txnid, info.me_meta1_sign, info.me_meta2_txnid,
info.me_meta2_sign) &&
info.me_meta1_txnid != info.me_last_txnid) {
print(" - meta-1 txn-id mismatch last-txn-id (%zi != %zi)\n",
info.me_meta1_txnid, info.me_last_txnid);
++problems_meta;
}
if (!meta_lt(info.me_meta2_txnid, info.me_meta2_sign, info.me_meta1_txnid,
info.me_meta1_sign) &&
info.me_meta2_txnid != info.me_last_txnid) {
print(" - meta-2 txn-id mismatch last-txn-id (%zi != %zi)\n",
info.me_meta2_txnid, info.me_last_txnid);
++problems_meta;
}
} else if (locktxn) {
if (verbose)
print(" - perform lite check last-txn-id with meta-pages (not a "
"monopolistic mode)\n");
size_t last = (info.me_meta2_txnid > info.me_meta1_txnid)
? info.me_meta2_txnid
: info.me_meta1_txnid;
if (last != info.me_last_txnid) {
print(" - last-meta mismatch last-txn-id (%zi != %zi)\n", last,
info.me_last_txnid);
++problems_meta;
}
} else if (verbose) {
print(" - skip check last-txn-id with meta-pages (monopolistic or "
"write-lock mode only)\n");
}
if (!dont_traversal) {
struct problem *saved_list;
size_t traversal_problems;
size_t empty_pages, lost_bytes;
print("Traversal b-tree...\n");
fflush(NULL);
walk.pagemap = calloc(lastpgno, sizeof(*walk.pagemap));
if (!walk.pagemap) {
rc = errno ? errno : ENOMEM;
error("calloc failed, error %d %s\n", rc, mdbx_strerror(rc));
goto bailout;
}
saved_list = problems_push();
rc = mdbx_env_pgwalk(txn, pgvisitor, NULL);
traversal_problems = problems_pop(saved_list);
if (rc) {
if (rc == EINTR && gotsignal) {
print(" - interrupted by signal\n");
fflush(NULL);
} else {
error("mdbx_env_pgwalk failed, error %d %s\n", rc, mdbx_strerror(rc));
}
goto bailout;
}
for (n = 0; n < lastpgno; ++n)
if (!walk.pagemap[n])
walk.dbi_pages[0] += 1;
empty_pages = lost_bytes = 0;
for (i = 1; i < MAX_DBI && walk.dbi_names[i]; ++i) {
empty_pages += walk.dbi_empty_pages[i];
lost_bytes += walk.dbi_lost_bytes[i];
}
if (verbose) {
size_t total_page_bytes = walk.pgcount * stat.ms_psize;
print(" - dbi pages: %zu total", walk.pgcount);
if (verbose > 1)
for (i = 1; i < MAX_DBI && walk.dbi_names[i]; ++i)
print(", %s %zu", walk.dbi_names[i], walk.dbi_pages[i]);
print(", %s %zu\n", walk.dbi_names[0], walk.dbi_pages[0]);
if (verbose > 1) {
print(" - space info: total %zu bytes, payload %zu (%.1f%%), unused "
"%zu (%.1f%%)\n",
total_page_bytes, walk.total_payload_bytes,
walk.total_payload_bytes * 100.0 / total_page_bytes,
total_page_bytes - walk.total_payload_bytes,
(total_page_bytes - walk.total_payload_bytes) * 100.0 /
total_page_bytes);
for (i = 1; i < MAX_DBI && walk.dbi_names[i]; ++i) {
size_t dbi_bytes = walk.dbi_pages[i] * stat.ms_psize;
print(" %s: subtotal %zu bytes (%.1f%%), payload %zu (%.1f%%), "
"unused %zu (%.1f%%)",
walk.dbi_names[i], dbi_bytes,
dbi_bytes * 100.0 / total_page_bytes, walk.dbi_payload_bytes[i],
walk.dbi_payload_bytes[i] * 100.0 / dbi_bytes,
dbi_bytes - walk.dbi_payload_bytes[i],
(dbi_bytes - walk.dbi_payload_bytes[i]) * 100.0 / dbi_bytes);
if (walk.dbi_empty_pages[i])
print(", %zu empty pages", walk.dbi_empty_pages[i]);
if (walk.dbi_lost_bytes[i])
print(", %zu bytes lost", walk.dbi_lost_bytes[i]);
print("\n");
}
}
print(" - summary: average fill %.1f%%",
walk.total_payload_bytes * 100.0 / total_page_bytes);
if (empty_pages)
print(", %zu empty pages", empty_pages);
if (lost_bytes)
print(", %zu bytes lost", lost_bytes);
print(", %zu problems\n", traversal_problems);
}
} else if (verbose) {
print("Skipping b-tree walk...\n");
fflush(NULL);
}
if (!verbose)
print("Iterating DBIs...\n");
problems_maindb = process_db(-1, /* MAIN_DBI */ NULL, NULL, 0);
problems_freedb = process_db(0 /* FREE_DBI */, "free", handle_freedb, 0);
if (verbose) {
size_t value = info.me_mapsize / stat.ms_psize;
double percent = value / 100.0;
print(" - pages info: %zu total", value);
print(", allocated %zu (%.1f%%)", lastpgno, lastpgno / percent);
if (verbose > 1) {
value = info.me_mapsize / stat.ms_psize - lastpgno;
print(", remained %zu (%.1f%%)", value, value / percent);
value = lastpgno - freedb_pages;
print(", used %zu (%.1f%%)", value, value / percent);
print(", gc %zu (%.1f%%)", freedb_pages, freedb_pages / percent);
value = freedb_pages - reclaimable_pages;
print(", detained %zu (%.1f%%)", value, value / percent);
print(", reclaimable %zu (%.1f%%)", reclaimable_pages,
reclaimable_pages / percent);
}
value = info.me_mapsize / stat.ms_psize - lastpgno + reclaimable_pages;
print(", available %zu (%.1f%%)\n", value, value / percent);
}
if (problems_maindb == 0 && problems_freedb == 0) {
if (!dont_traversal && (exclusive || locktxn)) {
if (walk.pgcount != lastpgno - freedb_pages) {
error("used pages mismatch (%zu != %zu)\n", walk.pgcount,
lastpgno - freedb_pages);
}
if (walk.dbi_pages[0] != freedb_pages) {
error("gc pages mismatch (%zu != %zu)\n", walk.dbi_pages[0],
freedb_pages);
}
} else if (verbose) {
print(" - skip check used and gc pages (btree-traversal with "
"monopolistic or write-lock mode only)\n");
}
if (!process_db(-1, NULL, handle_maindb, 1)) {
if (!userdb_count && verbose)
print(" - does not contain multiple databases\n");
}
}
bailout:
if (txn)
mdbx_txn_abort(txn);
if (locktxn)
mdbx_txn_abort(locktxn);
if (env)
mdbx_env_close(env);
fflush(NULL);
if (rc) {
if (rc < 0)
return gotsignal ? EXIT_INTERRUPTED : EXIT_FAILURE_SYS;
return EXIT_FAILURE_MDB;
}
if (clock_gettime(CLOCK_MONOTONIC, &timestamp_finish)) {
rc = errno;
error("clock_gettime failed, error %d %s\n", rc, mdbx_strerror(rc));
return EXIT_FAILURE_SYS;
}
elapsed = timestamp_finish.tv_sec - timestamp_start.tv_sec +
(timestamp_finish.tv_nsec - timestamp_start.tv_nsec) * 1e-9;
total_problems += problems_meta;
if (total_problems || problems_maindb || problems_freedb) {
print("Total %zu error(s) is detected, elapsed %.3f seconds.\n",
total_problems, elapsed);
if (problems_meta || problems_maindb || problems_freedb)
return EXIT_FAILURE_CHECK_MAJOR;
return EXIT_FAILURE_CHECK_MINOR;
}
print("No error is detected, elapsed %.3f seconds\n", elapsed);
return EXIT_SUCCESS;
}

57
src/mdbx_copy.1 Normal file
View File

@@ -0,0 +1,57 @@
.\" Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
.\" Copyright 2012-2017 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copyright 2015,2016 Peter-Service R&D LLC <http://billing.ru/>.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.TH MDB_COPY 1 "2014/06/20" "LMDB 0.9.14"
.SH NAME
mdbx_copy \- LMDB environment copy tool
.SH SYNOPSIS
.B mdbx_copy
[\c
.BR \-V ]
[\c
.BR \-c ]
[\c
.BR \-n ]
.B srcpath
[\c
.BR dstpath ]
.SH DESCRIPTION
The
.B mdbx_copy
utility copies an LMDB environment. The environment can
be copied regardless of whether it is currently in use.
No lockfile is created, since it gets recreated at need.
If
.I dstpath
is specified it must be the path of an empty directory
for storing the backup. Otherwise, the backup will be
written to stdout.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-c
Compact while copying. Only current data pages will be copied; freed
or unused pages will be omitted from the copy. This option will
slow down the backup process as it is more CPU-intensive.
Currently it fails if the environment has suffered a page leak.
.TP
.BR \-n
Open LDMB environment(s) which do not use subdirectories.
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
.SH CAVEATS
This utility can trigger significant file size growth if run
in parallel with write transactions, because pages which they
free during copying cannot be reused until the copy is done.
.SH "SEE ALSO"
.BR mdbx_stat (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>

76
src/mdbx_copy.c Normal file
View File

@@ -0,0 +1,76 @@
/* mdbx_copy.c - memory-mapped database backup tool */
/*
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
* Copyright 2012-2017 Howard Chu, Symas Corp.
* Copyright 2015,2016 Peter-Service R&D LLC.
* 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>.
*/
#include "mdbx.h"
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
static void sighandle(int sig) { (void)sig; }
int main(int argc, char *argv[]) {
int rc;
MDB_env *env = NULL;
const char *progname = argv[0], *act;
unsigned flags = MDB_RDONLY;
unsigned cpflags = 0;
for (; argc > 1 && argv[1][0] == '-'; argc--, argv++) {
if (argv[1][1] == 'n' && argv[1][2] == '\0')
flags |= MDB_NOSUBDIR;
else if (argv[1][1] == 'c' && argv[1][2] == '\0')
cpflags |= MDB_CP_COMPACT;
else if (argv[1][1] == 'V' && argv[1][2] == '\0') {
printf("%s\n", MDB_VERSION_STRING);
exit(0);
} else
argc = 0;
}
if (argc < 2 || argc > 3) {
fprintf(stderr, "usage: %s [-V] [-c] [-n] srcpath [dstpath]\n", progname);
exit(EXIT_FAILURE);
}
#ifdef SIGPIPE
signal(SIGPIPE, sighandle);
#endif
#ifdef SIGHUP
signal(SIGHUP, sighandle);
#endif
signal(SIGINT, sighandle);
signal(SIGTERM, sighandle);
act = "opening environment";
rc = mdbx_env_create(&env);
if (rc == MDB_SUCCESS) {
rc = mdbx_env_open(env, argv[1], flags, 0640);
}
if (rc == MDB_SUCCESS) {
act = "copying";
if (argc == 2)
rc = mdbx_env_copyfd2(env, STDOUT_FILENO, cpflags);
else
rc = mdbx_env_copy2(env, argv[2], cpflags);
}
if (rc)
fprintf(stderr, "%s: %s failed, error %d (%s)\n", progname, act, rc,
mdbx_strerror(rc));
mdbx_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}

77
src/mdbx_dump.1 Normal file
View File

@@ -0,0 +1,77 @@
.\" Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
.\" Copyright 2014-2017 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copyright 2015,2016 Peter-Service R&D LLC <http://billing.ru/>.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.TH MDB_DUMP 1 "2014/06/20" "LMDB 0.9.14"
.SH NAME
mdbx_dump \- LMDB environment export tool
.SH SYNOPSIS
.B mdbx_dump
[\c
.BR \-V ]
[\c
.BI \-f \ file\fR]
[\c
.BR \-l ]
[\c
.BR \-n ]
[\c
.BR \-p ]
[\c
.BR \-a \ |
.BI \-s \ subdb\fR]
.BR \ envpath
.SH DESCRIPTION
The
.B mdbx_dump
utility reads a database and writes its contents to the
standard output using a portable flat-text format
understood by the
.BR mdbx_load (1)
utility.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-f \ file
Write to the specified file instead of to the standard output.
.TP
.BR \-l
List the databases stored in the environment. Just the
names will be listed, no data will be output.
.TP
.BR \-n
Dump an LMDB database which does not use subdirectories.
.TP
.BR \-p
If characters in either the key or data items are printing characters (as
defined by isprint(3)), output them directly. This option permits users to
use standard text editors and tools to modify the contents of databases.
Note: different systems may have different notions about what characters
are considered printing characters, and databases dumped in this manner may
be less portable to external systems.
.TP
.BR \-a
Dump all of the subdatabases in the environment.
.TP
.BR \-s \ subdb
Dump a specific subdatabase. If no database is specified, only the main database is dumped.
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
Dumping and reloading databases that use user-defined comparison functions
will result in new databases that use the default comparison functions.
\fBIn this case it is quite likely that the reloaded database will be
damaged beyond repair permitting neither record storage nor retrieval.\fP
The only available workaround is to modify the source for the
.BR mdbx_load (1)
utility to load the database using the correct comparison functions.
.SH "SEE ALSO"
.BR mdbx_load (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>

316
src/mdbx_dump.c Normal file
View File

@@ -0,0 +1,316 @@
/* mdbx_dump.c - memory-mapped database dump tool */
/*
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
* Copyright 2011-2017 Howard Chu, Symas Corp.
* Copyright 2015,2016 Peter-Service R&D LLC.
* 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>.
*/
#include "mdbx.h"
#include <ctype.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define PRINT 1
static int mode;
typedef struct flagbit {
int bit;
char *name;
} flagbit;
flagbit dbflags[] = {{MDB_REVERSEKEY, "reversekey"},
{MDB_DUPSORT, "dupsort"},
{MDB_INTEGERKEY, "integerkey"},
{MDB_DUPFIXED, "dupfixed"},
{MDB_INTEGERDUP, "integerdup"},
{MDB_REVERSEDUP, "reversedup"},
{0, NULL}};
static volatile sig_atomic_t gotsig;
static void dumpsig(int sig) {
(void)sig;
gotsig = 1;
}
static const char hexc[] = "0123456789abcdef";
static void hex(unsigned char c) {
putchar(hexc[c >> 4]);
putchar(hexc[c & 0xf]);
}
static void text(MDB_val *v) {
unsigned char *c, *end;
putchar(' ');
c = v->mv_data;
end = c + v->mv_size;
while (c < end) {
if (isprint(*c)) {
putchar(*c);
} else {
putchar('\\');
hex(*c);
}
c++;
}
putchar('\n');
}
static void byte(MDB_val *v) {
unsigned char *c, *end;
putchar(' ');
c = v->mv_data;
end = c + v->mv_size;
while (c < end) {
hex(*c++);
}
putchar('\n');
}
/* Dump in BDB-compatible format */
static int dumpit(MDB_txn *txn, MDB_dbi dbi, char *name) {
MDB_cursor *mc;
MDBX_stat ms;
MDB_val key, data;
MDBX_envinfo info;
unsigned int flags;
int rc, i;
rc = mdbx_dbi_flags(txn, dbi, &flags);
if (rc)
return rc;
rc = mdbx_stat(txn, dbi, &ms, sizeof(ms));
if (rc)
return rc;
rc = mdbx_env_info(mdbx_txn_env(txn), &info, sizeof(info));
if (rc)
return rc;
printf("VERSION=3\n");
printf("format=%s\n", mode & PRINT ? "print" : "bytevalue");
if (name)
printf("database=%s\n", name);
printf("type=btree\n");
printf("mapsize=%zu\n", info.me_mapsize);
if (info.me_mapaddr)
printf("mapaddr=%p\n", info.me_mapaddr);
printf("maxreaders=%u\n", info.me_maxreaders);
for (i = 0; dbflags[i].bit; i++)
if (flags & dbflags[i].bit)
printf("%s=1\n", dbflags[i].name);
printf("db_pagesize=%d\n", ms.ms_psize);
printf("HEADER=END\n");
rc = mdbx_cursor_open(txn, dbi, &mc);
if (rc)
return rc;
while ((rc = mdbx_cursor_get(mc, &key, &data, MDB_NEXT)) == MDB_SUCCESS) {
if (gotsig) {
rc = EINTR;
break;
}
if (mode & PRINT) {
text(&key);
text(&data);
} else {
byte(&key);
byte(&data);
}
}
printf("DATA=END\n");
if (rc == MDB_NOTFOUND)
rc = MDB_SUCCESS;
return rc;
}
static void usage(char *prog) {
fprintf(stderr,
"usage: %s [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb] dbpath\n",
prog);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) {
int i, rc;
MDB_env *env;
MDB_txn *txn;
MDB_dbi dbi;
char *prog = argv[0];
char *envname;
char *subname = NULL;
int alldbs = 0, envflags = 0, list = 0;
if (argc < 2) {
usage(prog);
}
/* -a: dump main DB and all subDBs
* -s: dump only the named subDB
* -n: use NOSUBDIR flag on env_open
* -p: use printable characters
* -f: write to file instead of stdout
* -V: print version and exit
* (default) dump only the main DB
*/
while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) {
switch (i) {
case 'V':
printf("%s\n", MDB_VERSION_STRING);
exit(0);
break;
case 'l':
list = 1;
/*FALLTHROUGH*/;
case 'a':
if (subname)
usage(prog);
alldbs++;
break;
case 'f':
if (freopen(optarg, "w", stdout) == NULL) {
fprintf(stderr, "%s: %s: reopen: %s\n", prog, optarg, strerror(errno));
exit(EXIT_FAILURE);
}
break;
case 'n':
envflags |= MDB_NOSUBDIR;
break;
case 'p':
mode |= PRINT;
break;
case 's':
if (alldbs)
usage(prog);
subname = optarg;
break;
default:
usage(prog);
}
}
if (optind != argc - 1)
usage(prog);
#ifdef SIGPIPE
signal(SIGPIPE, dumpsig);
#endif
#ifdef SIGHUP
signal(SIGHUP, dumpsig);
#endif
signal(SIGINT, dumpsig);
signal(SIGTERM, dumpsig);
envname = argv[optind];
rc = mdbx_env_create(&env);
if (rc) {
fprintf(stderr, "mdbx_env_create failed, error %d %s\n", rc,
mdbx_strerror(rc));
return EXIT_FAILURE;
}
if (alldbs || subname) {
mdbx_env_set_maxdbs(env, 2);
}
rc = mdbx_env_open(env, envname, envflags | MDB_RDONLY, 0664);
if (rc) {
fprintf(stderr, "mdbx_env_open failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto env_close;
}
rc = mdbx_txn_begin(env, NULL, MDB_RDONLY, &txn);
if (rc) {
fprintf(stderr, "mdbx_txn_begin failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto env_close;
}
rc = mdbx_dbi_open(txn, subname, 0, &dbi);
if (rc) {
fprintf(stderr, "mdbx_open failed, error %d %s\n", rc, mdbx_strerror(rc));
goto txn_abort;
}
if (alldbs) {
MDB_cursor *cursor;
MDB_val key;
int count = 0;
rc = mdbx_cursor_open(txn, dbi, &cursor);
if (rc) {
fprintf(stderr, "mdbx_cursor_open failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto txn_abort;
}
while ((rc = mdbx_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
char *str;
MDB_dbi db2;
if (memchr(key.mv_data, '\0', key.mv_size))
continue;
count++;
str = malloc(key.mv_size + 1);
memcpy(str, key.mv_data, key.mv_size);
str[key.mv_size] = '\0';
rc = mdbx_dbi_open(txn, str, 0, &db2);
if (rc == MDB_SUCCESS) {
if (list) {
printf("%s\n", str);
list++;
} else {
rc = dumpit(txn, db2, str);
if (rc)
break;
}
mdbx_dbi_close(env, db2);
}
free(str);
if (rc)
continue;
}
mdbx_cursor_close(cursor);
if (!count) {
fprintf(stderr, "%s: %s does not contain multiple databases\n", prog,
envname);
rc = MDB_NOTFOUND;
} else if (rc == MDB_INCOMPATIBLE) {
/* LY: the record it not a named sub-db. */
rc = MDB_SUCCESS;
}
} else {
rc = dumpit(txn, dbi, subname);
}
if (rc && rc != MDB_NOTFOUND)
fprintf(stderr, "%s: %s: %s\n", prog, envname, mdbx_strerror(rc));
mdbx_dbi_close(env, dbi);
txn_abort:
mdbx_txn_abort(txn);
env_close:
mdbx_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}

79
src/mdbx_load.1 Normal file
View File

@@ -0,0 +1,79 @@
.\" Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
.\" Copyright 2014-2017 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copyright 2015,2016 Peter-Service R&D LLC <http://billing.ru/>.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.TH MDB_LOAD 1 "2014/06/20" "LMDB 0.9.14"
.SH NAME
mdbx_load \- LMDB environment import tool
.SH SYNOPSIS
.B mdbx_load
[\c
.BR \-V ]
[\c
.BI \-f \ file\fR]
[\c
.BR \-n ]
[\c
.BI \-s \ subdb\fR]
[\c
.BR \-N ]
[\c
.BR \-T ]
.BR \ envpath
.SH DESCRIPTION
The
.B mdbx_load
utility reads from the standard input and loads it into the
LMDB environment
.BR envpath .
The input to
.B mdbx_load
must be in the output format specified by the
.BR mdbx_dump (1)
utility or as specified by the
.B -T
option below.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-f \ file
Read from the specified file instead of from the standard input.
.TP
.BR \-n
Load an LMDB database which does not use subdirectories.
.TP
.BR \-s \ subdb
Load a specific subdatabase. If no database is specified, data is loaded into the main database.
.TP
.BR \-N
Don't overwrite existing records when loading into an already existing database; just skip them.
.TP
.BR \-T
Load data from simple text files. The input must be paired lines of text, where the first
line of the pair is the key item, and the second line of the pair is its corresponding
data item.
A simple escape mechanism, where newline and backslash (\\) characters are special, is
applied to the text input. Newline characters are interpreted as record separators.
Backslash characters in the text will be interpreted in one of two ways: If the backslash
character precedes another backslash character, the pair will be interpreted as a literal
backslash. If the backslash character precedes any other character, the two characters
following the backslash will be interpreted as a hexadecimal specification of a single
character; for example, \\0a is a newline character in the ASCII character set.
For this reason, any backslash or newline characters that naturally occur in the text
input must be escaped to avoid misinterpretation by
.BR mdbx_load .
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
.SH "SEE ALSO"
.BR mdbx_dump (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>

466
src/mdbx_load.c Normal file
View File

@@ -0,0 +1,466 @@
/* mdbx_load.c - memory-mapped database load tool */
/*
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
* Copyright 2011-2017 Howard Chu, Symas Corp.
* Copyright 2015,2016 Peter-Service R&D LLC.
* 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>.
*/
#include "mdbx.h"
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define PRINT 1
#define NOHDR 2
static int mode;
static char *subname = NULL;
static size_t lineno;
static int version;
static int dbi_flags;
static char *prog;
static int Eof;
static MDBX_envinfo info;
static MDB_val kbuf, dbuf;
#define STRLENOF(s) (sizeof(s) - 1)
typedef struct flagbit {
int bit;
char *name;
int len;
} flagbit;
#define S(s) s, STRLENOF(s)
flagbit dbflags[] = {{MDB_REVERSEKEY, S("reversekey")},
{MDB_DUPSORT, S("dupsort")},
{MDB_INTEGERKEY, S("integerkey")},
{MDB_DUPFIXED, S("dupfixed")},
{MDB_INTEGERDUP, S("integerdup")},
{MDB_REVERSEDUP, S("reversedup")},
{0, NULL, 0}};
static void readhdr(void) {
char *ptr;
dbi_flags = 0;
while (fgets(dbuf.mv_data, dbuf.mv_size, stdin) != NULL) {
lineno++;
if (!strncmp(dbuf.mv_data, "db_pagesize=", STRLENOF("db_pagesize=")) ||
!strncmp(dbuf.mv_data, "duplicates=", STRLENOF("duplicates="))) {
/* LY: silently ignore information fields. */
continue;
} else if (!strncmp(dbuf.mv_data, "VERSION=", STRLENOF("VERSION="))) {
version = atoi((char *)dbuf.mv_data + STRLENOF("VERSION="));
if (version > 3) {
fprintf(stderr, "%s: line %zd: unsupported VERSION %d\n", prog, lineno,
version);
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "HEADER=END", STRLENOF("HEADER=END"))) {
break;
} else if (!strncmp(dbuf.mv_data, "format=", STRLENOF("format="))) {
if (!strncmp((char *)dbuf.mv_data + STRLENOF("FORMAT="), "print",
STRLENOF("print")))
mode |= PRINT;
else if (strncmp((char *)dbuf.mv_data + STRLENOF("FORMAT="), "bytevalue",
STRLENOF("bytevalue"))) {
fprintf(stderr, "%s: line %zd: unsupported FORMAT %s\n", prog, lineno,
(char *)dbuf.mv_data + STRLENOF("FORMAT="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "database=", STRLENOF("database="))) {
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr)
*ptr = '\0';
if (subname)
free(subname);
subname = strdup((char *)dbuf.mv_data + STRLENOF("database="));
} else if (!strncmp(dbuf.mv_data, "type=", STRLENOF("type="))) {
if (strncmp((char *)dbuf.mv_data + STRLENOF("type="), "btree",
STRLENOF("btree"))) {
fprintf(stderr, "%s: line %zd: unsupported type %s\n", prog, lineno,
(char *)dbuf.mv_data + STRLENOF("type="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "mapaddr=", STRLENOF("mapaddr="))) {
int i;
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr)
*ptr = '\0';
i = sscanf((char *)dbuf.mv_data + STRLENOF("mapaddr="), "%p",
&info.me_mapaddr);
if (i != 1) {
fprintf(stderr, "%s: line %zd: invalid mapaddr %s\n", prog, lineno,
(char *)dbuf.mv_data + STRLENOF("mapaddr="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "mapsize=", STRLENOF("mapsize="))) {
int i;
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr)
*ptr = '\0';
i = sscanf((char *)dbuf.mv_data + STRLENOF("mapsize="), "%zu",
&info.me_mapsize);
if (i != 1) {
fprintf(stderr, "%s: line %zd: invalid mapsize %s\n", prog, lineno,
(char *)dbuf.mv_data + STRLENOF("mapsize="));
exit(EXIT_FAILURE);
}
} else if (!strncmp(dbuf.mv_data, "maxreaders=", STRLENOF("maxreaders="))) {
int i;
ptr = memchr(dbuf.mv_data, '\n', dbuf.mv_size);
if (ptr)
*ptr = '\0';
i = sscanf((char *)dbuf.mv_data + STRLENOF("maxreaders="), "%u",
&info.me_maxreaders);
if (i != 1) {
fprintf(stderr, "%s: line %zd: invalid maxreaders %s\n", prog, lineno,
(char *)dbuf.mv_data + STRLENOF("maxreaders="));
exit(EXIT_FAILURE);
}
} else {
int i;
for (i = 0; dbflags[i].bit; i++) {
if (!strncmp(dbuf.mv_data, dbflags[i].name, dbflags[i].len) &&
((char *)dbuf.mv_data)[dbflags[i].len] == '=') {
if (((char *)dbuf.mv_data)[dbflags[i].len + 1] == '1')
dbi_flags |= dbflags[i].bit;
break;
}
}
if (!dbflags[i].bit) {
ptr = memchr(dbuf.mv_data, '=', dbuf.mv_size);
if (!ptr) {
fprintf(stderr, "%s: line %zd: unexpected format\n", prog, lineno);
exit(EXIT_FAILURE);
} else {
*ptr = '\0';
fprintf(stderr, "%s: line %zd: unrecognized keyword ignored: %s\n",
prog, lineno, (char *)dbuf.mv_data);
}
}
}
}
}
static void badend(void) {
fprintf(stderr, "%s: line %zd: unexpected end of input\n", prog, lineno);
}
static int unhex(unsigned char *c2) {
int x, c;
x = *c2++ & 0x4f;
if (x & 0x40)
x -= 55;
c = x << 4;
x = *c2 & 0x4f;
if (x & 0x40)
x -= 55;
c |= x;
return c;
}
static int readline(MDB_val *out, MDB_val *buf) {
unsigned char *c1, *c2, *end;
size_t len, l2;
int c;
if (!(mode & NOHDR)) {
c = fgetc(stdin);
if (c == EOF) {
Eof = 1;
return EOF;
}
if (c != ' ') {
lineno++;
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
badend:
Eof = 1;
badend();
return EOF;
}
if (c == 'D' && !strncmp(buf->mv_data, "ATA=END", STRLENOF("ATA=END")))
return EOF;
goto badend;
}
}
if (fgets(buf->mv_data, buf->mv_size, stdin) == NULL) {
Eof = 1;
return EOF;
}
lineno++;
c1 = buf->mv_data;
len = strlen((char *)c1);
l2 = len;
/* Is buffer too short? */
while (c1[len - 1] != '\n') {
buf->mv_data = realloc(buf->mv_data, buf->mv_size * 2);
if (!buf->mv_data) {
Eof = 1;
fprintf(stderr, "%s: line %zd: out of memory, line too long\n", prog,
lineno);
return EOF;
}
c1 = buf->mv_data;
c1 += l2;
if (fgets((char *)c1, buf->mv_size + 1, stdin) == NULL) {
Eof = 1;
badend();
return EOF;
}
buf->mv_size *= 2;
len = strlen((char *)c1);
l2 += len;
}
c1 = c2 = buf->mv_data;
len = l2;
c1[--len] = '\0';
end = c1 + len;
if (mode & PRINT) {
while (c2 < end) {
if (*c2 == '\\') {
if (c2[1] == '\\') {
c1++;
c2 += 2;
} else {
if (c2 + 3 > end || !isxdigit(c2[1]) || !isxdigit(c2[2])) {
Eof = 1;
badend();
return EOF;
}
*c1++ = unhex(++c2);
c2 += 2;
}
} else {
/* copies are redundant when no escapes were used */
*c1++ = *c2++;
}
}
} else {
/* odd length not allowed */
if (len & 1) {
Eof = 1;
badend();
return EOF;
}
while (c2 < end) {
if (!isxdigit(*c2) || !isxdigit(c2[1])) {
Eof = 1;
badend();
return EOF;
}
*c1++ = unhex(c2);
c2 += 2;
}
}
c2 = out->mv_data = buf->mv_data;
out->mv_size = c1 - c2;
return 0;
}
static void usage(void) {
fprintf(stderr, "usage: %s [-V] [-f input] [-n] [-s name] [-N] [-T] dbpath\n",
prog);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) {
int i, rc;
MDB_env *env;
MDB_txn *txn;
MDB_cursor *mc;
MDB_dbi dbi;
char *envname;
int envflags = 0, putflags = 0;
prog = argv[0];
if (argc < 2) {
usage();
}
/* -f: load file instead of stdin
* -n: use NOSUBDIR flag on env_open
* -s: load into named subDB
* -N: use NOOVERWRITE on puts
* -T: read plaintext
* -V: print version and exit
*/
while ((i = getopt(argc, argv, "f:ns:NTV")) != EOF) {
switch (i) {
case 'V':
printf("%s\n", MDB_VERSION_STRING);
exit(0);
break;
case 'f':
if (freopen(optarg, "r", stdin) == NULL) {
fprintf(stderr, "%s: %s: reopen: %s\n", prog, optarg, strerror(errno));
exit(EXIT_FAILURE);
}
break;
case 'n':
envflags |= MDB_NOSUBDIR;
break;
case 's':
subname = strdup(optarg);
break;
case 'N':
putflags = MDB_NOOVERWRITE | MDB_NODUPDATA;
break;
case 'T':
mode |= NOHDR | PRINT;
break;
default:
usage();
}
}
if (optind != argc - 1)
usage();
dbuf.mv_size = 4096;
dbuf.mv_data = malloc(dbuf.mv_size);
if (!(mode & NOHDR))
readhdr();
envname = argv[optind];
rc = mdbx_env_create(&env);
if (rc) {
fprintf(stderr, "mdbx_env_create failed, error %d %s\n", rc,
mdbx_strerror(rc));
return EXIT_FAILURE;
}
mdbx_env_set_maxdbs(env, 2);
if (info.me_maxreaders)
mdbx_env_set_maxreaders(env, info.me_maxreaders);
if (info.me_mapsize)
mdbx_env_set_mapsize(env, info.me_mapsize);
if (info.me_mapaddr)
envflags |= MDB_FIXEDMAP;
rc = mdbx_env_open(env, envname, envflags, 0664);
if (rc) {
fprintf(stderr, "mdbx_env_open failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto env_close;
}
kbuf.mv_size = mdbx_env_get_maxkeysize(env) * 2 + 2;
kbuf.mv_data = malloc(kbuf.mv_size);
while (!Eof) {
MDB_val key, data;
int batch = 0;
rc = mdbx_txn_begin(env, NULL, 0, &txn);
if (rc) {
fprintf(stderr, "mdbx_txn_begin failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto env_close;
}
rc = mdbx_dbi_open(txn, subname, dbi_flags | MDB_CREATE, &dbi);
if (rc) {
fprintf(stderr, "mdbx_open failed, error %d %s\n", rc, mdbx_strerror(rc));
goto txn_abort;
}
rc = mdbx_cursor_open(txn, dbi, &mc);
if (rc) {
fprintf(stderr, "mdbx_cursor_open failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto txn_abort;
}
while (1) {
rc = readline(&key, &kbuf);
if (rc) /* rc == EOF */
break;
rc = readline(&data, &dbuf);
if (rc) {
fprintf(stderr, "%s: line %zd: failed to read key value\n", prog,
lineno);
goto txn_abort;
}
rc = mdbx_cursor_put(mc, &key, &data, putflags);
if (rc == MDB_KEYEXIST && putflags)
continue;
if (rc) {
fprintf(stderr, "mdbx_cursor_put failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto txn_abort;
}
batch++;
if (batch == 100) {
rc = mdbx_txn_commit(txn);
if (rc) {
fprintf(stderr, "%s: line %zd: txn_commit: %s\n", prog, lineno,
mdbx_strerror(rc));
goto env_close;
}
rc = mdbx_txn_begin(env, NULL, 0, &txn);
if (rc) {
fprintf(stderr, "mdbx_txn_begin failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto env_close;
}
rc = mdbx_cursor_open(txn, dbi, &mc);
if (rc) {
fprintf(stderr, "mdbx_cursor_open failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto txn_abort;
}
batch = 0;
}
}
rc = mdbx_txn_commit(txn);
txn = NULL;
if (rc) {
fprintf(stderr, "%s: line %zd: txn_commit: %s\n", prog, lineno,
mdbx_strerror(rc));
goto env_close;
}
mdbx_dbi_close(env, dbi);
if (!(mode & NOHDR))
readhdr();
}
txn_abort:
mdbx_txn_abort(txn);
env_close:
mdbx_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}

66
src/mdbx_stat.1 Normal file
View File

@@ -0,0 +1,66 @@
.\" Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
.\" Copyright 2012-2017 Howard Chu, Symas Corp. All Rights Reserved.
.\" Copyright 2015,2016 Peter-Service R&D LLC <http://billing.ru/>.
.\" Copying restrictions apply. See COPYRIGHT/LICENSE.
.TH MDB_STAT 1 "2014/06/20" "LMDB 0.9.14"
.SH NAME
mdbx_stat \- LMDB environment status tool
.SH SYNOPSIS
.B mdbx_stat
[\c
.BR \-V ]
[\c
.BR \-e ]
[\c
.BR \-f [ f [ f ]]]
[\c
.BR \-n ]
[\c
.BR \-r [ r ]]
[\c
.BR \-a \ |
.BI \-s \ subdb\fR]
.BR \ envpath
.SH DESCRIPTION
The
.B mdbx_stat
utility displays the status of an LMDB environment.
.SH OPTIONS
.TP
.BR \-V
Write the library version number to the standard output, and exit.
.TP
.BR \-e
Display information about the database environment.
.TP
.BR \-f
Display information about the environment freelist.
If \fB\-ff\fP is given, summarize each freelist entry.
If \fB\-fff\fP is given, display the full list of page IDs in the freelist.
.TP
.BR \-n
Display the status of an LMDB database which does not use subdirectories.
.TP
.BR \-r
Display information about the environment reader table.
Shows the process ID, thread ID, and transaction ID for each active
reader slot. The process ID and transaction ID are in decimal, the
thread ID is in hexadecimal. The transaction ID is displayed as "-"
if the reader does not currently have a read transaction open.
If \fB\-rr\fP is given, check for stale entries in the reader
table and clear them. The reader table will be printed again
after the check is performed.
.TP
.BR \-a
Display the status of all of the subdatabases in the environment.
.TP
.BR \-s \ subdb
Display the status of a specific subdatabase.
.SH DIAGNOSTICS
Exit status is zero if no errors occur.
Errors result in a non-zero exit status and
a diagnostic message being written to standard error.
.SH "SEE ALSO"
.BR mdbx_copy (1)
.SH AUTHOR
Howard Chu of Symas Corporation <http://www.symas.com>

306
src/mdbx_stat.c Normal file
View File

@@ -0,0 +1,306 @@
/* mdbx_stat.c - memory-mapped database status tool */
/*
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
* Copyright 2011-2017 Howard Chu, Symas Corp.
* Copyright 2015,2016 Peter-Service R&D LLC.
* 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>.
*/
#include "mdbx.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
static void prstat(MDBX_stat *ms) {
printf(" Page size: %u\n", ms->ms_psize);
printf(" Tree depth: %u\n", ms->ms_depth);
printf(" Branch pages: %zu\n", ms->ms_branch_pages);
printf(" Leaf pages: %zu\n", ms->ms_leaf_pages);
printf(" Overflow pages: %zu\n", ms->ms_overflow_pages);
printf(" Entries: %zu\n", ms->ms_entries);
}
static void usage(char *prog) {
fprintf(stderr,
"usage: %s [-V] [-n] [-e] [-r[r]] [-f[f[f]]] [-a|-s subdb] dbpath\n",
prog);
exit(EXIT_FAILURE);
}
int main(int argc, char *argv[]) {
int i, rc;
MDB_env *env;
MDB_txn *txn;
MDB_dbi dbi;
MDBX_stat mst;
MDBX_envinfo mei;
char *prog = argv[0];
char *envname;
char *subname = NULL;
int alldbs = 0, envinfo = 0, envflags = 0, freinfo = 0, rdrinfo = 0;
if (argc < 2) {
usage(prog);
}
/* -a: print stat of main DB and all subDBs
* -s: print stat of only the named subDB
* -e: print env info
* -f: print freelist info
* -r: print reader info
* -n: use NOSUBDIR flag on env_open
* -V: print version and exit
* (default) print stat of only the main DB
*/
while ((i = getopt(argc, argv, "Vaefnrs:")) != EOF) {
switch (i) {
case 'V':
printf("%s\n", MDB_VERSION_STRING);
exit(0);
break;
case 'a':
if (subname)
usage(prog);
alldbs++;
break;
case 'e':
envinfo++;
break;
case 'f':
freinfo++;
break;
case 'n':
envflags |= MDB_NOSUBDIR;
break;
case 'r':
rdrinfo++;
break;
case 's':
if (alldbs)
usage(prog);
subname = optarg;
break;
default:
usage(prog);
}
}
if (optind != argc - 1)
usage(prog);
envname = argv[optind];
rc = mdbx_env_create(&env);
if (rc) {
fprintf(stderr, "mdbx_env_create failed, error %d %s\n", rc,
mdbx_strerror(rc));
return EXIT_FAILURE;
}
if (alldbs || subname) {
mdbx_env_set_maxdbs(env, 4);
}
rc = mdbx_env_open(env, envname, envflags | MDB_RDONLY, 0664);
if (rc) {
fprintf(stderr, "mdbx_env_open failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto env_close;
}
if (envinfo) {
(void)mdbx_env_stat(env, &mst, sizeof(mst));
(void)mdbx_env_info(env, &mei, sizeof(mei));
printf("Environment Info\n");
printf(" Map address: %p\n", mei.me_mapaddr);
printf(" Map size: %zu\n", mei.me_mapsize);
printf(" Page size: %u\n", mst.ms_psize);
printf(" Max pages: %zu\n", mei.me_mapsize / mst.ms_psize);
printf(" Number of pages used: %zu\n", mei.me_last_pgno + 1);
printf(" Last transaction ID: %zu\n", mei.me_last_txnid);
printf(" Tail transaction ID: %zu (%zi)\n", mei.me_tail_txnid,
mei.me_tail_txnid - mei.me_last_txnid);
printf(" Max readers: %u\n", mei.me_maxreaders);
printf(" Number of readers used: %u\n", mei.me_numreaders);
} else {
/* LY: zap warnings from gcc */
memset(&mst, 0, sizeof(mst));
memset(&mei, 0, sizeof(mei));
}
if (rdrinfo) {
printf("Reader Table Status\n");
rc = mdbx_reader_list(env, (MDB_msg_func *)fputs, stdout);
if (rdrinfo > 1) {
int dead;
mdbx_reader_check(env, &dead);
printf(" %d stale readers cleared.\n", dead);
rc = mdbx_reader_list(env, (MDB_msg_func *)fputs, stdout);
}
if (!(subname || alldbs || freinfo))
goto env_close;
}
rc = mdbx_txn_begin(env, NULL, MDB_RDONLY, &txn);
if (rc) {
fprintf(stderr, "mdbx_txn_begin failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto env_close;
}
if (freinfo) {
MDB_cursor *cursor;
MDB_val key, data;
size_t pages = 0, *iptr;
size_t reclaimable = 0;
printf("Freelist Status\n");
dbi = 0;
rc = mdbx_cursor_open(txn, dbi, &cursor);
if (rc) {
fprintf(stderr, "mdbx_cursor_open failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto txn_abort;
}
rc = mdbx_stat(txn, dbi, &mst, sizeof(mst));
if (rc) {
fprintf(stderr, "mdbx_stat failed, error %d %s\n", rc, mdbx_strerror(rc));
goto txn_abort;
}
prstat(&mst);
while ((rc = mdbx_cursor_get(cursor, &key, &data, MDB_NEXT)) == 0) {
iptr = data.mv_data;
pages += *iptr;
if (envinfo && mei.me_tail_txnid > *(size_t *)key.mv_data)
reclaimable += *iptr;
if (freinfo > 1) {
char *bad = "";
size_t pg, prev;
ssize_t i, j, span = 0;
j = *iptr++;
for (i = j, prev = 1; --i >= 0;) {
pg = iptr[i];
if (pg <= prev)
bad = " [bad sequence]";
prev = pg;
pg += span;
for (; i >= span && iptr[i - span] == pg; span++, pg++)
;
}
printf(" Transaction %zu, %zd pages, maxspan %zd%s\n",
*(size_t *)key.mv_data, j, span, bad);
if (freinfo > 2) {
for (--j; j >= 0;) {
pg = iptr[j];
for (span = 1; --j >= 0 && iptr[j] == pg + span; span++)
;
if (span > 1)
printf(" %9zu[%zd]\n", pg, span);
else
printf(" %9zu\n", pg);
}
}
}
}
mdbx_cursor_close(cursor);
if (envinfo) {
size_t value = mei.me_mapsize / mst.ms_psize;
double percent = value / 100.0;
printf("Page Allocation Info\n");
printf(" Max pages: %9zu 100%%\n", value);
value = mei.me_last_pgno + 1;
printf(" Number of pages used: %zu %.1f%%\n", value, value / percent);
value = mei.me_mapsize / mst.ms_psize - (mei.me_last_pgno + 1);
printf(" Remained: %zu %.1f%%\n", value, value / percent);
value = mei.me_last_pgno + 1 - pages;
printf(" Used now: %zu %.1f%%\n", value, value / percent);
value = pages;
printf(" Unallocated: %zu %.1f%%\n", value, value / percent);
value = pages - reclaimable;
printf(" Detained: %zu %.1f%%\n", value, value / percent);
value = reclaimable;
printf(" Reclaimable: %zu %.1f%%\n", value, value / percent);
value =
mei.me_mapsize / mst.ms_psize - (mei.me_last_pgno + 1) + reclaimable;
printf(" Available: %zu %.1f%%\n", value, value / percent);
} else
printf(" Free pages: %zu\n", pages);
}
rc = mdbx_dbi_open(txn, subname, 0, &dbi);
if (rc) {
fprintf(stderr, "mdbx_open failed, error %d %s\n", rc, mdbx_strerror(rc));
goto txn_abort;
}
rc = mdbx_stat(txn, dbi, &mst, sizeof(mst));
if (rc) {
fprintf(stderr, "mdbx_stat failed, error %d %s\n", rc, mdbx_strerror(rc));
goto txn_abort;
}
printf("Status of %s\n", subname ? subname : "Main DB");
prstat(&mst);
if (alldbs) {
MDB_cursor *cursor;
MDB_val key;
rc = mdbx_cursor_open(txn, dbi, &cursor);
if (rc) {
fprintf(stderr, "mdbx_cursor_open failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto txn_abort;
}
while ((rc = mdbx_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
char *str;
MDB_dbi db2;
if (memchr(key.mv_data, '\0', key.mv_size))
continue;
str = malloc(key.mv_size + 1);
memcpy(str, key.mv_data, key.mv_size);
str[key.mv_size] = '\0';
rc = mdbx_dbi_open(txn, str, 0, &db2);
if (rc == MDB_SUCCESS)
printf("Status of %s\n", str);
free(str);
if (rc)
continue;
rc = mdbx_stat(txn, db2, &mst, sizeof(mst));
if (rc) {
fprintf(stderr, "mdbx_stat failed, error %d %s\n", rc,
mdbx_strerror(rc));
goto txn_abort;
}
prstat(&mst);
mdbx_dbi_close(env, db2);
}
mdbx_cursor_close(cursor);
}
if (rc == MDB_NOTFOUND)
rc = MDB_SUCCESS;
mdbx_dbi_close(env, dbi);
txn_abort:
mdbx_txn_abort(txn);
env_close:
mdbx_env_close(env);
return rc ? EXIT_FAILURE : EXIT_SUCCESS;
}

53
src/midl.h Normal file
View File

@@ -0,0 +1,53 @@
/** A generic unsigned ID number. These were entryIDs in back-bdb.
* Preferably it should have the same size as a pointer.
*/
typedef size_t MDB_ID;
/** An IDL is an ID List, a sorted array of IDs. The first
* element of the array is a counter for how many actual
* IDs are in the list. In the original back-bdb code, IDLs are
* sorted in ascending order. For libmdb IDLs are sorted in
* descending order.
*/
typedef MDB_ID *MDB_IDL;
/* IDL sizes - likely should be even bigger
* limiting factors: sizeof(ID), thread stack size
*/
#define MDB_IDL_LOGN 16 /* DB_SIZE is 2^16, UM_SIZE is 2^17 */
#define MDB_IDL_DB_SIZE (1 << MDB_IDL_LOGN)
#define MDB_IDL_UM_SIZE (1 << (MDB_IDL_LOGN + 1))
#define MDB_IDL_DB_MAX (MDB_IDL_DB_SIZE - 1)
#define MDB_IDL_UM_MAX (MDB_IDL_UM_SIZE - 1)
#define MDB_IDL_SIZEOF(ids) (((ids)[0] + 1) * sizeof(MDB_ID))
#define MDB_IDL_IS_ZERO(ids) ((ids)[0] == 0)
#define MDB_IDL_CPY(dst, src) (memcpy(dst, src, MDB_IDL_SIZEOF(src)))
#define MDB_IDL_FIRST(ids) ((ids)[1])
#define MDB_IDL_LAST(ids) ((ids)[(ids)[0]])
/** Current max length of an #mdbx_midl_alloc()ed IDL */
#define MDB_IDL_ALLOCLEN(ids) ((ids)[-1])
/** Append ID to IDL. The IDL must be big enough. */
#define mdbx_midl_xappend(idl, id) \
do { \
MDB_ID *xidl = (idl), xlen = ++(xidl[0]); \
xidl[xlen] = (id); \
} while (0)
/** An ID2 is an ID/pointer pair.
*/
typedef struct MDB_ID2 {
MDB_ID mid; /**< The ID */
void *mptr; /**< The pointer */
} MDB_ID2;
/** An ID2L is an ID2 List, a sorted array of ID2s.
* The first element's \b mid member is a count of how many actual
* elements are in the array. The \b mptr member of the first element is
* unused.
* The array is sorted in ascending order by \b mid.
*/
typedef MDB_ID2 *MDB_ID2L;

236
src/reopen.h Normal file
View File

@@ -0,0 +1,236 @@
/*
* Copyright 2015-2017 Leonid Yuriev <leo@yuriev.ru>.
* Copyright 2015,2016 Peter-Service R&D LLC.
* 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>.
*/
#pragma once
/* *INDENT-OFF* */
/* clang-format off */
#ifndef __CLANG_PREREQ
# ifdef __clang__
# define __CLANG_PREREQ(maj,min) \
((__clang_major__ << 16) + __clang_minor__ >= ((maj) << 16) + (min))
# else
# define __CLANG_PREREQ(maj,min) (0)
# endif
#endif /* __CLANG_PREREQ */
#ifndef __has_attribute
# define __has_attribute(x) (0)
#endif
#if !defined(__thread) && (defined(_MSC_VER) || defined(__DMC__))
# define __thread __declspec(thread)
#endif
#ifndef __forceinline
# if defined(__GNUC__) || defined(__clang__)
# define __forceinline __inline __attribute__((always_inline))
# elif ! defined(_MSC_VER)
# define __forceinline
# endif
#endif /* __forceinline */
#ifndef __noinline
# if defined(__GNUC__) || defined(__clang__)
# define __noinline __attribute__((noinline))
# elif defined(_MSC_VER)
# define __noinline __declspec(noinline)
# endif
#endif /* __noinline */
#ifndef __must_check_result
# if defined(__GNUC__) || defined(__clang__)
# define __must_check_result __attribute__((warn_unused_result))
# else
# define __must_check_result
# endif
#endif /* __must_check_result */
#ifndef __hot
# if defined(__OPTIMIZE__) && (defined(__GNUC__) && !defined(__clang__))
# define __hot __attribute__((hot, optimize("O3")))
# elif defined(__GNUC__)
/* cland case, just put frequently used functions in separate section */
# define __hot __attribute__((section("text.hot")))
# else
# define __hot
# endif
#endif /* __hot */
#ifndef __cold
# if defined(__OPTIMIZE__) && (defined(__GNUC__) && !defined(__clang__))
# define __cold __attribute__((cold, optimize("Os")))
# elif defined(__GNUC__)
/* cland case, just put infrequently used functions in separate section */
# define __cold __attribute__((section("text.unlikely")))
# else
# define __cold
# endif
#endif /* __cold */
#ifndef __flatten
# if defined(__OPTIMIZE__) && (defined(__GNUC__) || defined(__clang__))
# define __flatten __attribute__((flatten))
# else
# define __flatten
# endif
#endif /* __flatten */
#ifndef __aligned
# if defined(__GNUC__) || defined(__clang__)
# define __aligned(N) __attribute__((aligned(N)))
# elif defined(__MSC_VER)
# define __aligned(N) __declspec(align(N))
# else
# define __aligned(N)
# endif
#endif /* __align */
#ifndef __noreturn
# if defined(__GNUC__) || defined(__clang__)
# define __noreturn __attribute__((noreturn))
# elif defined(__MSC_VER)
# define __noreturn __declspec(noreturn)
# else
# define __noreturn
# endif
#endif
#ifndef __nothrow
# if defined(__GNUC__) || defined(__clang__)
# define __nothrow __attribute__((nothrow))
# elif defined(__MSC_VER)
# define __nothrow __declspec(nothrow)
# else
# define __nothrow
# endif
#endif
#ifndef CACHELINE_SIZE
# if defined(__ia64__) || defined(__ia64) || defined(_M_IA64)
# define CACHELINE_SIZE 128
# else
# define CACHELINE_SIZE 64
# endif
#endif
#ifndef __cache_aligned
# define __cache_aligned __aligned(CACHELINE_SIZE)
#endif
#ifndef likely
# if defined(__GNUC__) || defined(__clang__)
# ifdef __cplusplus
/* LY: workaround for "pretty" boost */
static __inline __attribute__((always_inline))
bool likely(bool cond) { return __builtin_expect(cond, 1); }
# else
# define likely(cond) __builtin_expect(!!(cond), 1)
# endif
# else
# define likely(x) (x)
# endif
#endif /* likely */
#ifndef unlikely
# if defined(__GNUC__) || defined(__clang__)
# ifdef __cplusplus
/* LY: workaround for "pretty" boost */
static __inline __attribute__((always_inline))
bool unlikely(bool cond) { return __builtin_expect(cond, 0); }
# else
# define unlikely(cond) __builtin_expect(!!(cond), 0)
# endif
# else
# define unlikely(x) (x)
# endif
#endif /* unlikely */
#ifndef __extern_C
# ifdef __cplusplus
# define __extern_C extern "C"
# else
# define __extern_C
# endif
#endif
#ifndef __noop
# define __noop() do {} while (0)
#endif
/* -------------------------------------------------------------------------- */
#include <assert.h>
/* Prototype should match libc runtime. ISO POSIX (2003) & LSB 3.1 */
__extern_C void __assert_fail(
const char* assertion,
const char* file,
unsigned line,
const char* function) __nothrow __noreturn;
/* -------------------------------------------------------------------------- */
#if defined(HAVE_VALGRIND) || defined(USE_VALGRIND)
/* Get debugging help from Valgrind */
# include <valgrind/memcheck.h>
# ifndef VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE
/* LY: available since Valgrind 3.10 */
# define VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s)
# define VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s)
# endif
#else
# define VALGRIND_CREATE_MEMPOOL(h,r,z)
# define VALGRIND_DESTROY_MEMPOOL(h)
# define VALGRIND_MEMPOOL_TRIM(h,a,s)
# define VALGRIND_MEMPOOL_ALLOC(h,a,s)
# define VALGRIND_MEMPOOL_FREE(h,a)
# define VALGRIND_MEMPOOL_CHANGE(h,a,b,s)
# define VALGRIND_MAKE_MEM_NOACCESS(a,s)
# define VALGRIND_MAKE_MEM_DEFINED(a,s)
# define VALGRIND_MAKE_MEM_UNDEFINED(a,s)
# define VALGRIND_DISABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s)
# define VALGRIND_ENABLE_ADDR_ERROR_REPORTING_IN_RANGE(a,s)
# define VALGRIND_CHECK_MEM_IS_ADDRESSABLE(a,s) (0)
# define VALGRIND_CHECK_MEM_IS_DEFINED(a,s) (0)
#endif /* ! USE_VALGRIND */
#if defined(__has_feature)
# if __has_feature(thread_sanitizer)
# define __SANITIZE_THREAD__ 1
# endif
#endif
#ifdef __SANITIZE_THREAD__
# define ATTRIBUTE_NO_SANITIZE_THREAD __attribute__((no_sanitize_thread, noinline))
#else
# define ATTRIBUTE_NO_SANITIZE_THREAD
#endif
#if defined(__has_feature)
# if __has_feature(address_sanitizer)
# define __SANITIZE_ADDRESS__ 1
# endif
#endif
#ifdef __SANITIZE_ADDRESS__
# include <sanitizer/asan_interface.h>
# define ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address, noinline))
#else
# define ASAN_POISON_MEMORY_REGION(addr, size) \
((void)(addr), (void)(size))
# define ASAN_UNPOISON_MEMORY_REGION(addr, size) \
((void)(addr), (void)(size))
# define ATTRIBUTE_NO_SANITIZE_ADDRESS
#endif /* __SANITIZE_ADDRESS__ */