* There were quite a few cases that just assumed that the disk access would succeed. * This also fixes bug #12962.
2341 lines
61 KiB
C++
2341 lines
61 KiB
C++
/*
|
|
* Copyright 2001-2017, Axel Dörfler, [email protected].
|
|
* This file may be used under the terms of the MIT License.
|
|
*/
|
|
|
|
|
|
//! Block bitmap handling and allocation policies
|
|
|
|
|
|
#include "BlockAllocator.h"
|
|
|
|
#include "bfs_control.h"
|
|
#include "BPlusTree.h"
|
|
#include "Debug.h"
|
|
#include "Inode.h"
|
|
#include "Volume.h"
|
|
|
|
|
|
// Things the BlockAllocator should do:
|
|
|
|
// - find a range of blocks of a certain size nearby a specific position
|
|
// - allocating an unsharp range of blocks for pre-allocation
|
|
// - free blocks
|
|
// - know how to deal with each allocation, special handling for directories,
|
|
// files, symlinks, etc. (type sensitive allocation policies)
|
|
|
|
// What makes the code complicated is the fact that we are not just reading
|
|
// in the whole bitmap and operate on that in memory - e.g. a 13 GB partition
|
|
// with a block size of 2048 bytes already has a 800kB bitmap, and the size
|
|
// of partitions will grow even more - so that's not an option.
|
|
// Instead we are reading in every block when it's used - since an allocation
|
|
// group can span several blocks in the block bitmap, the AllocationBlock
|
|
// class is there to make handling those easier.
|
|
|
|
// The current implementation is only slightly optimized and could probably
|
|
// be improved a lot. Furthermore, the allocation policies used here should
|
|
// have some real world tests.
|
|
|
|
#if BFS_TRACING && !defined(FS_SHELL)
|
|
namespace BFSBlockTracing {
|
|
|
|
|
|
class Allocate : public AbstractTraceEntry {
|
|
public:
|
|
Allocate(block_run run)
|
|
:
|
|
fRun(run)
|
|
{
|
|
Initialized();
|
|
}
|
|
|
|
virtual void AddDump(TraceOutput& out)
|
|
{
|
|
out.Print("bfs:alloc %lu.%u.%u", fRun.AllocationGroup(),
|
|
fRun.Start(), fRun.Length());
|
|
}
|
|
|
|
const block_run& Run() const { return fRun; }
|
|
|
|
private:
|
|
block_run fRun;
|
|
};
|
|
|
|
|
|
class Free : public AbstractTraceEntry {
|
|
public:
|
|
Free(block_run run)
|
|
:
|
|
fRun(run)
|
|
{
|
|
Initialized();
|
|
}
|
|
|
|
virtual void AddDump(TraceOutput& out)
|
|
{
|
|
out.Print("bfs:free %lu.%u.%u", fRun.AllocationGroup(),
|
|
fRun.Start(), fRun.Length());
|
|
}
|
|
|
|
const block_run& Run() const { return fRun; }
|
|
|
|
private:
|
|
block_run fRun;
|
|
};
|
|
|
|
|
|
static uint32
|
|
checksum(const uint8* data, size_t size)
|
|
{
|
|
const uint32* data4 = (const uint32*)data;
|
|
uint32 sum = 0;
|
|
while (size >= 4) {
|
|
sum += *data4;
|
|
data4++;
|
|
size -= 4;
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
|
|
class Block : public AbstractTraceEntry {
|
|
public:
|
|
Block(const char* label, off_t blockNumber, const uint8* data,
|
|
size_t size, uint32 start = 0, uint32 length = 0)
|
|
:
|
|
fBlock(blockNumber),
|
|
fData(data),
|
|
fStart(start),
|
|
fLength(length),
|
|
fLabel(label)
|
|
{
|
|
fSum = checksum(data, size);
|
|
Initialized();
|
|
}
|
|
|
|
virtual void AddDump(TraceOutput& out)
|
|
{
|
|
out.Print("bfs:%s: block %Ld (%p), sum %lu, s/l %lu/%lu", fLabel,
|
|
fBlock, fData, fSum, fStart, fLength);
|
|
}
|
|
|
|
private:
|
|
off_t fBlock;
|
|
const uint8* fData;
|
|
uint32 fStart;
|
|
uint32 fLength;
|
|
uint32 fSum;
|
|
const char* fLabel;
|
|
};
|
|
|
|
|
|
class BlockChange : public AbstractTraceEntry {
|
|
public:
|
|
BlockChange(const char* label, int32 block, uint32 oldData, uint32 newData)
|
|
:
|
|
fBlock(block),
|
|
fOldData(oldData),
|
|
fNewData(newData),
|
|
fLabel(label)
|
|
{
|
|
Initialized();
|
|
}
|
|
|
|
virtual void AddDump(TraceOutput& out)
|
|
{
|
|
out.Print("bfs:%s: block %ld, %08lx -> %08lx", fLabel,
|
|
fBlock, fOldData, fNewData);
|
|
}
|
|
|
|
private:
|
|
int32 fBlock;
|
|
uint32 fOldData;
|
|
uint32 fNewData;
|
|
const char* fLabel;
|
|
};
|
|
|
|
|
|
} // namespace BFSBlockTracing
|
|
|
|
# define T(x) new(std::nothrow) BFSBlockTracing::x;
|
|
#else
|
|
# define T(x) ;
|
|
#endif
|
|
|
|
#ifdef DEBUG_ALLOCATION_GROUPS
|
|
# define CHECK_ALLOCATION_GROUP(group) _CheckGroup(group)
|
|
#else
|
|
# define CHECK_ALLOCATION_GROUP(group) ;
|
|
#endif
|
|
|
|
|
|
struct check_index {
|
|
check_index()
|
|
:
|
|
inode(NULL)
|
|
{
|
|
}
|
|
|
|
char name[B_FILE_NAME_LENGTH];
|
|
block_run run;
|
|
Inode* inode;
|
|
};
|
|
|
|
|
|
struct check_cookie {
|
|
check_cookie()
|
|
{
|
|
}
|
|
|
|
uint32 pass;
|
|
block_run current;
|
|
Inode* parent;
|
|
mode_t parent_mode;
|
|
Stack<block_run> stack;
|
|
TreeIterator* iterator;
|
|
check_control control;
|
|
Stack<check_index*> indices;
|
|
};
|
|
|
|
|
|
class AllocationBlock : public CachedBlock {
|
|
public:
|
|
AllocationBlock(Volume* volume);
|
|
|
|
inline void Allocate(uint16 start, uint16 numBlocks);
|
|
inline void Free(uint16 start, uint16 numBlocks);
|
|
inline bool IsUsed(uint16 block);
|
|
|
|
status_t SetTo(AllocationGroup& group, uint16 block);
|
|
status_t SetToWritable(Transaction& transaction, AllocationGroup& group,
|
|
uint16 block);
|
|
|
|
uint32 NumBlockBits() const { return fNumBits; }
|
|
uint32& Block(int32 index) { return ((uint32*)fBlock)[index]; }
|
|
uint8* Block() const { return (uint8*)fBlock; }
|
|
|
|
private:
|
|
uint32 fNumBits;
|
|
#ifdef DEBUG
|
|
bool fWritable;
|
|
#endif
|
|
};
|
|
|
|
|
|
class AllocationGroup {
|
|
public:
|
|
AllocationGroup();
|
|
|
|
void AddFreeRange(int32 start, int32 blocks);
|
|
bool IsFull() const { return fFreeBits == 0; }
|
|
|
|
status_t Allocate(Transaction& transaction, uint16 start, int32 length);
|
|
status_t Free(Transaction& transaction, uint16 start, int32 length);
|
|
|
|
uint32 NumBits() const { return fNumBits; }
|
|
uint32 NumBlocks() const { return fNumBlocks; }
|
|
int32 Start() const { return fStart; }
|
|
|
|
private:
|
|
friend class BlockAllocator;
|
|
|
|
uint32 fNumBits;
|
|
uint32 fNumBlocks;
|
|
int32 fStart;
|
|
int32 fFirstFree;
|
|
int32 fFreeBits;
|
|
|
|
int32 fLargestStart;
|
|
int32 fLargestLength;
|
|
bool fLargestValid;
|
|
};
|
|
|
|
|
|
AllocationBlock::AllocationBlock(Volume* volume)
|
|
: CachedBlock(volume)
|
|
{
|
|
}
|
|
|
|
|
|
status_t
|
|
AllocationBlock::SetTo(AllocationGroup& group, uint16 block)
|
|
{
|
|
// 8 blocks per byte
|
|
fNumBits = fVolume->BlockSize() << 3;
|
|
// the last group may have less bits than the others
|
|
if ((block + 1) * fNumBits > group.NumBits())
|
|
fNumBits = group.NumBits() % fNumBits;
|
|
|
|
#ifdef DEBUG
|
|
fWritable = false;
|
|
#endif
|
|
return CachedBlock::SetTo(group.Start() + block) != NULL ? B_OK : B_ERROR;
|
|
}
|
|
|
|
|
|
status_t
|
|
AllocationBlock::SetToWritable(Transaction& transaction, AllocationGroup& group,
|
|
uint16 block)
|
|
{
|
|
// 8 blocks per byte
|
|
fNumBits = fVolume->BlockSize() << 3;
|
|
// the last group may have less bits in the last block
|
|
if ((block + 1) * fNumBits > group.NumBits())
|
|
fNumBits = group.NumBits() % fNumBits;
|
|
|
|
#ifdef DEBUG
|
|
fWritable = true;
|
|
#endif
|
|
return CachedBlock::SetToWritable(transaction, group.Start() + block)
|
|
!= NULL ? B_OK : B_ERROR;
|
|
}
|
|
|
|
|
|
bool
|
|
AllocationBlock::IsUsed(uint16 block)
|
|
{
|
|
if (block > fNumBits)
|
|
return true;
|
|
|
|
// the block bitmap is accessed in 32-bit blocks
|
|
return Block(block >> 5) & HOST_ENDIAN_TO_BFS_INT32(1UL << (block % 32));
|
|
}
|
|
|
|
|
|
void
|
|
AllocationBlock::Allocate(uint16 start, uint16 numBlocks)
|
|
{
|
|
ASSERT(start < fNumBits);
|
|
ASSERT(uint32(start + numBlocks) <= fNumBits);
|
|
#ifdef DEBUG
|
|
ASSERT(fWritable);
|
|
#endif
|
|
|
|
T(Block("b-alloc-in", fBlockNumber, fBlock, fVolume->BlockSize(),
|
|
start, numBlocks));
|
|
|
|
int32 block = start >> 5;
|
|
|
|
while (numBlocks > 0) {
|
|
uint32 mask = 0;
|
|
for (int32 i = start % 32; i < 32 && numBlocks; i++, numBlocks--)
|
|
mask |= 1UL << i;
|
|
|
|
T(BlockChange("b-alloc", block, Block(block),
|
|
Block(block) | HOST_ENDIAN_TO_BFS_INT32(mask)));
|
|
|
|
#if KDEBUG
|
|
// check for already set blocks
|
|
if (HOST_ENDIAN_TO_BFS_INT32(mask) & ((uint32*)fBlock)[block]) {
|
|
FATAL(("AllocationBlock::Allocate(): some blocks are already "
|
|
"allocated, start = %u, numBlocks = %u\n", start, numBlocks));
|
|
panic("blocks already set!");
|
|
}
|
|
#endif
|
|
|
|
Block(block++) |= HOST_ENDIAN_TO_BFS_INT32(mask);
|
|
start = 0;
|
|
}
|
|
T(Block("b-alloc-out", fBlockNumber, fBlock, fVolume->BlockSize(),
|
|
start, numBlocks));
|
|
}
|
|
|
|
|
|
void
|
|
AllocationBlock::Free(uint16 start, uint16 numBlocks)
|
|
{
|
|
ASSERT(start < fNumBits);
|
|
ASSERT(uint32(start + numBlocks) <= fNumBits);
|
|
#ifdef DEBUG
|
|
ASSERT(fWritable);
|
|
#endif
|
|
|
|
int32 block = start >> 5;
|
|
|
|
while (numBlocks > 0) {
|
|
uint32 mask = 0;
|
|
for (int32 i = start % 32; i < 32 && numBlocks; i++, numBlocks--)
|
|
mask |= 1UL << (i % 32);
|
|
|
|
T(BlockChange("b-free", block, Block(block),
|
|
Block(block) & HOST_ENDIAN_TO_BFS_INT32(~mask)));
|
|
|
|
Block(block++) &= HOST_ENDIAN_TO_BFS_INT32(~mask);
|
|
start = 0;
|
|
}
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
/*! The allocation groups are created and initialized in
|
|
BlockAllocator::Initialize() and BlockAllocator::InitializeAndClearBitmap()
|
|
respectively.
|
|
*/
|
|
AllocationGroup::AllocationGroup()
|
|
:
|
|
fFirstFree(-1),
|
|
fFreeBits(0),
|
|
fLargestValid(false)
|
|
{
|
|
}
|
|
|
|
|
|
void
|
|
AllocationGroup::AddFreeRange(int32 start, int32 blocks)
|
|
{
|
|
//D(if (blocks > 512)
|
|
// PRINT(("range of %ld blocks starting at %ld\n",blocks,start)));
|
|
|
|
if (fFirstFree == -1)
|
|
fFirstFree = start;
|
|
|
|
if (!fLargestValid || fLargestLength < blocks) {
|
|
fLargestStart = start;
|
|
fLargestLength = blocks;
|
|
fLargestValid = true;
|
|
}
|
|
|
|
fFreeBits += blocks;
|
|
}
|
|
|
|
|
|
/*! Allocates the specified run in the allocation group.
|
|
Doesn't check if the run is valid or already allocated partially, nor
|
|
does it maintain the free ranges hints or the volume's used blocks count.
|
|
It only does the low-level work of allocating some bits in the block bitmap.
|
|
Assumes that the block bitmap lock is hold.
|
|
*/
|
|
status_t
|
|
AllocationGroup::Allocate(Transaction& transaction, uint16 start, int32 length)
|
|
{
|
|
ASSERT(start + length <= (int32)fNumBits);
|
|
|
|
// Update the allocation group info
|
|
// TODO: this info will be incorrect if something goes wrong later
|
|
// Note, the fFirstFree block doesn't have to be really free
|
|
if (start == fFirstFree)
|
|
fFirstFree = start + length;
|
|
fFreeBits -= length;
|
|
|
|
if (fLargestValid) {
|
|
bool cut = false;
|
|
if (fLargestStart == start) {
|
|
// cut from start
|
|
fLargestStart += length;
|
|
fLargestLength -= length;
|
|
cut = true;
|
|
} else if (start > fLargestStart
|
|
&& start < fLargestStart + fLargestLength) {
|
|
// cut from end
|
|
fLargestLength = start - fLargestStart;
|
|
cut = true;
|
|
}
|
|
if (cut && (fLargestLength < fLargestStart
|
|
|| fLargestLength
|
|
< (int32)fNumBits - (fLargestStart + fLargestLength))) {
|
|
// might not be the largest block anymore
|
|
fLargestValid = false;
|
|
}
|
|
}
|
|
|
|
Volume* volume = transaction.GetVolume();
|
|
|
|
// calculate block in the block bitmap and position within
|
|
uint32 bitsPerBlock = volume->BlockSize() << 3;
|
|
uint32 block = start / bitsPerBlock;
|
|
start = start % bitsPerBlock;
|
|
|
|
AllocationBlock cached(volume);
|
|
|
|
while (length > 0) {
|
|
if (cached.SetToWritable(transaction, *this, block) < B_OK) {
|
|
fLargestValid = false;
|
|
RETURN_ERROR(B_IO_ERROR);
|
|
}
|
|
|
|
uint32 numBlocks = length;
|
|
if (start + numBlocks > cached.NumBlockBits())
|
|
numBlocks = cached.NumBlockBits() - start;
|
|
|
|
cached.Allocate(start, numBlocks);
|
|
|
|
length -= numBlocks;
|
|
start = 0;
|
|
block++;
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
/*! Frees the specified run in the allocation group.
|
|
Doesn't check if the run is valid or was not completely allocated, nor
|
|
does it maintain the free ranges hints or the volume's used blocks count.
|
|
It only does the low-level work of freeing some bits in the block bitmap.
|
|
Assumes that the block bitmap lock is hold.
|
|
*/
|
|
status_t
|
|
AllocationGroup::Free(Transaction& transaction, uint16 start, int32 length)
|
|
{
|
|
ASSERT(start + length <= (int32)fNumBits);
|
|
|
|
// Update the allocation group info
|
|
// TODO: this info will be incorrect if something goes wrong later
|
|
if (fFirstFree > start)
|
|
fFirstFree = start;
|
|
fFreeBits += length;
|
|
|
|
// The range to be freed cannot be part of the valid largest range
|
|
ASSERT(!fLargestValid || start + length <= fLargestStart
|
|
|| start > fLargestStart);
|
|
|
|
if (fLargestValid
|
|
&& (start + length == fLargestStart
|
|
|| fLargestStart + fLargestLength == start
|
|
|| (start < fLargestStart && fLargestStart > fLargestLength)
|
|
|| (start > fLargestStart
|
|
&& (int32)fNumBits - (fLargestStart + fLargestLength)
|
|
> fLargestLength))) {
|
|
fLargestValid = false;
|
|
}
|
|
|
|
Volume* volume = transaction.GetVolume();
|
|
|
|
// calculate block in the block bitmap and position within
|
|
uint32 bitsPerBlock = volume->BlockSize() << 3;
|
|
uint32 block = start / bitsPerBlock;
|
|
start = start % bitsPerBlock;
|
|
|
|
AllocationBlock cached(volume);
|
|
|
|
while (length > 0) {
|
|
if (cached.SetToWritable(transaction, *this, block) < B_OK)
|
|
RETURN_ERROR(B_IO_ERROR);
|
|
|
|
T(Block("free-1", block, cached.Block(), volume->BlockSize()));
|
|
uint16 freeLength = length;
|
|
if (uint32(start + length) > cached.NumBlockBits())
|
|
freeLength = cached.NumBlockBits() - start;
|
|
|
|
cached.Free(start, freeLength);
|
|
|
|
length -= freeLength;
|
|
start = 0;
|
|
T(Block("free-2", block, cached.Block(), volume->BlockSize()));
|
|
block++;
|
|
}
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
BlockAllocator::BlockAllocator(Volume* volume)
|
|
:
|
|
fVolume(volume),
|
|
fGroups(NULL),
|
|
fCheckBitmap(NULL),
|
|
fCheckCookie(NULL)
|
|
{
|
|
recursive_lock_init(&fLock, "bfs allocator");
|
|
}
|
|
|
|
|
|
BlockAllocator::~BlockAllocator()
|
|
{
|
|
recursive_lock_destroy(&fLock);
|
|
delete[] fGroups;
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::Initialize(bool full)
|
|
{
|
|
fNumGroups = fVolume->AllocationGroups();
|
|
fBlocksPerGroup = fVolume->SuperBlock().BlocksPerAllocationGroup();
|
|
fNumBlocks = (fVolume->NumBlocks() + fVolume->BlockSize() * 8 - 1)
|
|
/ (fVolume->BlockSize() * 8);
|
|
|
|
fGroups = new(std::nothrow) AllocationGroup[fNumGroups];
|
|
if (fGroups == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
if (!full)
|
|
return B_OK;
|
|
|
|
recursive_lock_lock(&fLock);
|
|
// the lock will be released by the _Initialize() method
|
|
|
|
thread_id id = spawn_kernel_thread((thread_func)BlockAllocator::_Initialize,
|
|
"bfs block allocator", B_LOW_PRIORITY, this);
|
|
if (id < B_OK)
|
|
return _Initialize(this);
|
|
|
|
recursive_lock_transfer_lock(&fLock, id);
|
|
|
|
return resume_thread(id);
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::InitializeAndClearBitmap(Transaction& transaction)
|
|
{
|
|
status_t status = Initialize(false);
|
|
if (status != B_OK)
|
|
return status;
|
|
|
|
uint32 numBits = 8 * fBlocksPerGroup * fVolume->BlockSize();
|
|
uint32 blockShift = fVolume->BlockShift();
|
|
|
|
uint32* buffer = (uint32*)malloc(numBits >> 3);
|
|
if (buffer == NULL)
|
|
RETURN_ERROR(B_NO_MEMORY);
|
|
|
|
memset(buffer, 0, numBits >> 3);
|
|
|
|
off_t offset = 1;
|
|
// the bitmap starts directly after the superblock
|
|
|
|
// initialize the AllocationGroup objects and clear the on-disk bitmap
|
|
|
|
for (int32 i = 0; i < fNumGroups; i++) {
|
|
if (write_pos(fVolume->Device(), offset << blockShift, buffer,
|
|
fBlocksPerGroup << blockShift) < B_OK) {
|
|
free(buffer);
|
|
return B_ERROR;
|
|
}
|
|
|
|
// the last allocation group may contain less blocks than the others
|
|
if (i == fNumGroups - 1) {
|
|
fGroups[i].fNumBits = fVolume->NumBlocks() - i * numBits;
|
|
fGroups[i].fNumBlocks = 1 + ((fGroups[i].NumBits() - 1)
|
|
>> (blockShift + 3));
|
|
} else {
|
|
fGroups[i].fNumBits = numBits;
|
|
fGroups[i].fNumBlocks = fBlocksPerGroup;
|
|
}
|
|
fGroups[i].fStart = offset;
|
|
fGroups[i].fFirstFree = fGroups[i].fLargestStart = 0;
|
|
fGroups[i].fFreeBits = fGroups[i].fLargestLength = fGroups[i].fNumBits;
|
|
fGroups[i].fLargestValid = true;
|
|
|
|
offset += fBlocksPerGroup;
|
|
}
|
|
free(buffer);
|
|
|
|
// reserve the boot block, the log area, and the block bitmap itself
|
|
uint32 reservedBlocks = fVolume->Log().Start() + fVolume->Log().Length();
|
|
|
|
if (fGroups[0].Allocate(transaction, 0, reservedBlocks) < B_OK) {
|
|
FATAL(("could not allocate reserved space for block bitmap/log!\n"));
|
|
return B_ERROR;
|
|
}
|
|
fVolume->SuperBlock().used_blocks
|
|
= HOST_ENDIAN_TO_BFS_INT64(reservedBlocks);
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::_Initialize(BlockAllocator* allocator)
|
|
{
|
|
// The lock must already be held at this point
|
|
RecursiveLocker locker(allocator->fLock, true);
|
|
|
|
Volume* volume = allocator->fVolume;
|
|
uint32 blocks = allocator->fBlocksPerGroup;
|
|
uint32 blockShift = volume->BlockShift();
|
|
off_t freeBlocks = 0;
|
|
|
|
uint32* buffer = (uint32*)malloc(blocks << blockShift);
|
|
if (buffer == NULL)
|
|
RETURN_ERROR(B_NO_MEMORY);
|
|
|
|
AllocationGroup* groups = allocator->fGroups;
|
|
off_t offset = 1;
|
|
uint32 bitsPerGroup = 8 * (blocks << blockShift);
|
|
int32 numGroups = allocator->fNumGroups;
|
|
|
|
for (int32 i = 0; i < numGroups; i++) {
|
|
if (read_pos(volume->Device(), offset << blockShift, buffer,
|
|
blocks << blockShift) < B_OK)
|
|
break;
|
|
|
|
// the last allocation group may contain less blocks than the others
|
|
if (i == numGroups - 1) {
|
|
groups[i].fNumBits = volume->NumBlocks() - i * bitsPerGroup;
|
|
groups[i].fNumBlocks = 1 + ((groups[i].NumBits() - 1)
|
|
>> (blockShift + 3));
|
|
} else {
|
|
groups[i].fNumBits = bitsPerGroup;
|
|
groups[i].fNumBlocks = blocks;
|
|
}
|
|
groups[i].fStart = offset;
|
|
|
|
// finds all free ranges in this allocation group
|
|
int32 start = -1, range = 0;
|
|
int32 numBits = groups[i].fNumBits, bit = 0;
|
|
int32 count = (numBits + 31) / 32;
|
|
|
|
for (int32 k = 0; k < count; k++) {
|
|
for (int32 j = 0; j < 32 && bit < numBits; j++, bit++) {
|
|
if (buffer[k] & (1UL << j)) {
|
|
// block is in use
|
|
if (range > 0) {
|
|
groups[i].AddFreeRange(start, range);
|
|
range = 0;
|
|
}
|
|
} else if (range++ == 0) {
|
|
// block is free, start new free range
|
|
start = bit;
|
|
}
|
|
}
|
|
}
|
|
if (range)
|
|
groups[i].AddFreeRange(start, range);
|
|
|
|
freeBlocks += groups[i].fFreeBits;
|
|
|
|
offset += blocks;
|
|
}
|
|
free(buffer);
|
|
|
|
// check if block bitmap and log area are reserved
|
|
uint32 reservedBlocks = volume->Log().Start() + volume->Log().Length();
|
|
|
|
if (allocator->CheckBlocks(0, reservedBlocks) != B_OK) {
|
|
if (volume->IsReadOnly()) {
|
|
FATAL(("Space for block bitmap or log area is not reserved "
|
|
"(volume is mounted read-only)!\n"));
|
|
} else {
|
|
Transaction transaction(volume, 0);
|
|
if (groups[0].Allocate(transaction, 0, reservedBlocks) != B_OK) {
|
|
FATAL(("Could not allocate reserved space for block "
|
|
"bitmap/log!\n"));
|
|
volume->Panic();
|
|
} else {
|
|
transaction.Done();
|
|
FATAL(("Space for block bitmap or log area was not "
|
|
"reserved!\n"));
|
|
}
|
|
}
|
|
}
|
|
|
|
off_t usedBlocks = volume->NumBlocks() - freeBlocks;
|
|
if (volume->UsedBlocks() != usedBlocks) {
|
|
// If the disk in a dirty state at mount time, it's
|
|
// normal that the values don't match
|
|
INFORM(("volume reports %" B_PRIdOFF " used blocks, correct is %"
|
|
B_PRIdOFF "\n", volume->UsedBlocks(), usedBlocks));
|
|
volume->SuperBlock().used_blocks = HOST_ENDIAN_TO_BFS_INT64(usedBlocks);
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
void
|
|
BlockAllocator::Uninitialize()
|
|
{
|
|
// We only have to make sure that the initializer thread isn't running
|
|
// anymore.
|
|
recursive_lock_lock(&fLock);
|
|
}
|
|
|
|
|
|
/*! Tries to allocate between \a minimum, and \a maximum blocks starting
|
|
at group \a groupIndex with offset \a start. The resulting allocation
|
|
is put into \a run.
|
|
|
|
The number of allocated blocks is always a multiple of \a minimum which
|
|
has to be a power of two value.
|
|
*/
|
|
status_t
|
|
BlockAllocator::AllocateBlocks(Transaction& transaction, int32 groupIndex,
|
|
uint16 start, uint16 maximum, uint16 minimum, block_run& run)
|
|
{
|
|
if (maximum == 0)
|
|
return B_BAD_VALUE;
|
|
|
|
FUNCTION_START(("group = %ld, start = %u, maximum = %u, minimum = %u\n",
|
|
groupIndex, start, maximum, minimum));
|
|
|
|
AllocationBlock cached(fVolume);
|
|
RecursiveLocker lock(fLock);
|
|
|
|
uint32 bitsPerFullBlock = fVolume->BlockSize() << 3;
|
|
|
|
// Find the block_run that can fulfill the request best
|
|
int32 bestGroup = -1;
|
|
int32 bestStart = -1;
|
|
int32 bestLength = -1;
|
|
|
|
for (int32 i = 0; i < fNumGroups + 1; i++, groupIndex++, start = 0) {
|
|
groupIndex = groupIndex % fNumGroups;
|
|
AllocationGroup& group = fGroups[groupIndex];
|
|
|
|
CHECK_ALLOCATION_GROUP(groupIndex);
|
|
|
|
if (start >= group.NumBits() || group.IsFull())
|
|
continue;
|
|
|
|
// The wanted maximum is smaller than the largest free block in the
|
|
// group or already smaller than the minimum
|
|
|
|
if (start < group.fFirstFree)
|
|
start = group.fFirstFree;
|
|
|
|
if (group.fLargestValid) {
|
|
if (group.fLargestLength < bestLength)
|
|
continue;
|
|
|
|
if (group.fLargestStart >= start) {
|
|
if (group.fLargestLength >= bestLength) {
|
|
bestGroup = groupIndex;
|
|
bestStart = group.fLargestStart;
|
|
bestLength = group.fLargestLength;
|
|
|
|
if (bestLength >= maximum)
|
|
break;
|
|
}
|
|
|
|
// We know everything about this group we have to, let's skip
|
|
// to the next
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// There may be more than one block per allocation group - and
|
|
// we iterate through it to find a place for the allocation.
|
|
// (one allocation can't exceed one allocation group)
|
|
|
|
uint32 block = start / (fVolume->BlockSize() << 3);
|
|
int32 currentStart = 0, currentLength = 0;
|
|
int32 groupLargestStart = -1;
|
|
int32 groupLargestLength = -1;
|
|
int32 currentBit = start;
|
|
bool canFindGroupLargest = start == 0;
|
|
|
|
for (; block < group.NumBlocks(); block++) {
|
|
if (cached.SetTo(group, block) < B_OK)
|
|
RETURN_ERROR(B_ERROR);
|
|
|
|
T(Block("alloc-in", group.Start() + block, cached.Block(),
|
|
fVolume->BlockSize(), groupIndex, currentStart));
|
|
|
|
// find a block large enough to hold the allocation
|
|
for (uint32 bit = start % bitsPerFullBlock;
|
|
bit < cached.NumBlockBits(); bit++) {
|
|
if (!cached.IsUsed(bit)) {
|
|
if (currentLength == 0) {
|
|
// start new range
|
|
currentStart = currentBit;
|
|
}
|
|
|
|
// have we found a range large enough to hold numBlocks?
|
|
if (++currentLength >= maximum) {
|
|
bestGroup = groupIndex;
|
|
bestStart = currentStart;
|
|
bestLength = currentLength;
|
|
break;
|
|
}
|
|
} else {
|
|
if (currentLength) {
|
|
// end of a range
|
|
if (currentLength > bestLength) {
|
|
bestGroup = groupIndex;
|
|
bestStart = currentStart;
|
|
bestLength = currentLength;
|
|
}
|
|
if (currentLength > groupLargestLength) {
|
|
groupLargestStart = currentStart;
|
|
groupLargestLength = currentLength;
|
|
}
|
|
currentLength = 0;
|
|
}
|
|
if ((int32)group.NumBits() - currentBit
|
|
<= groupLargestLength) {
|
|
// We can't find a bigger block in this group anymore,
|
|
// let's skip the rest.
|
|
block = group.NumBlocks();
|
|
break;
|
|
}
|
|
}
|
|
currentBit++;
|
|
}
|
|
|
|
T(Block("alloc-out", block, cached.Block(),
|
|
fVolume->BlockSize(), groupIndex, currentStart));
|
|
|
|
if (bestLength >= maximum) {
|
|
canFindGroupLargest = false;
|
|
break;
|
|
}
|
|
|
|
// start from the beginning of the next block
|
|
start = 0;
|
|
}
|
|
|
|
if (currentBit == (int32)group.NumBits()) {
|
|
if (currentLength > bestLength) {
|
|
bestGroup = groupIndex;
|
|
bestStart = currentStart;
|
|
bestLength = currentLength;
|
|
}
|
|
if (canFindGroupLargest && currentLength > groupLargestLength) {
|
|
groupLargestStart = currentStart;
|
|
groupLargestLength = currentLength;
|
|
}
|
|
}
|
|
|
|
if (canFindGroupLargest && !group.fLargestValid
|
|
&& groupLargestLength >= 0) {
|
|
group.fLargestStart = groupLargestStart;
|
|
group.fLargestLength = groupLargestLength;
|
|
group.fLargestValid = true;
|
|
}
|
|
|
|
if (bestLength >= maximum)
|
|
break;
|
|
}
|
|
|
|
// If we found a suitable range, mark the blocks as in use, and
|
|
// write the updated block bitmap back to disk
|
|
if (bestLength < minimum)
|
|
return B_DEVICE_FULL;
|
|
|
|
if (bestLength > maximum)
|
|
bestLength = maximum;
|
|
else if (minimum > 1) {
|
|
// make sure bestLength is a multiple of minimum
|
|
bestLength = round_down(bestLength, minimum);
|
|
}
|
|
|
|
if (fGroups[bestGroup].Allocate(transaction, bestStart, bestLength) != B_OK)
|
|
RETURN_ERROR(B_IO_ERROR);
|
|
|
|
CHECK_ALLOCATION_GROUP(bestGroup);
|
|
|
|
run.allocation_group = HOST_ENDIAN_TO_BFS_INT32(bestGroup);
|
|
run.start = HOST_ENDIAN_TO_BFS_INT16(bestStart);
|
|
run.length = HOST_ENDIAN_TO_BFS_INT16(bestLength);
|
|
|
|
fVolume->SuperBlock().used_blocks
|
|
= HOST_ENDIAN_TO_BFS_INT64(fVolume->UsedBlocks() + bestLength);
|
|
// We are not writing back the disk's superblock - it's
|
|
// either done by the journaling code, or when the disk
|
|
// is unmounted.
|
|
// If the value is not correct at mount time, it will be
|
|
// fixed anyway.
|
|
|
|
// We need to flush any remaining blocks in the new allocation to make sure
|
|
// they won't interfere with the file cache.
|
|
block_cache_discard(fVolume->BlockCache(), fVolume->ToBlock(run),
|
|
run.Length());
|
|
|
|
T(Allocate(run));
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::AllocateForInode(Transaction& transaction,
|
|
const block_run* parent, mode_t type, block_run& run)
|
|
{
|
|
// Apply some allocation policies here (AllocateBlocks() will break them
|
|
// if necessary) - we will start with those described in Dominic Giampaolo's
|
|
// "Practical File System Design", and see how good they work
|
|
|
|
// Files are going in the same allocation group as its parent,
|
|
// sub-directories will be inserted 8 allocation groups after
|
|
// the one of the parent
|
|
uint16 group = parent->AllocationGroup();
|
|
if ((type & (S_DIRECTORY | S_INDEX_DIR | S_ATTR_DIR)) == S_DIRECTORY)
|
|
group += 8;
|
|
|
|
return AllocateBlocks(transaction, group, 0, 1, 1, run);
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::Allocate(Transaction& transaction, Inode* inode,
|
|
off_t numBlocks, block_run& run, uint16 minimum)
|
|
{
|
|
if (numBlocks <= 0)
|
|
return B_ERROR;
|
|
|
|
// one block_run can't hold more data than there is in one allocation group
|
|
if (numBlocks > fGroups[0].NumBits())
|
|
numBlocks = fGroups[0].NumBits();
|
|
|
|
// since block_run.length is uint16, the largest number of blocks that
|
|
// can be covered by a block_run is 65535
|
|
// TODO: if we drop compatibility, couldn't we do this any better?
|
|
// There are basically two possibilities:
|
|
// a) since a length of zero doesn't have any sense, take that for 65536 -
|
|
// but that could cause many problems (bugs) in other areas
|
|
// 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
|
|
// c) have BlockRun::Length() return (length + 1).
|
|
if (numBlocks > MAX_BLOCK_RUN_LENGTH)
|
|
numBlocks = MAX_BLOCK_RUN_LENGTH;
|
|
|
|
// Apply some allocation policies here (AllocateBlocks() will break them
|
|
// if necessary)
|
|
uint16 group = inode->BlockRun().AllocationGroup();
|
|
uint16 start = 0;
|
|
|
|
// Are there already allocated blocks? (then just try to allocate near the
|
|
// last one)
|
|
if (inode->Size() > 0) {
|
|
const data_stream& data = inode->Node().data;
|
|
// TODO: we currently don't care for when the data stream
|
|
// is already grown into the indirect ranges
|
|
if (data.max_double_indirect_range == 0
|
|
&& data.max_indirect_range == 0) {
|
|
// Since size > 0, there must be a valid block run in this stream
|
|
int32 last = 0;
|
|
for (; last < NUM_DIRECT_BLOCKS - 1; last++)
|
|
if (data.direct[last + 1].IsZero())
|
|
break;
|
|
|
|
group = data.direct[last].AllocationGroup();
|
|
start = data.direct[last].Start() + data.direct[last].Length();
|
|
}
|
|
} else if (inode->IsContainer() || inode->IsSymLink()) {
|
|
// directory and symbolic link data will go in the same allocation
|
|
// group as the inode is in but after the inode data
|
|
start = inode->BlockRun().Start();
|
|
} else {
|
|
// file data will start in the next allocation group
|
|
group = inode->BlockRun().AllocationGroup() + 1;
|
|
}
|
|
|
|
return AllocateBlocks(transaction, group, start, numBlocks, minimum, run);
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::Free(Transaction& transaction, block_run run)
|
|
{
|
|
RecursiveLocker lock(fLock);
|
|
|
|
int32 group = run.AllocationGroup();
|
|
uint16 start = run.Start();
|
|
uint16 length = run.Length();
|
|
|
|
FUNCTION_START(("group = %ld, start = %u, length = %u\n", group, start,
|
|
length));
|
|
T(Free(run));
|
|
|
|
// doesn't use Volume::IsValidBlockRun() here because it can check better
|
|
// against the group size (the last group may have a different length)
|
|
if (group < 0 || group >= fNumGroups
|
|
|| start > fGroups[group].NumBits()
|
|
|| uint32(start + length) > fGroups[group].NumBits()
|
|
|| length == 0) {
|
|
FATAL(("tried to free an invalid block_run (%d, %u, %u)\n", (int)group,
|
|
start, length));
|
|
DEBUGGER(("tried to free invalid block_run"));
|
|
return B_BAD_VALUE;
|
|
}
|
|
// check if someone tries to free reserved areas at the beginning of the
|
|
// drive
|
|
if (group == 0
|
|
&& start < uint32(fVolume->Log().Start() + fVolume->Log().Length())) {
|
|
FATAL(("tried to free a reserved block_run (%d, %u, %u)\n", (int)group,
|
|
start, length));
|
|
DEBUGGER(("tried to free reserved block"));
|
|
return B_BAD_VALUE;
|
|
}
|
|
#ifdef DEBUG
|
|
if (CheckBlockRun(run) != B_OK)
|
|
return B_BAD_DATA;
|
|
#endif
|
|
|
|
CHECK_ALLOCATION_GROUP(group);
|
|
|
|
if (fGroups[group].Free(transaction, start, length) != B_OK)
|
|
RETURN_ERROR(B_IO_ERROR);
|
|
|
|
CHECK_ALLOCATION_GROUP(group);
|
|
|
|
#ifdef DEBUG
|
|
if (CheckBlockRun(run, NULL, false) != B_OK) {
|
|
DEBUGGER(("CheckBlockRun() reports allocated blocks (which were just "
|
|
"freed)\n"));
|
|
}
|
|
#endif
|
|
|
|
fVolume->SuperBlock().used_blocks =
|
|
HOST_ENDIAN_TO_BFS_INT64(fVolume->UsedBlocks() - run.Length());
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
size_t
|
|
BlockAllocator::BitmapSize() const
|
|
{
|
|
return fVolume->BlockSize() * fNumBlocks;
|
|
}
|
|
|
|
|
|
#ifdef DEBUG_FRAGMENTER
|
|
void
|
|
BlockAllocator::Fragment()
|
|
{
|
|
AllocationBlock cached(fVolume);
|
|
RecursiveLocker lock(fLock);
|
|
|
|
// only leave 4 block holes
|
|
static const uint32 kMask = 0x0f0f0f0f;
|
|
uint32 valuesPerBlock = fVolume->BlockSize() / 4;
|
|
|
|
for (int32 i = 0; i < fNumGroups; i++) {
|
|
AllocationGroup& group = fGroups[i];
|
|
|
|
for (uint32 block = 0; block < group.NumBlocks(); block++) {
|
|
Transaction transaction(fVolume, 0);
|
|
|
|
if (cached.SetToWritable(transaction, group, block) != B_OK)
|
|
return;
|
|
|
|
for (int32 index = 0; index < valuesPerBlock; index++) {
|
|
cached.Block(index) |= HOST_ENDIAN_TO_BFS_INT32(kMask);
|
|
}
|
|
|
|
transaction.Done();
|
|
}
|
|
}
|
|
}
|
|
#endif // DEBUG_FRAGMENTER
|
|
|
|
|
|
#ifdef DEBUG_ALLOCATION_GROUPS
|
|
void
|
|
BlockAllocator::_CheckGroup(int32 groupIndex) const
|
|
{
|
|
AllocationBlock cached(fVolume);
|
|
ASSERT_LOCKED_RECURSIVE(&fLock);
|
|
|
|
AllocationGroup& group = fGroups[groupIndex];
|
|
|
|
int32 currentStart = 0, currentLength = 0;
|
|
int32 firstFree = -1;
|
|
int32 largestStart = -1;
|
|
int32 largestLength = 0;
|
|
int32 currentBit = 0;
|
|
|
|
for (uint32 block = 0; block < group.NumBlocks(); block++) {
|
|
if (cached.SetTo(group, block) < B_OK) {
|
|
panic("setting group block %d failed\n", (int)block);
|
|
return;
|
|
}
|
|
|
|
for (uint32 bit = 0; bit < cached.NumBlockBits(); bit++) {
|
|
if (!cached.IsUsed(bit)) {
|
|
if (firstFree < 0) {
|
|
firstFree = currentBit;
|
|
if (!group.fLargestValid) {
|
|
if (firstFree >= 0 && firstFree < group.fFirstFree) {
|
|
// mostly harmless but noteworthy
|
|
dprintf("group %d first free too late: should be "
|
|
"%d, is %d\n", (int)groupIndex, (int)firstFree,
|
|
(int)group.fFirstFree);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (currentLength == 0) {
|
|
// start new range
|
|
currentStart = currentBit;
|
|
}
|
|
currentLength++;
|
|
} else if (currentLength) {
|
|
// end of a range
|
|
if (currentLength > largestLength) {
|
|
largestStart = currentStart;
|
|
largestLength = currentLength;
|
|
}
|
|
currentLength = 0;
|
|
}
|
|
currentBit++;
|
|
}
|
|
}
|
|
|
|
if (currentLength > largestLength) {
|
|
largestStart = currentStart;
|
|
largestLength = currentLength;
|
|
}
|
|
|
|
if (firstFree >= 0 && firstFree < group.fFirstFree) {
|
|
// mostly harmless but noteworthy
|
|
dprintf("group %d first free too late: should be %d, is %d\n",
|
|
(int)groupIndex, (int)firstFree, (int)group.fFirstFree);
|
|
}
|
|
if (group.fLargestValid
|
|
&& (largestStart != group.fLargestStart
|
|
|| largestLength != group.fLargestLength)) {
|
|
panic("bfs %p: group %d largest differs: %d.%d, checked %d.%d.\n",
|
|
fVolume, (int)groupIndex, (int)group.fLargestStart,
|
|
(int)group.fLargestLength, (int)largestStart, (int)largestLength);
|
|
}
|
|
}
|
|
#endif // DEBUG_ALLOCATION_GROUPS
|
|
|
|
|
|
status_t
|
|
BlockAllocator::Trim(uint64 offset, uint64 size, uint64& trimmedSize)
|
|
{
|
|
const uint32 kTrimRanges = 128;
|
|
fs_trim_data* trimData = (fs_trim_data*)malloc(sizeof(fs_trim_data)
|
|
+ sizeof(uint64) * kTrimRanges);
|
|
if (trimData == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
MemoryDeleter deleter(trimData);
|
|
RecursiveLocker locker(fLock);
|
|
|
|
// TODO: take given offset and size into account!
|
|
int32 lastGroup = fNumGroups - 1;
|
|
uint32 firstBlock = 0;
|
|
uint32 firstBit = 0;
|
|
uint64 currentBlock = 0;
|
|
uint32 blockShift = fVolume->BlockShift();
|
|
|
|
uint64 firstFree = 0;
|
|
size_t freeLength = 0;
|
|
|
|
trimData->range_count = 0;
|
|
trimmedSize = 0;
|
|
|
|
AllocationBlock cached(fVolume);
|
|
for (int32 groupIndex = 0; groupIndex <= lastGroup; groupIndex++) {
|
|
AllocationGroup& group = fGroups[groupIndex];
|
|
|
|
for (uint32 block = firstBlock; block < group.NumBlocks(); block++) {
|
|
cached.SetTo(group, block);
|
|
|
|
for (uint32 i = firstBit; i < cached.NumBlockBits(); i++) {
|
|
if (cached.IsUsed(i)) {
|
|
// Block is in use
|
|
if (freeLength > 0) {
|
|
status_t status = _TrimNext(*trimData, kTrimRanges,
|
|
firstFree << blockShift, freeLength << blockShift,
|
|
false, trimmedSize);
|
|
if (status != B_OK)
|
|
return status;
|
|
|
|
freeLength = 0;
|
|
}
|
|
} else if (freeLength++ == 0) {
|
|
// Block is free, start new free range
|
|
firstFree = currentBlock;
|
|
}
|
|
|
|
currentBlock++;
|
|
}
|
|
}
|
|
|
|
firstBlock = 0;
|
|
firstBit = 0;
|
|
}
|
|
|
|
return _TrimNext(*trimData, kTrimRanges, firstFree << blockShift,
|
|
freeLength << blockShift, true, trimmedSize);
|
|
}
|
|
|
|
|
|
// #pragma mark - Bitmap validity checking
|
|
|
|
// TODO: implement new FS checking API
|
|
// Functions to check the validity of the bitmap - they are used from
|
|
// the "checkfs" command (since this does even a bit more, maybe we should
|
|
// move this some place else?)
|
|
|
|
|
|
bool
|
|
BlockAllocator::_IsValidCheckControl(const check_control* control)
|
|
{
|
|
if (control == NULL
|
|
|| control->magic != BFS_IOCTL_CHECK_MAGIC) {
|
|
FATAL(("invalid check_control (%p)!\n", control));
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::StartChecking(const check_control* control)
|
|
{
|
|
if (!_IsValidCheckControl(control))
|
|
return B_BAD_VALUE;
|
|
|
|
fVolume->GetJournal(0)->Lock(NULL, true);
|
|
// Lock the volume's journal
|
|
|
|
recursive_lock_lock(&fLock);
|
|
|
|
size_t size = BitmapSize();
|
|
fCheckBitmap = (uint32*)malloc(size);
|
|
if (fCheckBitmap == NULL) {
|
|
recursive_lock_unlock(&fLock);
|
|
fVolume->GetJournal(0)->Unlock(NULL, true);
|
|
return B_NO_MEMORY;
|
|
}
|
|
|
|
fCheckCookie = new(std::nothrow) check_cookie();
|
|
if (fCheckCookie == NULL) {
|
|
free(fCheckBitmap);
|
|
fCheckBitmap = NULL;
|
|
recursive_lock_unlock(&fLock);
|
|
fVolume->GetJournal(0)->Unlock(NULL, true);
|
|
|
|
return B_NO_MEMORY;
|
|
}
|
|
|
|
memcpy(&fCheckCookie->control, control, sizeof(check_control));
|
|
memset(&fCheckCookie->control.stats, 0, sizeof(control->stats));
|
|
|
|
// initialize bitmap
|
|
memset(fCheckBitmap, 0, size);
|
|
for (int32 block = fVolume->Log().Start() + fVolume->Log().Length();
|
|
block-- > 0;) {
|
|
_SetCheckBitmapAt(block);
|
|
}
|
|
|
|
fCheckCookie->pass = BFS_CHECK_PASS_BITMAP;
|
|
fCheckCookie->stack.Push(fVolume->Root());
|
|
fCheckCookie->stack.Push(fVolume->Indices());
|
|
fCheckCookie->iterator = NULL;
|
|
fCheckCookie->control.stats.block_size = fVolume->BlockSize();
|
|
|
|
// Put removed vnodes to the stack -- they are not reachable by traversing
|
|
// the file system anymore.
|
|
InodeList::Iterator iterator = fVolume->RemovedInodes().GetIterator();
|
|
while (Inode* inode = iterator.Next()) {
|
|
fCheckCookie->stack.Push(inode->BlockRun());
|
|
}
|
|
|
|
// TODO: check reserved area in bitmap!
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::StopChecking(check_control* control)
|
|
{
|
|
if (fCheckCookie == NULL)
|
|
return B_NO_INIT;
|
|
|
|
if (fCheckCookie->iterator != NULL) {
|
|
delete fCheckCookie->iterator;
|
|
fCheckCookie->iterator = NULL;
|
|
|
|
// the current directory inode is still locked in memory
|
|
put_vnode(fVolume->FSVolume(), fVolume->ToVnode(fCheckCookie->current));
|
|
}
|
|
|
|
if (fVolume->IsReadOnly()) {
|
|
// We can't fix errors on this volume
|
|
fCheckCookie->control.flags = 0;
|
|
}
|
|
|
|
if (fCheckCookie->control.status != B_ENTRY_NOT_FOUND)
|
|
FATAL(("BlockAllocator::CheckNextNode() didn't run through\n"));
|
|
|
|
switch (fCheckCookie->pass) {
|
|
case BFS_CHECK_PASS_BITMAP:
|
|
// if CheckNextNode() could completely work through, we can
|
|
// fix any damages of the bitmap
|
|
if (fCheckCookie->control.status == B_ENTRY_NOT_FOUND)
|
|
_WriteBackCheckBitmap();
|
|
break;
|
|
|
|
case BFS_CHECK_PASS_INDEX:
|
|
_FreeIndices();
|
|
break;
|
|
}
|
|
|
|
fVolume->SetCheckingThread(-1);
|
|
|
|
if (control != NULL)
|
|
user_memcpy(control, &fCheckCookie->control, sizeof(check_control));
|
|
|
|
free(fCheckBitmap);
|
|
fCheckBitmap = NULL;
|
|
delete fCheckCookie;
|
|
fCheckCookie = NULL;
|
|
recursive_lock_unlock(&fLock);
|
|
fVolume->GetJournal(0)->Unlock(NULL, true);
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::CheckNextNode(check_control* control)
|
|
{
|
|
if (fCheckCookie == NULL)
|
|
return B_NO_INIT;
|
|
|
|
fVolume->SetCheckingThread(find_thread(NULL));
|
|
|
|
// Make sure the user control is copied on exit
|
|
class CopyControlOnExit {
|
|
public:
|
|
CopyControlOnExit(check_control* source, check_control* userTarget)
|
|
:
|
|
fSource(source),
|
|
fTarget(userTarget)
|
|
{
|
|
}
|
|
|
|
~CopyControlOnExit()
|
|
{
|
|
if (fTarget != NULL)
|
|
user_memcpy(fTarget, fSource, sizeof(check_control));
|
|
}
|
|
|
|
private:
|
|
check_control* fSource;
|
|
check_control* fTarget;
|
|
} copyControl(&fCheckCookie->control, control);
|
|
|
|
while (true) {
|
|
if (fCheckCookie->iterator == NULL) {
|
|
if (!fCheckCookie->stack.Pop(&fCheckCookie->current)) {
|
|
// No more runs on the stack, we might be finished!
|
|
if (fCheckCookie->pass == BFS_CHECK_PASS_BITMAP
|
|
&& !fCheckCookie->indices.IsEmpty()) {
|
|
// Start second pass to repair indices
|
|
_WriteBackCheckBitmap();
|
|
|
|
fCheckCookie->pass = BFS_CHECK_PASS_INDEX;
|
|
fCheckCookie->control.pass = BFS_CHECK_PASS_INDEX;
|
|
|
|
status_t status = _PrepareIndices();
|
|
if (status != B_OK) {
|
|
fCheckCookie->control.status = status;
|
|
return status;
|
|
}
|
|
|
|
fCheckCookie->stack.Push(fVolume->Root());
|
|
continue;
|
|
}
|
|
|
|
fCheckCookie->control.status = B_ENTRY_NOT_FOUND;
|
|
return B_ENTRY_NOT_FOUND;
|
|
}
|
|
|
|
Vnode vnode(fVolume, fCheckCookie->current);
|
|
Inode* inode;
|
|
if (vnode.Get(&inode) != B_OK) {
|
|
FATAL(("check: Could not open inode at %" B_PRIdOFF "\n",
|
|
fVolume->ToBlock(fCheckCookie->current)));
|
|
continue;
|
|
}
|
|
|
|
fCheckCookie->control.inode = inode->ID();
|
|
fCheckCookie->control.mode = inode->Mode();
|
|
|
|
if (!inode->IsContainer()) {
|
|
// Check file
|
|
fCheckCookie->control.errors = 0;
|
|
fCheckCookie->control.status = CheckInode(inode, NULL);
|
|
|
|
if (inode->GetName(fCheckCookie->control.name) < B_OK)
|
|
strcpy(fCheckCookie->control.name, "(node has no name)");
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
// Check directory
|
|
BPlusTree* tree = inode->Tree();
|
|
if (tree == NULL) {
|
|
FATAL(("check: could not open b+tree from inode at %" B_PRIdOFF
|
|
"\n", fVolume->ToBlock(fCheckCookie->current)));
|
|
continue;
|
|
}
|
|
|
|
fCheckCookie->parent = inode;
|
|
fCheckCookie->parent_mode = inode->Mode();
|
|
|
|
// get iterator for the next directory
|
|
fCheckCookie->iterator = new(std::nothrow) TreeIterator(tree);
|
|
if (fCheckCookie->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
|
|
fCheckCookie->control.errors = 0;
|
|
fCheckCookie->control.status = CheckInode(inode, NULL);
|
|
|
|
if (inode->GetName(fCheckCookie->control.name) != B_OK)
|
|
strcpy(fCheckCookie->control.name, "(dir has no name)");
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
char name[B_FILE_NAME_LENGTH];
|
|
uint16 length;
|
|
ino_t id;
|
|
|
|
status_t status = fCheckCookie->iterator->GetNextEntry(name, &length,
|
|
B_FILE_NAME_LENGTH, &id);
|
|
if (status != B_OK) {
|
|
// we no longer need this iterator
|
|
delete fCheckCookie->iterator;
|
|
fCheckCookie->iterator = NULL;
|
|
|
|
// unlock the directory's inode from memory
|
|
put_vnode(fVolume->FSVolume(),
|
|
fVolume->ToVnode(fCheckCookie->current));
|
|
|
|
if (status == B_ENTRY_NOT_FOUND) {
|
|
// We iterated over all entries already, just go on to the next
|
|
continue;
|
|
}
|
|
|
|
// Iterating over the B+tree failed - we let the checkfs run
|
|
// fail completely, as we would delete all files we cannot
|
|
// access.
|
|
// TODO: maybe have a force parameter that actually does that.
|
|
// TODO: we also need to be able to repair broken B+trees!
|
|
return status;
|
|
}
|
|
|
|
// ignore "." and ".." entries
|
|
if (!strcmp(name, ".") || !strcmp(name, ".."))
|
|
continue;
|
|
|
|
// fill in the control data as soon as we have them
|
|
strlcpy(fCheckCookie->control.name, name, B_FILE_NAME_LENGTH);
|
|
fCheckCookie->control.inode = id;
|
|
fCheckCookie->control.errors = 0;
|
|
|
|
Vnode vnode(fVolume, id);
|
|
Inode* inode;
|
|
if (vnode.Get(&inode) != B_OK) {
|
|
FATAL(("Could not open inode ID %" B_PRIdINO "!\n", id));
|
|
fCheckCookie->control.errors |= BFS_COULD_NOT_OPEN;
|
|
|
|
if ((fCheckCookie->control.flags & BFS_REMOVE_INVALID) != 0) {
|
|
status = _RemoveInvalidNode(fCheckCookie->parent,
|
|
fCheckCookie->iterator->Tree(), NULL, name);
|
|
} else
|
|
status = B_ERROR;
|
|
|
|
fCheckCookie->control.status = status;
|
|
return B_OK;
|
|
}
|
|
|
|
// check if the inode's name is the same as in the b+tree
|
|
if (fCheckCookie->pass == BFS_CHECK_PASS_BITMAP
|
|
&& inode->IsRegularNode()) {
|
|
RecursiveLocker locker(inode->SmallDataLock());
|
|
NodeGetter node(fVolume, inode);
|
|
if (node.Node() == NULL) {
|
|
fCheckCookie->control.errors |= BFS_COULD_NOT_OPEN;
|
|
fCheckCookie->control.status = B_IO_ERROR;
|
|
return B_OK;
|
|
}
|
|
|
|
const char* localName = inode->Name(node.Node());
|
|
if (localName == NULL || strcmp(localName, name)) {
|
|
fCheckCookie->control.errors |= BFS_NAMES_DONT_MATCH;
|
|
FATAL(("Names differ: tree \"%s\", inode \"%s\"\n", name,
|
|
localName));
|
|
|
|
if ((fCheckCookie->control.flags & BFS_FIX_NAME_MISMATCHES)
|
|
!= 0) {
|
|
// Rename the inode
|
|
Transaction transaction(fVolume, inode->BlockNumber());
|
|
|
|
// Note, this may need extra blocks, but the inode will
|
|
// only be checked afterwards, so that it won't be lost
|
|
status = inode->SetName(transaction, name);
|
|
if (status == B_OK)
|
|
status = inode->WriteBack(transaction);
|
|
if (status == B_OK)
|
|
status = transaction.Done();
|
|
if (status != B_OK) {
|
|
fCheckCookie->control.status = status;
|
|
return B_OK;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
fCheckCookie->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 (fCheckCookie->pass == BFS_CHECK_PASS_BITMAP
|
|
&& (((fCheckCookie->parent_mode & S_ATTR_DIR) != 0
|
|
&& !inode->IsAttribute())
|
|
|| ((fCheckCookie->parent_mode & S_INDEX_DIR) != 0
|
|
&& !inode->IsIndex())
|
|
|| (is_directory(fCheckCookie->parent_mode)
|
|
&& !inode->IsRegularNode()))) {
|
|
FATAL(("inode at %" B_PRIdOFF " is of wrong type: %o (parent "
|
|
"%o at %" B_PRIdOFF ")!\n", inode->BlockNumber(),
|
|
inode->Mode(), fCheckCookie->parent_mode,
|
|
fCheckCookie->parent->BlockNumber()));
|
|
|
|
// if we are allowed to fix errors, we should remove the file
|
|
if ((fCheckCookie->control.flags & BFS_REMOVE_WRONG_TYPES) != 0
|
|
&& (fCheckCookie->control.flags & BFS_FIX_BITMAP_ERRORS)
|
|
!= 0) {
|
|
status = _RemoveInvalidNode(fCheckCookie->parent, NULL,
|
|
inode, name);
|
|
} else
|
|
status = B_ERROR;
|
|
|
|
fCheckCookie->control.errors |= BFS_WRONG_TYPE;
|
|
fCheckCookie->control.status = status;
|
|
return B_OK;
|
|
}
|
|
|
|
// push the directory on the stack so that it will be scanned later
|
|
if (inode->IsContainer() && !inode->IsIndex())
|
|
fCheckCookie->stack.Push(inode->BlockRun());
|
|
else {
|
|
// check it now
|
|
fCheckCookie->control.status = CheckInode(inode, name);
|
|
return B_OK;
|
|
}
|
|
}
|
|
// is never reached
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::_RemoveInvalidNode(Inode* parent, BPlusTree* tree, Inode* inode,
|
|
const char* name)
|
|
{
|
|
// 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, parent->BlockNumber());
|
|
status_t status;
|
|
|
|
if (inode != NULL) {
|
|
inode->Node().flags |= HOST_ENDIAN_TO_BFS_INT32(INODE_DONT_FREE_SPACE);
|
|
|
|
status = parent->Remove(transaction, name, NULL, false, true);
|
|
} else {
|
|
parent->WriteLockInTransaction(transaction);
|
|
|
|
// does the file even exist?
|
|
off_t id;
|
|
status = tree->Find((uint8*)name, (uint16)strlen(name), &id);
|
|
if (status == B_OK)
|
|
status = tree->Remove(transaction, name, id);
|
|
}
|
|
|
|
if (status == B_OK) {
|
|
entry_cache_remove(fVolume->ID(), parent->ID(), name);
|
|
transaction.Done();
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
|
|
bool
|
|
BlockAllocator::_CheckBitmapIsUsedAt(off_t block) const
|
|
{
|
|
size_t size = BitmapSize();
|
|
uint32 index = block / 32; // 32bit resolution
|
|
if (index > size / 4)
|
|
return false;
|
|
|
|
return BFS_ENDIAN_TO_HOST_INT32(fCheckBitmap[index])
|
|
& (1UL << (block & 0x1f));
|
|
}
|
|
|
|
|
|
void
|
|
BlockAllocator::_SetCheckBitmapAt(off_t block)
|
|
{
|
|
size_t size = BitmapSize();
|
|
uint32 index = block / 32; // 32bit resolution
|
|
if (index > size / 4)
|
|
return;
|
|
|
|
fCheckBitmap[index] |= HOST_ENDIAN_TO_BFS_INT32(1UL << (block & 0x1f));
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::_WriteBackCheckBitmap()
|
|
{
|
|
if (fVolume->IsReadOnly())
|
|
return B_OK;
|
|
|
|
// calculate the number of used blocks in the check bitmap
|
|
size_t size = BitmapSize();
|
|
off_t usedBlocks = 0LL;
|
|
|
|
// TODO: update the allocation groups used blocks info
|
|
for (uint32 i = size >> 2; i-- > 0;) {
|
|
uint32 compare = 1;
|
|
// Count the number of bits set
|
|
for (int16 j = 0; j < 32; j++, compare <<= 1) {
|
|
if ((compare & fCheckBitmap[i]) != 0)
|
|
usedBlocks++;
|
|
}
|
|
}
|
|
|
|
fCheckCookie->control.stats.freed = fVolume->UsedBlocks() - usedBlocks
|
|
+ fCheckCookie->control.stats.missing;
|
|
if (fCheckCookie->control.stats.freed < 0)
|
|
fCheckCookie->control.stats.freed = 0;
|
|
|
|
// Should we fix errors? Were there any errors we can fix?
|
|
if ((fCheckCookie->control.flags & BFS_FIX_BITMAP_ERRORS) != 0
|
|
&& (fCheckCookie->control.stats.freed != 0
|
|
|| fCheckCookie->control.stats.missing != 0)) {
|
|
// If so, write the check bitmap back over the original one,
|
|
// and use transactions here to play safe - we even use several
|
|
// transactions, so that we don't blow the maximum log size
|
|
// on large disks, since we don't need to make this atomic.
|
|
#if 0
|
|
// prints the blocks that differ
|
|
off_t block = 0;
|
|
for (int32 i = 0; i < fNumGroups; i++) {
|
|
AllocationBlock cached(fVolume);
|
|
for (uint32 j = 0; j < fGroups[i].NumBlocks(); j++) {
|
|
cached.SetTo(fGroups[i], j);
|
|
for (uint32 k = 0; k < cached.NumBlockBits(); k++) {
|
|
if (cached.IsUsed(k) != _CheckBitmapIsUsedAt(block)) {
|
|
dprintf("differ block %lld (should be %d)\n", block,
|
|
_CheckBitmapIsUsedAt(block));
|
|
}
|
|
block++;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
fVolume->SuperBlock().used_blocks
|
|
= HOST_ENDIAN_TO_BFS_INT64(usedBlocks);
|
|
|
|
size_t blockSize = fVolume->BlockSize();
|
|
|
|
for (uint32 i = 0; i < fNumBlocks; i += 512) {
|
|
Transaction transaction(fVolume, 1 + i);
|
|
|
|
uint32 blocksToWrite = 512;
|
|
if (blocksToWrite + i > fNumBlocks)
|
|
blocksToWrite = fNumBlocks - i;
|
|
|
|
status_t status = transaction.WriteBlocks(1 + i,
|
|
(uint8*)fCheckBitmap + i * blockSize, blocksToWrite);
|
|
if (status < B_OK) {
|
|
FATAL(("error writing bitmap: %s\n", strerror(status)));
|
|
return status;
|
|
}
|
|
transaction.Done();
|
|
}
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
/*! Checks whether or not the specified block range is allocated or not,
|
|
depending on the \a allocated argument.
|
|
*/
|
|
status_t
|
|
BlockAllocator::CheckBlocks(off_t start, off_t length, bool allocated)
|
|
{
|
|
if (start < 0 || start + length > fVolume->NumBlocks())
|
|
return B_BAD_VALUE;
|
|
|
|
int32 group = start >> fVolume->AllocationGroupShift();
|
|
uint32 bitmapBlock = start / (fVolume->BlockSize() << 3);
|
|
uint32 blockOffset = start % (fVolume->BlockSize() << 3);
|
|
|
|
uint32 groupBlock = bitmapBlock % fBlocksPerGroup;
|
|
|
|
AllocationBlock cached(fVolume);
|
|
|
|
while (groupBlock < fGroups[group].NumBlocks() && length > 0) {
|
|
if (cached.SetTo(fGroups[group], groupBlock) != B_OK)
|
|
RETURN_ERROR(B_IO_ERROR);
|
|
|
|
for (; blockOffset < cached.NumBlockBits() && length > 0;
|
|
blockOffset++, length--) {
|
|
if (cached.IsUsed(blockOffset) != allocated) {
|
|
RETURN_ERROR(B_BAD_DATA);
|
|
}
|
|
}
|
|
|
|
blockOffset = 0;
|
|
|
|
if (++groupBlock >= fGroups[group].NumBlocks()) {
|
|
groupBlock = 0;
|
|
group++;
|
|
}
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::CheckBlockRun(block_run run, const char* type, bool allocated)
|
|
{
|
|
if (run.AllocationGroup() < 0 || run.AllocationGroup() >= fNumGroups
|
|
|| run.Start() > fGroups[run.AllocationGroup()].fNumBits
|
|
|| uint32(run.Start() + run.Length())
|
|
> fGroups[run.AllocationGroup()].fNumBits
|
|
|| run.length == 0) {
|
|
PRINT(("%s: block_run(%ld, %u, %u) is invalid!\n", type,
|
|
run.AllocationGroup(), run.Start(), run.Length()));
|
|
if (fCheckCookie == NULL)
|
|
return B_BAD_DATA;
|
|
|
|
fCheckCookie->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;
|
|
off_t firstMissing = -1, firstSet = -1;
|
|
off_t firstGroupBlock
|
|
= (off_t)run.AllocationGroup() << fVolume->AllocationGroupShift();
|
|
|
|
AllocationBlock cached(fVolume);
|
|
|
|
for (; block < fBlocksPerGroup && length < run.Length(); block++, pos = 0) {
|
|
if (cached.SetTo(fGroups[run.AllocationGroup()], block) < B_OK)
|
|
RETURN_ERROR(B_IO_ERROR);
|
|
|
|
if (pos >= cached.NumBlockBits()) {
|
|
// something very strange has happened...
|
|
RETURN_ERROR(B_ERROR);
|
|
}
|
|
|
|
while (length < run.Length() && pos < cached.NumBlockBits()) {
|
|
if (cached.IsUsed(pos) != allocated) {
|
|
if (fCheckCookie == NULL) {
|
|
PRINT(("%s: block_run(%ld, %u, %u) is only partially "
|
|
"allocated (pos = %ld, length = %ld)!\n", type,
|
|
run.AllocationGroup(), run.Start(), run.Length(),
|
|
pos, length));
|
|
return B_BAD_DATA;
|
|
}
|
|
if (firstMissing == -1) {
|
|
firstMissing = firstGroupBlock + pos + block * bitsPerBlock;
|
|
fCheckCookie->control.errors |= BFS_MISSING_BLOCKS;
|
|
}
|
|
fCheckCookie->control.stats.missing++;
|
|
} else if (firstMissing != -1) {
|
|
PRINT(("%s: block_run(%ld, %u, %u): blocks %Ld - %Ld are "
|
|
"%sallocated!\n", type, run.AllocationGroup(), run.Start(),
|
|
run.Length(), firstMissing,
|
|
firstGroupBlock + pos + block * bitsPerBlock - 1,
|
|
allocated ? "not " : ""));
|
|
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 = firstGroupBlock + offset;
|
|
fCheckCookie->control.errors |= BFS_BLOCKS_ALREADY_SET;
|
|
dprintf("block %" B_PRIdOFF " is already set!!!\n",
|
|
firstGroupBlock + offset);
|
|
}
|
|
fCheckCookie->control.stats.already_set++;
|
|
} else {
|
|
if (firstSet != -1) {
|
|
FATAL(("%s: block_run(%d, %u, %u): blocks %" B_PRIdOFF
|
|
" - %" B_PRIdOFF " are already set!\n", type,
|
|
(int)run.AllocationGroup(), run.Start(),
|
|
run.Length(), firstSet,
|
|
firstGroupBlock + offset - 1));
|
|
firstSet = -1;
|
|
}
|
|
_SetCheckBitmapAt(firstGroupBlock + offset);
|
|
}
|
|
}
|
|
length++;
|
|
pos++;
|
|
}
|
|
|
|
if (block + 1 >= fBlocksPerGroup || length >= run.Length()) {
|
|
if (firstMissing != -1) {
|
|
PRINT(("%s: block_run(%ld, %u, %u): blocks %Ld - %Ld are not "
|
|
"allocated!\n", type, run.AllocationGroup(), run.Start(),
|
|
run.Length(), firstMissing,
|
|
firstGroupBlock + pos + block * bitsPerBlock - 1));
|
|
}
|
|
if (firstSet != -1) {
|
|
FATAL(("%s: block_run(%d, %u, %u): blocks %" B_PRIdOFF " - %"
|
|
B_PRIdOFF " are already set!\n", type,
|
|
(int)run.AllocationGroup(), run.Start(), run.Length(),
|
|
firstSet,
|
|
firstGroupBlock + pos + block * bitsPerBlock - 1));
|
|
}
|
|
}
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::CheckInode(Inode* inode, const char* name)
|
|
{
|
|
if (fCheckCookie != NULL && fCheckBitmap == NULL)
|
|
return B_NO_INIT;
|
|
if (inode == NULL)
|
|
return B_BAD_VALUE;
|
|
|
|
switch (fCheckCookie->pass) {
|
|
case BFS_CHECK_PASS_BITMAP:
|
|
{
|
|
status_t status = _CheckInodeBlocks(inode, name);
|
|
if (status != B_OK)
|
|
return status;
|
|
|
|
// Check the B+tree as well
|
|
if (inode->IsContainer()) {
|
|
bool repairErrors
|
|
= (fCheckCookie->control.flags & BFS_FIX_BPLUSTREES) != 0;
|
|
bool errorsFound = false;
|
|
status = inode->Tree()->Validate(repairErrors, errorsFound);
|
|
if (errorsFound) {
|
|
fCheckCookie->control.errors |= BFS_INVALID_BPLUSTREE;
|
|
if (inode->IsIndex() && name != NULL && repairErrors) {
|
|
// We completely rebuild corrupt indices
|
|
check_index* index = new(std::nothrow) check_index;
|
|
if (index == NULL)
|
|
return B_NO_MEMORY;
|
|
|
|
strlcpy(index->name, name, sizeof(index->name));
|
|
index->run = inode->BlockRun();
|
|
fCheckCookie->indices.Push(index);
|
|
}
|
|
}
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
case BFS_CHECK_PASS_INDEX:
|
|
return _AddInodeToIndex(inode);
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::_CheckInodeBlocks(Inode* inode, const char* name)
|
|
{
|
|
status_t status = CheckBlockRun(inode->BlockRun(), "inode");
|
|
if (status != B_OK)
|
|
return status;
|
|
|
|
// If the inode has an attribute directory, push it on the stack
|
|
if (!inode->Attributes().IsZero())
|
|
fCheckCookie->stack.Push(inode->Attributes());
|
|
|
|
if (inode->IsSymLink() && (inode->Flags() & INODE_LONG_SYMLINK) == 0) {
|
|
// symlinks may not have a valid data stream
|
|
if (strlen(inode->Node().short_symlink) >= SHORT_SYMLINK_NAME_LENGTH)
|
|
return B_BAD_DATA;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
data_stream* data = &inode->Node().data;
|
|
|
|
// check the direct range
|
|
|
|
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");
|
|
if (status < B_OK)
|
|
return status;
|
|
|
|
fCheckCookie->control.stats.direct_block_runs++;
|
|
fCheckCookie->control.stats.blocks_in_direct
|
|
+= data->direct[i].Length();
|
|
}
|
|
}
|
|
|
|
CachedBlock cached(fVolume);
|
|
|
|
// check the indirect range
|
|
|
|
if (data->max_indirect_range) {
|
|
status = CheckBlockRun(data->indirect, "indirect");
|
|
if (status < B_OK)
|
|
return status;
|
|
|
|
off_t block = fVolume->ToBlock(data->indirect);
|
|
|
|
for (int32 i = 0; i < data->indirect.Length(); i++) {
|
|
block_run* runs = (block_run*)cached.SetTo(block + i);
|
|
if (runs == NULL)
|
|
RETURN_ERROR(B_IO_ERROR);
|
|
|
|
int32 runsPerBlock = fVolume->BlockSize() / sizeof(block_run);
|
|
int32 index = 0;
|
|
for (; index < runsPerBlock; index++) {
|
|
if (runs[index].IsZero())
|
|
break;
|
|
|
|
status = CheckBlockRun(runs[index], "indirect->run");
|
|
if (status < B_OK)
|
|
return status;
|
|
|
|
fCheckCookie->control.stats.indirect_block_runs++;
|
|
fCheckCookie->control.stats.blocks_in_indirect
|
|
+= runs[index].Length();
|
|
}
|
|
fCheckCookie->control.stats.indirect_array_blocks++;
|
|
|
|
if (index < runsPerBlock)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// check the double indirect range
|
|
|
|
if (data->max_double_indirect_range) {
|
|
status = CheckBlockRun(data->double_indirect, "double indirect");
|
|
if (status != B_OK)
|
|
return status;
|
|
|
|
int32 runsPerBlock = runs_per_block(fVolume->BlockSize());
|
|
int32 runsPerArray = runsPerBlock * data->double_indirect.Length();
|
|
|
|
CachedBlock cachedDirect(fVolume);
|
|
|
|
for (int32 indirectIndex = 0; indirectIndex < runsPerArray;
|
|
indirectIndex++) {
|
|
// get the indirect array block
|
|
block_run* array = (block_run*)cached.SetTo(
|
|
fVolume->ToBlock(data->double_indirect)
|
|
+ indirectIndex / runsPerBlock);
|
|
if (array == NULL)
|
|
return B_IO_ERROR;
|
|
|
|
block_run indirect = array[indirectIndex % runsPerBlock];
|
|
// are we finished yet?
|
|
if (indirect.IsZero())
|
|
return B_OK;
|
|
|
|
status = CheckBlockRun(indirect, "double indirect->runs");
|
|
if (status != B_OK)
|
|
return status;
|
|
|
|
int32 maxIndex
|
|
= ((uint32)indirect.Length() << fVolume->BlockShift())
|
|
/ sizeof(block_run);
|
|
|
|
for (int32 index = 0; index < maxIndex; ) {
|
|
block_run* runs = (block_run*)cachedDirect.SetTo(
|
|
fVolume->ToBlock(indirect) + index / runsPerBlock);
|
|
if (runs == NULL)
|
|
return B_IO_ERROR;
|
|
|
|
do {
|
|
// are we finished yet?
|
|
if (runs[index % runsPerBlock].IsZero())
|
|
return B_OK;
|
|
|
|
status = CheckBlockRun(runs[index % runsPerBlock],
|
|
"double indirect->runs->run");
|
|
if (status != B_OK)
|
|
return status;
|
|
|
|
fCheckCookie->control.stats.double_indirect_block_runs++;
|
|
fCheckCookie->control.stats.blocks_in_double_indirect
|
|
+= runs[index % runsPerBlock].Length();
|
|
} while ((++index % runsPerBlock) != 0);
|
|
}
|
|
|
|
fCheckCookie->control.stats.double_indirect_array_blocks++;
|
|
}
|
|
}
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::_PrepareIndices()
|
|
{
|
|
int32 count = 0;
|
|
|
|
for (int32 i = 0; i < fCheckCookie->indices.CountItems(); i++) {
|
|
check_index* index = fCheckCookie->indices.Array()[i];
|
|
Vnode vnode(fVolume, index->run);
|
|
Inode* inode;
|
|
status_t status = vnode.Get(&inode);
|
|
if (status != B_OK) {
|
|
FATAL(("check: Could not open index at %" B_PRIdOFF "\n",
|
|
fVolume->ToBlock(index->run)));
|
|
return status;
|
|
}
|
|
|
|
BPlusTree* tree = inode->Tree();
|
|
if (tree == NULL) {
|
|
// TODO: We can't yet repair those
|
|
continue;
|
|
}
|
|
|
|
status = tree->MakeEmpty();
|
|
if (status != B_OK)
|
|
return status;
|
|
|
|
index->inode = inode;
|
|
vnode.Keep();
|
|
count++;
|
|
}
|
|
|
|
return count == 0 ? B_ENTRY_NOT_FOUND : B_OK;
|
|
}
|
|
|
|
|
|
void
|
|
BlockAllocator::_FreeIndices()
|
|
{
|
|
for (int32 i = 0; i < fCheckCookie->indices.CountItems(); i++) {
|
|
check_index* index = fCheckCookie->indices.Array()[i];
|
|
if (index->inode != NULL) {
|
|
put_vnode(fVolume->FSVolume(),
|
|
fVolume->ToVnode(index->inode->BlockRun()));
|
|
}
|
|
}
|
|
fCheckCookie->indices.MakeEmpty();
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::_AddInodeToIndex(Inode* inode)
|
|
{
|
|
Transaction transaction(fVolume, inode->BlockNumber());
|
|
|
|
for (int32 i = 0; i < fCheckCookie->indices.CountItems(); i++) {
|
|
check_index* index = fCheckCookie->indices.Array()[i];
|
|
if (index->inode == NULL)
|
|
continue;
|
|
|
|
index->inode->WriteLockInTransaction(transaction);
|
|
|
|
BPlusTree* tree = index->inode->Tree();
|
|
if (tree == NULL)
|
|
return B_ERROR;
|
|
|
|
status_t status = B_OK;
|
|
|
|
if (!strcmp(index->name, "name")) {
|
|
if (inode->InNameIndex()) {
|
|
char name[B_FILE_NAME_LENGTH];
|
|
if (inode->GetName(name, B_FILE_NAME_LENGTH) != B_OK)
|
|
return B_ERROR;
|
|
|
|
status = tree->Insert(transaction, name, inode->ID());
|
|
}
|
|
} else if (!strcmp(index->name, "last_modified")) {
|
|
if (inode->InLastModifiedIndex()) {
|
|
status = tree->Insert(transaction, inode->OldLastModified(),
|
|
inode->ID());
|
|
}
|
|
} else if (!strcmp(index->name, "size")) {
|
|
if (inode->InSizeIndex())
|
|
status = tree->Insert(transaction, inode->Size(), inode->ID());
|
|
} else {
|
|
uint8 key[BPLUSTREE_MAX_KEY_LENGTH];
|
|
size_t keyLength = BPLUSTREE_MAX_KEY_LENGTH;
|
|
if (inode->ReadAttribute(index->name, B_ANY_TYPE, 0, key,
|
|
&keyLength) == B_OK) {
|
|
status = tree->Insert(transaction, key, keyLength, inode->ID());
|
|
}
|
|
}
|
|
|
|
if (status != B_OK)
|
|
return status;
|
|
}
|
|
|
|
return transaction.Done();
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::_AddTrim(fs_trim_data& trimData, uint32 maxRanges,
|
|
uint64 offset, uint64 size)
|
|
{
|
|
if (trimData.range_count < maxRanges && size > 0) {
|
|
trimData.ranges[trimData.range_count].offset = offset;
|
|
trimData.ranges[trimData.range_count].size = size;
|
|
trimData.range_count++;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
status_t
|
|
BlockAllocator::_TrimNext(fs_trim_data& trimData, uint32 maxRanges,
|
|
uint64 offset, uint64 size, bool force, uint64& trimmedSize)
|
|
{
|
|
PRINT(("_TrimNext(index %" B_PRIu32 ", offset %" B_PRIu64 ", size %"
|
|
B_PRIu64 ")\n", trimData.range_count, offset, size));
|
|
|
|
bool pushed = _AddTrim(trimData, maxRanges, offset, size);
|
|
|
|
if (!pushed || force) {
|
|
// Trim now
|
|
trimData.trimmed_size = 0;
|
|
dprintf("TRIM FS:\n");
|
|
for (uint32 i = 0; i < trimData.range_count; i++) {
|
|
dprintf("[%3" B_PRIu32 "] %" B_PRIu64 " : %" B_PRIu64 "\n", i,
|
|
trimData.ranges[i].offset, trimData.ranges[i].size);
|
|
}
|
|
if (ioctl(fVolume->Device(), B_TRIM_DEVICE, &trimData,
|
|
sizeof(fs_trim_data)) != 0) {
|
|
return errno;
|
|
}
|
|
|
|
trimmedSize += trimData.trimmed_size;
|
|
trimData.range_count = 0;
|
|
}
|
|
|
|
if (!pushed)
|
|
_AddTrim(trimData, maxRanges, offset, size);
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// #pragma mark - debugger commands
|
|
|
|
|
|
#ifdef BFS_DEBUGGER_COMMANDS
|
|
|
|
void
|
|
BlockAllocator::Dump(int32 index)
|
|
{
|
|
kprintf("allocation groups: %" B_PRId32 " (base %p)\n", fNumGroups, fGroups);
|
|
kprintf("blocks per group: %" B_PRId32 "\n", fBlocksPerGroup);
|
|
|
|
for (int32 i = 0; i < fNumGroups; i++) {
|
|
if (index != -1 && i != index)
|
|
continue;
|
|
|
|
AllocationGroup& group = fGroups[i];
|
|
|
|
kprintf("[%3" B_PRId32 "] num bits: %" B_PRIu32 " (%p)\n", i,
|
|
group.NumBits(), &group);
|
|
kprintf(" num blocks: %" B_PRIu32 "\n", group.NumBlocks());
|
|
kprintf(" start: %" B_PRId32 "\n", group.Start());
|
|
kprintf(" first free: %" B_PRId32 "\n", group.fFirstFree);
|
|
kprintf(" largest start: %" B_PRId32 "%s\n", group.fLargestStart,
|
|
group.fLargestValid ? "" : " (invalid)");
|
|
kprintf(" largest length: %" B_PRId32 "\n", group.fLargestLength);
|
|
kprintf(" free bits: %" B_PRId32 "\n", group.fFreeBits);
|
|
}
|
|
}
|
|
|
|
|
|
#if BFS_TRACING
|
|
static char kTraceBuffer[256];
|
|
|
|
|
|
int
|
|
dump_block_allocator_blocks(int argc, char** argv)
|
|
{
|
|
if (argc != 3 || !strcmp(argv[1], "--help")) {
|
|
kprintf("usage: %s <ptr-to-volume> <block>\n", argv[0]);
|
|
return 0;
|
|
}
|
|
|
|
Volume* volume = (Volume*)parse_expression(argv[1]);
|
|
off_t block = parse_expression(argv[2]);
|
|
|
|
// iterate over all tracing entries to find overlapping actions
|
|
|
|
using namespace BFSBlockTracing;
|
|
|
|
LazyTraceOutput out(kTraceBuffer, sizeof(kTraceBuffer), 0);
|
|
TraceEntryIterator iterator;
|
|
while (TraceEntry* _entry = iterator.Next()) {
|
|
if (Allocate* entry = dynamic_cast<Allocate*>(_entry)) {
|
|
off_t first = volume->ToBlock(entry->Run());
|
|
off_t last = first - 1 + entry->Run().Length();
|
|
if (block >= first && block <= last) {
|
|
out.Clear();
|
|
const char* dump = out.DumpEntry(entry);
|
|
kprintf("%5ld. %s\n", iterator.Index(), dump);
|
|
}
|
|
} else if (Free* entry = dynamic_cast<Free*>(_entry)) {
|
|
off_t first = volume->ToBlock(entry->Run());
|
|
off_t last = first - 1 + entry->Run().Length();
|
|
if (block >= first && block <= last) {
|
|
out.Clear();
|
|
const char* dump = out.DumpEntry(entry);
|
|
kprintf("%5ld. %s\n", iterator.Index(), dump);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
|
|
int
|
|
dump_block_allocator(int argc, char** argv)
|
|
{
|
|
int32 group = -1;
|
|
if (argc == 3) {
|
|
group = parse_expression(argv[2]);
|
|
argc--;
|
|
}
|
|
|
|
if (argc != 2 || !strcmp(argv[1], "--help")) {
|
|
kprintf("usage: %s <ptr-to-volume> [group]\n", argv[0]);
|
|
return 0;
|
|
}
|
|
|
|
Volume* volume = (Volume*)parse_expression(argv[1]);
|
|
BlockAllocator& allocator = volume->Allocator();
|
|
|
|
allocator.Dump(group);
|
|
return 0;
|
|
}
|
|
|
|
#endif // BFS_DEBUGGER_COMMANDS
|
|
|