* Copyright (c) 2001-2008 pinc Software. All Rights Reserved.
* Released under the terms of the MIT license.
*/
#include "Disk.h"
#include "dump.h"
#include <Drivers.h>
#include <File.h>
#include <Entry.h>
#include <List.h>
#include <fs_info.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#define MIN_BLOCK_SIZE_INODES 50
#define MAX_BLOCK_SIZE_INODES 500
struct bfs_disk_info {
off_t offset;
disk_super_block super_block;
};
class CacheableBlockRun : public BlockRunCache::Cacheable {
public:
CacheableBlockRun(block_run run,uint8 *data)
:
fRun(run),
fData(data)
{
}
virtual ~CacheableBlockRun()
{
free(fData);
}
virtual bool Equals(block_run run)
{
return run == fRun;
}
void SetData(uint8 *data)
{
fData = data;
}
uint8 *Data()
{
return fData;
}
protected:
block_run fRun;
uint8 *fData;
};
BlockRunCache::BlockRunCache(Disk *disk)
: Cache<block_run>(),
fDisk(disk)
{
}
Cache<block_run>::Cacheable *BlockRunCache::NewCacheable(block_run run)
{
ssize_t length = (int32)run.length << fDisk->BlockShift();
void *buffer = malloc(length);
if (buffer == NULL)
return NULL;
ssize_t read = fDisk->ReadAt(fDisk->ToOffset(run),buffer,length);
if (read < length) {
free(buffer);
return NULL;
}
return new CacheableBlockRun(run,(uint8 *)buffer);
}
Disk::Disk(const char *deviceName, bool rawMode, off_t start, off_t stop)
:
fBufferedFile(NULL),
fRawDiskOffset(0),
fSize(0LL),
fCache(this),
fRawMode(rawMode)
{
BEntry entry(deviceName);
fPath.SetTo(deviceName);
if (entry.IsDirectory()) {
dev_t on = dev_for_path(deviceName);
fs_info info;
if (fs_stat_dev(on, &info) != B_OK)
return;
fPath.SetTo(info.device_name);
deviceName = fPath.Path();
}
if (deviceName == NULL) {
fprintf(stderr, "Path is not mapped to any device.\n");
return;
}
if (!rawMode && !strncmp(deviceName, "/dev/", 5)
&& !strcmp(deviceName + strlen(deviceName) - 3, "raw"))
fprintf(stderr, "Raw mode not selected, but raw device specified.\n");
if (fFile.SetTo(deviceName, B_READ_WRITE) < B_OK) {
return;
}
fBufferedFile = new BBufferIO(&fFile, 1024 * 1024, false);
int device = open(deviceName, O_RDONLY);
if (device < B_OK) {
return;
}
partition_info partitionInfo;
device_geometry geometry;
if (ioctl(device, B_GET_PARTITION_INFO, &partitionInfo,
sizeof(partition_info)) == 0) {
fSize = partitionInfo.size;
} else if (ioctl(device, B_GET_GEOMETRY, &geometry, sizeof(device_geometry))
== 0) {
fSize = (off_t)geometry.cylinder_count * geometry.sectors_per_track
* geometry.head_count * geometry.bytes_per_sector;
} else {
fprintf(stderr,"Disk: Could not get partition info.\n Use file size as partition size\n");
fFile.GetSize(&fSize);
}
close(device);
if (fSize == 0LL) {
fprintf(stderr,"Disk: Invalid file size (%" B_PRIdOFF " bytes)!\n",
fSize);
}
if (rawMode && ScanForSuperBlock(start, stop) < B_OK) {
fFile.Unset();
return;
}
if (fBufferedFile->ReadAt(512 + fRawDiskOffset, &fSuperBlock,
sizeof(disk_super_block)) < 1)
fprintf(stderr,"Disk: Could not read superblock\n");
}
Disk::~Disk()
{
delete fBufferedFile;
}
status_t Disk::InitCheck()
{
status_t status = fFile.InitCheck();
if (status == B_OK)
return fSize == 0LL ? B_ERROR : B_OK;
return status;
}
block_run Disk::ToBlockRun(off_t start, int16 length) const
{
block_run run;
run.allocation_group = start >> fSuperBlock.ag_shift;
run.start = start & ((1UL << fSuperBlock.ag_shift) - 1);
run.length = length;
return run;
}
off_t Disk::LogSize() const
{
if (fSuperBlock.num_blocks >= 4096)
return 2048;
return 512;
}
uint8 *Disk::ReadBlockRun(block_run run)
{
CacheableBlockRun *entry = (CacheableBlockRun *)fCache.Get(run);
if (entry)
return entry->Data();
return NULL;
}
status_t
Disk::DumpBootBlockToFile()
{
BFile file("/boot/home/bootblock.old", B_READ_WRITE | B_CREATE_FILE);
if (file.InitCheck() < B_OK)
return file.InitCheck();
char buffer[BlockSize()];
ssize_t bytes = ReadAt(0, buffer, BlockSize());
if (bytes < (int32)BlockSize())
return bytes < B_OK ? bytes : B_ERROR;
file.Write(buffer, BlockSize());
memset(buffer, 0, BlockSize());
memcpy(buffer + 512, &fSuperBlock, sizeof(disk_super_block));
WriteAt(0, buffer, BlockSize());
return B_OK;
}
status_t
Disk::ScanForSuperBlock(off_t start, off_t stop)
{
printf("Disk size %" B_PRIdOFF " bytes, %.2f GB\n", fSize, 1.0 * fSize
/ (1024*1024*1024));
uint32 blockSize = 4096;
char buffer[blockSize + 1024];
if (stop == -1)
stop = fSize;
char escape[3] = {0x1b, '[', 0};
BList superBlocks;
printf("Scanning Disk from %" B_PRIdOFF " to %" B_PRIdOFF "\n", start,
stop);
for (off_t offset = start; offset < stop; offset += blockSize)
{
if (((offset-start) % (blockSize * 100)) == 0) {
printf(" %12" B_PRIdOFF ", %.2f GB %s1A\n", offset,
1.0 * offset / (1024*1024*1024),escape);
}
ssize_t bytes = fBufferedFile->ReadAt(offset, buffer, blockSize + 1024);
if (bytes < B_OK)
{
fprintf(stderr,"Could not read from device: %s\n", strerror(bytes));
return -1;
}
for (uint32 i = 0;i < blockSize - 2;i++)
{
disk_super_block *super = (disk_super_block *)&buffer[i];
if (super->magic1 == (int32)SUPER_BLOCK_MAGIC1
&& super->magic2 == (int32)SUPER_BLOCK_MAGIC2
&& super->magic3 == (int32)SUPER_BLOCK_MAGIC3)
{
printf("\n(%" B_PRId32 ") *** BFS superblock found at: %"
B_PRIdOFF "\n", superBlocks.CountItems() + 1, offset);
dump_super_block(super);
bfs_disk_info *info = (bfs_disk_info *)malloc(sizeof(bfs_disk_info));
if (info == NULL)
return B_NO_MEMORY;
memcpy(&info->super_block, super, sizeof(disk_super_block));
info->offset = offset + i - 512;
superBlocks.AddItem(info);
}
}
}
if (superBlocks.CountItems() == 0) {
puts("\nCouldn't find any BFS superblocks!");
return B_ENTRY_NOT_FOUND;
}
puts("\n\nThe following disks were found:");
for (int32 i = 0; i < superBlocks.CountItems(); i++) {
bfs_disk_info *info = (bfs_disk_info *)superBlocks.ItemAt(i);
printf("%" B_PRId32 ") %s, offset %" B_PRIdOFF ", size %g GB (%svalid)"
"\n", i + 1, info->super_block.name, info->offset,
1.0 * info->super_block.num_blocks * info->super_block.block_size
/ (1024*1024*1024),
ValidateSuperBlock(info->super_block) < B_OK ? "in" : "");
}
char answer[16];
printf("\nChoose one (by number): ");
fflush(stdout);
fgets(answer, 15, stdin);
int32 index = atol(answer);
if (index > superBlocks.CountItems() || index < 1) {
puts("No disk there... exiting.");
return B_BAD_VALUE;
}
bfs_disk_info *info = (bfs_disk_info *)superBlocks.ItemAt(index - 1);
fRawDiskOffset = info->offset;
fBufferedFile->Seek(fRawDiskOffset, SEEK_SET);
if (ValidateSuperBlock(info->super_block))
fSize = info->super_block.block_size * info->super_block.block_size;
else {
fSize -= fRawDiskOffset;
}
return B_OK;
}
status_t
Disk::ValidateSuperBlock(disk_super_block &superBlock)
{
if (superBlock.magic1 != (int32)SUPER_BLOCK_MAGIC1
|| superBlock.magic2 != (int32)SUPER_BLOCK_MAGIC2
|| superBlock.magic3 != (int32)SUPER_BLOCK_MAGIC3
|| (int32)superBlock.block_size != superBlock.inode_size
|| superBlock.fs_byte_order != SUPER_BLOCK_FS_LENDIAN
|| (1UL << superBlock.block_shift) != superBlock.block_size
|| superBlock.num_ags < 1
|| superBlock.ag_shift < 1
|| superBlock.blocks_per_ag < 1
|| superBlock.num_blocks < 10
|| superBlock.used_blocks > superBlock.num_blocks
|| superBlock.num_ags != divide_roundup(superBlock.num_blocks,
1L << superBlock.ag_shift))
return B_ERROR;
return B_OK;
}
status_t
Disk::ValidateSuperBlock()
{
if (ValidateSuperBlock(fSuperBlock) < B_OK)
return B_ERROR;
fBitmap.SetTo(this);
return B_OK;
}
status_t
Disk::RecreateSuperBlock()
{
memset(&fSuperBlock, 0, sizeof(disk_super_block));
puts("\n*** Determine block size");
status_t status = DetermineBlockSize();
if (status < B_OK)
return status;
printf("\tblock size = %" B_PRId32 "\n", BlockSize());
strcpy(fSuperBlock.name,"recovered");
fSuperBlock.magic1 = SUPER_BLOCK_MAGIC1;
fSuperBlock.fs_byte_order = SUPER_BLOCK_FS_LENDIAN;
fSuperBlock.block_shift = get_shift(BlockSize());
fSuperBlock.num_blocks = fSize / BlockSize();
fSuperBlock.inode_size = BlockSize();
fSuperBlock.magic2 = SUPER_BLOCK_MAGIC2;
fSuperBlock.flags = SUPER_BLOCK_CLEAN;
fLogStart = 1 + divide_roundup(fSuperBlock.num_blocks,BlockSize() * 8);
fSuperBlock.log_start = fLogStart + (LogSize() >> 1);
fSuperBlock.log_end = fSuperBlock.log_start;
puts("\n*** Scanning for indices and root node...");
fValidOffset = 0LL;
bfs_inode indexDir;
bfs_inode rootDir;
if (ScanForIndexAndRoot(&indexDir,&rootDir) < B_OK) {
fprintf(stderr,"ERROR: Could not find root directory! Trying to recreate the superblock will have no effect!\n\tSetting standard values for the root dir.\n");
rootDir.inode_num.allocation_group = 8;
rootDir.inode_num.start = 0;
rootDir.inode_num.length = 1;
}
if (fValidOffset == 0LL) {
printf("No valid inode found so far - perform search.\n");
off_t offset = 8LL * 65536 * BlockSize();
char buffer[1024];
GetNextSpecialInode(buffer,&offset,offset + 32LL * 65536 * BlockSize(),true);
if (fValidOffset == 0LL)
{
fprintf(stderr,"FATAL ERROR: Could not find valid inode!\n");
return B_ERROR;
}
}
int32 allocationGroupSize = (fValidOffset - fValidBlockRun.start
* BlockSize()
+ BlockSize() - 1) / (BlockSize() * fValidBlockRun.allocation_group);
fSuperBlock.blocks_per_ag = allocationGroupSize / (BlockSize() << 3);
fSuperBlock.ag_shift = get_shift(allocationGroupSize);
fSuperBlock.num_ags = divide_roundup(fSuperBlock.num_blocks,allocationGroupSize);
fSuperBlock.log_blocks.allocation_group = fLogStart / allocationGroupSize;
fSuperBlock.log_blocks.start = fLogStart - fSuperBlock.log_blocks.allocation_group * allocationGroupSize;
fSuperBlock.log_blocks.length = LogSize();
if (fLogStart != ((indexDir.inode_num.allocation_group
<< fSuperBlock.ag_shift) + indexDir.inode_num.start - LogSize())) {
fprintf(stderr,"ERROR: start of logging area doesn't fit assumed value "
"(%" B_PRIdOFF " blocks before indices)!\n", LogSize());
}
fSuperBlock.magic3 = SUPER_BLOCK_MAGIC3;
fSuperBlock.root_dir = rootDir.inode_num;
fSuperBlock.indices = indexDir.inode_num;
fBitmap.SetTo(this);
if (fBitmap.UsedBlocks())
fSuperBlock.used_blocks = fBitmap.UsedBlocks();
else {
fprintf(stderr,"ERROR: couldn't calculate number of used blocks, marking all blocks as used!\n");
fSuperBlock.used_blocks = fSuperBlock.num_blocks;
}
return B_OK;
}
status_t
Disk::DetermineBlockSize()
{
off_t offset = 1024 + divide_roundup(fSize / 1024,8 * 1024) * 1024;
off_t inodesFound = 0;
char buffer[1024];
bfs_inode *inode = (bfs_inode *)buffer;
int32 blockSizeCounter[6] = {0};
status_t status = B_OK;
for (; offset < (fSize >> 2); offset += 1024) {
if (fBufferedFile->ReadAt(offset, buffer, sizeof(buffer)) < B_OK) {
fprintf(stderr, "could not read from device (offset = %" B_PRIdOFF
", size = %ld)!\n", offset, sizeof(buffer));
status = B_IO_ERROR;
break;
}
if (inode->magic1 != INODE_MAGIC1
|| inode->inode_size != 1024
&& inode->inode_size != 2048
&& inode->inode_size != 4096
&& inode->inode_size != 8192)
continue;
inodesFound++;
for (int i = 0;i < 6;i++)
if ((1 << (i + 10)) == inode->inode_size)
blockSizeCounter[i]++;
int32 count = 0;
for (int32 i = 0;i < 6;i++)
if (blockSizeCounter[i])
count++;
if (inodesFound >= MAX_BLOCK_SIZE_INODES
|| (inodesFound >= MIN_BLOCK_SIZE_INODES && count == 1))
break;
}
int32 maxCounter = -1;
int32 maxIndex = 0;
for (int32 i = 0; i < 6; i++) {
if (maxCounter < blockSizeCounter[i]) {
maxIndex = i;
maxCounter = blockSizeCounter[i];
}
}
fSuperBlock.block_size = (1 << (maxIndex + 10));
return status;
}
status_t
Disk::GetNextSpecialInode(char *buffer, off_t *_offset, off_t end,
bool skipAfterValidInode = false)
{
off_t offset = *_offset;
if (end == offset)
end++;
bfs_inode *inode = (bfs_inode *)buffer;
for (; offset < end; offset += BlockSize()) {
if (fBufferedFile->ReadAt(offset, buffer, 1024) < B_OK) {
fprintf(stderr,"could not read from device (offset = %" B_PRIdOFF
", size = %d)!\n", offset, 1024);
*_offset = offset;
return B_IO_ERROR;
}
if (inode->magic1 != INODE_MAGIC1
|| inode->inode_size != fSuperBlock.inode_size)
continue;
if (inode->inode_num.allocation_group > 0
&& offset >= (BlockSize() * (fLogStart + LogSize()))) {
fValidBlockRun = inode->inode_num;
fValidOffset = offset;
if (skipAfterValidInode)
return B_OK;
}
if (!memcmp(&inode->parent, &inode->inode_num, sizeof(inode_addr))) {
printf("\t special inode found at %" B_PRIdOFF "\n", offset);
*_offset = offset;
return B_OK;
}
}
*_offset = offset;
return B_ENTRY_NOT_FOUND;
}
void
Disk::SaveInode(bfs_inode *inode, bool *indices, bfs_inode *indexDir,
bool *root, bfs_inode *rootDir)
{
if ((inode->mode & S_INDEX_DIR) == 0) {
*root = true;
printf("\troot node found!\n");
if (inode->inode_num.allocation_group != 8
|| inode->inode_num.start != 0
|| inode->inode_num.length != 1) {
printf("WARNING: root node has unexpected position: (ag = %"
B_PRId32 ", start = %d, length = %d)\n",
inode->inode_num.allocation_group,
inode->inode_num.start, inode->inode_num.length);
}
if (rootDir)
memcpy(rootDir, inode, sizeof(bfs_inode));
} else if (inode->mode & S_INDEX_DIR) {
*indices = true;
printf("\tindices node found!\n");
if (indexDir)
memcpy(indexDir, inode, sizeof(bfs_inode));
}
}
status_t
Disk::ScanForIndexAndRoot(bfs_inode *indexDir,bfs_inode *rootDir)
{
memset(rootDir,0,sizeof(bfs_inode));
memset(indexDir,0,sizeof(bfs_inode));
bool indices = false,root = false;
char buffer[1024];
bfs_inode *inode = (bfs_inode *)buffer;
off_t offset = (fLogStart + LogSize()) * BlockSize();
if (GetNextSpecialInode(buffer,&offset,offset + 65536LL * BlockSize()) == B_OK)
SaveInode(inode,&indices,indexDir,&root,rootDir);
if (!indices) {
fputs("ERROR: no indices node found!\n",stderr);
}
for (off_t start = 8LL * 1024 * BlockSize();start <= 8LL * 65536 * BlockSize();start <<= 1) {
if (start < fLogStart)
continue;
off_t commonOffset = start;
if (GetNextSpecialInode(buffer, &commonOffset, commonOffset) == B_OK) {
SaveInode(inode, &indices, indexDir, &root, rootDir);
if (root)
break;
}
}
if (!root) {
printf("WARNING: Could not find root node at common places!\n");
printf("\tScanning log area for root node\n");
off_t logOffset = ToOffset(fSuperBlock.log_blocks);
if (GetNextSpecialInode(buffer,&logOffset,logOffset + LogSize() * BlockSize()) == B_OK)
{
SaveInode(inode,&indices,indexDir,&root,rootDir);
printf("root node at: 0x%" B_PRIxOFF " (DiskProbe)\n",
logOffset / 512);
}
}
{
char txt[64];
printf("Should I perform a deeper search (that will take some time) (Y/N) [N]? ");
gets(txt);
if (!strcasecmp("y",txt))
{
// search not so common places for the root node (all places)
if (indices)
offset += BlockSize(); // the block after the indices inode
else
offset = (fLogStart + 1) * BlockSize();
if (GetNextSpecialInode(buffer,&offset,65536LL * 16 * BlockSize()) == B_OK)
SaveInode(inode,&indices,indexDir,&root,rootDir);
}
}*/
if (root)
return B_OK;
return B_ERROR;
}
ssize_t
Disk::Read(void *buffer, size_t size)
{
return fBufferedFile->Read(buffer, size);
}
ssize_t
Disk::Write(const void *buffer, size_t size)
{
return fBufferedFile->Write(buffer, size);
}
ssize_t
Disk::ReadAt(off_t pos, void *buffer, size_t size)
{
return fBufferedFile->ReadAt(pos + fRawDiskOffset, buffer, size);
}
ssize_t
Disk::WriteAt(off_t pos, const void *buffer, size_t size)
{
return fBufferedFile->WriteAt(pos + fRawDiskOffset, buffer, size);
}
off_t
Disk::Seek(off_t position, uint32 seekMode)
{
if (seekMode != SEEK_SET)
puts("OH NO, I AM BROKEN!");
return fBufferedFile->Seek(position + fRawDiskOffset, seekMode);
}
off_t
Disk::Position() const
{
return fBufferedFile->Position() - fRawDiskOffset;
}
status_t
Disk::SetSize(off_t )
{
return B_ERROR;
}