root/src/bin/bfs_tools/bfsinfo.cpp
/*
 * Copyright 2001-2010 pinc Software. All Rights Reserved.
 * Released under the terms of the MIT license.
 */


//!     Dumps various information about BFS volumes.


#include "Disk.h"
#include "BPlusTree.h"
#include "Inode.h"
#include "dump.h"

#include <StringForSize.h>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>


using namespace BPrivate;


void
dump_bplustree(Disk &disk, BPositionIO *file, off_t size, bool hexDump)
{
        uint8 *buffer = (uint8 *)malloc(size);
        if (buffer == NULL) {
                puts("no buffer");
                return;
        }

        if (file->ReadAt(0, buffer, size) != size) {
                puts("couldn't read whole file");
                return;
        }

        bplustree_header *header = (bplustree_header *)buffer;
        int32 nodeSize = header->node_size;

        dump_bplustree_header(header);

        bplustree_node *node = (bplustree_node *)(buffer + nodeSize);
        while ((addr_t)node < (addr_t)buffer + size) {
                printf("\n\n-------------------\n"
                        "** node at offset: %" B_PRIuADDR "\n** used: %" B_PRId32 " bytes"
                        "\n", (addr_t)node - (addr_t)buffer, node->Used());
                dump_bplustree_node(node, header, &disk);

                if (hexDump) {
                        putchar('\n');
                        dump_block((char *)node, header->node_size, 0);
                }

                node = (bplustree_node *)((addr_t)node + nodeSize);
        }
}


void
dump_indirect_stream(Disk &disk, bfs_inode *node, bool showOffsets)
{
        if (node->data.max_indirect_range == 0)
                return;

        int32 bytes = node->data.indirect.length * disk.BlockSize();
        int32 count = bytes / sizeof(block_run);
        block_run runs[count];

        off_t offset = node->data.max_direct_range;

        ssize_t bytesRead = disk.ReadAt(disk.ToOffset(node->data.indirect),
                (uint8 *)runs, bytes);
        if (bytesRead < bytes) {
                fprintf(stderr, "couldn't read indirect runs: %s\n",
                        strerror(bytesRead));
                return;
        }

        puts("indirect stream:");

        for (int32 i = 0; i < count; i++) {
                if (runs[i].IsZero())
                        return;

                printf("  indirect[%04" B_PRId32 "]            = ", i);

                char buffer[256];
                if (showOffsets)
                        snprintf(buffer, sizeof(buffer), " %16" B_PRIdOFF, offset);
                else
                        buffer[0] = '\0';

                dump_block_run("", runs[i], buffer);

                offset += runs[i].length * disk.BlockSize();
        }
}


void
dump_double_indirect_stream(Disk& disk, bfs_inode* node, bool showOffsets)
{
        if (node->data.max_double_indirect_range == 0)
                return;

        int32 bytes = node->data.double_indirect.length * disk.BlockSize();
        int32 count = bytes / sizeof(block_run);
        block_run runs[count];

        off_t offset = node->data.max_indirect_range;

        ssize_t bytesRead = disk.ReadAt(disk.ToOffset(node->data.double_indirect),
                (uint8*)runs, bytes);
        if (bytesRead < bytes) {
                fprintf(stderr, "couldn't read double indirect runs: %s\n",
                        strerror(bytesRead));
                return;
        }

        puts("double indirect stream:");

        for (int32 i = 0; i < count; i++) {
                if (runs[i].IsZero())
                        return;

                printf("  double_indirect[%02" B_PRId32 "]       = ", i);

                dump_block_run("", runs[i], "");

                int32 indirectBytes = runs[i].length * disk.BlockSize();
                int32 indirectCount = indirectBytes / sizeof(block_run);
                block_run indirectRuns[indirectCount];

                bytesRead = disk.ReadAt(disk.ToOffset(runs[i]), (uint8*)indirectRuns,
                        indirectBytes);
                if (bytesRead < indirectBytes) {
                        fprintf(stderr, "couldn't read double indirect runs: %s\n",
                                strerror(bytesRead));
                        continue;
                }

                for (int32 j = 0; j < indirectCount; j++) {
                        if (indirectRuns[j].IsZero())
                                break;

                        printf("                     [%04" B_PRId32 "] = ", j);

                        char buffer[256];
                        if (showOffsets)
                                snprintf(buffer, sizeof(buffer), " %16" B_PRIdOFF, offset);
                        else
                                buffer[0] = '\0';

                        dump_block_run("", indirectRuns[j], buffer);

                        offset += indirectRuns[j].length * disk.BlockSize();
                }
        }
}


void
list_bplustree(Disk& disk, Directory* directory, off_t size)
{
        directory->Rewind();

        char name[B_FILE_NAME_LENGTH];
        char buffer[512];
        uint64 count = 0;
        block_run run;
        while (directory->GetNextEntry(name, &run) == B_OK) {
                snprintf(buffer, sizeof(buffer), " %s", name);
                dump_block_run("", run, buffer);
                count++;
        }

        printf("--\n%" B_PRId64 " items.\n", count);
}


void
count_bplustree(Disk& disk, Directory* directory, off_t size)
{
        directory->Rewind();

        char name[B_FILE_NAME_LENGTH];
        uint64 count = 0;
        block_run run;
        while (directory->GetNextEntry(name, &run) == B_OK)
                count++;

        printf("%" B_PRId64 " items.\n", count);
}


block_run
parseBlockRun(Disk &disk, char *first, char *last)
{
        char *comma;

        if (last) {
                return block_run::Run(atol(first), atol(last), 1);
        } else if ((comma = strchr(first, ',')) != NULL) {
                *comma++ = '\0';
                return block_run::Run(atol(first), atol(comma));
        }

        return disk.ToBlockRun(atoll(first));
}


int
main(int argc, char **argv)
{
        if (argc < 2 || !strcmp(argv[1], "--help")) {
                char *filename = strrchr(argv[0],'/');
                fprintf(stderr,"usage: %s [-srib] <device> [allocation_group start]\n"
                                "\t-s\tdump superblock\n"
                                "\t-r\tdump root node\n"
                                "       the following options need the allocation_group/start "
                                        "parameters:\n"
                                "\t-i\tdump inode\n"
                                "\t-b\tdump b+tree\n"
                                "\t-c\tlist b+tree leaves\n"
                                "\t-c\tcount b+tree leaves\n"
                                "\t-v\tvalidate b+tree\n"
                                "\t-h\thexdump\n"
                                "\t-o\tshow disk offsets\n",
                                filename ? filename + 1 : argv[0]);
                return -1;
        }

        bool dumpRootNode = false;
        bool dumpInode = false;
        bool dumpSuperBlock = false;
        bool dumpBTree = false;
        bool listBTree = false;
        bool countBTree = false;
        bool validateBTree = false;
        bool dumpHex = false;
        bool showOffsets = false;

        while (*++argv) {
                char *arg = *argv;
                if (*arg == '-') {
                        while (*++arg && isalpha(*arg)) {
                                switch (*arg) {
                                        case 's':
                                                dumpSuperBlock = true;
                                                break;
                                        case 'r':
                                                dumpRootNode = true;
                                                break;
                                        case 'i':
                                                dumpInode = true;
                                                break;
                                        case 'b':
                                                dumpBTree = true;
                                                break;
                                        case 'l':
                                                listBTree = true;
                                                break;
                                        case 'c':
                                                countBTree = true;
                                                break;
                                        case 'v':
                                                validateBTree = true;
                                                break;
                                        case 'h':
                                                dumpHex = true;
                                                break;
                                        case 'o':
                                                showOffsets = true;
                                                break;
                                }
                        }
                } else
                        break;
        }

        Disk disk(argv[0]);
        if (disk.InitCheck() < B_OK)
        {
                fprintf(stderr, "Could not open device or file: %s\n", strerror(disk.InitCheck()));
                return -1;
        }
        putchar('\n');

        if (!dumpSuperBlock && !dumpRootNode && !dumpInode && !dumpBTree
                && !dumpHex && !listBTree && !countBTree) {
                char buffer[16];
                printf("  Name:\t\t\t\"%s\"\n", disk.SuperBlock()->name);
                printf("  SuperBlock:\t\t%s\n\n",
                        disk.ValidateSuperBlock() == B_OK ? "valid" : "invalid!");
                printf("  Block Size:%*" B_PRIu32 " bytes\n", 23, disk.BlockSize());
                string_for_size(disk.NumBlocks() * disk.BlockSize(), buffer,
                        sizeof(buffer));
                printf("  Number of Blocks:%*" B_PRIdOFF "\t%*s\n", 17, disk.NumBlocks(),
                        16, buffer);
                if (disk.BlockBitmap() != NULL) {
                        string_for_size(disk.BlockBitmap()->UsedBlocks() * disk.BlockSize(),
                                buffer, sizeof(buffer));
                        printf("  Used Blocks:%*" B_PRIdOFF "\t%*s\n", 22,
                                disk.BlockBitmap()->UsedBlocks(), 16, buffer);

                        string_for_size(disk.BlockBitmap()->FreeBlocks() * disk.BlockSize(),
                                buffer, sizeof(buffer));
                        printf("  Free Blocks:%*" B_PRIdOFF "\t%*s\n", 22,
                                disk.BlockBitmap()->FreeBlocks(), 16, buffer);
                }
                int32 size
                        = (disk.AllocationGroups() * disk.SuperBlock()->blocks_per_ag);
                string_for_size(disk.BlockSize() * size, buffer, sizeof(buffer));
                printf("  Bitmap Blocks:%*" B_PRId32 "\t%*s\n", 20, size, 16, buffer);
                printf("  Allocation Group Size:%*" B_PRId32 " blocks\n", 12,
                        disk.SuperBlock()->blocks_per_ag);
                printf("  Allocation Groups:%*" B_PRIu32 "\n\n", 16,
                        disk.AllocationGroups());
                dump_block_run("  Log:\t\t\t", disk.Log());
                printf("\t\t\t%s\n\n", disk.SuperBlock()->flags == SUPER_BLOCK_CLEAN
                        ? "cleanly unmounted" : "not unmounted cleanly!");
                dump_block_run("  Root Directory:\t", disk.Root());
                putchar('\n');
        } else if (dumpSuperBlock) {
                dump_super_block(disk.SuperBlock());
                putchar('\n');
        }

        if (disk.ValidateSuperBlock() < B_OK) {
                fprintf(stderr, "The disk's superblock is corrupt (or it's not a BFS "
                        "device)!\n");
                return 0;
        }

        if (dumpRootNode) {
                bfs_inode inode;
                if (disk.ReadAt(disk.ToOffset(disk.Root()), (void *)&inode,
                                sizeof(bfs_inode)) < B_OK) {
                        fprintf(stderr,"Could not read root node from disk!\n");
                } else {
                        puts("Root node:\n-----------------------------------------");
                        dump_inode(NULL, &inode, showOffsets);
                        dump_indirect_stream(disk, &inode, showOffsets);
                        putchar('\n');
                }
        }

        char buffer[disk.BlockSize()];
        bfs_inode* bfsInode = (bfs_inode*)buffer;
        block_run run;
        Inode *inode = NULL;

        if (dumpInode || dumpBTree || dumpHex || validateBTree || listBTree
                || countBTree) {
                // Set the block_run to the right value (as specified on the command
                // line)
                if (!argv[1]) {
                        fprintf(stderr, "The -i/b/f options need the allocation group and "
                                "starting offset (or the block number) of the node to dump!\n");
                        return -1;
                }
                run = parseBlockRun(disk, argv[1], argv[2]);

                if (disk.ReadAt(disk.ToOffset(run), buffer, disk.BlockSize()) <= 0) {
                        fprintf(stderr,"Could not read node from disk!\n");
                        return -1;
                }

                inode = Inode::Factory(&disk, bfsInode, false);
                if (inode == NULL || inode->InitCheck() < B_OK) {
                        fprintf(stderr,"Not a valid inode!\n");
                        delete inode;
                        inode = NULL;
                }
        }

        if (dumpInode) {
                printf("Inode at block %" B_PRIdOFF ":\n------------------------------"
                        "-----------\n", disk.ToBlock(run));
                dump_inode(inode, bfsInode, showOffsets);
                dump_indirect_stream(disk, bfsInode, showOffsets);
                dump_double_indirect_stream(disk, bfsInode, showOffsets);
                dump_small_data(inode);
                putchar('\n');
        }

        if (dumpBTree && inode != NULL) {
                printf("B+Tree at block %" B_PRIdOFF ":\n-----------------------------"
                        "------------\n", disk.ToBlock(run));
                if (inode->IsDirectory() || inode->IsAttributeDirectory()) {
                        dump_bplustree(disk, (Directory*)inode, inode->Size(), dumpHex);
                        putchar('\n');
                } else
                        fprintf(stderr, "Inode is not a directory!\n");
        }

        if (listBTree && inode != NULL) {
                printf("Directory contents: ------------------------------------------\n");
                if (inode->IsDirectory() || inode->IsAttributeDirectory()) {
                        list_bplustree(disk, (Directory*)inode, inode->Size());
                        putchar('\n');
                } else
                        fprintf(stderr, "Inode is not a directory!\n");
        }

        if (countBTree && inode != NULL) {
                printf("Count contents: ------------------------------------------\n");
                if (inode->IsDirectory() || inode->IsAttributeDirectory()) {
                        count_bplustree(disk, (Directory*)inode, inode->Size());
                        putchar('\n');
                } else
                        fprintf(stderr, "Inode is not a directory!\n");
        }

        if (validateBTree && inode != NULL) {
                printf("Validating B+Tree at block %" B_PRIdOFF ":\n------------------"
                        "-----------------------\n", disk.ToBlock(run));
                if (inode->IsDirectory() || inode->IsAttributeDirectory()) {
                        BPlusTree *tree;
                        if (((Directory *)inode)->GetTree(&tree) == B_OK) {
                                if (tree->Validate(true) < B_OK)
                                        puts("B+Tree is corrupt!");
                                else
                                        puts("B+Tree seems to be okay.");
                        }
                } else
                        fprintf(stderr, "Inode is not a directory!\n");
        }

        if (dumpHex) {
                printf("Hexdump from inode at block %" B_PRIdOFF ":\n-----------------"
                        "------------------------\n", disk.ToBlock(run));
                dump_block(buffer, disk.BlockSize());
                putchar('\n');
        }

        delete inode;

        return 0;
}