xref: /6.0.3/couchstore/src/dbdump.cc (revision 2fff2ce5)
1/* -*- Mode: C; tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*- */
2#include "config.h"
3
4#include <memcached/protocol_binary.h>
5#include <platform/cb_malloc.h>
6#include <string.h>
7#include <stdbool.h>
8#include <stdio.h>
9#include <stdlib.h>
10#include <sys/types.h>
11#include <unistd.h>
12#include <inttypes.h>
13#include <libcouchstore/couch_db.h>
14#include <snappy-c.h>
15#include <platform/sized_buffer.h>
16#include <xattr/utils.h>
17#include <xattr/blob.h>
18#include "couch_btree.h"
19#include "util.h"
20#include "bitfield.h"
21#include "internal.h"
22#include "node_types.h"
23#include "views/util.h"
24#include "views/index_header.h"
25#include "views/view_group.h"
26#include "tracking_file_ops.h"
27
28#define MAX_HEADER_SIZE (64 * 1024)
29
30typedef enum {
31    DumpBySequence,
32    DumpByID,
33    DumpLocals,
34    DumpFileMap,
35} DumpMode;
36
37static DumpMode mode = DumpBySequence;
38static bool dumpTree = false;
39static bool dumpJson = false;
40static bool dumpHex = false;
41static bool oneKey = false;
42static bool dumpBody = true;
43static bool decodeVbucket = true;
44static bool decodeIndex = false;
45static bool decodeNamespace = false;
46static bool iterateHeaders = false;
47static sized_buf dumpKey;
48
49typedef struct {
50    raw_64 cas;
51    raw_32 expiry;
52    raw_32 flags;
53} CouchbaseRevMeta;
54
55static int view_btree_cmp(const sized_buf *key1, const sized_buf *key2)
56{
57    return view_key_cmp(key1, key2, NULL);
58}
59
60static void printsb(const sized_buf *sb)
61{
62    if (sb->buf == NULL) {
63        printf("null\n");
64        return;
65    }
66    printf("%.*s\n", (int) sb->size, sb->buf);
67}
68
69static void printsbhexraw(const sized_buf* sb) {
70    size_t ii;
71    for (ii = 0; ii < sb->size; ++ii) {
72        printf("%.02x", (uint8_t)sb->buf[ii]);
73    }
74}
75
76static void printsbhex(const sized_buf *sb, int with_ascii)
77{
78    size_t i;
79
80    if (sb->buf == NULL) {
81        printf("null\n");
82        return;
83    }
84    printf("{");
85    for (i = 0; i < sb->size; ++i) {
86        printf("%.02x", (uint8_t)sb->buf[i]);
87        if (i % 4 == 3) {
88            printf(" ");
89        }
90    }
91    printf("}");
92    if (with_ascii) {
93        printf("  (\"");
94        for (i = 0; i < sb->size; ++i) {
95            uint8_t ch = sb->buf[i];
96            if (ch < 32 || ch >= 127) {
97                ch = '?';
98            }
99            printf("%c", ch);
100        }
101        printf("\")");
102    }
103    printf("\n");
104}
105
106static void printjquote(const sized_buf *sb)
107{
108    const char* i = sb->buf;
109    const char* end = sb->buf + sb->size;
110    if (sb->buf == NULL) {
111        return;
112    }
113    for (; i < end; i++) {
114        if (*i > 31 && *i != '\"' && *i != '\\') {
115            fputc(*i, stdout);
116        } else {
117            fputc('\\', stdout);
118            switch(*i)
119            {
120                case '\\': fputc('\\', stdout);break;
121                case '\"': fputc('\"', stdout);break;
122                case '\b': fputc('b', stdout);break;
123                case '\f': fputc('f', stdout);break;
124                case '\n': fputc('n', stdout);break;
125                case '\r': fputc('r', stdout);break;
126                case '\t': fputc('t', stdout);break;
127                default:
128                           printf("u00%.02x", *i);
129            }
130        }
131    }
132}
133
134static void print_datatype_as_json(const std::string& datatype) {
135    printf("\"datatype_as_text\":[");
136
137    std::string::size_type start = 0;
138    std::string::size_type end;
139    bool need_comma = false;
140    while ((end = datatype.find(',', start)) != std::string::npos) {
141        auto token = datatype.substr(start, end - start);
142        if (need_comma) {
143            printf(",");
144        }
145        printf("\"%s\"", token.c_str());
146        start = end + 1;
147        need_comma = true;
148    }
149
150    if (need_comma) {
151        printf(",");
152    }
153    auto token = datatype.substr(start);
154    printf("\"%s\"", token.c_str());
155    printf("],");
156}
157
158static const char* getNamespaceString(char ns) {
159    switch (ns) {
160    case 0:
161        return "default";
162    case 1:
163        return "collections";
164    case 2:
165        return "system";
166    default:
167        return "unknown";
168    }
169}
170
171static void printDocId(const char* prefix, const sized_buf* sb) {
172    if (decodeNamespace && sb->size > 0) {
173        // Byte 0 is namespace
174        printf("%s(%s) %.*s\n",
175               prefix,
176               getNamespaceString(sb->buf[0]),
177               (int)sb->size - 1,
178               sb->buf + 1);
179    } else {
180        printf("%s%.*s\n", prefix, (int)sb->size, sb->buf);
181    }
182}
183
184static int foldprint(Db *db, DocInfo *docinfo, void *ctx)
185{
186    int *count = (int *) ctx;
187    Doc *doc = NULL;
188    uint64_t cas;
189    uint32_t expiry, flags;
190    protocol_binary_datatype_t datatype = PROTOCOL_BINARY_RAW_BYTES;
191    couchstore_error_t docerr;
192    (*count)++;
193
194    if (dumpJson) {
195        printf("{\"seq\":%" PRIu64 ",\"id\":\"", docinfo->db_seq);
196        printjquote(&docinfo->id);
197        printf("\",");
198    } else {
199        if (mode == DumpBySequence) {
200            printf("Doc seq: %" PRIu64 "\n", docinfo->db_seq);
201            printDocId("     id: ", &docinfo->id);
202        } else {
203            printDocId("  Doc ID: ", &docinfo->id);
204            if (docinfo->db_seq > 0) {
205                printf("     seq: %" PRIu64 "\n", docinfo->db_seq);
206            }
207        }
208    }
209    if (docinfo->bp == 0 && docinfo->deleted == 0 && !dumpJson) {
210        printf("         ** This b-tree node is corrupt; raw node value follows:*\n");
211        printf("    raw: ");
212        printsbhex(&docinfo->rev_meta, 1);
213        return 0;
214    }
215    if (dumpJson) {
216        printf("\"rev\":%" PRIu64 ",\"content_meta\":%d,", docinfo->rev_seq,
217                                                         docinfo->content_meta);
218        printf("\"physical_size\":%" PRIu64 ",", (uint64_t)docinfo->size);
219    } else {
220        printf("     rev: %" PRIu64 "\n", docinfo->rev_seq);
221        printf("     content_meta: %d\n", docinfo->content_meta);
222        printf("     size (on disk): %" PRIu64 "\n", (uint64_t)docinfo->size);
223    }
224    if (docinfo->rev_meta.size >= sizeof(CouchbaseRevMeta)) {
225        const CouchbaseRevMeta* meta = (const CouchbaseRevMeta*)docinfo->rev_meta.buf;
226        cas = decode_raw64(meta->cas);
227        expiry = decode_raw32(meta->expiry);
228        flags = decode_raw32(meta->flags);
229        if (docinfo->rev_meta.size > sizeof(CouchbaseRevMeta)) {
230            // 18 bytes of rev_meta indicates CouchbaseRevMeta along with
231            // flex_meta_code (1B) and datatype (1B)
232            if (docinfo->rev_meta.size < sizeof(CouchbaseRevMeta) + 2) {
233                printf("     Error parsing the document: Possible corruption\n");
234                return 1;
235            }
236            const auto flex_code = *((uint8_t*)(docinfo->rev_meta.buf +
237                                                sizeof(CouchbaseRevMeta)));
238            if (flex_code < 0x01) {
239                printf("     Error: Flex code mismatch (bad code: %d)\n",
240                       flex_code);
241                return 1;
242            }
243            datatype = *((uint8_t *)(docinfo->rev_meta.buf + sizeof(CouchbaseRevMeta) +
244                        sizeof(uint8_t)));
245            const auto datatype_string = mcbp::datatype::to_string(datatype);
246
247            if (dumpJson) {
248                printf("\"cas\":\"%" PRIu64 "\",\"expiry\":%" PRIu32
249                       ",\"flags\":%" PRIu32 ",\"datatype\":%d,",
250                       cas, expiry, flags, datatype);
251                print_datatype_as_json(datatype_string);
252            } else {
253                printf("     cas: %" PRIu64 ", expiry: %" PRIu32
254                       ", flags: %" PRIu32 ", datatype: 0x%02x (%s)",
255                       cas, expiry, flags, datatype,
256                       datatype_string.c_str());
257            }
258
259            if (docinfo->rev_meta.size > sizeof(CouchbaseRevMeta) + 2) {
260                // 19 bytes of rev_meta indicates CouchbaseRevMeta along with
261                // flex_meta_code (1B) and datatype (1B), along with the conflict
262                // resolution flag (1B).
263                auto conf_res_mode = *((uint8_t *)(docinfo->rev_meta.buf +
264                                  sizeof(CouchbaseRevMeta) + sizeof(uint8_t) +
265                                  sizeof(uint8_t)));
266
267                if (dumpJson) {
268                    printf("\"conflict_resolution_mode\":%d,", conf_res_mode);
269                } else {
270                    printf(", conflict_resolution_mode: %d\n", conf_res_mode);
271                }
272            } else {
273                if (!dumpJson) {
274                    printf("\n");
275                }
276            }
277        } else {
278            if (dumpJson) {
279                printf("\"cas\":\"%" PRIu64 "\",\"expiry\":%" PRIu32
280                       ",\"flags\":%" PRIu32 ",",
281                        cas, expiry, flags);
282            } else {
283                printf("     cas: %" PRIu64 ", expiry: %" PRIu32 ", flags: %"
284                       PRIu32 "\n",
285                        cas, expiry, flags);
286            }
287        }
288    }
289    if (docinfo->deleted) {
290        if (dumpJson) {
291            printf("\"deleted\":true,");
292        } else {
293            printf("     doc deleted\n");
294        }
295    }
296
297    if (dumpBody) {
298        docerr = couchstore_open_doc_with_docinfo(db, docinfo, &doc, DECOMPRESS_DOC_BODIES);
299        if (docerr != COUCHSTORE_SUCCESS) {
300            if (dumpJson) {
301                printf("\"body\":null}\n");
302            } else {
303                printf("     could not read document body: %s\n", couchstore_strerror(docerr));
304            }
305        } else if (doc) {
306            std::string xattrs;
307            sized_buf body = doc->data;
308
309            if (mcbp::datatype::is_xattr(datatype)) {
310                auto offset = cb::xattr::get_body_offset({doc->data.buf,
311                                                          doc->data.size});
312                cb::xattr::Blob blob({doc->data.buf, offset});
313                xattrs = to_string(blob.to_json(),false);
314                body = _sized_buf{doc->data.buf + offset, doc->data.size - offset};
315            }
316
317            if (dumpJson) {
318                printf("\"size\":%" PRIu64 ",", (uint64_t)doc->data.size);
319                if (docinfo->content_meta & COUCH_DOC_IS_COMPRESSED) {
320                    printf("\"snappy\":true,\"display\":\"inflated\",");
321                }
322
323                if (xattrs.size() > 0) {
324                    sized_buf xa{const_cast<char*>(xattrs.data()), xattrs.size()};
325                    printf("\"xattr\":\"");
326                    printjquote(&xa);
327                    printf("\",");
328                }
329
330                printf("\"body\":\"");
331                printjquote(&body);
332                printf("\"}\n");
333            } else {
334                printf("     size: %" PRIu64 "\n", (uint64_t)doc->data.size);
335                if (xattrs.size() > 0) {
336                    printf("     xattrs: ");
337                    sized_buf xa{const_cast<char*>(xattrs.data()), xattrs.size()};
338                    if (dumpHex) {
339                        printsbhexraw(&xa);
340                        printf("\n");
341                    } else {
342                        printsb(&xa);
343                    }
344                }
345                printf("     data: ");
346
347                if (docinfo->content_meta & COUCH_DOC_IS_COMPRESSED) {
348                    printf("(snappy) ");
349                }
350
351                if (dumpHex) {
352                    printsbhexraw(&body);
353                    printf("\n");
354                } else {
355                    printsb(&body);
356                }
357            }
358        }
359    } else {
360        if (dumpJson) {
361            printf("\"body\":null}\n");
362        } else {
363            printf("\n");
364        }
365    }
366
367    couchstore_free_document(doc);
368    return 0;
369}
370
371
372static int visit_node(Db *db,
373                      int depth,
374                      const DocInfo* docinfo,
375                      uint64_t subtreeSize,
376                      const sized_buf* reduceValue,
377                      void *ctx)
378{
379    int i;
380    (void) db;
381
382    for (i = 0; i < depth; ++i)
383        printf("  ");
384    if (reduceValue) {
385        /* This is a tree node: */
386        printf("+ (%" PRIu64 ") ", subtreeSize);
387        printsbhex(reduceValue, 0);
388    } else if (docinfo->bp > 0) {
389        int *count;
390        /* This is a document: */
391        printf("%c (%" PRIu64 ") ", (docinfo->deleted ? 'x' : '*'),
392               (uint64_t)docinfo->size);
393        if (mode == DumpBySequence) {
394            printf("#%" PRIu64 " ", docinfo->db_seq);
395        }
396        printsb(&docinfo->id);
397
398        count = (int *) ctx;
399        (*count)++;
400    } else {
401        /* Document, but not in a known format: */
402        printf("**corrupt?** ");
403        printsbhex(&docinfo->rev_meta, 1);
404    }
405    return 0;
406}
407
408/// Visitor function for filemap mode - just trigger a read of the document
409/// so the FileMap ops can record where they reside on disk.
410static int filemap_visit(Db* db,
411                         int depth,
412                         const DocInfo* docinfo,
413                         uint64_t subtreeSize,
414                         const sized_buf* reduceValue,
415                         void* ctx) {
416    if (docinfo == nullptr) {
417        // Tree node.
418        return 0;
419    }
420    Doc* doc = nullptr;
421    ScopedFileTag tag(db->file.ops, db->file.handle, FileTag::Document);
422    couchstore_open_doc_with_docinfo(db, docinfo, &doc, DECOMPRESS_DOC_BODIES);
423    couchstore_free_document(doc);
424    return 0;
425}
426
427static int noop_visit(Db* db,
428                      int depth,
429                      const DocInfo* docinfo,
430                      uint64_t subtreeSize,
431                      const sized_buf* reduceValue,
432                      void* ctx) {
433    return 0;
434}
435
436static couchstore_error_t local_doc_print(couchfile_lookup_request *rq,
437                                          const sized_buf *k,
438                                          const sized_buf *v)
439{
440    int* count = (int*) rq->callback_ctx;
441    if (!v) {
442        return COUCHSTORE_ERROR_DOC_NOT_FOUND;
443    }
444    (*count)++;
445    sized_buf *id = (sized_buf *) k;
446    if (dumpJson) {
447        printf("{\"id\":\"");
448        printjquote(id);
449        printf("\",");
450    } else {
451        printf("Key: ");
452        printsb(id);
453    }
454
455    if (dumpJson) {
456        printf("\"value\":\"");
457        printjquote(v);
458        printf("\"}\n");
459    } else {
460        printf("Value: ");
461        printsb(v);
462        printf("\n");
463    }
464
465    return COUCHSTORE_SUCCESS;
466}
467
468static couchstore_error_t local_doc_ignore(couchfile_lookup_request* rq,
469                                           const sized_buf* k,
470                                           const sized_buf* v) {
471    return COUCHSTORE_SUCCESS;
472}
473
474typedef couchstore_error_t (*fetch_callback_fn)(
475        struct couchfile_lookup_request* rq,
476        const sized_buf* k,
477        const sized_buf* v);
478
479static couchstore_error_t couchstore_print_local_docs(
480        Db* db, fetch_callback_fn fetch_cb, int* count) {
481    sized_buf key;
482    sized_buf *keylist = &key;
483    couchfile_lookup_request rq;
484    couchstore_error_t errcode;
485
486    if (db->header.local_docs_root == NULL) {
487        if (oneKey) {
488            return COUCHSTORE_ERROR_DOC_NOT_FOUND;
489        } else {
490            return COUCHSTORE_SUCCESS;
491        }
492    }
493
494    key.buf = (char *)"\0";
495    key.size = 0;
496
497    rq.cmp.compare = ebin_cmp;
498    rq.file = &db->file;
499    rq.num_keys = 1;
500    rq.keys = &keylist;
501    rq.callback_ctx = count;
502    rq.fetch_callback = fetch_cb;
503    rq.node_callback = NULL;
504    rq.fold = 1;
505
506    if (oneKey) {
507        rq.fold = 0;
508        key = dumpKey;
509    }
510
511    errcode = btree_lookup(&rq, db->header.local_docs_root->pointer);
512    return errcode;
513}
514
515static int process_vbucket_file(const char *file, int *total)
516{
517    Db *db;
518    couchstore_error_t errcode;
519    int count = 0;
520
521    TrackingFileOps* trackingFileOps = nullptr;
522    couchstore_open_flags flags = COUCHSTORE_OPEN_FLAG_RDONLY;
523    if (mode == DumpFileMap) {
524        flags |= COUCHSTORE_OPEN_FLAG_UNBUFFERED;
525        trackingFileOps = new TrackingFileOps();
526        errcode = couchstore_open_db_ex(file, flags, trackingFileOps, &db);
527    } else {
528        errcode = couchstore_open_db(file, flags, &db);
529    }
530    if (errcode != COUCHSTORE_SUCCESS) {
531        fprintf(stderr, "Failed to open \"%s\": %s\n",
532                file, couchstore_strerror(errcode));
533        return -1;
534    } else {
535        printf("Dumping \"%s\":\n", file);
536    }
537
538next_header:
539    switch (mode) {
540    case DumpBySequence:
541        if (dumpTree) {
542            errcode = couchstore_walk_seq_tree(
543                    db, 0, COUCHSTORE_TOLERATE_CORRUPTION,
544                    visit_node, &count);
545        } else {
546            errcode = couchstore_changes_since(
547                    db, 0, COUCHSTORE_TOLERATE_CORRUPTION,
548                    foldprint, &count);
549        }
550        break;
551    case DumpByID:
552        if (dumpTree) {
553            errcode = couchstore_walk_id_tree(
554                    db, NULL, COUCHSTORE_TOLERATE_CORRUPTION,
555                    visit_node, &count);
556        } else if (oneKey) {
557            DocInfo* info;
558            errcode = couchstore_docinfo_by_id(db, dumpKey.buf, dumpKey.size, &info);
559            if (errcode == COUCHSTORE_SUCCESS) {
560                foldprint(db, info, &count);
561                couchstore_free_docinfo(info);
562            }
563        } else {
564            errcode = couchstore_all_docs(
565                    db, NULL, COUCHSTORE_TOLERATE_CORRUPTION,
566                    foldprint, &count);
567        }
568        break;
569    case DumpLocals:
570        errcode = couchstore_print_local_docs(db, local_doc_print, &count);
571        break;
572
573    case DumpFileMap:
574        // Visit all three indexes in the file. Note we don't actually need to
575        // do anything in the callback; the map is built up using a custom
576        // FileOps class and annotations in couchstore itself to tag the
577        // different structures.
578        cb_assert(trackingFileOps != nullptr);
579        trackingFileOps->setTree(db->file.handle,
580                                 TrackingFileOps::Tree::Sequence);
581        couchstore_walk_seq_tree(
582                db, 0, COUCHSTORE_TOLERATE_CORRUPTION, filemap_visit, &count);
583
584        // Note for the ID tree we specify a different (noop) callback; as we
585        // don't want or need to read the document bodies again.
586        trackingFileOps->setTree(db->file.handle, TrackingFileOps::Tree::Id);
587        couchstore_walk_id_tree(
588                db, NULL, COUCHSTORE_TOLERATE_CORRUPTION, noop_visit, &count);
589
590        trackingFileOps->setTree(db->file.handle, TrackingFileOps::Tree::Local);
591        int dummy = 0;
592        couchstore_print_local_docs(db, local_doc_ignore, &dummy);
593
594        // Mark that we are now on old headers
595        trackingFileOps->setHistoricData(db->file.handle, true);
596        break;
597    }
598    if (iterateHeaders) {
599        if (couchstore_rewind_db_header(db) == COUCHSTORE_SUCCESS) {
600            printf("\n");
601            goto next_header;
602        }
603    } else { /* rewind_db_header does its own cleanup on failure */
604        couchstore_close_file(db);
605        couchstore_free_db(db);
606    }
607
608    if (errcode < 0) {
609        fprintf(stderr, "Failed to dump database \"%s\": %s\n",
610                file, couchstore_strerror(errcode));
611        return -1;
612    }
613
614    *total += count;
615    return 0;
616}
617
618static couchstore_error_t lookup_callback(couchfile_lookup_request *rq,
619                                          const sized_buf *k,
620                                          const sized_buf *v)
621{
622    const uint16_t json_key_len = decode_raw16(*((raw_16 *) k->buf));
623    sized_buf json_key;
624    sized_buf json_value;
625
626    json_key.buf = k->buf + sizeof(uint16_t);
627    json_key.size = json_key_len;
628
629    json_value.size = v->size - sizeof(raw_kv_length);
630    json_value.buf = v->buf + sizeof(raw_kv_length);
631
632    if (dumpJson) {
633        printf("{\"id\":\"");
634        printjquote(&json_key);
635        printf("\",\"data\":\"");
636        printjquote(&json_value);
637        printf("\"}\n");
638    } else {
639        printf("Doc ID: ");
640        printsb(&json_key);
641        printf("data: ");
642        printsb(&json_value);
643    }
644
645    printf("\n");
646    rq->num_keys++;
647
648    return COUCHSTORE_SUCCESS;
649}
650
651static couchstore_error_t find_view_header_at_pos(view_group_info_t *info,
652                                                cs_off_t pos)
653{
654    couchstore_error_t errcode = COUCHSTORE_SUCCESS;
655    uint8_t buf;
656    ssize_t readsize = info->file.ops->pread(&info->file.lastError,
657                                            info->file.handle,
658                                            &buf, 1, pos);
659    error_unless(readsize == 1, static_cast<couchstore_error_t>(readsize));
660    if (buf == 0) {
661        return COUCHSTORE_ERROR_NO_HEADER;
662    } else if (buf != 1) {
663        return COUCHSTORE_ERROR_CORRUPT;
664    }
665
666    info->header_pos = pos;
667
668    return COUCHSTORE_SUCCESS;
669
670cleanup:
671    return errcode;
672}
673
674static couchstore_error_t find_view_header(view_group_info_t *info,
675                                        int64_t start_pos)
676{
677    couchstore_error_t last_header_errcode = COUCHSTORE_ERROR_NO_HEADER;
678    int64_t pos = start_pos;
679    pos -= pos % COUCH_BLOCK_SIZE;
680    for (; pos >= 0; pos -= COUCH_BLOCK_SIZE) {
681        couchstore_error_t errcode = find_view_header_at_pos(info, pos);
682        switch(errcode) {
683            case COUCHSTORE_SUCCESS:
684                // Found it!
685                return COUCHSTORE_SUCCESS;
686            case COUCHSTORE_ERROR_NO_HEADER:
687                // No header here, so keep going
688                break;
689            case COUCHSTORE_ERROR_ALLOC_FAIL:
690                // Fatal error
691                return errcode;
692            default:
693                // Invalid header; continue, but remember the last error
694                last_header_errcode = errcode;
695                break;
696        }
697    }
698    return last_header_errcode;
699}
700
701static int process_view_file(const char *file, int *total)
702{
703    view_group_info_t *info;
704    couchstore_error_t errcode;
705    index_header_t *header = NULL;
706    char *header_buf = NULL;
707    int header_len;
708
709    info = (view_group_info_t *)cb_calloc(1, sizeof(view_group_info_t));
710    if (info == NULL) {
711        fprintf(stderr, "Unable to allocate memory\n");
712        return -1;
713    }
714    info->type = VIEW_INDEX_TYPE_MAPREDUCE;
715
716    errcode = open_view_group_file(file, COUCHSTORE_OPEN_FLAG_RDONLY, &info->file);
717    if (errcode != COUCHSTORE_SUCCESS) {
718        fprintf(stderr, "Failed to open \"%s\": %s\n",
719                file, couchstore_strerror(errcode));
720        return -1;
721    } else {
722        printf("Dumping \"%s\":\n", file);
723    }
724
725    info->file.pos = info->file.ops->goto_eof(&info->file.lastError,
726                                              info->file.handle);
727
728    errcode = find_view_header(info, info->file.pos - 2);
729    if (errcode != COUCHSTORE_SUCCESS) {
730        fprintf(stderr, "Unable to find header position \"%s\": %s\n",
731                file, couchstore_strerror(errcode));
732        return -1;
733    }
734
735    header_len = pread_header(&info->file, (cs_off_t)info->header_pos, &header_buf,
736                            MAX_HEADER_SIZE);
737
738    if (header_len < 0) {
739        return -1;
740    }
741
742    errcode = decode_index_header(header_buf, (size_t) header_len, &header);
743    if (errcode != COUCHSTORE_SUCCESS) {
744        fprintf(stderr, "Unable to decode header \"%s\": %s\n",
745                file, couchstore_strerror(errcode));
746        return -1;
747    }
748    cb_free(header_buf);
749    printf("Num views: %d\n", header->num_views);
750
751    for (int i = 0; i < header->num_views; ++i) {
752        printf("\nKV pairs from index: %d\n", i);
753        sized_buf nullkey = {NULL, 0};
754        sized_buf *lowkeys = &nullkey;
755        couchfile_lookup_request rq;
756
757        rq.cmp.compare = view_btree_cmp;
758        rq.file = &info->file;
759        rq.num_keys = 1;
760        rq.keys = &lowkeys;
761        rq.callback_ctx = NULL;
762        rq.fetch_callback = lookup_callback;
763        rq.node_callback = NULL;
764        rq.fold = 1;
765
766        errcode = btree_lookup(&rq, header->view_states[i]->pointer);
767        if (errcode != COUCHSTORE_SUCCESS) {
768            return -1;
769        }
770        *total = rq.num_keys - 1;
771    }
772    return 0;
773}
774
775static void usage(void) {
776    printf("USAGE: couch_dbdump [options] file.couch [main_xxxx.view.X ...]\n");
777    printf("\nOptions:\n");
778    printf("    --vbucket <vb_file> decode vbucket file\n");
779    printf("    --view <view_file> decode view index file\n");
780    printf("    --key <key>  dump only the specified document\n");
781    printf("    --hex-body   convert document body data to hex (for binary data)\n");
782    printf("    --no-body    don't retrieve document bodies (metadata only, faster)\n");
783    printf("    --byid       sort output by document ID\n");
784    printf("    --byseq      sort output by document sequence number (default)\n");
785    printf("    --json       dump data as JSON objects (one per line)\n");
786    printf("    --namespace  decode namespaces\n");
787    printf("    --iterate-headers  Iterate through all headers\n");
788    printf("\nAlternate modes:\n");
789    printf("    --tree       show file b-tree structure instead of data\n");
790    printf("    --local      dump local documents\n");
791    printf("    --map        dump block map \n");
792    exit(EXIT_FAILURE);
793}
794
795int main(int argc, char **argv)
796{
797    int error = 0;
798    int count = 0;
799    int ii = 1;
800
801    if (argc < 2) {
802        usage();
803    }
804
805    while (ii < argc && strncmp(argv[ii], "-", 1) == 0) {
806        if (strcmp(argv[ii], "--view") == 0) {
807            decodeIndex = true;
808        } else if (strcmp(argv[ii], "--vbucket") == 0) {
809            decodeVbucket = true;
810        } else if (strcmp(argv[ii], "--byid") == 0) {
811            mode = DumpByID;
812        } else if (strcmp(argv[ii], "--byseq") == 0) {
813            mode = DumpBySequence;
814        } else if (strcmp(argv[ii], "--tree") == 0) {
815            dumpTree = true;
816        } else if (strcmp(argv[ii], "--json") == 0) {
817            dumpJson = true;
818        } else if (strcmp(argv[ii], "--hex-body") == 0) {
819            dumpHex = true;
820        } else if (strcmp(argv[ii], "--no-body") == 0) {
821            dumpBody = false;
822        } else if (strcmp(argv[ii], "--namespace") == 0) {
823            decodeNamespace = true;
824        } else if (strcmp(argv[ii], "--key") == 0) {
825            if (argc < (ii + 1)) {
826                usage();
827            }
828            oneKey = true;
829            dumpKey.buf = argv[ii+1];
830            dumpKey.size = strlen(argv[ii+1]);
831            if (mode == DumpBySequence) {
832                mode = DumpByID;
833            }
834            ii++;
835        } else if (strcmp(argv[ii], "--local") == 0) {
836            mode = DumpLocals;
837        } else if (strcmp(argv[ii], "--map") == 0) {
838            mode = DumpFileMap;
839        } else if (strcmp(argv[ii], "--iterate-headers") == 0) {
840            iterateHeaders = true;
841        } else {
842            usage();
843        }
844        ++ii;
845    }
846
847    if (ii >= argc || (mode == DumpLocals && dumpTree)) {
848        usage();
849    }
850
851    for (; ii < argc; ++ii) {
852        if (decodeIndex) {
853            error += process_view_file(argv[ii], &count);
854        } else if (decodeVbucket) {
855            error += process_vbucket_file(argv[ii], &count);
856        } else {
857            usage();
858        }
859    }
860
861    printf("\nTotal docs: %d\n", count);
862    if (error) {
863        exit(EXIT_FAILURE);
864    } else {
865        exit(EXIT_SUCCESS);
866    }
867}
868