1 /* $NetBSD: mdb_dump.c,v 1.3 2021/08/14 16:14:57 christos Exp $ */
2
3 /* mdb_dump.c - memory-mapped database dump tool */
4 /*
5 * Copyright 2011-2021 Howard Chu, Symas Corp.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted only as authorized by the OpenLDAP
10 * Public License.
11 *
12 * A copy of this license is available in the file LICENSE in the
13 * top-level directory of the distribution or, alternatively, at
14 * <http://www.OpenLDAP.org/license.html>.
15 */
16 #include <stdio.h>
17 #include <errno.h>
18 #include <stdlib.h>
19 #include <string.h>
20 #include <ctype.h>
21 #include <unistd.h>
22 #include <signal.h>
23 #include "lmdb.h"
24
25 #ifdef _WIN32
26 #define Z "I"
27 #else
28 #define Z "z"
29 #endif
30
31 #define PRINT 1
32 static int mode;
33
34 typedef struct flagbit {
35 int bit;
36 char *name;
37 } flagbit;
38
39 flagbit dbflags[] = {
40 { MDB_REVERSEKEY, "reversekey" },
41 { MDB_DUPSORT, "dupsort" },
42 { MDB_INTEGERKEY, "integerkey" },
43 { MDB_DUPFIXED, "dupfixed" },
44 { MDB_INTEGERDUP, "integerdup" },
45 { MDB_REVERSEDUP, "reversedup" },
46 { 0, NULL }
47 };
48
49 static volatile sig_atomic_t gotsig;
50
dumpsig(int sig)51 static void dumpsig( int sig )
52 {
53 gotsig=1;
54 }
55
56 static const char hexc[] = "0123456789abcdef";
57
hex(unsigned char c)58 static void hex(unsigned char c)
59 {
60 putchar(hexc[c >> 4]);
61 putchar(hexc[c & 0xf]);
62 }
63
text(MDB_val * v)64 static void text(MDB_val *v)
65 {
66 unsigned char *c, *end;
67
68 putchar(' ');
69 c = v->mv_data;
70 end = c + v->mv_size;
71 while (c < end) {
72 if (isprint(*c)) {
73 if (*c == '\\')
74 putchar('\\');
75 putchar(*c);
76 } else {
77 putchar('\\');
78 hex(*c);
79 }
80 c++;
81 }
82 putchar('\n');
83 }
84
byte(MDB_val * v)85 static void byte(MDB_val *v)
86 {
87 unsigned char *c, *end;
88
89 putchar(' ');
90 c = v->mv_data;
91 end = c + v->mv_size;
92 while (c < end) {
93 hex(*c++);
94 }
95 putchar('\n');
96 }
97
98 /* Dump in BDB-compatible format */
dumpit(MDB_txn * txn,MDB_dbi dbi,char * name)99 static int dumpit(MDB_txn *txn, MDB_dbi dbi, char *name)
100 {
101 MDB_cursor *mc;
102 MDB_stat ms;
103 MDB_val key, data;
104 MDB_envinfo info;
105 unsigned int flags;
106 int rc, i;
107
108 rc = mdb_dbi_flags(txn, dbi, &flags);
109 if (rc) return rc;
110
111 rc = mdb_stat(txn, dbi, &ms);
112 if (rc) return rc;
113
114 rc = mdb_env_info(mdb_txn_env(txn), &info);
115 if (rc) return rc;
116
117 printf("VERSION=3\n");
118 printf("format=%s\n", mode & PRINT ? "print" : "bytevalue");
119 if (name)
120 printf("database=%s\n", name);
121 printf("type=btree\n");
122 printf("mapsize=%" Z "u\n", info.me_mapsize);
123 if (info.me_mapaddr)
124 printf("mapaddr=%p\n", info.me_mapaddr);
125 printf("maxreaders=%u\n", info.me_maxreaders);
126
127 if (flags & MDB_DUPSORT)
128 printf("duplicates=1\n");
129
130 for (i=0; dbflags[i].bit; i++)
131 if (flags & dbflags[i].bit)
132 printf("%s=1\n", dbflags[i].name);
133
134 printf("db_pagesize=%d\n", ms.ms_psize);
135 printf("HEADER=END\n");
136
137 rc = mdb_cursor_open(txn, dbi, &mc);
138 if (rc) return rc;
139
140 while ((rc = mdb_cursor_get(mc, &key, &data, MDB_NEXT) == MDB_SUCCESS)) {
141 if (gotsig) {
142 rc = EINTR;
143 break;
144 }
145 if (mode & PRINT) {
146 text(&key);
147 text(&data);
148 } else {
149 byte(&key);
150 byte(&data);
151 }
152 }
153 printf("DATA=END\n");
154 if (rc == MDB_NOTFOUND)
155 rc = MDB_SUCCESS;
156
157 return rc;
158 }
159
usage(char * prog)160 static void usage(char *prog)
161 {
162 fprintf(stderr, "usage: %s [-V] [-f output] [-l] [-n] [-p] [-a|-s subdb] dbpath\n", prog);
163 exit(EXIT_FAILURE);
164 }
165
main(int argc,char * argv[])166 int main(int argc, char *argv[])
167 {
168 int i, rc;
169 MDB_env *env;
170 MDB_txn *txn;
171 MDB_dbi dbi;
172 char *prog = argv[0];
173 char *envname;
174 char *subname = NULL;
175 int alldbs = 0, envflags = 0, list = 0;
176
177 if (argc < 2) {
178 usage(prog);
179 }
180
181 /* -a: dump main DB and all subDBs
182 * -s: dump only the named subDB
183 * -n: use NOSUBDIR flag on env_open
184 * -p: use printable characters
185 * -f: write to file instead of stdout
186 * -V: print version and exit
187 * (default) dump only the main DB
188 */
189 while ((i = getopt(argc, argv, "af:lnps:V")) != EOF) {
190 switch(i) {
191 case 'V':
192 printf("%s\n", MDB_VERSION_STRING);
193 exit(0);
194 break;
195 case 'l':
196 list = 1;
197 /*FALLTHROUGH*/;
198 case 'a':
199 if (subname)
200 usage(prog);
201 alldbs++;
202 break;
203 case 'f':
204 if (freopen(optarg, "w", stdout) == NULL) {
205 fprintf(stderr, "%s: %s: reopen: %s\n",
206 prog, optarg, strerror(errno));
207 exit(EXIT_FAILURE);
208 }
209 break;
210 case 'n':
211 envflags |= MDB_NOSUBDIR;
212 break;
213 case 'p':
214 mode |= PRINT;
215 break;
216 case 's':
217 if (alldbs)
218 usage(prog);
219 subname = optarg;
220 break;
221 default:
222 usage(prog);
223 }
224 }
225
226 if (optind != argc - 1)
227 usage(prog);
228
229 #ifdef SIGPIPE
230 signal(SIGPIPE, dumpsig);
231 #endif
232 #ifdef SIGHUP
233 signal(SIGHUP, dumpsig);
234 #endif
235 signal(SIGINT, dumpsig);
236 signal(SIGTERM, dumpsig);
237
238 envname = argv[optind];
239 rc = mdb_env_create(&env);
240 if (rc) {
241 fprintf(stderr, "mdb_env_create failed, error %d %s\n", rc, mdb_strerror(rc));
242 return EXIT_FAILURE;
243 }
244
245 if (alldbs || subname) {
246 mdb_env_set_maxdbs(env, 2);
247 }
248
249 rc = mdb_env_open(env, envname, envflags | MDB_RDONLY, 0664);
250 if (rc) {
251 fprintf(stderr, "mdb_env_open failed, error %d %s\n", rc, mdb_strerror(rc));
252 goto env_close;
253 }
254
255 rc = mdb_txn_begin(env, NULL, MDB_RDONLY, &txn);
256 if (rc) {
257 fprintf(stderr, "mdb_txn_begin failed, error %d %s\n", rc, mdb_strerror(rc));
258 goto env_close;
259 }
260
261 rc = mdb_open(txn, subname, 0, &dbi);
262 if (rc) {
263 fprintf(stderr, "mdb_open failed, error %d %s\n", rc, mdb_strerror(rc));
264 goto txn_abort;
265 }
266
267 if (alldbs) {
268 MDB_cursor *cursor;
269 MDB_val key;
270 int count = 0;
271
272 rc = mdb_cursor_open(txn, dbi, &cursor);
273 if (rc) {
274 fprintf(stderr, "mdb_cursor_open failed, error %d %s\n", rc, mdb_strerror(rc));
275 goto txn_abort;
276 }
277 while ((rc = mdb_cursor_get(cursor, &key, NULL, MDB_NEXT_NODUP)) == 0) {
278 char *str;
279 MDB_dbi db2;
280 if (memchr(key.mv_data, '\0', key.mv_size))
281 continue;
282 count++;
283 str = malloc(key.mv_size+1);
284 memcpy(str, key.mv_data, key.mv_size);
285 str[key.mv_size] = '\0';
286 rc = mdb_open(txn, str, 0, &db2);
287 if (rc == MDB_SUCCESS) {
288 if (list) {
289 printf("%s\n", str);
290 list++;
291 } else {
292 rc = dumpit(txn, db2, str);
293 if (rc)
294 break;
295 }
296 mdb_close(env, db2);
297 }
298 free(str);
299 if (rc) continue;
300 }
301 mdb_cursor_close(cursor);
302 if (!count) {
303 fprintf(stderr, "%s: %s does not contain multiple databases\n", prog, envname);
304 rc = MDB_NOTFOUND;
305 } else if (rc == MDB_NOTFOUND) {
306 rc = MDB_SUCCESS;
307 }
308 } else {
309 rc = dumpit(txn, dbi, subname);
310 }
311 if (rc && rc != MDB_NOTFOUND)
312 fprintf(stderr, "%s: %s: %s\n", prog, envname, mdb_strerror(rc));
313
314 mdb_close(env, dbi);
315 txn_abort:
316 mdb_txn_abort(txn);
317 env_close:
318 mdb_env_close(env);
319
320 return rc ? EXIT_FAILURE : EXIT_SUCCESS;
321 }
322