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