⛏️ index : haiku.git

/*
 * 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;
}