xref: /6.0.3/couchstore/src/dbdump.cc (revision 00098337)
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 datatype is snappy (and not marked compressed) we must inflate
310            cb::compression::Buffer inflated;
311            if (mcbp::datatype::is_snappy(datatype) &&
312                !(docinfo->content_meta & COUCH_DOC_IS_COMPRESSED)) {
313                // Inflate the entire document so we can work with it
314                if (!cb::compression::inflate(
315                            cb::compression::Algorithm::Snappy,
316                            {doc->data.buf, doc->data.size},
317                            inflated)) {
318                    if (dumpJson) {
319                        printf("\"body\":null}\n");
320                    } else {
321                        printf("     could not inflate document body\n");
322                    }
323                    return 0;
324                }
325
326                body = _sized_buf{inflated.data(), inflated.size()};
327            }
328
329            if (mcbp::datatype::is_xattr(datatype)) {
330                cb::xattr::Blob blob({body.buf, body.size}, false);
331                xattrs = to_string(blob.to_json(),false);
332                body = _sized_buf{doc->data.buf + blob.size(),
333                                  doc->data.size - blob.size()};
334            }
335
336            if (dumpJson) {
337                printf("\"size\":%" PRIu64 ",", (uint64_t)doc->data.size);
338                if (docinfo->content_meta & COUCH_DOC_IS_COMPRESSED) {
339                    printf("\"snappy\":true,\"display\":\"inflated\",");
340                }
341
342                if (xattrs.size() > 0) {
343                    sized_buf xa{const_cast<char*>(xattrs.data()), xattrs.size()};
344                    printf("\"xattr\":\"");
345                    printjquote(&xa);
346                    printf("\",");
347                }
348
349                printf("\"body\":\"");
350                printjquote(&body);
351                printf("\"}\n");
352            } else {
353                printf("     size: %" PRIu64 "\n", (uint64_t)doc->data.size);
354                if (xattrs.size() > 0) {
355                    printf("     xattrs: ");
356                    sized_buf xa{const_cast<char*>(xattrs.data()), xattrs.size()};
357                    if (dumpHex) {
358                        printsbhexraw(&xa);
359                        printf("\n");
360                    } else {
361                        printsb(&xa);
362                    }
363                }
364                printf("     data: ");
365
366                if (docinfo->content_meta & COUCH_DOC_IS_COMPRESSED) {
367                    printf("(snappy) ");
368                }
369
370                if (dumpHex) {
371                    printsbhexraw(&body);
372                    printf("\n");
373                } else {
374                    printsb(&body);
375                }
376            }
377        }
378    } else {
379        if (dumpJson) {
380            printf("\"body\":null}\n");
381        } else {
382            printf("\n");
383        }
384    }
385
386    couchstore_free_document(doc);
387    return 0;
388}
389
390
391static int visit_node(Db *db,
392                      int depth,
393                      const DocInfo* docinfo,
394                      uint64_t subtreeSize,
395                      const sized_buf* reduceValue,
396                      void *ctx)
397{
398    int i;
399    (void) db;
400
401    for (i = 0; i < depth; ++i)
402        printf("  ");
403    if (reduceValue) {
404        /* This is a tree node: */
405        printf("+ (%" PRIu64 ") ", subtreeSize);
406        printsbhex(reduceValue, 0);
407    } else if (docinfo->bp > 0) {
408        int *count;
409        /* This is a document: */
410        printf("%c (%" PRIu64 ") ", (docinfo->deleted ? 'x' : '*'),
411               (uint64_t)docinfo->size);
412        if (mode == DumpBySequence) {
413            printf("#%" PRIu64 " ", docinfo->db_seq);
414        }
415        printsb(&docinfo->id);
416
417        count = (int *) ctx;
418        (*count)++;
419    } else {
420        /* Document, but not in a known format: */
421        printf("**corrupt?** ");
422        printsbhex(&docinfo->rev_meta, 1);
423    }
424    return 0;
425}
426
427/// Visitor function for filemap mode - just trigger a read of the document
428/// so the FileMap ops can record where they reside on disk.
429static int filemap_visit(Db* db,
430                         int depth,
431                         const DocInfo* docinfo,
432                         uint64_t subtreeSize,
433                         const sized_buf* reduceValue,
434                         void* ctx) {
435    if (docinfo == nullptr) {
436        // Tree node.
437        return 0;
438    }
439    Doc* doc = nullptr;
440    ScopedFileTag tag(db->file.ops, db->file.handle, FileTag::Document);
441    couchstore_open_doc_with_docinfo(db, docinfo, &doc, DECOMPRESS_DOC_BODIES);
442    couchstore_free_document(doc);
443    return 0;
444}
445
446static int noop_visit(Db* db,
447                      int depth,
448                      const DocInfo* docinfo,
449                      uint64_t subtreeSize,
450                      const sized_buf* reduceValue,
451                      void* ctx) {
452    return 0;
453}
454
455static couchstore_error_t local_doc_print(couchfile_lookup_request *rq,
456                                          const sized_buf *k,
457                                          const sized_buf *v)
458{
459    int* count = (int*) rq->callback_ctx;
460    if (!v) {
461        return COUCHSTORE_ERROR_DOC_NOT_FOUND;
462    }
463    (*count)++;
464    sized_buf *id = (sized_buf *) k;
465    if (dumpJson) {
466        printf("{\"id\":\"");
467        printjquote(id);
468        printf("\",");
469    } else {
470        printf("Key: ");
471        printsb(id);
472    }
473
474    if (dumpJson) {
475        printf("\"value\":\"");
476        printjquote(v);
477        printf("\"}\n");
478    } else {
479        printf("Value: ");
480        printsb(v);
481        printf("\n");
482    }
483
484    return COUCHSTORE_SUCCESS;
485}
486
487static couchstore_error_t local_doc_ignore(couchfile_lookup_request* rq,
488                                           const sized_buf* k,
489                                           const sized_buf* v) {
490    return COUCHSTORE_SUCCESS;
491}
492
493typedef couchstore_error_t (*fetch_callback_fn)(
494        struct couchfile_lookup_request* rq,
495        const sized_buf* k,
496        const sized_buf* v);
497
498static couchstore_error_t couchstore_print_local_docs(
499        Db* db, fetch_callback_fn fetch_cb, int* count) {
500    sized_buf key;
501    sized_buf *keylist = &key;
502    couchfile_lookup_request rq;
503    couchstore_error_t errcode;
504
505    if (db->header.local_docs_root == NULL) {
506        if (oneKey) {
507            return COUCHSTORE_ERROR_DOC_NOT_FOUND;
508        } else {
509            return COUCHSTORE_SUCCESS;
510        }
511    }
512
513    key.buf = (char *)"\0";
514    key.size = 0;
515
516    rq.cmp.compare = ebin_cmp;
517    rq.file = &db->file;
518    rq.num_keys = 1;
519    rq.keys = &keylist;
520    rq.callback_ctx = count;
521    rq.fetch_callback = fetch_cb;
522    rq.node_callback = NULL;
523    rq.fold = 1;
524
525    if (oneKey) {
526        rq.fold = 0;
527        key = dumpKey;
528    }
529
530    errcode = btree_lookup(&rq, db->header.local_docs_root->pointer);
531    return errcode;
532}
533
534static int process_vbucket_file(const char *file, int *total)
535{
536    Db *db;
537    couchstore_error_t errcode;
538    int count = 0;
539
540    TrackingFileOps* trackingFileOps = nullptr;
541    couchstore_open_flags flags = COUCHSTORE_OPEN_FLAG_RDONLY;
542    if (mode == DumpFileMap) {
543        flags |= COUCHSTORE_OPEN_FLAG_UNBUFFERED;
544        trackingFileOps = new TrackingFileOps();
545        errcode = couchstore_open_db_ex(file, flags, trackingFileOps, &db);
546    } else {
547        errcode = couchstore_open_db(file, flags, &db);
548    }
549    if (errcode != COUCHSTORE_SUCCESS) {
550        fprintf(stderr, "Failed to open \"%s\": %s\n",
551                file, couchstore_strerror(errcode));
552        return -1;
553    } else {
554        printf("Dumping \"%s\":\n", file);
555    }
556
557next_header:
558    switch (mode) {
559    case DumpBySequence:
560        if (dumpTree) {
561            errcode = couchstore_walk_seq_tree(
562                    db, 0, COUCHSTORE_TOLERATE_CORRUPTION,
563                    visit_node, &count);
564        } else {
565            errcode = couchstore_changes_since(
566                    db, 0, COUCHSTORE_TOLERATE_CORRUPTION,
567                    foldprint, &count);
568        }
569        break;
570    case DumpByID:
571        if (dumpTree) {
572            errcode = couchstore_walk_id_tree(
573                    db, NULL, COUCHSTORE_TOLERATE_CORRUPTION,
574                    visit_node, &count);
575        } else if (oneKey) {
576            DocInfo* info;
577            errcode = couchstore_docinfo_by_id(db, dumpKey.buf, dumpKey.size, &info);
578            if (errcode == COUCHSTORE_SUCCESS) {
579                foldprint(db, info, &count);
580                couchstore_free_docinfo(info);
581            }
582        } else {
583            errcode = couchstore_all_docs(
584                    db, NULL, COUCHSTORE_TOLERATE_CORRUPTION,
585                    foldprint, &count);
586        }
587        break;
588    case DumpLocals:
589        errcode = couchstore_print_local_docs(db, local_doc_print, &count);
590        break;
591
592    case DumpFileMap:
593        // Visit all three indexes in the file. Note we don't actually need to
594        // do anything in the callback; the map is built up using a custom
595        // FileOps class and annotations in couchstore itself to tag the
596        // different structures.
597        cb_assert(trackingFileOps != nullptr);
598        trackingFileOps->setTree(db->file.handle,
599                                 TrackingFileOps::Tree::Sequence);
600        couchstore_walk_seq_tree(
601                db, 0, COUCHSTORE_TOLERATE_CORRUPTION, filemap_visit, &count);
602
603        // Note for the ID tree we specify a different (noop) callback; as we
604        // don't want or need to read the document bodies again.
605        trackingFileOps->setTree(db->file.handle, TrackingFileOps::Tree::Id);
606        couchstore_walk_id_tree(
607                db, NULL, COUCHSTORE_TOLERATE_CORRUPTION, noop_visit, &count);
608
609        trackingFileOps->setTree(db->file.handle, TrackingFileOps::Tree::Local);
610        int dummy = 0;
611        couchstore_print_local_docs(db, local_doc_ignore, &dummy);
612
613        // Mark that we are now on old headers
614        trackingFileOps->setHistoricData(db->file.handle, true);
615        break;
616    }
617    if (iterateHeaders) {
618        if (couchstore_rewind_db_header(db) == COUCHSTORE_SUCCESS) {
619            printf("\n");
620            goto next_header;
621        }
622    } else { /* rewind_db_header does its own cleanup on failure */
623        couchstore_close_file(db);
624        couchstore_free_db(db);
625    }
626
627    if (errcode < 0) {
628        fprintf(stderr, "Failed to dump database \"%s\": %s\n",
629                file, couchstore_strerror(errcode));
630        return -1;
631    }
632
633    *total += count;
634    return 0;
635}
636
637static couchstore_error_t lookup_callback(couchfile_lookup_request *rq,
638                                          const sized_buf *k,
639                                          const sized_buf *v)
640{
641    const uint16_t json_key_len = decode_raw16(*((raw_16 *) k->buf));
642    sized_buf json_key;
643    sized_buf json_value;
644
645    json_key.buf = k->buf + sizeof(uint16_t);
646    json_key.size = json_key_len;
647
648    json_value.size = v->size - sizeof(raw_kv_length);
649    json_value.buf = v->buf + sizeof(raw_kv_length);
650
651    if (dumpJson) {
652        printf("{\"id\":\"");
653        printjquote(&json_key);
654        printf("\",\"data\":\"");
655        printjquote(&json_value);
656        printf("\"}\n");
657    } else {
658        printf("Doc ID: ");
659        printsb(&json_key);
660        printf("data: ");
661        printsb(&json_value);
662    }
663
664    printf("\n");
665    rq->num_keys++;
666
667    return COUCHSTORE_SUCCESS;
668}
669
670static couchstore_error_t find_view_header_at_pos(view_group_info_t *info,
671                                                cs_off_t pos)
672{
673    couchstore_error_t errcode = COUCHSTORE_SUCCESS;
674    uint8_t buf;
675    ssize_t readsize = info->file.ops->pread(&info->file.lastError,
676                                            info->file.handle,
677                                            &buf, 1, pos);
678    error_unless(readsize == 1, static_cast<couchstore_error_t>(readsize));
679    if (buf == 0) {
680        return COUCHSTORE_ERROR_NO_HEADER;
681    } else if (buf != 1) {
682        return COUCHSTORE_ERROR_CORRUPT;
683    }
684
685    info->header_pos = pos;
686
687    return COUCHSTORE_SUCCESS;
688
689cleanup:
690    return errcode;
691}
692
693static couchstore_error_t find_view_header(view_group_info_t *info,
694                                        int64_t start_pos)
695{
696    couchstore_error_t last_header_errcode = COUCHSTORE_ERROR_NO_HEADER;
697    int64_t pos = start_pos;
698    pos -= pos % COUCH_BLOCK_SIZE;
699    for (; pos >= 0; pos -= COUCH_BLOCK_SIZE) {
700        couchstore_error_t errcode = find_view_header_at_pos(info, pos);
701        switch(errcode) {
702            case COUCHSTORE_SUCCESS:
703                // Found it!
704                return COUCHSTORE_SUCCESS;
705            case COUCHSTORE_ERROR_NO_HEADER:
706                // No header here, so keep going
707                break;
708            case COUCHSTORE_ERROR_ALLOC_FAIL:
709                // Fatal error
710                return errcode;
711            default:
712                // Invalid header; continue, but remember the last error
713                last_header_errcode = errcode;
714                break;
715        }
716    }
717    return last_header_errcode;
718}
719
720static int process_view_file(const char *file, int *total)
721{
722    view_group_info_t *info;
723    couchstore_error_t errcode;
724    index_header_t *header = NULL;
725    char *header_buf = NULL;
726    int header_len;
727
728    info = (view_group_info_t *)cb_calloc(1, sizeof(view_group_info_t));
729    if (info == NULL) {
730        fprintf(stderr, "Unable to allocate memory\n");
731        return -1;
732    }
733    info->type = VIEW_INDEX_TYPE_MAPREDUCE;
734
735    errcode = open_view_group_file(file, COUCHSTORE_OPEN_FLAG_RDONLY, &info->file);
736    if (errcode != COUCHSTORE_SUCCESS) {
737        fprintf(stderr, "Failed to open \"%s\": %s\n",
738                file, couchstore_strerror(errcode));
739        return -1;
740    } else {
741        printf("Dumping \"%s\":\n", file);
742    }
743
744    info->file.pos = info->file.ops->goto_eof(&info->file.lastError,
745                                              info->file.handle);
746
747    errcode = find_view_header(info, info->file.pos - 2);
748    if (errcode != COUCHSTORE_SUCCESS) {
749        fprintf(stderr, "Unable to find header position \"%s\": %s\n",
750                file, couchstore_strerror(errcode));
751        return -1;
752    }
753
754    header_len = pread_header(&info->file, (cs_off_t)info->header_pos, &header_buf,
755                            MAX_HEADER_SIZE);
756
757    if (header_len < 0) {
758        return -1;
759    }
760
761    errcode = decode_index_header(header_buf, (size_t) header_len, &header);
762    if (errcode != COUCHSTORE_SUCCESS) {
763        fprintf(stderr, "Unable to decode header \"%s\": %s\n",
764                file, couchstore_strerror(errcode));
765        return -1;
766    }
767    cb_free(header_buf);
768    printf("Num views: %d\n", header->num_views);
769
770    for (int i = 0; i < header->num_views; ++i) {
771        printf("\nKV pairs from index: %d\n", i);
772        sized_buf nullkey = {NULL, 0};
773        sized_buf *lowkeys = &nullkey;
774        couchfile_lookup_request rq;
775
776        rq.cmp.compare = view_btree_cmp;
777        rq.file = &info->file;
778        rq.num_keys = 1;
779        rq.keys = &lowkeys;
780        rq.callback_ctx = NULL;
781        rq.fetch_callback = lookup_callback;
782        rq.node_callback = NULL;
783        rq.fold = 1;
784
785        errcode = btree_lookup(&rq, header->view_states[i]->pointer);
786        if (errcode != COUCHSTORE_SUCCESS) {
787            return -1;
788        }
789        *total = rq.num_keys - 1;
790    }
791    return 0;
792}
793
794static void usage(void) {
795    printf("USAGE: couch_dbdump [options] file.couch [main_xxxx.view.X ...]\n");
796    printf("\nOptions:\n");
797    printf("    --vbucket <vb_file> decode vbucket file\n");
798    printf("    --view <view_file> decode view index file\n");
799    printf("    --key <key>  dump only the specified document\n");
800    printf("    --hex-body   convert document body data to hex (for binary data)\n");
801    printf("    --no-body    don't retrieve document bodies (metadata only, faster)\n");
802    printf("    --byid       sort output by document ID\n");
803    printf("    --byseq      sort output by document sequence number (default)\n");
804    printf("    --json       dump data as JSON objects (one per line)\n");
805    printf("    --namespace  decode namespaces\n");
806    printf("    --iterate-headers  Iterate through all headers\n");
807    printf("\nAlternate modes:\n");
808    printf("    --tree       show file b-tree structure instead of data\n");
809    printf("    --local      dump local documents\n");
810    printf("    --map        dump block map \n");
811    exit(EXIT_FAILURE);
812}
813
814int main(int argc, char **argv)
815{
816    int error = 0;
817    int count = 0;
818    int ii = 1;
819
820    if (argc < 2) {
821        usage();
822    }
823
824    while (ii < argc && strncmp(argv[ii], "-", 1) == 0) {
825        if (strcmp(argv[ii], "--view") == 0) {
826            decodeIndex = true;
827        } else if (strcmp(argv[ii], "--vbucket") == 0) {
828            decodeVbucket = true;
829        } else if (strcmp(argv[ii], "--byid") == 0) {
830            mode = DumpByID;
831        } else if (strcmp(argv[ii], "--byseq") == 0) {
832            mode = DumpBySequence;
833        } else if (strcmp(argv[ii], "--tree") == 0) {
834            dumpTree = true;
835        } else if (strcmp(argv[ii], "--json") == 0) {
836            dumpJson = true;
837        } else if (strcmp(argv[ii], "--hex-body") == 0) {
838            dumpHex = true;
839        } else if (strcmp(argv[ii], "--no-body") == 0) {
840            dumpBody = false;
841        } else if (strcmp(argv[ii], "--namespace") == 0) {
842            decodeNamespace = true;
843        } else if (strcmp(argv[ii], "--key") == 0) {
844            if (argc < (ii + 1)) {
845                usage();
846            }
847            oneKey = true;
848            dumpKey.buf = argv[ii+1];
849            dumpKey.size = strlen(argv[ii+1]);
850            if (mode == DumpBySequence) {
851                mode = DumpByID;
852            }
853            ii++;
854        } else if (strcmp(argv[ii], "--local") == 0) {
855            mode = DumpLocals;
856        } else if (strcmp(argv[ii], "--map") == 0) {
857            mode = DumpFileMap;
858        } else if (strcmp(argv[ii], "--iterate-headers") == 0) {
859            iterateHeaders = true;
860        } else {
861            usage();
862        }
863        ++ii;
864    }
865
866    if (ii >= argc || (mode == DumpLocals && dumpTree)) {
867        usage();
868    }
869
870    for (; ii < argc; ++ii) {
871        if (decodeIndex) {
872            error += process_view_file(argv[ii], &count);
873        } else if (decodeVbucket) {
874            error += process_vbucket_file(argv[ii], &count);
875        } else {
876            usage();
877        }
878    }
879
880    printf("\nTotal docs: %d\n", count);
881    if (error) {
882        exit(EXIT_FAILURE);
883    } else {
884        exit(EXIT_SUCCESS);
885    }
886}
887