Almost complete and working "chkbfs" functionality for BFS. It can be used

via the private ioctls defined in bfs_control.h.
Detects all kinds of errors, and can correct most of them. It doesn't fix
any errors in the b+trees (like the ones created by an earlier version
of the BPlusTree class ;-), and it also can't fix the "blocks are already set"
error, which occurs if more than one file claims a particular block (or
range of blocks) for itself.
The double indirect data range is not yet supported, so use with care, or
run the original chkbfs afterwards to fix any errors because of that.
Might not yet analyze 100% correct (shows way to many errors for my tastes...).
A "chkbfs" command will be made available shortly.


git-svn-id: file:///srv/svn/repos/haiku/trunk/current@1994 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler 2002-11-18 01:28:47 +00:00
parent 27fe0a4b4b
commit 3f870da9c3
2 changed files with 370 additions and 52 deletions

View File

@ -10,11 +10,15 @@
#include "BlockAllocator.h"
#include "Volume.h"
#include "Inode.h"
#include "BPlusTree.h"
#include "Stack.h"
#include "bfs_control.h"
#ifdef USER
# define spawn_kernel_thread spawn_thread
#endif
// Things the BlockAllocator should do:
// - find a range of blocks of a certain size nearby a specific position
@ -36,6 +40,17 @@
// have some real world tests.
struct check_cookie {
check_cookie() {}
block_run current;
Inode *parent;
mode_t parent_mode;
Stack<block_run> stack;
TreeIterator *iterator;
};
class AllocationBlock : public CachedBlock {
public:
AllocationBlock(Volume *volume);
@ -47,6 +62,7 @@ class AllocationBlock : public CachedBlock {
status_t SetTo(AllocationGroup &group, uint16 block);
int32 NumBlockBits() const { return fNumBits; }
uint32 &Block(int32 index) { return ((uint32 *)fBlock)[index]; }
private:
int32 fNumBits;
@ -95,7 +111,7 @@ AllocationBlock::IsUsed(uint16 block)
if (block > fNumBits)
return true;
// the block bitmap is accessed in 32-bit blocks
return ((uint32 *)fBlock)[block >> 5] & (1UL << (block % 32));
return Block(block >> 5) & (1UL << (block % 32));
}
@ -117,7 +133,7 @@ AllocationBlock::Allocate(uint16 start, uint16 numBlocks)
while (numBlocks > 0) {
uint32 mask = 0;
for (int32 i = start % 32;i < 32 && numBlocks;i++, numBlocks--)
for (int32 i = start % 32; i < 32 && numBlocks; i++, numBlocks--)
mask |= 1UL << (i % 32);
#ifdef DEBUG
@ -128,7 +144,7 @@ AllocationBlock::Allocate(uint16 start, uint16 numBlocks)
}
*/
#endif
((uint32 *)fBlock)[block++] |= mask;
Block(block++) |= mask;
start = 0;
}
}
@ -152,10 +168,10 @@ AllocationBlock::Free(uint16 start, uint16 numBlocks)
while (numBlocks > 0) {
uint32 mask = 0;
for (int32 i = start % 32;i < 32 && numBlocks;i++,numBlocks--)
for (int32 i = start % 32; i < 32 && numBlocks; i++, numBlocks--)
mask |= 1UL << (i % 32);
((uint32 *)fBlock)[block++] &= ~mask;
Block(block++) &= ~mask;
start = 0;
}
}
@ -516,7 +532,8 @@ BlockAllocator::AllocateForInode(Transaction *transaction,const block_run *paren
status_t
BlockAllocator::Allocate(Transaction *transaction,const Inode *inode, off_t numBlocks, block_run &run, uint16 minimum)
BlockAllocator::Allocate(Transaction *transaction, const Inode *inode, off_t numBlocks,
block_run &run, uint16 minimum)
{
if (numBlocks <= 0)
return B_ERROR;
@ -534,8 +551,8 @@ BlockAllocator::Allocate(Transaction *transaction,const Inode *inode, off_t numB
// b) reduce the maximum amount of blocks per block_run, so that the remaining
// number of free blocks can be used in a useful manner (like 4 blocks) -
// but that would also reduce the maximum file size
if (numBlocks == 65536)
numBlocks = 65535;
if (numBlocks > MAX_BLOCK_RUN_LENGTH)
numBlocks = MAX_BLOCK_RUN_LENGTH;
// apply some allocation policies here (AllocateBlocks() will break them
// if necessary)
@ -612,30 +629,98 @@ BlockAllocator::Free(Transaction *transaction, block_run run)
}
// #pragma mark -
// Functions to check the validity of the bitmap - they are used from
// the "chkbfs" command
status_t
BlockAllocator::StartChecking()
BlockAllocator::StartChecking(check_control *control)
{
status_t status = fLock.Lock();
if (status < B_OK)
return status;
size_t size = fVolume->BlockSize() * fNumGroups * fBlocksPerGroup;
fCheckBitmap = (uint32 *)malloc(size);
if (fCheckBitmap == NULL) {
fLock.Unlock();
return B_NO_MEMORY;
}
check_cookie *cookie = new check_cookie();
if (cookie == NULL) {
free(fCheckBitmap);
fCheckBitmap = NULL;
fLock.Unlock();
return B_NO_MEMORY;
}
// initialize bitmap
memset(fCheckBitmap, 0, size);
for (int32 block = fVolume->Log().start + fVolume->Log().length; block-- > 0;)
SetCheckBitmapAt(block);
cookie->stack.Push(fVolume->Root());
cookie->stack.Push(fVolume->Indices());
cookie->iterator = NULL;
control->cookie = cookie;
// ToDo: check reserved area in bitmap!
return B_OK;
}
status_t
BlockAllocator::StopChecking()
BlockAllocator::StopChecking(check_control *control)
{
check_cookie *cookie = (check_cookie *)control->cookie;
if (cookie->iterator != NULL) {
delete cookie->iterator;
cookie->iterator = NULL;
// the current directory inode is still locked in memory
put_vnode(fVolume->ID(), fVolume->ToVnode(cookie->current));
}
// if CheckNextNode() could completely work through, we can
// fix any damages of the bitmap
if (control->status == B_ENTRY_NOT_FOUND) {
// calculate the number of used blocks in the check bitmap
size_t size = fVolume->BlockSize() * fNumGroups * fBlocksPerGroup;
off_t usedBlocks = 0LL;
for (uint32 i = size >> 2; i-- > 0;) {
uint32 compare = 1;
for (int16 j = 0; j < 32; j++, compare <<= 1) {
if (compare & fCheckBitmap[i])
usedBlocks++;
}
}
control->stats.freed = fVolume->UsedBlocks() - usedBlocks + control->stats.missing;
if (control->stats.freed < 0)
control->stats.freed = 0;
// Should we fix errors? Were there any errors we can fix?
if (control->flags & BFS_FIX_BITMAP_ERRORS
&& (control->stats.freed != 0 || control->stats.missing != 0)) {
// if so, write the check bitmap back over the original one
fVolume->SuperBlock().used_blocks = usedBlocks;
ssize_t written = cached_write(fVolume->Device(), 1, fCheckBitmap, fNumGroups * fBlocksPerGroup, fVolume->BlockSize());
if (written != size)
PRINT(("write bitmap failed: %ld, %s\n", written, strerror(written)));
}
} else
FATAL(("BlockAllocator::CheckNextNode() didn't run through\n"));
free(fCheckBitmap);
fCheckBitmap = NULL;
delete cookie;
fLock.Unlock();
return B_OK;
@ -643,52 +728,282 @@ BlockAllocator::StopChecking()
status_t
BlockAllocator::CheckBlockRun(block_run run)
BlockAllocator::CheckNextNode(check_control *control)
{
uint32 block = run.start / (fVolume->BlockSize() << 3);
uint32 start = run.start;
uint32 pos = 0;
check_cookie *cookie = (check_cookie *)control->cookie;
while (true) {
if (cookie->iterator == NULL) {
if (!cookie->stack.Pop(&cookie->current)) {
// no more runs on the stack, we are obviously finished!
control->status = B_ENTRY_NOT_FOUND;
return B_ENTRY_NOT_FOUND;
}
// get iterator for the next directory
Vnode vnode(fVolume, cookie->current);
Inode *inode;
if (vnode.Get(&inode) < B_OK) {
FATAL(("check: Could not open inode at %Ld\n", fVolume->ToBlock(cookie->current)));
continue;
}
if (!inode->IsDirectory()) {
FATAL(("check: inode at %Ld should have been a directory\n", fVolume->ToBlock(cookie->current)));
continue;
}
BPlusTree *tree;
if (inode->GetTree(&tree) != B_OK) {
FATAL(("check: could not open b+tree from inode at %Ld\n", fVolume->ToBlock(cookie->current)));
continue;
}
cookie->parent = inode;
cookie->parent_mode = inode->Mode();
cookie->iterator = new TreeIterator(tree);
if (cookie->iterator == NULL)
RETURN_ERROR(B_NO_MEMORY);
// the inode must stay locked in memory until the iterator is freed
vnode.Keep();
// check the inode of the directory
control->errors = 0;
control->status = CheckInode(inode, control);
const char *name = inode->Name();
strcpy(control->name, name ? name : "(node has no name)");
control->inode = inode->ID();
control->mode = inode->Mode();
return B_OK;
}
char name[B_FILE_NAME_LENGTH];
uint16 length;
vnode_id id;
status_t status = cookie->iterator->GetNextEntry(name, &length, B_FILE_NAME_LENGTH, &id);
if (status == B_ENTRY_NOT_FOUND) {
// there are no more entries in this iterator, free it and go on
delete cookie->iterator;
cookie->iterator = NULL;
// unlock the directory's inode from memory
put_vnode(fVolume->ID(), fVolume->ToVnode(cookie->current));
continue;
} else if (status == B_OK) {
// ignore "." and ".." entries
if (!strcmp(name, ".") || !strcmp(name, ".."))
continue;
// fill in the control data as soon as we have them
strcpy(control->name, name);
control->inode = id;
control->errors = 0;
Vnode vnode(fVolume, id);
Inode *inode;
if (vnode.Get(&inode) < B_OK) {
FATAL(("Could not open inode ID %Ld!\n", id));
control->errors |= BFS_COULD_NOT_OPEN;
control->status = B_ERROR;
return B_OK;
}
// check if the inode's name is the same as in the b+tree
if (inode->IsRegularNode()) {
const char *localName = inode->Name();
if (localName == NULL || strcmp(localName, name)) {
control->errors |= BFS_NAMES_DONT_MATCH;
FATAL(("Names differ: tree \"%s\", inode \"%s\"\n", name, localName));
}
}
strcpy(control->name, name ? name : "(node has no name)");
control->inode = inode->ID();
control->mode = inode->Mode();
control->mode = inode->Mode();
// Check for the correct mode of the node (if the mode of the
// file don't fit to its parent, there is a serious problem)
if ((cookie->parent_mode & S_ATTR_DIR && !inode->IsAttribute())
|| (cookie->parent_mode & S_INDEX_DIR && !inode->IsIndex())
|| ((cookie->parent_mode & S_DIRECTORY | S_ATTR_DIR | S_INDEX_DIR) == S_DIRECTORY
&& inode->Mode() & (S_ATTR | S_ATTR_DIR | S_INDEX_DIR))) {
FATAL(("inode at %Ld is of wrong type: %lo (parent %lo at %Ld)!\n",
inode->BlockNumber(), inode->Mode(), cookie->parent_mode, cookie->parent->BlockNumber()));
// if we are allowed to fix errors, we should remove the file
if (control->flags & BFS_REMOVE_WRONG_TYPES
&& control->flags & BFS_FIX_BITMAP_ERRORS) {
// it's safe to start a transaction, because Inode::Remove()
// won't touch the block bitmap (which we hold the lock for)
// if we set the INODE_DONT_FREE_SPACE flag - since we fix
// the bitmap anyway
Transaction transaction(fVolume, cookie->parent->BlockNumber());
inode->Node()->flags |= INODE_DONT_FREE_SPACE;
status = cookie->parent->Remove(&transaction, name, NULL, inode->IsDirectory());
if (status == B_OK)
transaction.Done();
} else
status = B_ERROR;
control->errors |= BFS_WRONG_TYPE;
control->status = status;
return B_OK;
}
// If the inode has an attribute directory, push it on the stack
if (!inode->Attributes().IsZero())
cookie->stack.Push(inode->Attributes());
// push the directory on the stack so that it will be scanned later
if (inode->IsDirectory() && !inode->IsIndex())
cookie->stack.Push(inode->BlockRun());
else {
// check it now
control->status = CheckInode(inode, control);
return B_OK;
}
}
}
// is never reached
}
bool
BlockAllocator::CheckBitmapIsUsedAt(off_t block) const
{
size_t size = fVolume->BlockSize() * fNumGroups * fBlocksPerGroup;
uint32 index = block / 32; // 32bit resolution
if (index > size / 4)
return false;
return fCheckBitmap[index] & (1UL << (block & 0x1f));
}
void
BlockAllocator::SetCheckBitmapAt(off_t block)
{
size_t size = fVolume->BlockSize() * fNumGroups * fBlocksPerGroup;
uint32 index = block / 32; // 32bit resolution
if (index > size / 4)
return;
fCheckBitmap[index] |= (1UL << (block & 0x1f));
}
status_t
BlockAllocator::CheckBlockRun(block_run run, const char *type, check_control *control)
{
if (run.allocation_group < 0 || run.allocation_group >= fNumGroups
|| run.start > fGroups[run.allocation_group].fNumBits
|| run.start + run.length > fGroups[run.allocation_group].fNumBits
|| run.length == 0) {
PRINT(("%s: block_run(%ld, %u, %u) is invalid!\n", type, run.allocation_group, run.start, run.length));
if (control == NULL)
return B_BAD_DATA;
control->errors |= BFS_INVALID_BLOCK_RUN;
return B_OK;
}
uint32 bitsPerBlock = fVolume->BlockSize() << 3;
uint32 block = run.start / bitsPerBlock;
uint32 pos = run.start % bitsPerBlock;
int32 length = 0;
int64 firstMissing = -1, firstSet = -1;
off_t firstGroupBlock = (off_t)run.allocation_group << fVolume->AllocationGroupShift();
AllocationBlock cached(fVolume);
for (; block < fBlocksPerGroup; block++) {
for (; block < fBlocksPerGroup && length < run.length; block++, pos = 0) {
if (cached.SetTo(fGroups[run.allocation_group], block) < B_OK)
RETURN_ERROR(B_IO_ERROR);
start = start % cached.NumBlockBits();
while (pos < run.length && start + pos < cached.NumBlockBits()) {
if (!cached.IsUsed(start + pos)) {
PRINT(("block_run(%ld,%u,%u) is only partially allocated!\n", run.allocation_group, run.start, run.length));
return B_BAD_DATA;
while (length < run.length && pos < cached.NumBlockBits()) {
if (!cached.IsUsed(pos)) {
if (control == NULL) {
PRINT(("%s: block_run(%ld, %u, %u) is only partially allocated!\n", type, run.allocation_group, run.start, run.length));
return B_BAD_DATA;
}
if (firstMissing == -1) {
firstMissing = pos + block * bitsPerBlock;
control->errors |= BFS_MISSING_BLOCKS;
}
control->stats.missing++;
} else if (firstMissing != -1) {
PRINT(("%s: block_run(%ld, %u, %u): blocks %Ld - %ld are not allocated!\n", type, run.allocation_group, run.start, run.length, firstMissing, pos + block * bitsPerBlock - 1));
firstMissing = -1;
}
if (fCheckBitmap != NULL) {
// Set the block in the check bitmap as well, but have a look if it
// is already allocated first
uint32 offset = pos + block * bitsPerBlock;
if (CheckBitmapIsUsedAt(firstGroupBlock + offset)) {
if (firstSet == -1) {
firstSet = offset;
control->errors |= BFS_BLOCKS_ALREADY_SET;
}
control->stats.already_set++;
} else {
if (firstSet != -1) {
FATAL(("%s: block_run(%ld, %u, %u): blocks %Ld - %ld are already set!\n", type, run.allocation_group, run.start, run.length, firstSet, offset - 1));
firstSet = -1;
}
SetCheckBitmapAt(firstGroupBlock + offset);
}
}
length++;
pos++;
}
start = 0;
}
if (firstMissing != -1)
PRINT(("%s: block_run(%ld, %u, %u): blocks %Ld - %ld are not allocated!\n", type, run.allocation_group, run.start, run.length, firstMissing, pos + (block - 1) * bitsPerBlock - 1));
if (firstSet != -1)
FATAL(("%s: block_run(%ld, %u, %u): blocks %Ld - %ld are already set!\n", type, run.allocation_group, run.start, run.length, firstSet, pos + (block - 1) * bitsPerBlock - 1));
return B_OK;
}
status_t
BlockAllocator::CheckInode(Inode *inode)
BlockAllocator::CheckInode(Inode *inode, check_control *control)
{
if (fCheckBitmap == NULL)
if (control != NULL && fCheckBitmap == NULL)
return B_NO_INIT;
if (inode == NULL)
return B_BAD_VALUE;
status_t status = CheckBlockRun(inode->BlockRun());
status_t status = CheckBlockRun(inode->BlockRun(), "inode", control);
if (status < B_OK)
return status;
data_stream *data = &inode->Node()->data;
// check the direct range
data_stream *data = &inode->Node()->data;
for (int32 i = 0;i < NUM_DIRECT_BLOCKS;i++) {
if (data->direct[i].IsZero())
break;
status = CheckBlockRun(data->direct[i]);
if (status < B_OK)
return status;
if (data->max_direct_range) {
for (int32 i = 0;i < NUM_DIRECT_BLOCKS;i++) {
if (data->direct[i].IsZero())
break;
status = CheckBlockRun(data->direct[i], "direct", control);
if (status < B_OK)
return status;
}
}
CachedBlock cached(fVolume);
@ -696,7 +1011,7 @@ BlockAllocator::CheckInode(Inode *inode)
// check the indirect range
if (data->max_indirect_range) {
status = CheckBlockRun(data->indirect);
status = CheckBlockRun(data->indirect, "indirect", control);
if (status < B_OK)
return status;
@ -713,7 +1028,7 @@ BlockAllocator::CheckInode(Inode *inode)
if (runs[index].IsZero())
break;
status = CheckBlockRun(runs[index]);
status = CheckBlockRun(runs[index], "indirect->run", control);
if (status < B_OK)
return status;
}
@ -725,11 +1040,12 @@ BlockAllocator::CheckInode(Inode *inode)
// check the double indirect range
if (data->max_double_indirect_range) {
status = CheckBlockRun(data->double_indirect);
status = CheckBlockRun(data->double_indirect, "double indirect", control);
if (status < B_OK)
return status;
// ToDo: add test for double indirect range!
FATAL(("*** test for the double indirect range is not yet done ***\n"));
}
return B_OK;

View File

@ -7,7 +7,7 @@
*/
#include <Lock.h>
#include "Lock.h"
class AllocationGroup;
@ -16,12 +16,7 @@ class Volume;
class Inode;
struct disk_super_block;
struct block_run;
struct check_result {
int8 type;
uint64 missing_blocks;
};
struct check_control;
class BlockAllocator {
@ -31,19 +26,26 @@ class BlockAllocator {
status_t Initialize();
status_t AllocateForInode(Transaction *transaction,const block_run *parent,mode_t type,block_run &run);
status_t Allocate(Transaction *transaction,const Inode *inode,off_t numBlocks,block_run &run,uint16 minimum = 1);
status_t AllocateForInode(Transaction *transaction, const block_run *parent,
mode_t type, block_run &run);
status_t Allocate(Transaction *transaction, const Inode *inode, off_t numBlocks,
block_run &run, uint16 minimum = 1);
status_t Free(Transaction *transaction, block_run run);
status_t AllocateBlocks(Transaction *transaction,int32 group, uint16 start, uint16 numBlocks, uint16 minimum, block_run &run);
status_t AllocateBlocks(Transaction *transaction, int32 group, uint16 start,
uint16 numBlocks, uint16 minimum, block_run &run);
status_t StartChecking();
status_t StopChecking();
status_t StartChecking(check_control *control);
status_t StopChecking(check_control *control);
status_t CheckNextNode(check_control *control);
status_t CheckBlockRun(block_run run);
status_t CheckInode(Inode *inode/*, bool fix, check_result &result, uint32 numResults*/);
status_t CheckBlockRun(block_run run, const char *type = NULL, check_control *control = NULL);
status_t CheckInode(Inode *inode, check_control *control = NULL);
private:
bool CheckBitmapIsUsedAt(off_t block) const;
void SetCheckBitmapAt(off_t block);
static status_t initialize(BlockAllocator *);
Volume *fVolume;