1 /* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2 #include "config.h"
3 #include <string.h>
4 #include <stdbool.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <inttypes.h>
8 #include <libcouchstore/couch_db.h>
9 #include <snappy-c.h>
10 #include "couch_btree.h"
11 #include "util.h"
12 #include "bitfield.h"
13 #include "internal.h"
14
15 typedef enum {
16 DumpBySequence,
17 DumpByID,
18 DumpLocals
19 } DumpMode;
20
21 static DumpMode mode = DumpBySequence;
22 static bool dumpTree = false;
23 static bool dumpJson = false;
24 static bool dumpHex = false;
25 static bool oneKey = false;
26 static bool noBody = false;
27 static sized_buf dumpKey;
28
29 typedef struct {
30 raw_64 cas;
31 raw_32 expiry;
32 raw_32 flags;
33 } CouchbaseRevMeta;
34
printsb(const sized_buf *sb)35 static void printsb(const sized_buf *sb)
36 {
37 if (sb->buf == NULL) {
38 printf("null\n");
39 return;
40 }
41 printf("%.*s\n", (int) sb->size, sb->buf);
42 }
43
printsbhexraw(const sized_buf* sb)44 static void printsbhexraw(const sized_buf* sb) {
45 size_t ii;
46 for (ii = 0; ii < sb->size; ++ii) {
47 printf("%.02x", (uint8_t)sb->buf[ii]);
48 }
49 }
50
printsbhex(const sized_buf *sb, int with_ascii)51 static void printsbhex(const sized_buf *sb, int with_ascii)
52 {
53 size_t i;
54
55 if (sb->buf == NULL) {
56 printf("null\n");
57 return;
58 }
59 printf("{");
60 for (i = 0; i < sb->size; ++i) {
61 printf("%.02x", (uint8_t)sb->buf[i]);
62 if (i % 4 == 3) {
63 printf(" ");
64 }
65 }
66 printf("}");
67 if (with_ascii) {
68 printf(" (\"");
69 for (i = 0; i < sb->size; ++i) {
70 uint8_t ch = sb->buf[i];
71 if (ch < 32 || ch >= 127) {
72 ch = '?';
73 }
74 printf("%c", ch);
75 }
76 printf("\")");
77 }
78 printf("\n");
79 }
80
printjquote(const sized_buf *sb)81 static void printjquote(const sized_buf *sb)
82 {
83 const char* i = sb->buf;
84 const char* end = sb->buf + sb->size;
85 if (sb->buf == NULL) {
86 return;
87 }
88 for (; i < end; i++) {
89 if (*i > 31 && *i != '\"' && *i != '\\') {
90 fputc(*i, stdout);
91 } else {
92 fputc('\\', stdout);
93 switch(*i)
94 {
95 case '\\': fputc('\\', stdout);break;
96 case '\"': fputc('\"', stdout);break;
97 case '\b': fputc('b', stdout);break;
98 case '\f': fputc('f', stdout);break;
99 case '\n': fputc('n', stdout);break;
100 case '\r': fputc('r', stdout);break;
101 case '\t': fputc('t', stdout);break;
102 default:
103 printf("u00%.02x", *i);
104 }
105 }
106 }
107 }
108
foldprint(Db *db, DocInfo *docinfo, void *ctx)109 static int foldprint(Db *db, DocInfo *docinfo, void *ctx)
110 {
111 int *count = (int *) ctx;
112 Doc *doc = NULL;
113 uint64_t cas;
114 uint32_t expiry, flags;
115 uint8_t datatype = 0x00, flex_code;
116 couchstore_error_t docerr;
117 (*count)++;
118
119 if (dumpJson) {
120 printf("{\"seq\":%"PRIu64",\"id\":\"", docinfo->db_seq);
121 printjquote(&docinfo->id);
122 printf("\",");
123 } else {
124 if (mode == DumpBySequence) {
125 printf("Doc seq: %"PRIu64"\n", docinfo->db_seq);
126 printf(" id: ");
127 printsb(&docinfo->id);
128 } else {
129 printf(" Doc ID: ");
130 printsb(&docinfo->id);
131 if (docinfo->db_seq > 0) {
132 printf(" seq: %"PRIu64"\n", docinfo->db_seq);
133 }
134 }
135 }
136 if (docinfo->bp == 0 && docinfo->deleted == 0 && !dumpJson) {
137 printf(" ** This b-tree node is corrupt; raw node value follows:*\n");
138 printf(" raw: ");
139 printsbhex(&docinfo->rev_meta, 1);
140 return 0;
141 }
142 if (dumpJson) {
143 printf("\"rev\":%"PRIu64",\"content_meta\":%d,", docinfo->rev_seq,
144 docinfo->content_meta);
145 printf("\"physical_size\":%"PRIu64",", (uint64_t)docinfo->size);
146 } else {
147 printf(" rev: %"PRIu64"\n", docinfo->rev_seq);
148 printf(" content_meta: %d\n", docinfo->content_meta);
149 printf(" size (on disk): %"PRIu64"\n", (uint64_t)docinfo->size);
150 }
151 if (docinfo->rev_meta.size >= sizeof(CouchbaseRevMeta)) {
152 const CouchbaseRevMeta* meta = (const CouchbaseRevMeta*)docinfo->rev_meta.buf;
153 cas = decode_raw64(meta->cas);
154 expiry = decode_raw32(meta->expiry);
155 flags = decode_raw32(meta->flags);
156 if (docinfo->rev_meta.size > sizeof(CouchbaseRevMeta)) {
157 flex_code = *((uint8_t *)(docinfo->rev_meta.buf + sizeof(CouchbaseRevMeta)));
158 assert(flex_code >= 0x01);
159 datatype = *((uint8_t *)(docinfo->rev_meta.buf + sizeof(CouchbaseRevMeta) +
160 sizeof(uint8_t)));
161 if (dumpJson) {
162 printf("\"cas\":\"%"PRIu64"\",\"expiry\":%"PRIu32",\"flags\":%"PRIu32","
163 "\"datatype\":%d,",
164 cas, expiry, flags, datatype);
165 } else {
166 printf(" cas: %"PRIu64", expiry: %"PRIu32", flags: %"PRIu32", "
167 "datatype: %d\n",
168 cas, expiry, flags, datatype);
169 }
170 } else {
171 if (dumpJson) {
172 printf("\"cas\":\"%"PRIu64"\",\"expiry\":%"PRIu32",\"flags\":%"PRIu32",",
173 cas, expiry, flags);
174 } else {
175 printf(" cas: %"PRIu64", expiry: %"PRIu32", flags: %"PRIu32"\n",
176 cas, expiry, flags);
177 }
178 }
179 }
180 if (docinfo->deleted) {
181 if (dumpJson) {
182 printf("\"deleted\":true,");
183 } else {
184 printf(" doc deleted\n");
185 }
186 }
187
188 if (!noBody) {
189 docerr = couchstore_open_doc_with_docinfo(db, docinfo, &doc, 0);
190 if (docerr != COUCHSTORE_SUCCESS) {
191 if (dumpJson) {
192 printf("\"body\":null}\n");
193 } else {
194 printf(" could not read document body: %s\n", couchstore_strerror(docerr));
195 }
196 } else if (doc && (docinfo->content_meta & COUCH_DOC_IS_COMPRESSED)) {
197 size_t rlen;
198 snappy_uncompressed_length(doc->data.buf, doc->data.size, &rlen);
199 char *decbuf = (char *) malloc(rlen);
200 size_t uncompr_len;
201 snappy_uncompress(doc->data.buf, doc->data.size, decbuf, &uncompr_len);
202 sized_buf uncompr_body;
203 uncompr_body.size = uncompr_len;
204 uncompr_body.buf = decbuf;
205 if (dumpJson) {
206 printf("\"size\":%"PRIu64",", (uint64_t)uncompr_body.size);
207 printf("\"snappy\":true,\"body\":\"");
208 if (dumpHex) {
209 printsbhexraw(&uncompr_body);
210 } else {
211 printjquote(&uncompr_body);
212 }
213 printf("\"}\n");
214 } else {
215 printf(" size: %"PRIu64"\n", (uint64_t)uncompr_body.size);
216 printf(" data: (snappy) ");
217 if (dumpHex) {
218 printsbhexraw(&uncompr_body);
219 printf("\n");
220 } else {
221 printsb(&uncompr_body);
222 }
223 }
224 free (uncompr_body.buf);
225 } else if (doc) {
226 sized_buf new_body;
227 if (datatype >= 0x02) {
228 size_t rlen;
229 snappy_uncompressed_length(doc->data.buf, doc->data.size, &rlen);
230 char *decbuf = (char *) malloc(rlen);
231 size_t new_len;
232 snappy_uncompress(doc->data.buf, doc->data.size, decbuf, &new_len);
233 new_body.size = new_len;
234 new_body.buf = decbuf;
235 } else {
236 new_body = doc->data;
237 }
238 if (dumpJson) {
239 printf("\"size\":%"PRIu64",", (uint64_t)new_body.size);
240 printf("\"body\":\"");
241 printjquote(&new_body);
242 printf("\"}\n");
243 } else {
244 printf(" size: %"PRIu64"\n", (uint64_t)new_body.size);
245 printf(" data: ");
246 if (dumpHex) {
247 printsbhexraw(&new_body);
248 printf("\n");
249 } else {
250 printsb(&new_body);
251 }
252 }
253 if (datatype >= 0x02) {
254 free (new_body.buf);
255 }
256 }
257 } else {
258 if (dumpJson) {
259 printf("\"body\":null}\n");
260 } else {
261 printf("\n");
262 }
263 }
264
265 couchstore_free_document(doc);
266 return 0;
267 }
268
269
visit_node(Db *db, int depth, const DocInfo* docinfo, uint64_t subtreeSize, const sized_buf* reduceValue, void *ctx)270 static int visit_node(Db *db,
271 int depth,
272 const DocInfo* docinfo,
273 uint64_t subtreeSize,
274 const sized_buf* reduceValue,
275 void *ctx)
276 {
277 int i;
278 (void) db;
279
280 for (i = 0; i < depth; ++i)
281 printf(" ");
282 if (reduceValue) {
283 /* This is a tree node: */
284 printf("+ (%"PRIu64") ", subtreeSize);
285 printsbhex(reduceValue, 0);
286 } else if (docinfo->bp > 0) {
287 int *count;
288 /* This is a document: */
289 printf("%c (%"PRIu64") ", (docinfo->deleted ? 'x' : '*'),
290 (uint64_t)docinfo->size);
291 if (mode == DumpBySequence) {
292 printf("#%"PRIu64" ", docinfo->db_seq);
293 }
294 printsb(&docinfo->id);
295
296 count = (int *) ctx;
297 (*count)++;
298 } else {
299 /* Document, but not in a known format: */
300 printf("**corrupt?** ");
301 printsbhex(&docinfo->rev_meta, 1);
302 }
303 return 0;
304 }
305
local_doc_print(couchfile_lookup_request *rq, const sized_buf *k, const sized_buf *v)306 static couchstore_error_t local_doc_print(couchfile_lookup_request *rq,
307 const sized_buf *k,
308 const sized_buf *v)
309 {
310 int* count = (int*) rq->callback_ctx;
311 if (!v) {
312 return COUCHSTORE_ERROR_DOC_NOT_FOUND;
313 }
314 (*count)++;
315 sized_buf *id = (sized_buf *) k;
316 if (dumpJson) {
317 printf("{\"id\":\"");
318 printjquote(id);
319 printf("\",");
320 } else {
321 printf("Key: ");
322 printsb(id);
323 }
324
325 if (dumpJson) {
326 printf("\"value\":\"");
327 printjquote(v);
328 printf("\"}\n");
329 } else {
330 printf("Value: ");
331 printsb(v);
332 printf("\n");
333 }
334
335 return COUCHSTORE_SUCCESS;
336 }
337
couchstore_print_local_docs(Db *db, int *count)338 static couchstore_error_t couchstore_print_local_docs(Db *db, int *count)
339 {
340 sized_buf key;
341 sized_buf *keylist = &key;
342 couchfile_lookup_request rq;
343 couchstore_error_t errcode;
344
345 if (db->header.local_docs_root == NULL) {
346 if (oneKey) {
347 return COUCHSTORE_ERROR_DOC_NOT_FOUND;
348 } else {
349 return COUCHSTORE_SUCCESS;
350 }
351 }
352
353 key.buf = (char *)"\0";
354 key.size = 0;
355
356 rq.cmp.compare = ebin_cmp;
357 rq.file = &db->file;
358 rq.num_keys = 1;
359 rq.keys = &keylist;
360 rq.callback_ctx = count;
361 rq.fetch_callback = local_doc_print;
362 rq.node_callback = NULL;
363 rq.fold = 1;
364
365 if (oneKey) {
366 rq.fold = 0;
367 key = dumpKey;
368 }
369
370 errcode = btree_lookup(&rq, db->header.local_docs_root->pointer);
371 return errcode;
372 }
373
process_file(const char *file, int *total)374 static int process_file(const char *file, int *total)
375 {
376 Db *db;
377 couchstore_error_t errcode;
378 int count = 0;
379
380 errcode = couchstore_open_db(file, COUCHSTORE_OPEN_FLAG_RDONLY, &db);
381 if (errcode != COUCHSTORE_SUCCESS) {
382 fprintf(stderr, "Failed to open \"%s\": %s\n",
383 file, couchstore_strerror(errcode));
384 return -1;
385 } else {
386 fprintf(stderr, "Dumping \"%s\":\n", file);
387 }
388
389 switch (mode) {
390 case DumpBySequence:
391 if (dumpTree) {
392 errcode = couchstore_walk_seq_tree(db, 0, COUCHSTORE_INCLUDE_CORRUPT_DOCS,
393 visit_node, &count);
394 } else {
395 errcode = couchstore_changes_since(db, 0, COUCHSTORE_INCLUDE_CORRUPT_DOCS,
396 foldprint, &count);
397 }
398 break;
399 case DumpByID:
400 if (dumpTree) {
401 errcode = couchstore_walk_id_tree(db, NULL, COUCHSTORE_INCLUDE_CORRUPT_DOCS,
402 visit_node, &count);
403 } else if (oneKey) {
404 DocInfo* info;
405 errcode = couchstore_docinfo_by_id(db, dumpKey.buf, dumpKey.size, &info);
406 if (errcode == COUCHSTORE_SUCCESS) {
407 foldprint(db, info, &count);
408 couchstore_free_docinfo(info);
409 }
410 } else {
411 errcode = couchstore_all_docs(db, NULL, COUCHSTORE_INCLUDE_CORRUPT_DOCS,
412 foldprint, &count);
413 }
414 break;
415 case DumpLocals:
416 errcode = couchstore_print_local_docs(db, &count);
417 break;
418 }
419 (void)couchstore_close_db(db);
420
421 if (errcode < 0) {
422 fprintf(stderr, "Failed to dump database \"%s\": %s\n",
423 file, couchstore_strerror(errcode));
424 return -1;
425 }
426
427 *total += count;
428 return 0;
429 }
430
usage(void)431 static void usage(void) {
432 printf("USAGE: couch_dbdump [options] file.couch [file2.couch ...]\n");
433 printf("\nOptions:\n");
434 printf(" --key <key> dump only the specified document\n");
435 printf(" --hex-body convert document body data to hex (for binary data)\n");
436 printf(" --no-body don't retrieve document bodies (metadata only, faster)\n");
437 printf(" --byid sort output by document ID\n");
438 printf(" --byseq sort output by document sequence number (default)\n");
439 printf(" --json dump data as JSON objects (one per line)\n");
440 printf("\nAlternate modes:\n");
441 printf(" --tree show file b-tree structure instead of data\n");
442 printf(" --local dump local documents\n");
443 exit(EXIT_FAILURE);
444 }
445
main(int argc, char **argv)446 int main(int argc, char **argv)
447 {
448 int error = 0;
449 int count = 0;
450 int ii = 1;
451
452 if (argc < 2) {
453 usage();
454 }
455
456 while (ii < argc && strncmp(argv[ii], "-", 1) == 0) {
457 if (strcmp(argv[ii], "--byid") == 0) {
458 mode = DumpByID;
459 } else if (strcmp(argv[ii], "--byseq") == 0) {
460 mode = DumpBySequence;
461 } else if (strcmp(argv[ii], "--tree") == 0) {
462 dumpTree = true;
463 } else if (strcmp(argv[ii], "--json") == 0) {
464 dumpJson = true;
465 } else if (strcmp(argv[ii], "--hex-body") == 0) {
466 dumpHex = true;
467 } else if (strcmp(argv[ii], "--no-body") == 0) {
468 noBody = true;
469 } else if (strcmp(argv[ii], "--key") == 0) {
470 if (argc < (ii + 1)) {
471 usage();
472 }
473 oneKey = true;
474 dumpKey.buf = argv[ii+1];
475 dumpKey.size = strlen(argv[ii+1]);
476 if (mode == DumpBySequence) {
477 mode = DumpByID;
478 }
479 ii++;
480 } else if (strcmp(argv[ii], "--local") == 0) {
481 mode = DumpLocals;
482 } else {
483 usage();
484 }
485 ++ii;
486 }
487
488 if (ii >= argc || (mode == DumpLocals && dumpTree)) {
489 usage();
490 }
491
492 for (; ii < argc; ++ii) {
493 error += process_file(argv[ii], &count);
494 }
495
496 fprintf(stderr, "\nTotal docs: %d\n", count);
497 if (error) {
498 exit(EXIT_FAILURE);
499 } else {
500 exit(EXIT_SUCCESS);
501 }
502 }
503