xref: /3.0.2-MP2/couchstore/src/dbdump.c (revision ab4276bc)
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
15typedef enum {
16    DumpBySequence,
17    DumpByID,
18    DumpLocals
19} DumpMode;
20
21static DumpMode mode = DumpBySequence;
22static bool dumpTree = false;
23static bool dumpJson = false;
24static bool dumpHex = false;
25static bool oneKey = false;
26static bool noBody = false;
27static sized_buf dumpKey;
28
29typedef struct {
30    raw_64 cas;
31    raw_32 expiry;
32    raw_32 flags;
33} CouchbaseRevMeta;
34
35static 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
44static 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
51static 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
81static 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
109static 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    couchstore_error_t docerr;
116    (*count)++;
117
118    if (dumpJson) {
119        printf("{\"seq\":%"PRIu64",\"id\":\"", docinfo->db_seq);
120        printjquote(&docinfo->id);
121        printf("\",");
122    } else {
123        if (mode == DumpBySequence) {
124            printf("Doc seq: %"PRIu64"\n", docinfo->db_seq);
125            printf("     id: ");
126            printsb(&docinfo->id);
127        } else {
128            printf("  Doc ID: ");
129            printsb(&docinfo->id);
130            if (docinfo->db_seq > 0) {
131                printf("     seq: %"PRIu64"\n", docinfo->db_seq);
132            }
133        }
134    }
135    if (docinfo->bp == 0 && docinfo->deleted == 0 && !dumpJson) {
136        printf("         ** This b-tree node is corrupt; raw node value follows:*\n");
137        printf("    raw: ");
138        printsbhex(&docinfo->rev_meta, 1);
139        return 0;
140    }
141    if (dumpJson) {
142        printf("\"rev\":%"PRIu64",\"content_meta\":%d,", docinfo->rev_seq,
143                                                         docinfo->content_meta);
144        printf("\"physical_size\":%"PRIu64",", (uint64_t)docinfo->size);
145    } else {
146        printf("     rev: %"PRIu64"\n", docinfo->rev_seq);
147        printf("     content_meta: %d\n", docinfo->content_meta);
148        printf("     size (on disk): %"PRIu64"\n", (uint64_t)docinfo->size);
149    }
150    if (docinfo->rev_meta.size >= sizeof(CouchbaseRevMeta)) {
151        const CouchbaseRevMeta* meta = (const CouchbaseRevMeta*)docinfo->rev_meta.buf;
152        cas = decode_raw64(meta->cas);
153        expiry = decode_raw32(meta->expiry);
154        flags = decode_raw32(meta->flags);
155        if (dumpJson) {
156            printf("\"cas\":\"%"PRIu64"\",\"expiry\":%"PRIu32",\"flags\":%"PRIu32",",
157                    cas, expiry, flags);
158        } else {
159            printf("     cas: %"PRIu64", expiry: %"PRIu32", flags: %"PRIu32"\n", cas,
160                    expiry,
161                    flags);
162        }
163    }
164    if (docinfo->deleted) {
165        if (dumpJson) {
166            printf("\"deleted\":true,");
167        } else {
168            printf("     doc deleted\n");
169        }
170    }
171
172    if (!noBody) {
173        docerr = couchstore_open_doc_with_docinfo(db, docinfo, &doc, 0);
174        if (docerr != COUCHSTORE_SUCCESS) {
175            if (dumpJson) {
176                printf("\"body\":null}\n");
177            } else {
178                printf("     could not read document body: %s\n", couchstore_strerror(docerr));
179            }
180        } else if (doc && (docinfo->content_meta & COUCH_DOC_IS_COMPRESSED)) {
181            size_t rlen;
182            snappy_uncompressed_length(doc->data.buf, doc->data.size, &rlen);
183            char *decbuf = (char *) malloc(rlen);
184            size_t uncompr_len;
185            snappy_uncompress(doc->data.buf, doc->data.size, decbuf, &uncompr_len);
186            sized_buf uncompr_body;
187            uncompr_body.size = uncompr_len;
188            uncompr_body.buf = decbuf;
189            if (dumpJson) {
190                printf("\"size\":%"PRIu64",", (uint64_t)uncompr_body.size);
191                printf("\"snappy\":true,\"body\":\"");
192                if (dumpHex) {
193                    printsbhexraw(&uncompr_body);
194                } else {
195                    printjquote(&uncompr_body);
196                }
197                printf("\"}\n");
198            } else {
199                printf("     size: %"PRIu64"\n", (uint64_t)uncompr_body.size);
200                printf("     data: (snappy) ");
201                if (dumpHex) {
202                    printsbhexraw(&uncompr_body);
203                    printf("\n");
204                } else {
205                    printsb(&uncompr_body);
206                }
207            }
208        } else if (doc) {
209            if (dumpJson) {
210                printf("\"size\":%"PRIu64",", (uint64_t)doc->data.size);
211                printf("\"body\":\"");
212                printjquote(&doc->data);
213                printf("\"}\n");
214            } else {
215                printf("     size: %"PRIu64"\n", (uint64_t)doc->data.size);
216                printf("     data: ");
217                if (dumpHex) {
218                    printsbhexraw(&doc->data);
219                    printf("\n");
220                } else {
221                    printsb(&doc->data);
222                }
223            }
224        }
225    } else {
226        if (dumpJson) {
227            printf("\"body\":null}\n");
228        } else {
229            printf("\n");
230        }
231    }
232
233    couchstore_free_document(doc);
234    return 0;
235}
236
237
238static int visit_node(Db *db,
239                      int depth,
240                      const DocInfo* docinfo,
241                      uint64_t subtreeSize,
242                      const sized_buf* reduceValue,
243                      void *ctx)
244{
245    int i;
246    (void) db;
247
248    for (i = 0; i < depth; ++i)
249        printf("  ");
250    if (reduceValue) {
251        /* This is a tree node: */
252        printf("+ (%"PRIu64") ", subtreeSize);
253        printsbhex(reduceValue, 0);
254    } else if (docinfo->bp > 0) {
255        int *count;
256        /* This is a document: */
257        printf("%c (%"PRIu64") ", (docinfo->deleted ? 'x' : '*'),
258               (uint64_t)docinfo->size);
259        if (mode == DumpBySequence) {
260            printf("#%"PRIu64" ", docinfo->db_seq);
261        }
262        printsb(&docinfo->id);
263
264        count = (int *) ctx;
265        (*count)++;
266    } else {
267        /* Document, but not in a known format: */
268        printf("**corrupt?** ");
269        printsbhex(&docinfo->rev_meta, 1);
270    }
271    return 0;
272}
273
274static couchstore_error_t local_doc_print(couchfile_lookup_request *rq,
275                                          const sized_buf *k,
276                                          const sized_buf *v)
277{
278    int* count = (int*) rq->callback_ctx;
279    if (!v) {
280        return COUCHSTORE_ERROR_DOC_NOT_FOUND;
281    }
282    (*count)++;
283    sized_buf *id = (sized_buf *) k;
284    if (dumpJson) {
285        printf("{\"id\":\"");
286        printjquote(id);
287        printf("\",");
288    } else {
289        printf("Key: ");
290        printsb(id);
291    }
292
293    if (dumpJson) {
294        printf("\"value\":\"");
295        printjquote(v);
296        printf("\"}\n");
297    } else {
298        printf("Value: ");
299        printsb(v);
300        printf("\n");
301    }
302
303    return COUCHSTORE_SUCCESS;
304}
305
306static couchstore_error_t couchstore_print_local_docs(Db *db, int *count)
307{
308    sized_buf key;
309    sized_buf *keylist = &key;
310    couchfile_lookup_request rq;
311    couchstore_error_t errcode;
312
313    if (db->header.local_docs_root == NULL) {
314        if (oneKey) {
315            return COUCHSTORE_ERROR_DOC_NOT_FOUND;
316        } else {
317            return COUCHSTORE_SUCCESS;
318        }
319    }
320
321    key.buf = (char *)"\0";
322    key.size = 0;
323
324    rq.cmp.compare = ebin_cmp;
325    rq.file = &db->file;
326    rq.num_keys = 1;
327    rq.keys = &keylist;
328    rq.callback_ctx = count;
329    rq.fetch_callback = local_doc_print;
330    rq.node_callback = NULL;
331    rq.fold = 1;
332
333    if (oneKey) {
334        rq.fold = 0;
335        key = dumpKey;
336    }
337
338    errcode = btree_lookup(&rq, db->header.local_docs_root->pointer);
339    return errcode;
340}
341
342static int process_file(const char *file, int *total)
343{
344    Db *db;
345    couchstore_error_t errcode;
346    int count = 0;
347
348    errcode = couchstore_open_db(file, COUCHSTORE_OPEN_FLAG_RDONLY, &db);
349    if (errcode != COUCHSTORE_SUCCESS) {
350        fprintf(stderr, "Failed to open \"%s\": %s\n",
351                file, couchstore_strerror(errcode));
352        return -1;
353    } else {
354        fprintf(stderr, "Dumping \"%s\":\n", file);
355    }
356
357    switch (mode) {
358        case DumpBySequence:
359            if (dumpTree) {
360                errcode = couchstore_walk_seq_tree(db, 0, COUCHSTORE_INCLUDE_CORRUPT_DOCS,
361                                                   visit_node, &count);
362            } else {
363                errcode = couchstore_changes_since(db, 0, COUCHSTORE_INCLUDE_CORRUPT_DOCS,
364                                                   foldprint, &count);
365            }
366            break;
367        case DumpByID:
368            if (dumpTree) {
369                errcode = couchstore_walk_id_tree(db, NULL, COUCHSTORE_INCLUDE_CORRUPT_DOCS,
370                                                  visit_node, &count);
371            } else if (oneKey) {
372                DocInfo* info;
373                errcode = couchstore_docinfo_by_id(db, dumpKey.buf, dumpKey.size, &info);
374                if (errcode == COUCHSTORE_SUCCESS) {
375                    foldprint(db, info, &count);
376                    couchstore_free_docinfo(info);
377                }
378            } else {
379                errcode = couchstore_all_docs(db, NULL, COUCHSTORE_INCLUDE_CORRUPT_DOCS,
380                                              foldprint, &count);
381            }
382            break;
383        case DumpLocals:
384            errcode = couchstore_print_local_docs(db, &count);
385            break;
386    }
387    (void)couchstore_close_db(db);
388
389    if (errcode < 0) {
390        fprintf(stderr, "Failed to dump database \"%s\": %s\n",
391                file, couchstore_strerror(errcode));
392        return -1;
393    }
394
395    *total += count;
396    return 0;
397}
398
399static void usage(void) {
400    printf("USAGE: couch_dbdump [options] file.couch [file2.couch ...]\n");
401    printf("\nOptions:\n");
402    printf("       --key <key>  dump only the specified document\n");
403    printf("       --hex-body   convert document body data to hex (for binary data)\n");
404    printf("       --no-body    don't retrieve document bodies (metadata only, faster)\n");
405    printf("       --byid       sort output by document ID\n");
406    printf("       --byseq      sort output by document sequence number (default)\n");
407    printf("       --json       dump data as JSON objects (one per line)\n");
408    printf("\nAlternate modes:\n");
409    printf("       --tree       show file b-tree structure instead of data\n");
410    printf("       --local      dump local documents\n");
411    exit(EXIT_FAILURE);
412}
413
414int main(int argc, char **argv)
415{
416    int error = 0;
417    int count = 0;
418    int ii = 1;
419
420    if (argc < 2) {
421        usage();
422    }
423
424    while (ii < argc && strncmp(argv[ii], "-", 1) == 0) {
425        if (strcmp(argv[ii], "--byid") == 0) {
426            mode = DumpByID;
427        } else if (strcmp(argv[ii], "--byseq") == 0) {
428            mode = DumpBySequence;
429        } else if (strcmp(argv[ii], "--tree") == 0) {
430            dumpTree = true;
431        } else if (strcmp(argv[ii], "--json") == 0) {
432            dumpJson = true;
433        } else if (strcmp(argv[ii], "--hex-body") == 0) {
434            dumpHex = true;
435        } else if (strcmp(argv[ii], "--no-body") == 0) {
436            noBody = true;
437        } else if (strcmp(argv[ii], "--key") == 0) {
438            if (argc < (ii + 1)) {
439                usage();
440            }
441            oneKey = true;
442            dumpKey.buf = argv[ii+1];
443            dumpKey.size = strlen(argv[ii+1]);
444            if (mode == DumpBySequence) {
445                mode = DumpByID;
446            }
447            ii++;
448        } else if (strcmp(argv[ii], "--local") == 0) {
449            mode = DumpLocals;
450        } else {
451            usage();
452        }
453        ++ii;
454    }
455
456    if (ii >= argc || (mode == DumpLocals && dumpTree)) {
457        usage();
458    }
459
460    for (; ii < argc; ++ii) {
461        error += process_file(argv[ii], &count);
462    }
463
464    fprintf(stderr, "\nTotal docs: %d\n", count);
465    if (error) {
466        exit(EXIT_FAILURE);
467    } else {
468        exit(EXIT_SUCCESS);
469    }
470}
471