diff --git a/src/add-ons/kernel/file_systems/ext2/BitmapBlock.cpp b/src/add-ons/kernel/file_systems/ext2/BitmapBlock.cpp new file mode 100644 index 0000000000..1f4e25a0b5 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/BitmapBlock.cpp @@ -0,0 +1,664 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ + + +#include "BitmapBlock.h" + + +//#define TRACE_EXT2 +#ifdef TRACE_EXT2 +# define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) +#else +# define TRACE(x...) ; +#endif + + +BitmapBlock::BitmapBlock(Volume* volume, uint32 numBits) + : + CachedBlock(volume), + fData(NULL), + fReadOnlyData(NULL), + fNumBits(numBits) +{ + TRACE("BitmapBlock::BitmapBlock(): num bits: %lu\n", fNumBits); +} + + +BitmapBlock::~BitmapBlock() +{ +} + + +/*virtual*/ bool +BitmapBlock::SetTo(uint32 block) +{ + fData = NULL; + fReadOnlyData = (uint32*)CachedBlock::SetTo(block); + + return fReadOnlyData != NULL; +} + + +/*virtual*/ bool +BitmapBlock::SetToWritable(Transaction& transaction, uint32 block, bool empty) +{ + fReadOnlyData = NULL; + fData = (uint32*)CachedBlock::SetToWritable(transaction, block, empty); + + return fData != NULL; +} + + +/*virtual*/ bool +BitmapBlock::CheckUnmarked(uint32 start, uint32 length) +{ + const uint32* data = fData == NULL ? fReadOnlyData : fData; + if (data == NULL) + return false; + + if (start + length > fNumBits) + return false; + + uint32 startIndex = start >> 5; + uint32 startBit = start & 0x1F; + uint32 remainingBits = (length - startBit) & 0x1F; + + uint32 iterations; + + if (length < 32) { + if (startBit + length < 32) { + uint32 bits = B_LENDIAN_TO_HOST_INT32(fData[startIndex]); + + uint32 mask = (1 << (startBit + length)) - 1; + mask &= ~((1 << startBit) - 1); + + return (bits & mask) == 0; + } else + iterations = 0; + } else + iterations = (length - 32 + startBit) >> 5; + + uint32 index = startIndex; + uint32 mask = 0; + + if (startBit != 0) { + mask = ~((1 << startBit) - 1); + uint32 bits = B_LENDIAN_TO_HOST_INT32(data[index]); + + if ((bits & mask) != 0) + return false; + + index += 1; + } + + for (; iterations > 0; --iterations) { + if (data[index++] != 0) + return false; + } + + if (remainingBits != 0) { + mask = (1 << (remainingBits + 1)) - 1; + + uint32 bits = B_LENDIAN_TO_HOST_INT32(data[index]); + if ((bits & mask) != 0) + return false; + } + + return true; +} + + +/*virtual*/ bool +BitmapBlock::CheckMarked(uint32 start, uint32 length) +{ + const uint32* data = fData == NULL ? fReadOnlyData : fData; + if (data == NULL) + return false; + + if (start + length > fNumBits) + return false; + + uint32 startIndex = start >> 5; + uint32 startBit = start & 0x1F; + uint32 remainingBits = (length - startBit) & 0x1F; + + uint32 iterations; + + if (length < 32) { + if (startBit + length < 32) { + uint32 bits = B_LENDIAN_TO_HOST_INT32(fData[startIndex]); + + uint32 mask = (1 << (startBit + length)) - 1; + mask &= ~((1 << startBit) - 1); + + return (bits & mask) != 0; + } else + iterations = 0; + } else + iterations = (length - 32 + startBit) >> 5; + + uint32 index = startIndex; + uint32 mask = 0; + + if (startBit != 0) { + mask = ~((1 << startBit) - 1); + uint32 bits = B_LENDIAN_TO_HOST_INT32(data[index]); + + if ((bits & mask) != mask) + return false; + + index += 1; + } + + mask = 0xFFFFFFFF; + for (; iterations > 0; --iterations) { + if (data[index++] != mask) + return false; + } + + if (remainingBits != 0) { + mask = (1 << (remainingBits + 1)) - 1; + uint32 bits = B_HOST_TO_LENDIAN_INT32(data[index]); + + if ((bits & mask) != mask) + return false; + } + + return true; +} + + +/*virtual*/ bool +BitmapBlock::Mark(uint32 start, uint32 length, bool force) +{ + if (fData == NULL || start + length > fNumBits) + return false; + + uint32 startIndex = start >> 5; + uint32 startBit = start & 0x1F; + uint32 remainingBits = (length - 32 + startBit) & 0x1F; + + uint32 iterations; + + if (length < 32) { + if (startBit + length < 32) { + uint32 bits = B_LENDIAN_TO_HOST_INT32(fData[startIndex]); + + uint32 mask = (1 << (startBit + length)) - 1; + mask &= ~((1 << startBit) - 1); + + if ((bits & mask) != 0) + return false; + + bits |= mask; + + fData[startIndex] = B_HOST_TO_LENDIAN_INT32(bits); + + return true; + } else + iterations = 0; + } else + iterations = (length - 32 + startBit) >> 5; + + uint32 index = startIndex; + uint32 mask = 0; + + TRACE("BitmapBlock::Mark(): start: %lu, length: %lu, startIndex: %lu, " + "startBit: %lu, iterations: %lu, remainingBits: %lu\n", start, length, + startIndex, startBit, iterations, remainingBits); + + if (startBit != 0) { + mask = ~((1 << startBit) - 1); + uint32 bits = B_LENDIAN_TO_HOST_INT32(fData[index]); + + TRACE("BitmapBlock::Mark(): mask: %lX, bits: %lX\n", mask, bits); + + if (!force && (bits & mask) != 0) + return false; + + bits |= mask; + fData[index] = B_HOST_TO_LENDIAN_INT32(bits); + + index += 1; + } + + mask = 0xFFFFFFFF; + for (; iterations > 0; --iterations) { + if (!force && fData[index] != 0) + return false; + fData[index++] |= mask; + } + + if (remainingBits != 0) { + mask = (1 << remainingBits) - 1; + uint32 bits = B_LENDIAN_TO_HOST_INT32(fData[index]); + TRACE("BitmapBlock::(): marking remaining %lu bits: %lX, mask: %lX\n", + remainingBits, bits, mask); + + if (!force && (bits & mask) != 0) + return false; + + bits |= mask; + fData[index] = B_HOST_TO_LENDIAN_INT32(bits); + } + + return true; +} + + +/*virtual*/ bool +BitmapBlock::Unmark(uint32 start, uint32 length, bool force) +{ + TRACE("BitmapBlock::Unmark(%lu, %lu, %c)\n", start, length, + force ? 't' : 'f'); + + if (fData == NULL || start + length > fNumBits) + return false; + + uint32 startIndex = start >> 5; + uint32 startBit = start & 0x1F; + uint32 remainingBits = (length - 32 + startBit) & 0x1F; + + TRACE("BitmapBlock::Unmark(): start index: %lu, start bit: %lu, remaining " + "bits: %lu)\n", startIndex, startBit, remainingBits); + uint32 iterations; + + if (length < 32) { + if (startBit + length < 32) { + uint32 bits = B_LENDIAN_TO_HOST_INT32(fData[startIndex]); + TRACE("BitmapBlock::Unmark(): bits: %lx\n", bits); + + uint32 mask = (1 << (startBit + length)) - 1; + mask &= ~((1 << startBit) - 1); + + TRACE("BitmapBlock::Unmark(): mask: %lx\n", mask); + + if ((bits & mask) != mask) + return false; + + bits &= ~mask; + + TRACE("BitmapBlock::Unmark(): updated bits: %lx\n", bits); + fData[startIndex] = B_HOST_TO_LENDIAN_INT32(bits); + + return true; + } else + iterations = 0; + } else + iterations = (length - 32 + startBit) >> 5; + + TRACE("BitmapBlock::Unmark(): iterations: %lu\n", iterations); + uint32 index = startIndex; + uint32 mask = 0; + + if (startBit != 0) { + mask = ~((1 << startBit) - 1); + uint32 bits = B_LENDIAN_TO_HOST_INT32(fData[index]); + + TRACE("BitmapBlock::Unmark(): mask: %lx, bits: %lx\n", mask, bits); + + if (!force && (bits & mask) != mask) + return false; + + bits &= ~mask; + fData[index] = B_HOST_TO_LENDIAN_INT32(bits); + + TRACE("BitmapBlock::Unmark(): updated bits: %lx\n", bits); + index += 1; + } + + mask = 0xFFFFFFFF; + for (; iterations > 0; --iterations) { + if (!force && fData[index] != mask) + return false; + fData[index++] = 0; + } + + TRACE("BitmapBlock::Unmark(): Finished iterations\n"); + + if (remainingBits != 0) { + mask = (1 << remainingBits) - 1; + uint32 bits = B_LENDIAN_TO_HOST_INT32(fData[index]); + + TRACE("BitmapBlock::Unmark(): mask: %lx, bits: %lx\n", mask, bits); + + if (!force && (bits & mask) != mask) + return false; + + bits &= ~mask; + fData[index] = B_HOST_TO_LENDIAN_INT32(bits); + + TRACE("BitmapBlock::Unmark(): updated bits: %lx\n", bits); + } + + return true; +} + + +void +BitmapBlock::FindNextMarked(uint32& pos) +{ + TRACE("BitmapBlock::FindNextMarked(): pos: %lu\n", pos); + + const uint32* data = fData == NULL ? fReadOnlyData : fData; + if (data == NULL) + return; + + if (pos >= fNumBits) { + pos = fNumBits; + return; + } + + uint32 index = pos >> 5; + uint32 bit = pos & 0x1F; + + uint32 mask = (1 << bit) - 1; + uint32 bits = B_LENDIAN_TO_HOST_INT32(data[index]); + + TRACE("BitmapBlock::FindNextMarked(): index: %lu, bit: %lu, mask: %lX, " + "bits: %lX\n", index, bit, mask, bits); + + bits = bits & ~mask; + + if (bits == 0) { + // Find a block of 32 bits that has a marked bit + uint32 maxIndex = fNumBits >> 5; + TRACE("BitmapBlock::FindNextMarked(): max index: %lu\n", maxIndex); + + do { + index++; + } while (index < maxIndex && data[index] == 0); + + if (index >= maxIndex) { + // Not found + TRACE("BitmapBlock::FindNextMarked(): reached end of block, num " + "bits: %lu\n", fNumBits); + pos = fNumBits; + return; + } + + bits = B_LENDIAN_TO_HOST_INT32(data[index]); + bit = 0; + } + + for (; bit < 32; ++bit) { + // Find the marked bit + if ((bits >> bit & 1) != 0) { + pos = index << 5 | bit; + TRACE("BitmapBlock::FindNextMarked(): found bit: %lu\n", pos); + return; + } + } + + panic("Couldn't find marked bit inside an int32 which is different than " + "zero!?\n"); +} + + +void +BitmapBlock::FindNextUnmarked(uint32& pos) +{ + TRACE("BitmapBlock::FindNextUnmarked(): pos: %lu\n", pos); + + const uint32* data = fData == NULL ? fReadOnlyData : fData; + if (data == NULL) + return; + + if (pos >= fNumBits) { + pos = fNumBits; + return; + } + + uint32 index = pos >> 5; + uint32 bit = pos & 0x1F; + + uint32 mask = (1 << bit) - 1; + uint32 bits = B_LENDIAN_TO_HOST_INT32(data[index]); + + TRACE("BitmapBlock::FindNextUnmarked(): index: %lu, bit: %lu, mask: %lX, " + "bits: %lX\n", index, bit, mask, bits); + + bits &= ~mask; + + if (bits == ~mask) { + // Find an block of 32 bits that has a unmarked bit + uint32 maxIndex = fNumBits >> 5; + TRACE("BitmapBlock::FindNextUnmarked(): max index: %lu\n", maxIndex); + + do { + index++; + } while (index < maxIndex && data[index] == 0xFFFFFFFF); + + if (index >= maxIndex) { + // Not found + TRACE("BitmapBlock::FindNextUnmarked(): reached end of block, num " + "bits: %lu\n", fNumBits); + pos = fNumBits; + return; + } + + bits = B_LENDIAN_TO_HOST_INT32(data[index]); + bit = 0; + } + + for (; bit < 32; ++bit) { + // Find the unmarked bit + if ((bits >> bit & 1) == 0) { + pos = index << 5 | bit; + TRACE("BitmapBlock::FindNextUnmarked(): found bit: %lu\n", pos); + return; + } + } + + panic("Couldn't find unmarked bit inside an int32 whith value zero!?\n"); +} + + +void +BitmapBlock::FindPreviousMarked(uint32& pos) +{ + TRACE("BitmapBlock::FindPreviousMarked(%lu)\n", pos); + const uint32* data = fData == NULL ? fReadOnlyData : fData; + if (data == NULL) + return; + + if (pos >= fNumBits) + pos = fNumBits; + + if (pos == 0) + return; + + uint32 index = pos >> 5; + int32 bit = pos & 0x1F; + + uint32 mask = (1 << (bit + 1)) - 1; + uint32 bits = B_LENDIAN_TO_HOST_INT32(data[index]); + bits = bits & mask; + + TRACE("BitmapBlock::FindPreviousMarked(): index: %lu, bit: %lu\n", index, + bit); + + if (bits == 0) { + // Find an block of 32 bits that has a marked bit + do { + index--; + } while (data[index] == 0 && index >= 0); + + bits = B_LENDIAN_TO_HOST_INT32(data[index]); + if (bits == 0) { + // Not found + pos = 0; + return; + } + + bit = 31; + } + + for (; bit >= 0; --bit) { + // Find the unmarked bit + if ((bits >> bit & 1) != 0) { + pos = index << 5 | bit; + return; + } + } + + panic("Couldn't find marked bit inside an int32 whith value different than " + "zero!?\n"); +} + + +void +BitmapBlock::FindLargestUnmarkedRange(uint32& start, uint32& length) +{ + const uint32* data = fData == NULL ? fReadOnlyData : fData; + if (data == NULL) + return; + + uint32 wordSpan = length >> 5; + uint32 lastIndex = fNumBits >> 5; + uint32 startIndex = 0; + uint32 index = 0; + uint32 bits = B_LENDIAN_TO_HOST_INT32(data[0]); + + TRACE("BitmapBlock::FindLargestUnmarkedRange(): word span: %lu, last " + "index: %lu, start index: %lu, index: %lu, bits: %lX, start: %lu, " + "length: %lu\n", wordSpan, lastIndex, startIndex, index, bits, start, + length); + + if (wordSpan == 0) { + uint32 startPos = 0; + uint32 endPos = 0; + + while (endPos < fNumBits) { + FindNextUnmarked(startPos); + endPos = startPos; + + if (startPos != fNumBits) { + FindNextMarked(endPos); + + uint32 newLength = endPos - startPos; + + if (newLength > length) { + start = startPos; + length = newLength; + TRACE("BitmapBlock::FindLargestUnmarkedRange(): Found " + "larger length %lu starting at %lu\n", length, start); + } + + startPos = endPos; + + if (newLength >= 32) + break; + } + } + + if (endPos >= fNumBits) + return; + + wordSpan = length >> 5; + startIndex = startPos >> 5; + index = (endPos >> 5) + 1; + bits = B_LENDIAN_TO_HOST_INT32(data[index]); + } + + for (; index < lastIndex; ++index) { + bits = B_LENDIAN_TO_HOST_INT32(data[index]); + + if (bits != 0) { + // Contains marked bits + if (index - startIndex >= wordSpan) { + uint32 newLength = (index - startIndex - 1) << 5; + uint32 newStart = (startIndex + 1) << 5; + + uint32 startBits = + B_LENDIAN_TO_HOST_INT32(data[startIndex]); + + for (int32 bit = 31; bit >= 0; --bit) { + if ((startBits >> bit & 1) != 0) + break; + + ++newLength; + --newStart; + } + + for (int32 bit = 0; bit < 32; ++bit) { + if ((bits >> bit & 1) != 0) + break; + + ++newLength; + } + + if (newLength > length) { + start = newStart; + length = newLength; + wordSpan = length >> 5; + + TRACE("BitmapBlock::FindLargestUnmarkedRange(): Found " + "larger length %lu starting at %lu; word span: " + "%lu\n", length, start, wordSpan); + } + } + + startIndex = index; + } + } + + --index; + + if (index - startIndex >= wordSpan) { + uint32 newLength = (index - startIndex) << 5; + uint32 newStart = (startIndex + 1) << 5; + + TRACE("BitmapBlock::FindLargestUnmarkedRange(): Possibly found a " + "larger range. index: %lu, start index: %lu, word span: %lu, " + "new length: %lu, new start: %lu\n", index, startIndex, wordSpan, + newLength, newStart); + + if (newStart != 0) { + uint32 startBits = B_LENDIAN_TO_HOST_INT32(data[startIndex]); + + TRACE("BitmapBlock::FindLargestUnmarkedRange(): start bits: %lu\n", + startBits); + + for (int32 bit = 31; bit >= 0; --bit) { + if ((startBits >> bit & 1) != 0) + break; + + ++newLength; + --newStart; + } + + TRACE("BitmapBlock::FindLargestUnmarkedRange(): updated new start " + "to %lu and new length to %lu\n", newStart, newLength); + } + + for (int32 bit = 0; bit < 32; ++bit) { + if ((bits >> bit & 1) == 0) + break; + + ++newLength; + } + + TRACE("BitmapBlock::FindLargestUnmarkedRange(): updated new length to " + "%lu\n", newLength); + + if (newLength > length) { + start = newStart; + length = newLength; + TRACE("BitmapBlock::FindLargestUnmarkedRange(): Found " + "largest length %lu starting at %lu\n", length, start); + } + } +} + + +uint32 +BitmapBlock::NumBits() const +{ + return fNumBits; +} diff --git a/src/add-ons/kernel/file_systems/ext2/BitmapBlock.h b/src/add-ons/kernel/file_systems/ext2/BitmapBlock.h new file mode 100644 index 0000000000..8575c8f306 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/BitmapBlock.h @@ -0,0 +1,48 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ +#ifndef BITMAPBLOCK_H +#define BITMAPBLOCK_H + +#include "CachedBlock.h" + + +class BitmapBlock : public CachedBlock { +public: + BitmapBlock(Volume* volume, uint32 numBits); + ~BitmapBlock(); + + bool SetTo(uint32 block); + bool SetToWritable(Transaction& transaction, + uint32 block, bool empty = false); + + bool CheckMarked(uint32 start, uint32 length); + bool CheckUnmarked(uint32 start, uint32 length); + + bool Mark(uint32 start, uint32 length, + bool force = false); + bool Unmark(uint32 start, uint32 length, + bool force = false); + + void FindNextMarked(uint32& pos); + void FindNextUnmarked(uint32& pos); + + void FindPreviousMarked(uint32& pos); + + void FindLargestUnmarkedRange(uint32& start, + uint32& length); + + uint32 NumBits() const; + +protected: + uint32* fData; + const uint32* fReadOnlyData; + + uint32 fNumBits; +}; + +#endif // BITMAPBLOCK_H diff --git a/src/add-ons/kernel/file_systems/ext2/BlockAllocator.cpp b/src/add-ons/kernel/file_systems/ext2/BlockAllocator.cpp new file mode 100644 index 0000000000..a9d33cb7d1 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/BlockAllocator.cpp @@ -0,0 +1,714 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ + + +#include "BlockAllocator.h" + +#include + +#include "BitmapBlock.h" +#include "Inode.h" + + +//#define TRACE_EXT2 +#ifdef TRACE_EXT2 +# define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) +#else +# define TRACE(x...) ; +#endif + + +class AllocationBlockGroup : public TransactionListener { +public: + AllocationBlockGroup(); + ~AllocationBlockGroup(); + + status_t Initialize(Volume* volume, uint32 blockGroup, + uint32 numBits); + + status_t ScanFreeRanges(); + bool IsFull() const; + + status_t Allocate(Transaction& transaction, uint32 start, + uint32 length); + status_t Free(Transaction& transaction, uint32 start, + uint32 length); + status_t FreeAll(Transaction& transaction); + status_t Check(uint32 start, uint32 length); + + uint32 NumBits() const; + uint32 FreeBits() const; + uint32 Start() const; + + uint32 LargestStart() const; + uint32 LargestLength() const; + + // TransactionListener implementation + void TransactionDone(bool success); + void RemovedFromTransaction(); + +private: + void _AddFreeRange(uint32 start, uint32 length); + void _LockInTransaction(Transaction& transaction); + + + Volume* fVolume; + uint32 fBlockGroup; + ext2_block_group* fGroupDescriptor; + + mutex fLock; + mutex fTransactionLock; + int32 fCurrentTransaction; + + uint32 fStart; + uint32 fNumBits; + uint32 fBitmapBlock; + + uint32 fFreeBits; + uint32 fFirstFree; + uint32 fLargestStart; + uint32 fLargestLength; + + uint32 fPreviousFreeBits; + uint32 fPreviousFirstFree; + uint32 fPreviousLargestStart; + uint32 fPreviousLargestLength; +}; + + +AllocationBlockGroup::AllocationBlockGroup() + : + fVolume(NULL), + fBlockGroup(0), + fGroupDescriptor(NULL), + fStart(0), + fNumBits(0), + fBitmapBlock(0), + fFreeBits(0), + fFirstFree(0), + fLargestStart(0), + fLargestLength(0), + fPreviousFreeBits(0), + fPreviousFirstFree(0), + fPreviousLargestStart(0), + fPreviousLargestLength(0) +{ + mutex_init(&fLock, "ext2 allocation block group"); + mutex_init(&fTransactionLock, "ext2 allocation block group transaction"); +} + + +AllocationBlockGroup::~AllocationBlockGroup() +{ + mutex_destroy(&fLock); + mutex_destroy(&fTransactionLock); +} + + +status_t +AllocationBlockGroup::Initialize(Volume* volume, uint32 blockGroup, + uint32 numBits) +{ + fVolume = volume; + fBlockGroup = blockGroup; + fNumBits = numBits; + + status_t status = fVolume->GetBlockGroup(blockGroup, &fGroupDescriptor); + if (status != B_OK) + return status; + + fBitmapBlock = fGroupDescriptor->BlockBitmap(); + + status = ScanFreeRanges(); + + if (fGroupDescriptor->FreeBlocks() != fFreeBits) { + TRACE("AllocationBlockGroup::Initialize(): Mismatch between counted " + "free blocks (%lu) and what is set on the group descriptor " + "(%lu)\n", fFreeBits, (uint32)fGroupDescriptor->FreeBlocks()); + return B_BAD_DATA; + } + + fPreviousFreeBits = fFreeBits; + fPreviousFirstFree = fFirstFree; + fPreviousLargestStart = fLargestStart; + fPreviousLargestLength = fLargestLength; + + return status; +} + + +status_t +AllocationBlockGroup::ScanFreeRanges() +{ + TRACE("AllocationBlockGroup::ScanFreeRanges()\n"); + BitmapBlock block(fVolume, fNumBits); + + if (!block.SetTo(fBitmapBlock)) + return B_ERROR; + + uint32 start = 0; + uint32 end = 0; + + while (end < fNumBits) { + block.FindNextUnmarked(start); + end = start; + + if (start != block.NumBits()) { + block.FindNextMarked(end); + _AddFreeRange(start, end - start); + start = end; + } + } + + return B_OK; +} + + +bool +AllocationBlockGroup::IsFull() const +{ + return fFreeBits == 0; +} + + +status_t +AllocationBlockGroup::Allocate(Transaction& transaction, uint32 start, + uint32 length) +{ + TRACE("AllocationBlockGroup::Allocate()\n"); + uint32 end = start + length; + if (end > fNumBits) + return B_BAD_DATA; + + _LockInTransaction(transaction); + + BitmapBlock block(fVolume, fNumBits); + + if (!block.SetToWritable(transaction, fBitmapBlock)) + return B_ERROR; + + if (!block.Mark(start, length)) { + TRACE("Failed to allocate blocks from %lu to %lu. Some were " + "already allocated.\n", start, start + length); + return B_ERROR; + } + + fFreeBits -= length; + fGroupDescriptor->SetFreeBlocks((uint16)fFreeBits); + fVolume->WriteBlockGroup(transaction, fBlockGroup); + + if (start == fLargestStart) { + if (fFirstFree == fLargestStart) + fFirstFree += length; + + fLargestStart += length; + fLargestLength -= length; + } else if (start + length == fLargestStart + fLargestLength) { + fLargestLength -= length; + } else if (start < fLargestStart + fLargestLength + && start > fLargestStart) { + uint32 firstLength = start - fLargestStart; + uint32 secondLength = fLargestStart + fLargestLength + - (start + length); + + if (firstLength >= secondLength) { + fLargestLength = firstLength; + } else { + fLargestLength = secondLength; + fLargestStart = start + length; + } + } else { + // No need to revalidate the largest free range + return B_OK; + } + + if (fLargestLength < fNumBits / 2) + block.FindLargestUnmarkedRange(fLargestStart, fLargestLength); + + return B_OK; +} + + +status_t +AllocationBlockGroup::Free(Transaction& transaction, uint32 start, + uint32 length) +{ + TRACE("AllocationBlockGroup::Free(): start: %lu, length %lu\n", start, + length); + + if (length == 0) + return B_OK; + + uint32 end = start + length; + + if (end > fNumBits) + return B_BAD_DATA; + + _LockInTransaction(transaction); + + BitmapBlock block(fVolume, fNumBits); + + if (!block.SetToWritable(transaction, fBitmapBlock)) + return B_ERROR; + + if (!block.Unmark(start, length)) { + TRACE("Failed to free blocks from %lu to %lu. Some were " + "already freed.\n", start, start + length); + return B_ERROR; + } + + TRACE("AllocationGroup::Free(): Unmarked bits in bitmap\n"); + + if (fFirstFree > start) + fFirstFree = start; + + if (start + length == fLargestStart) { + fLargestStart = start; + fLargestLength += length; + } else if (start == fLargestStart + fLargestLength) { + fLargestLength += length; + } else if (fLargestLength <= fNumBits / 2) { + // May have merged with some free blocks, becoming the largest + uint32 newEnd = start + length; + block.FindNextMarked(newEnd); + + uint32 newStart = start; + block.FindPreviousMarked(newStart); + + if (newEnd - newStart > fLargestLength) { + fLargestLength = newEnd - newStart; + fLargestStart = newStart; + } + } + + fFreeBits += length; + fGroupDescriptor->SetFreeBlocks((uint16)fFreeBits); + fVolume->WriteBlockGroup(transaction, fBlockGroup); + + return B_OK; +} + + +status_t +AllocationBlockGroup::FreeAll(Transaction& transaction) +{ + return Free(transaction, 0, fNumBits); +} + + +uint32 +AllocationBlockGroup::NumBits() const +{ + return fNumBits; +} + + +uint32 +AllocationBlockGroup::FreeBits() const +{ + return fFreeBits; +} + + +uint32 +AllocationBlockGroup::Start() const +{ + return fStart; +} + + +uint32 +AllocationBlockGroup::LargestStart() const +{ + return fLargestStart; +} + + +uint32 +AllocationBlockGroup::LargestLength() const +{ + return fLargestLength; +} + + +void +AllocationBlockGroup::_AddFreeRange(uint32 start, uint32 length) +{ + if (IsFull()) { + fFirstFree = start; + fLargestStart = start; + fLargestLength = length; + } else if (length > fLargestLength) { + fLargestStart = start; + fLargestLength = length; + } + + fFreeBits += length; +} + + +void +AllocationBlockGroup::_LockInTransaction(Transaction& transaction) +{ + mutex_lock(&fLock); + + if (transaction.ID() != fCurrentTransaction) { + mutex_unlock(&fLock); + + mutex_lock(&fTransactionLock); + mutex_lock(&fLock); + + fCurrentTransaction = transaction.ID(); + transaction.AddListener(this); + } + + mutex_unlock(&fLock); +} + + +void +AllocationBlockGroup::TransactionDone(bool success) +{ + if (success) { + TRACE("AllocationBlockGroup::TransactionDone(): The transaction " + "succeeded, discarding previous state\n"); + fPreviousFreeBits = fFreeBits; + fPreviousFirstFree = fFirstFree; + fPreviousLargestStart = fLargestStart; + fPreviousLargestLength = fLargestLength; + } else { + TRACE("AllocationBlockGroup::TransactionDone(): The transaction " + "failed, restoring to previous state\n"); + fFreeBits = fPreviousFreeBits; + fFirstFree = fPreviousFirstFree; + fLargestStart = fPreviousLargestStart; + fLargestLength = fPreviousLargestLength; + } +} + + +void +AllocationBlockGroup::RemovedFromTransaction() +{ + mutex_unlock(&fTransactionLock); + fCurrentTransaction = -1; +} + + +BlockAllocator::BlockAllocator(Volume* volume) + : + fVolume(volume), + fGroups(NULL), + fBlocksPerGroup(0), + fNumBlocks(0), + fNumGroups(0) +{ + mutex_init(&fLock, "ext2 block allocator"); +} + + +BlockAllocator::~BlockAllocator() +{ + mutex_destroy(&fLock); + + if (fGroups != NULL) + delete [] fGroups; +} + + +status_t +BlockAllocator::Initialize() +{ + fBlocksPerGroup = fVolume->BlocksPerGroup(); + fNumGroups = fVolume->NumGroups(); + fFirstBlock = fVolume->FirstDataBlock(); + fNumBlocks = fVolume->NumBlocks(); + + TRACE("BlockAllocator::Initialize(): blocks per group: %lu, block groups: " + "%lu, first block: %lu, num blocks: %lu\n", fBlocksPerGroup, + fNumGroups, fFirstBlock, fNumBlocks); + + fGroups = new(std::nothrow) AllocationBlockGroup[fNumGroups]; + if (fGroups == NULL) + return B_NO_MEMORY; + + TRACE("BlockAllocator::Initialize(): allocated allocation block groups\n"); + + mutex_lock(&fLock); + // Released by _Initialize + + thread_id id = -1; // spawn_kernel_thread( + // (thread_func)BlockAllocator::_Initialize, "ext2 block allocator", + // B_LOW_PRIORITY, this); + if (id < B_OK) + return _Initialize(this); + + // mutex_transfer_lock(&fLock, id); + + // return resume_thread(id); + panic("Failed to fall back to synchronous block allocator" + "initialization.\n"); + return B_ERROR; +} + + +status_t +BlockAllocator::AllocateBlocks(Transaction& transaction, uint32 minimum, + uint32 maximum, uint32& blockGroup, uint32& start, uint32& length) +{ + TRACE("BlockAllocator::AllocateBlocks()\n"); + MutexLocker lock(fLock); + TRACE("BlockAllocator::AllocateBlocks(): Aquired lock\n"); + + TRACE("BlockAllocator::AllocateBlocks(): transaction: %ld, min: %lu, " + "max: %lu, block group: %lu, start: %lu, num groups: %lu\n", + transaction.ID(), minimum, maximum, blockGroup, start, fNumGroups); + + uint32 bestStart = 0; + uint32 bestLength = 0; + uint32 bestGroup = 0; + + uint32 groupNum = blockGroup; + + AllocationBlockGroup* last = &fGroups[fNumGroups]; + AllocationBlockGroup* group = &fGroups[blockGroup]; + + for (int32 iterations = 0; iterations < 2; iterations++) { + for (; group < last; ++group, ++groupNum) { + TRACE("BlockAllocator::AllocateBlocks(): Group %lu has largest " + "length of %lu\n", groupNum, group->LargestLength()); + + if (group->LargestLength() > bestLength) { + if (start <= group->LargestStart()) { + bestStart = group->LargestStart(); + bestLength = group->LargestLength(); + bestGroup = groupNum; + + TRACE("BlockAllocator::AllocateBlocks(): Found a better " + "range: block group: %lu, %lu-%lu\n", groupNum, + bestStart, bestStart + bestLength); + + if (bestLength >= maximum) + break; + } + } + + start = 0; + } + + if (bestLength >= maximum) + break; + + groupNum = 0; + + group = &fGroups[0]; + last = &fGroups[blockGroup + 1]; + } + + if (bestLength < minimum) { + TRACE("BlockAllocator::AllocateBlocks(): best range (length %lu) " + "doesn't have minimum length of %lu\n", bestLength, minimum); + return B_DEVICE_FULL; + } + + if (bestLength > maximum) + bestLength = maximum; + + TRACE("BlockAllocator::AllocateBlocks(): Selected range: block group %lu, " + "%lu-%lu\n", bestGroup, bestStart, bestStart + bestLength); + + status_t status = fGroups[bestGroup].Allocate(transaction, bestStart, + bestLength); + if (status != B_OK) { + TRACE("BlockAllocator::AllocateBlocks(): Failed to allocate %lu blocks " + "inside block group %lu.\n", bestLength, bestGroup); + return status; + } + + start = bestStart; + length = bestLength; + blockGroup = bestGroup; + + return B_OK; +} + + +status_t +BlockAllocator::Allocate(Transaction& transaction, Inode* inode, + off_t numBlocks, uint32 minimum, uint32& start, uint32& allocated) +{ + if (numBlocks <= 0) + return B_ERROR; + + uint32 group = inode->ID() / fVolume->InodesPerGroup(); + uint32 preferred = 0; + + if (inode->Size() > 0) { + // Try to allocate near it's last blocks + ext2_data_stream* dataStream = &inode->Node().stream; + uint32 numBlocks = inode->Size() / fVolume->BlockSize() + 1; + uint32 lastBlock = 0; + + // DANGER! What happens with sparse files? + if (numBlocks < EXT2_DIRECT_BLOCKS) { + // Only direct blocks + lastBlock = dataStream->direct[numBlocks]; + } else { + numBlocks -= EXT2_DIRECT_BLOCKS - 1; + uint32 numIndexes = fVolume->BlockSize() / 4; + // block size / sizeof(int32) + uint32 numIndexes2 = numIndexes * numIndexes; + uint32 numIndexes3 = numIndexes2 * numIndexes; + uint32 indexesInIndirect = numIndexes; + uint32 indexesInDoubleIndirect = indexesInIndirect + + numIndexes2; + // uint32 indexesInTripleIndirect = indexesInDoubleIndirect + // + numIndexes3; + + uint32 doubleIndirectBlock = EXT2_DIRECT_BLOCKS + 1; + uint32 indirectBlock = EXT2_DIRECT_BLOCKS; + + CachedBlock cached(fVolume); + uint32* indirectData; + + if (numBlocks > indexesInDoubleIndirect) { + // Triple indirect blocks + indirectData = (uint32*)cached.SetTo(EXT2_DIRECT_BLOCKS + 2); + if (indirectData == NULL) + return B_IO_ERROR; + + uint32 index = (numBlocks - indexesInDoubleIndirect) + / numIndexes3; + doubleIndirectBlock = indirectData[index]; + } + + if (numBlocks > indexesInIndirect) { + // Double indirect blocks + indirectData = (uint32*)cached.SetTo(doubleIndirectBlock); + if (indirectData == NULL) + return B_IO_ERROR; + + uint32 index = (numBlocks - indexesInIndirect) / numIndexes2; + indirectBlock = indirectData[index]; + } + + indirectData = (uint32*)cached.SetTo(indirectBlock); + if (indirectData == NULL) + return B_IO_ERROR; + + uint32 index = numBlocks / numIndexes; + lastBlock = indirectData[index]; + } + + group = (lastBlock - fFirstBlock) / fBlocksPerGroup; + preferred = (lastBlock - fFirstBlock) % fBlocksPerGroup + 1; + } + + // TODO: Apply some more policies + + return AllocateBlocks(transaction, minimum, minimum + 8, group, start, + allocated); +} + + +status_t +BlockAllocator::Free(Transaction& transaction, uint32 start, uint32 length) +{ + TRACE("BlockAllocator::Free(%lu, %lu)\n", start, length); + MutexLocker lock(fLock); + + if (start <= fFirstBlock) { + panic("Trying to free superblock!\n"); + return B_BAD_DATA; + } + + if (length == 0) + return B_OK; + + TRACE("BlockAllocator::Free(): first block: %lu, blocks per group: %lu\n", + fFirstBlock, fBlocksPerGroup); + + start -= fFirstBlock; + uint32 end = start + length - 1; + + uint32 group = start / fBlocksPerGroup; + uint32 lastGroup = end / fBlocksPerGroup; + start = start % fBlocksPerGroup; + + if (group == lastGroup) + return fGroups[group].Free(transaction, start, length); + + TRACE("BlockAllocator::Free(): Freeing from group %lu: %lu, %lu\n", group, + start, fGroups[group].NumBits() - start); + + status_t status = fGroups[group].Free(transaction, start, + fGroups[group].NumBits() - start); + if (status != B_OK) + return status; + + for (++group; group < lastGroup; ++group) { + TRACE("BlockAllocator::Free(): Freeing all from group %lu\n", group); + status = fGroups[group].FreeAll(transaction); + if (status != B_OK) + return status; + } + + TRACE("BlockAllocator::Free(): Freeing from group %lu: 0-%lu \n", group, + end % fBlocksPerGroup); + return fGroups[group].Free(transaction, 0, (end + 1) % fBlocksPerGroup); +} + + +/*static*/ status_t +BlockAllocator::_Initialize(BlockAllocator* allocator) +{ + TRACE("BlockAllocator::_Initialize()\n"); + // fLock is already heald + Volume* volume = allocator->fVolume; + + AllocationBlockGroup* groups = allocator->fGroups; + uint32 numGroups = allocator->fNumGroups - 1; + + uint32 freeBlocks = 0; + TRACE("BlockAllocator::_Initialize(): free blocks: %lu\n", freeBlocks); + + for (uint32 i = 0; i < numGroups; ++i) { + status_t status = groups[i].Initialize(volume, i, + allocator->fBlocksPerGroup); + if (status != B_OK) { + mutex_unlock(&allocator->fLock); + return status; + } + + freeBlocks += groups[i].FreeBits(); + TRACE("BlockAllocator::_Initialize(): free blocks: %lu\n", freeBlocks); + } + + // Last block group may have less blocks + status_t status = groups[numGroups].Initialize(volume, numGroups, + allocator->fNumBlocks - allocator->fBlocksPerGroup * numGroups + - allocator->fFirstBlock); + if (status != B_OK) { + mutex_unlock(&allocator->fLock); + return status; + } + + freeBlocks += groups[numGroups].FreeBits(); + + TRACE("BlockAllocator::_Initialize(): free blocks: %lu\n", freeBlocks); + + mutex_unlock(&allocator->fLock); + + if (freeBlocks != volume->NumFreeBlocks()) { + TRACE("Counted free blocks (%lu) doesn't match value in the " + "superblock (%lu).\n", freeBlocks, (uint32)volume->NumFreeBlocks()); + return B_BAD_DATA; + } + + return B_OK; +} diff --git a/src/add-ons/kernel/file_systems/ext2/BlockAllocator.h b/src/add-ons/kernel/file_systems/ext2/BlockAllocator.h new file mode 100644 index 0000000000..d827623f17 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/BlockAllocator.h @@ -0,0 +1,53 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ +#ifndef BLOCKALLOCATOR_H +#define BLOCKALLOCATOR_H + +#include + +#include "Transaction.h" + + +class AllocationBlockGroup; +class Inode; +class Volume; + + +class BlockAllocator { +public: + BlockAllocator(Volume* volume); + ~BlockAllocator(); + + status_t Initialize(); + + status_t AllocateBlocks(Transaction& transaction, + uint32 minimum, uint32 maximum, uint32& blockGroup, + uint32& start, uint32& length); + status_t Allocate(Transaction& transaction, Inode* inode, + off_t numBlocks, uint32 minimum, uint32& start, + uint32& length); + status_t Free(Transaction& transaction, uint32 start, + uint32 length); + + uint32 FreeBlocks(); + +protected: + static status_t _Initialize(BlockAllocator* allocator); + + + Volume* fVolume; + mutex fLock; + + AllocationBlockGroup* fGroups; + uint32 fBlocksPerGroup; + uint32 fNumBlocks; + uint32 fNumGroups; + uint32 fFirstBlock; +}; + +#endif // BLOCKALLOCATOR_H diff --git a/src/add-ons/kernel/file_systems/ext2/CachedBlock.h b/src/add-ons/kernel/file_systems/ext2/CachedBlock.h index 161bef6f8e..95430f4e9c 100644 --- a/src/add-ons/kernel/file_systems/ext2/CachedBlock.h +++ b/src/add-ons/kernel/file_systems/ext2/CachedBlock.h @@ -9,32 +9,40 @@ #include +#include "Transaction.h" #include "Volume.h" class CachedBlock { public: - CachedBlock(Volume* volume); - CachedBlock(Volume* volume, uint32 block); - ~CachedBlock(); + CachedBlock(Volume* volume); + CachedBlock(Volume* volume, uint32 block); + ~CachedBlock(); - void Keep(); - void Unset(); + void Keep(); + void Unset(); - const uint8* SetTo(uint32 block); + const uint8* SetTo(uint32 block); + uint8* SetToWritable(Transaction& transaction, + uint32 block, bool empty = false); + uint8* SetToWritableWithoutTransaction(uint32 block, + bool empty = false); - const uint8* Block() const { return fBlock; } - off_t BlockNumber() const { return fBlockNumber; } + const uint8* Block() const { return fBlock; } + off_t BlockNumber() const { return fBlockNumber; } private: - CachedBlock(const CachedBlock &); - CachedBlock &operator=(const CachedBlock &); - // no implementation + CachedBlock(const CachedBlock &); + CachedBlock &operator=(const CachedBlock &); + // no implementation + + uint8* _SetToWritableEtc(int32 transaction, uint32 block, + bool empty); protected: - Volume* fVolume; - uint32 fBlockNumber; - uint8* fBlock; + Volume* fVolume; + uint32 fBlockNumber; + uint8* fBlock; }; @@ -94,4 +102,35 @@ CachedBlock::SetTo(uint32 block) return fBlock = (uint8 *)block_cache_get(fVolume->BlockCache(), block); } + +inline uint8* +CachedBlock::SetToWritable(Transaction& transaction, uint32 block, bool empty) +{ + return _SetToWritableEtc(transaction.ID(), block, empty); +} + + +inline uint8* +CachedBlock::SetToWritableWithoutTransaction(uint32 block, bool empty) +{ + return _SetToWritableEtc((int32)-1, block, empty); +} + +inline uint8* +CachedBlock::_SetToWritableEtc(int32 transaction, uint32 block, bool empty) +{ + Unset(); + fBlockNumber = block; + + if (empty) { + fBlock = (uint8*)block_cache_get_empty(fVolume->BlockCache(), + block, transaction); + } else { + fBlock = (uint8*)block_cache_get_writable(fVolume->BlockCache(), + block, transaction); + } + + return fBlock; +} + #endif // CACHED_BLOCK_H diff --git a/src/add-ons/kernel/file_systems/ext2/DataStream.cpp b/src/add-ons/kernel/file_systems/ext2/DataStream.cpp new file mode 100644 index 0000000000..c2ede840bd --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/DataStream.cpp @@ -0,0 +1,650 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ + + +#include "DataStream.h" + +#include "CachedBlock.h" +#include "Volume.h" + + +//#define TRACE_EXT2 +#ifdef TRACE_EXT2 +# define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) +#else +# define TRACE(x...) ; +#endif + + +DataStream::DataStream(Volume* volume, ext2_data_stream* stream, + off_t size) + : + kBlockSize(volume->BlockSize()), + kIndirectsPerBlock(kBlockSize / 4), + kIndirectsPerBlock2(kIndirectsPerBlock * kIndirectsPerBlock), + kIndirectsPerBlock3(kIndirectsPerBlock2 * kIndirectsPerBlock), + kMaxDirect(EXT2_DIRECT_BLOCKS), + kMaxIndirect(kMaxDirect + kIndirectsPerBlock), + kMaxDoubleIndirect(kMaxIndirect + kIndirectsPerBlock2), + fVolume(volume), + fStream(stream), + fFirstBlock(volume->FirstDataBlock()), + fAllocated(0), + fAllocatedPos(fFirstBlock), + fWaiting(0), + fFreeStart(0), + fFreeCount(0), + fRemovedBlocks(0) +{ + fNumBlocks = size == 0 ? 0 : (size - 1) / kBlockSize + 1; +} + + +DataStream::~DataStream() +{ +} + + +status_t +DataStream::Enlarge(Transaction& transaction, uint32& numBlocks) +{ + TRACE("DataStream::Enlarge(): current size: %lu, target size: %lu\n", + fNumBlocks, numBlocks); + + uint32 targetBlocks = numBlocks; + fWaiting = _BlocksNeeded(numBlocks); + numBlocks = fWaiting; + + status_t status; + + if (fNumBlocks <= kMaxDirect) { + status = _AddForDirectBlocks(transaction, targetBlocks); + + if (status != B_OK) + return status; + + TRACE("DataStream::Enlarge(): current size: %lu, target size: %lu\n", + fNumBlocks, targetBlocks); + + if (fNumBlocks == targetBlocks) + return B_OK; + } + + if (fNumBlocks <= kMaxIndirect) { + status = _AddForIndirectBlock(transaction, targetBlocks); + + if (status != B_OK) + return status; + + TRACE("DataStream::Enlarge(): current size: %lu, target size: %lu\n", + fNumBlocks, targetBlocks); + + if (fNumBlocks == targetBlocks) + return B_OK; + } + + if (fNumBlocks <= kMaxDoubleIndirect) { + status = _AddForDoubleIndirectBlock(transaction, targetBlocks); + + if (status != B_OK) + return status; + + TRACE("DataStream::Enlarge(): current size: %lu, target size: %lu\n", + fNumBlocks, targetBlocks); + + if (fNumBlocks == targetBlocks) + return B_OK; + } + + TRACE("DataStream::Enlarge(): allocated: %lu, waiting: %lu\n", fAllocated, + fWaiting); + + return _AddForTripleIndirectBlock(transaction, targetBlocks); +} + + +status_t +DataStream::Shrink(Transaction& transaction, uint32& numBlocks) +{ + TRACE("DataStream::Shrink(): current size: %lu, target size: %lu\n", + fNumBlocks, numBlocks); + + fFreeStart = 0; + fFreeCount = 0; + fRemovedBlocks = 0; + + uint32 oldNumBlocks = fNumBlocks; + uint32 blocksToRemove = fNumBlocks - numBlocks; + + status_t status; + + if (numBlocks < kMaxDirect) { + status = _RemoveFromDirectBlocks(transaction, numBlocks); + + if (status != B_OK) + return status; + + if (fRemovedBlocks == blocksToRemove) { + fNumBlocks -= fRemovedBlocks; + numBlocks = _BlocksNeeded(oldNumBlocks); + + return _PerformFree(transaction); + } + } + + if (numBlocks < kMaxIndirect) { + status = _RemoveFromIndirectBlock(transaction, numBlocks); + + if (status != B_OK) + return status; + + if (fRemovedBlocks == blocksToRemove) { + fNumBlocks -= fRemovedBlocks; + numBlocks = _BlocksNeeded(oldNumBlocks); + + return _PerformFree(transaction); + } + } + + if (numBlocks < kMaxDoubleIndirect) { + status = _RemoveFromDoubleIndirectBlock(transaction, numBlocks); + + if (status != B_OK) + return status; + + if (fRemovedBlocks == blocksToRemove) { + fNumBlocks -= fRemovedBlocks; + numBlocks = _BlocksNeeded(oldNumBlocks); + + return _PerformFree(transaction); + } + } + + status = _RemoveFromTripleIndirectBlock(transaction, numBlocks); + + if (status != B_OK) + return status; + + fNumBlocks -= fRemovedBlocks; + numBlocks = _BlocksNeeded(oldNumBlocks); + + return _PerformFree(transaction); +} + + +uint32 +DataStream::_BlocksNeeded(uint32 numBlocks) +{ + TRACE("DataStream::BlocksNeeded(): num blocks %lu\n", numBlocks); + uint32 blocksNeeded = 0; + + if (numBlocks > fNumBlocks) { + blocksNeeded += numBlocks - fNumBlocks; + + if (numBlocks > kMaxDirect) { + if (fNumBlocks <= kMaxDirect) + blocksNeeded += 1; + + if (numBlocks > kMaxIndirect) { + if (fNumBlocks <= kMaxIndirect) { + blocksNeeded += 2 + (numBlocks - kMaxIndirect - 1) + / kIndirectsPerBlock; + } else { + blocksNeeded += (numBlocks - fNumBlocks) + / kIndirectsPerBlock; + + uint32 halfIndirectsPerBlock = kIndirectsPerBlock / 2; + uint32 remCurrent = (fNumBlocks - kMaxIndirect - 1) + % kIndirectsPerBlock; + uint32 remTarget = (numBlocks - kMaxIndirect - 1) + % kIndirectsPerBlock; + + if ((remCurrent >= halfIndirectsPerBlock + && remTarget < halfIndirectsPerBlock) + || (remCurrent > halfIndirectsPerBlock + && remTarget <= halfIndirectsPerBlock)) + blocksNeeded++; + } + + if (numBlocks > kMaxDoubleIndirect) { + if (fNumBlocks <= kMaxDoubleIndirect) { + blocksNeeded += 2 + (numBlocks - kMaxDoubleIndirect - 1) + / kIndirectsPerBlock2; + } else { + blocksNeeded += (numBlocks - fNumBlocks) + / kIndirectsPerBlock2; + + uint32 halfIndirectsPerBlock2 = kIndirectsPerBlock2 / 2; + uint32 remCurrent = (fNumBlocks - kMaxDoubleIndirect + - 1) + % kIndirectsPerBlock2; + uint32 remTarget = (numBlocks - kMaxDoubleIndirect - 1) + % kIndirectsPerBlock2; + + if ((remCurrent >= halfIndirectsPerBlock2 + && remTarget < halfIndirectsPerBlock2) + || (remCurrent > halfIndirectsPerBlock2 + && remTarget <= halfIndirectsPerBlock2)) + blocksNeeded++; + } + } + } + } + } + + TRACE("DataStream::BlocksNeeded(): %lu\n", blocksNeeded); + return blocksNeeded; +} + + +status_t +DataStream::_GetBlock(Transaction& transaction, uint32& block) +{ + TRACE("DataStream::_GetBlock(): allocated: %lu, pos: %lu, waiting: %lu\n", + fAllocated, fAllocatedPos, fWaiting); + + if (fAllocated == 0) { + uint32 blockGroup = (fAllocatedPos - fFirstBlock) + / fVolume->BlocksPerGroup(); + fAllocatedPos %= fVolume->BlocksPerGroup(); + + status_t status = fVolume->AllocateBlocks(transaction, 1, fWaiting, + blockGroup, fAllocatedPos, fAllocated); + if (status != B_OK) + return status; + + fWaiting -= fAllocated; + fAllocatedPos += fVolume->BlocksPerGroup() * blockGroup + fFirstBlock; + } + + fAllocated--; + block = fAllocatedPos++; + + return B_OK; +} + + +status_t +DataStream::_PrepareBlock(Transaction& transaction, uint32* pos, + uint32& blockNum, bool& clear) +{ + blockNum = B_LENDIAN_TO_HOST_INT32(*pos); + clear = false; + + if (blockNum == 0) { + status_t status = _GetBlock(transaction, blockNum); + if (status != B_OK) + return status; + + *pos = B_HOST_TO_LENDIAN_INT32(blockNum); + clear = true; + } + + return B_OK; +} + + +status_t +DataStream::_AddBlocks(Transaction& transaction, uint32* block, uint32 _count) +{ + uint32 count = _count; + TRACE("DataStream::_AddBlocks(): count: %lu\n", count); + + while (count > 0) { + uint32 blockNum; + status_t status = _GetBlock(transaction, blockNum); + if (status != B_OK) + return status; + + *(block++) = B_HOST_TO_LENDIAN_INT32(blockNum); + --count; + } + + fNumBlocks += _count; + + return B_OK; +} + + +status_t +DataStream::_AddBlocks(Transaction& transaction, uint32* block, uint32 start, + uint32 end, int recursion) +{ + TRACE("DataStream::_AddBlocks(): start: %lu, end %lu, recursion: %d\n", + start, end, recursion); + + bool clear; + uint32 blockNum; + status_t status = _PrepareBlock(transaction, block, blockNum, clear); + if (status != B_OK) + return status; + + CachedBlock cached(fVolume); + uint32* childBlock = (uint32*)cached.SetToWritable(transaction, blockNum, + clear); + if (childBlock == NULL) + return B_IO_ERROR; + + if (recursion == 0) + return _AddBlocks(transaction, &childBlock[start], end - start); + + uint32 elementWidth; + if (recursion == 1) + elementWidth = kIndirectsPerBlock; + else if (recursion == 2) + elementWidth = kIndirectsPerBlock2; + else { + panic("Undefinied recursion level\n"); + elementWidth = 0; + } + + uint32 elementPos = start / elementWidth; + uint32 endPos = end / elementWidth; + + TRACE("DataStream::_AddBlocks(): element pos: %lu, end pos: %lu\n", + elementPos, endPos); + + recursion--; + + if (elementPos == endPos) { + return _AddBlocks(transaction, &childBlock[elementPos], + start % elementWidth, end % elementWidth, recursion); + } + + if (start % elementWidth != 0) { + status = _AddBlocks(transaction, &childBlock[elementPos], + start % elementWidth, elementWidth, recursion); + if (status != B_OK) + return status; + + elementPos++; + } + + while (elementPos < endPos) { + status = _AddBlocks(transaction, &childBlock[elementPos], 0, + elementWidth, recursion); + if (status != B_OK) + return status; + + elementPos++; + } + + if (end % elementWidth != 0) { + status = _AddBlocks(transaction, &childBlock[elementPos], 0, + end % elementWidth, recursion); + if (status != B_OK) + return status; + } + + return B_OK; +} + + +status_t +DataStream::_AddForDirectBlocks(Transaction& transaction, uint32 numBlocks) +{ + TRACE("DataStream::_AddForDirectBlocks(): current size: %lu, target size: " + "%lu\n", fNumBlocks, numBlocks); + uint32* direct = &fStream->direct[fNumBlocks]; + uint32 end = numBlocks > kMaxDirect ? kMaxDirect : numBlocks; + + return _AddBlocks(transaction, direct, end - fNumBlocks); +} + + +status_t +DataStream::_AddForIndirectBlock(Transaction& transaction, uint32 numBlocks) +{ + TRACE("DataStream::_AddForIndirectBlocks(): current size: %lu, target " + "size: %lu\n", fNumBlocks, numBlocks); + uint32 *indirect = &fStream->indirect; + uint32 start = fNumBlocks - kMaxDirect; + uint32 end = numBlocks - kMaxDirect; + + if (end > kIndirectsPerBlock) + end = kIndirectsPerBlock; + + return _AddBlocks(transaction, indirect, start, end, 0); +} + + +status_t +DataStream::_AddForDoubleIndirectBlock(Transaction& transaction, + uint32 numBlocks) +{ + TRACE("DataStream::_AddForDoubleIndirectBlock(): current size: %lu, " + "target size: %lu\n", fNumBlocks, numBlocks); + uint32 *doubleIndirect = &fStream->double_indirect; + uint32 start = fNumBlocks - kMaxIndirect; + uint32 end = numBlocks - kMaxIndirect; + + if (end > kIndirectsPerBlock2) + end = kIndirectsPerBlock2; + + return _AddBlocks(transaction, doubleIndirect, start, end, 1); +} + + +status_t +DataStream::_AddForTripleIndirectBlock(Transaction& transaction, + uint32 numBlocks) +{ + TRACE("DataStream::_AddForTripleIndirectBlock(): current size: %lu, " + "target size: %lu\n", fNumBlocks, numBlocks); + uint32 *tripleIndirect = &fStream->triple_indirect; + uint32 start = fNumBlocks - kMaxDoubleIndirect; + uint32 end = numBlocks - kMaxDoubleIndirect; + + return _AddBlocks(transaction, tripleIndirect, start, end, 2); +} + + +status_t +DataStream::_PerformFree(Transaction& transaction) +{ + TRACE("DataStream::_PerformFree(): start: %lu, count: %lu\n", fFreeStart, + fFreeCount); + status_t status; + + if (fFreeCount == 0) + status = B_OK; + else + status = fVolume->FreeBlocks(transaction, fFreeStart, fFreeCount); + + fFreeStart = 0; + fFreeCount = 0; + + return status; +} + + +status_t +DataStream::_MarkBlockForRemoval(Transaction& transaction, uint32* block) +{ + TRACE("DataStream::_MarkBlockForRemoval(*(%p) = %lu): free start: %lu, " + "free count: %lu\n", block, *block, fFreeStart, fFreeCount); + uint32 blockNum = B_LENDIAN_TO_HOST_INT32(*block); + *block = 0; + + if (blockNum != fFreeStart + fFreeCount) { + if (fFreeCount != 0) { + status_t status = fVolume->FreeBlocks(transaction, fFreeStart, + fFreeCount); + if (status != B_OK) + return status; + } + + fFreeStart = blockNum; + fFreeCount = 0; + } + + fFreeCount++; + + return B_OK; +} + + +status_t +DataStream::_FreeBlocks(Transaction& transaction, uint32* block, uint32 _count) +{ + uint32 count = _count; + TRACE("DataStream::_FreeBlocks(%p, %lu)\n", block, count); + + while (count > 0) { + status_t status = _MarkBlockForRemoval(transaction, block); + if (status != B_OK) + return status; + + block++; + count--; + } + + fRemovedBlocks += _count; + + return B_OK; +} + + +status_t +DataStream::_FreeBlocks(Transaction& transaction, uint32* block, uint32 start, + uint32 end, bool freeParent, int recursion) +{ + // TODO: Designed specifically for shrinking. Perhaps make it more general? + TRACE("DataStream::_FreeBlocks(%p, %lu, %lu, %c, %d)\n", + block, start, end, freeParent ? 't' : 'f', recursion); + + uint32 blockNum = B_LENDIAN_TO_HOST_INT32(*block); + + if (freeParent) { + status_t status = _MarkBlockForRemoval(transaction, block); + if (status != B_OK) + return status; + } + + CachedBlock cached(fVolume); + uint32* childBlock = (uint32*)cached.SetToWritable(transaction, blockNum); + if (childBlock == NULL) + return B_IO_ERROR; + + if (recursion == 0) + return _FreeBlocks(transaction, &childBlock[start], end - start); + + uint32 elementWidth; + if (recursion == 1) + elementWidth = kIndirectsPerBlock; + else if (recursion == 2) + elementWidth = kIndirectsPerBlock2; + else { + panic("Undefinied recursion level\n"); + elementWidth = 0; + } + + uint32 elementPos = start / elementWidth; + uint32 endPos = end / elementWidth; + + recursion--; + + if (elementPos == endPos) { + bool free = freeParent || start % elementWidth == 0; + return _FreeBlocks(transaction, &childBlock[elementPos], + start % elementWidth, end % elementWidth, free, recursion); + } + + status_t status = B_OK; + + if (start % elementWidth != 0) { + status = _FreeBlocks(transaction, &childBlock[elementPos], + start % elementWidth, elementWidth, false, recursion); + if (status != B_OK) + return status; + + elementPos++; + } + + while (elementPos < endPos) { + status = _FreeBlocks(transaction, &childBlock[elementPos], 0, + elementWidth, true, recursion); + if (status != B_OK) + return status; + + elementPos++; + } + + if (end % elementWidth != 0) { + status = _FreeBlocks(transaction, &childBlock[elementPos], 0, + end % elementWidth, true, recursion); + } + + return status; +} + + +status_t +DataStream::_RemoveFromDirectBlocks(Transaction& transaction, uint32 numBlocks) +{ + TRACE("DataStream::_RemoveFromDirectBlocks(): current size: %lu, " + "target size: %lu\n", fNumBlocks, numBlocks); + uint32* direct = &fStream->direct[numBlocks]; + uint32 end = fNumBlocks > kMaxDirect ? kMaxDirect : fNumBlocks; + + return _FreeBlocks(transaction, direct, end - numBlocks); +} + + +status_t +DataStream::_RemoveFromIndirectBlock(Transaction& transaction, uint32 numBlocks) +{ + TRACE("DataStream::_RemoveFromIndirectBlock(): current size: %lu, " + "target size: %lu\n", fNumBlocks, numBlocks); + uint32* indirect = &fStream->indirect; + uint32 start = numBlocks <= kMaxDirect ? 0 : numBlocks - kMaxDirect; + uint32 end = fNumBlocks - kMaxDirect; + + if (end > kIndirectsPerBlock) + end = kIndirectsPerBlock; + + bool freeAll = start == 0; + + return _FreeBlocks(transaction, indirect, start, end, freeAll, 0); +} + + +status_t +DataStream::_RemoveFromDoubleIndirectBlock(Transaction& transaction, + uint32 numBlocks) +{ + TRACE("DataStream::_RemoveFromDoubleIndirectBlock(): current size: %lu, " + "target size: %lu\n", fNumBlocks, numBlocks); + uint32* doubleIndirect = &fStream->double_indirect; + uint32 start = numBlocks <= kMaxIndirect ? 0 : numBlocks - kMaxIndirect; + uint32 end = fNumBlocks - kMaxIndirect; + + if (end > kIndirectsPerBlock2) + end = kIndirectsPerBlock2; + + bool freeAll = start == 0; + + return _FreeBlocks(transaction, doubleIndirect, start, end, freeAll, 1); +} + + +status_t +DataStream::_RemoveFromTripleIndirectBlock(Transaction& transaction, + uint32 numBlocks) +{ + TRACE("DataStream::_RemoveFromTripleIndirectBlock(): current size: %lu, " + "target size: %lu\n", fNumBlocks, numBlocks); + uint32* tripleIndirect = &fStream->triple_indirect; + uint32 start = numBlocks <= kMaxDoubleIndirect ? 0 + : numBlocks - kMaxDoubleIndirect; + uint32 end = fNumBlocks - kMaxDoubleIndirect; + + bool freeAll = start == 0; + + return _FreeBlocks(transaction, tripleIndirect, start, end, freeAll, 2); +} diff --git a/src/add-ons/kernel/file_systems/ext2/DataStream.h b/src/add-ons/kernel/file_systems/ext2/DataStream.h new file mode 100644 index 0000000000..eee6a282f4 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/DataStream.h @@ -0,0 +1,94 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ +#ifndef DATASTREAM_H +#define DATASTREAM_H + +#include "ext2.h" +#include "Transaction.h" + + +class Volume; + + +class DataStream +{ +public: + DataStream(Volume* volume, ext2_data_stream* stream, + off_t size); + ~DataStream(); + + status_t Enlarge(Transaction& transaction, uint32& numBlocks); + status_t Shrink(Transaction& transaction, uint32& numBlocks); + +private: + uint32 _BlocksNeeded(uint32 end); + + status_t _GetBlock(Transaction& transaction, uint32& block); + status_t _PrepareBlock(Transaction& transaction, uint32* pos, + uint32& blockNum, bool& clear); + + status_t _AddBlocks(Transaction& transaction, uint32* block, + uint32 count); + status_t _AddBlocks(Transaction& transaction, uint32* block, + uint32 start, uint32 end, int recursion); + + status_t _AddForDirectBlocks(Transaction& transaction, + uint32 numBlocks); + status_t _AddForIndirectBlock(Transaction& transaction, + uint32 numBlocks); + status_t _AddForDoubleIndirectBlock(Transaction& transaction, + uint32 numBlocks); + status_t _AddForTripleIndirectBlock(Transaction& transaction, + uint32 numBlocks); + + status_t _PerformFree(Transaction& transaction); + status_t _MarkBlockForRemoval(Transaction& transaction, + uint32* block); + + status_t _FreeBlocks(Transaction& transaction, uint32* block, + uint32 count); + status_t _FreeBlocks(Transaction& transaction, uint32* block, + uint32 start, uint32 end, bool freeParent, + int recursion); + + status_t _RemoveFromDirectBlocks(Transaction& transaction, + uint32 numBlocks); + status_t _RemoveFromIndirectBlock(Transaction& transaction, + uint32 numBlocks); + status_t _RemoveFromDoubleIndirectBlock(Transaction& transaction, + uint32 numBlocks); + status_t _RemoveFromTripleIndirectBlock(Transaction& transaction, + uint32 numBlocks); + + + const uint32 kBlockSize; + const uint32 kIndirectsPerBlock; + const uint32 kIndirectsPerBlock2; + const uint32 kIndirectsPerBlock3; + + const uint32 kMaxDirect; + const uint32 kMaxIndirect; + const uint32 kMaxDoubleIndirect; + + Volume* fVolume; + ext2_data_stream* fStream; + uint32 fFirstBlock; + + uint32 fAllocated; + uint32 fAllocatedPos; + uint32 fWaiting; + + uint32 fFreeStart; + uint32 fFreeCount; + + uint32 fNumBlocks; + uint32 fRemovedBlocks; +}; + +#endif // DATASTREAM_H + diff --git a/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.cpp b/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.cpp index d7d7404d6a..6ac4e200e7 100644 --- a/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.cpp +++ b/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.cpp @@ -8,6 +8,10 @@ #include +#include +#include + +#include "CachedBlock.h" #include "HTree.h" #include "Inode.h" @@ -20,65 +24,292 @@ #endif -DirectoryIterator::DirectoryIterator(Inode* inode) - : - fInode(inode), - fOffset(0) +struct HashedEntry { + uint8* position; + uint32 hash; + + bool operator<(const HashedEntry& other) const + { + return hash <= other.hash; + } + + bool operator>(const HashedEntry& other) const + { + return hash >= other.hash; + } +}; + + +DirectoryIterator::DirectoryIterator(Inode* directory, off_t start, + HTreeEntryIterator* parent) + : + fDirectory(directory), + fVolume(directory->GetVolume()), + fBlockSize(fVolume->BlockSize()), + fParent(parent), + fNumBlocks(directory->Size() == 0 ? 0 + : (directory->Size() - 1) / fBlockSize + 1), + fLogicalBlock(start / fBlockSize), + fDisplacement(start % fBlockSize), + fPreviousDisplacement(fPreviousDisplacement), + fStartLogicalBlock(fLogicalBlock), + fStartDisplacement(fDisplacement) +{ + TRACE("DirectoryIterator::DirectoryIterator(): num blocks: %lu\n", + fNumBlocks); + fIndexing = parent != NULL; + fInitStatus = fDirectory->FindBlock(start, fPhysicalBlock); + fStartPhysicalBlock = fPhysicalBlock; } DirectoryIterator::~DirectoryIterator() { + TRACE("DirectoryIterator::~DirectoryIterator(): %p, parent: %p\n", this, + fParent); + delete fParent; + TRACE("DirectoryIterator::~DirectoryIterator(): Deleted the parent\n"); } status_t -DirectoryIterator::GetNext(char* name, size_t* _nameLength, ino_t* _id) +DirectoryIterator::InitCheck() { - if (fOffset + sizeof(ext2_dir_entry) >= fInode->Size()) { - TRACE("DirectoryIterator::GetNext() out of entries\n"); + return fInitStatus; +} + + +status_t +DirectoryIterator::Get(char* name, size_t* _nameLength, ino_t* _id) +{ + if (fLogicalBlock * fBlockSize + fDisplacement >= fDirectory->Size()) { + TRACE("DirectoryIterator::Get() out of entries\n"); return B_ENTRY_NOT_FOUND; } - ext2_dir_entry entry; + CachedBlock cached(fVolume); + const uint8* block = cached.SetTo(fPhysicalBlock); + if (block == NULL) + return B_IO_ERROR; - while (true) { - size_t length = ext2_dir_entry::MinimumSize(); - status_t status = fInode->ReadAt(fOffset, (uint8*)&entry, &length); - if (status != B_OK) - return status; - if (length < ext2_dir_entry::MinimumSize() || entry.Length() == 0) - return B_ENTRY_NOT_FOUND; - if (!entry.IsValid()) - return B_BAD_DATA; + TRACE("DirectoryIterator::Get(): Displacement: %lu\n", fDisplacement); + const ext2_dir_entry* entry = (const ext2_dir_entry*)&block[fDisplacement]; - if (entry.NameLength() != 0) - break; + if (entry->NameLength() != 0) { + size_t length = entry->NameLength(); - fOffset += entry.Length(); - TRACE("DirectoryIterator::GetNext() skipping entry\n"); + TRACE("block %lu, displacement %lu: entry ino %lu, length %u, " + "name length %lu, type %lu\n", fLogicalBlock, fDisplacement, + entry->InodeID(), entry->Length(), (uint32)length, + (uint32)entry->FileType()); + + if (*_nameLength > 0) { + if (*_nameLength < length) + length = *_nameLength - 1; + + memcpy(name, entry->name, length); + name[length] = '\0'; + + *_nameLength = length; + } + + *_id = entry->InodeID(); + } else + *_nameLength = 0; + + return B_OK; +} + + +status_t +DirectoryIterator::Next() +{ + TRACE("DirectoryIterator::Next()\n"); + + if (fLogicalBlock * fBlockSize + fDisplacement >= fDirectory->Size()) { + TRACE("DirectoryIterator::Next() out of entries\n"); + return B_ENTRY_NOT_FOUND; } - TRACE("offset %Ld: entry ino %lu, length %u, name length %u, type %u\n", - fOffset, entry.InodeID(), entry.Length(), entry.NameLength(), - entry.FileType()); + TRACE("DirectoryIterator::Next(): Creating cached block\n"); - // read name + CachedBlock cached(fVolume); + ext2_dir_entry* entry; - size_t length = entry.NameLength(); - status_t status = fInode->ReadAt(fOffset + ext2_dir_entry::MinimumSize(), - (uint8*)entry.name, &length); - if (status == B_OK) { - if (*_nameLength < length) - length = *_nameLength - 1; - memcpy(name, entry.name, length); - name[length] = '\0'; + TRACE("DirectoryIterator::Next(): Loading cached block\n"); + const uint8* block = cached.SetTo(fPhysicalBlock); + if (block == NULL) + return B_IO_ERROR; - *_id = entry.InodeID(); - *_nameLength = length; + entry = (ext2_dir_entry*)(block + fDisplacement); - fOffset += entry.Length(); + do { + TRACE("Checking entry at block %lu, displacement %lu\n", fPhysicalBlock, + fDisplacement); + + if (entry->Length() == 0) { + TRACE("empty entry.\n"); + return B_ENTRY_NOT_FOUND; + } + if (!entry->IsValid()) { + TRACE("invalid entry.\n"); + return B_BAD_DATA; + } + + fPreviousDisplacement = fDisplacement; + fDisplacement += entry->Length(); + + if (fDisplacement == fBlockSize) { + TRACE("Reached end of block\n"); + + fDisplacement = 0; + + status_t status = _NextBlock(); + if (status != B_OK) + return status; + + if (fLogicalBlock * fBlockSize + ext2_dir_entry::MinimumSize() + < fDirectory->Size()) { + status_t status = fDirectory->FindBlock( + fLogicalBlock * fBlockSize, fPhysicalBlock); + if (status != B_OK) + return status; + } else { + TRACE("DirectoryIterator::Next() end of directory file\n"); + return B_ENTRY_NOT_FOUND; + } + + if (entry->Length() == 0) { + block = cached.SetTo(fPhysicalBlock); + if (block == NULL) + return B_IO_ERROR; + } + } else if (fDisplacement > fBlockSize) { + TRACE("The entry isn't block aligned.\n"); + // TODO: Is block alignment obligatory? + return B_BAD_DATA; + } + + entry = (ext2_dir_entry*)(block + fDisplacement); + + TRACE("DirectoryIterator::Next() skipping entry\n"); + } while (entry->Length() == 0); + + return B_OK; +} + + +status_t +DirectoryIterator::Rewind() +{ + fDisplacement = 0; + fPreviousDisplacement = 0; + fLogicalBlock = 0; + + return fDirectory->FindBlock(0, fPhysicalBlock); +} + + +void +DirectoryIterator::Restart() +{ + TRACE("DirectoryIterator::Restart(): (logical, physical, displacement): " + "current: (%lu, %lu, %lu), start: (%lu, %lu, %lu)\n", fLogicalBlock, + fPhysicalBlock, fDisplacement, fStartLogicalBlock, fStartPhysicalBlock, + fStartDisplacement); + fLogicalBlock = fStartLogicalBlock; + fPhysicalBlock = fStartPhysicalBlock; + fDisplacement = fPreviousDisplacement = fStartDisplacement; +} + + +status_t +DirectoryIterator::AddEntry(Transaction& transaction, const char* name, + size_t _nameLength, ino_t id, uint8 type) +{ + TRACE("DirectoryIterator::AddEntry(%s, ...)\n", name); + + uint8 nameLength = _nameLength > EXT2_NAME_LENGTH ? EXT2_NAME_LENGTH + : _nameLength; + + status_t status = B_OK; + while (status == B_OK) { + uint16 pos = 0; + uint16 newLength; + + status = _AllocateBestEntryInBlock(nameLength, pos, newLength); + if (status == B_OK) { + return _AddEntry(transaction, name, nameLength, id, type, newLength, + pos); + } else if (status != B_DEVICE_FULL) + return status; + + status = _NextBlock(); + if (status == B_OK) { + status = fDirectory->FindBlock(fLogicalBlock * fBlockSize, + fPhysicalBlock); + } + } + + if (status != B_ENTRY_NOT_FOUND) + return status; + + bool firstSplit = fNumBlocks == 1 && fVolume->IndexedDirectories(); + + fNumBlocks++; + + if (fIndexing) { + TRACE("DirectoryIterator::AddEntry(): Adding another HTree leaf\n"); + fNumBlocks += fParent->BlocksNeededForNewEntry(); + } else if (firstSplit) { + // Allocate another block (fNumBlocks should become 3) + TRACE("DirectoryIterator::AddEntry(): Creating index for directory\n"); + fNumBlocks++; + } else + TRACE("DirectoryIterator::AddEntry(): Enlarging directory\n"); + + status = fDirectory->Resize(transaction, fNumBlocks * fBlockSize); + if (status != B_OK) + return status; + + if (firstSplit || fIndexing) { + // firstSplit and fIndexing are mutually exclusive + return _SplitIndexedBlock(transaction, name, nameLength, id, type, + fNumBlocks - 1, firstSplit); + } + + fLogicalBlock = fNumBlocks - 1; + status = fDirectory->FindBlock(fLogicalBlock * fBlockSize, fPhysicalBlock); + if (status != B_OK) + return status; + + return _AddEntry(transaction, name, nameLength, id, type, fBlockSize, 0, + false); +} + + +status_t +DirectoryIterator::FindEntry(const char* name, ino_t* _id) +{ + TRACE("DirectoryIterator::FindEntry(): %p %p\n", this, name); + char buffer[EXT2_NAME_LENGTH + 1]; + ino_t id; + + status_t status = B_OK; + while (status == B_OK) { + size_t nameLength = EXT2_NAME_LENGTH; + status = Get(buffer, &nameLength, &id); + if (status != B_OK) + return status; + + if (strcmp(name, buffer) == 0) { + if (_id != NULL) + *_id = id; + return B_OK; + } + + status = Next(); } return status; @@ -86,8 +317,456 @@ DirectoryIterator::GetNext(char* name, size_t* _nameLength, ino_t* _id) status_t -DirectoryIterator::Rewind() +DirectoryIterator::RemoveEntry(Transaction& transaction) { - fOffset = 0; + ext2_dir_entry* previousEntry; + ext2_dir_entry* dirEntry; + CachedBlock cached(fVolume); + + uint8* block = cached.SetToWritable(transaction, fPhysicalBlock); + + if (fDisplacement == 0) { + previousEntry = (ext2_dir_entry*)&block[fDisplacement]; + + fPreviousDisplacement = fDisplacement; + fDisplacement += previousEntry->Length(); + + if (fDisplacement == fBlockSize) { + memset(&previousEntry->name_length, 0, fBlockSize - 6); + fDisplacement = 0; + return Next(); + } else if (fDisplacement > fBlockSize) { + TRACE("DirectoryIterator::RemoveEntry(): Entry isn't aligned to " + "block entry."); + return B_BAD_DATA; + } + + dirEntry = (ext2_dir_entry*)&block[fDisplacement]; + memcpy(&block[fPreviousDisplacement], &block[fDisplacement], + dirEntry->Length()); + + previousEntry->SetLength(fDisplacement - fPreviousDisplacement + + previousEntry->Length()); + + return B_OK; + } + + if (fPreviousDisplacement == fDisplacement) { + char buffer[EXT2_NAME_LENGTH + 1]; + + dirEntry = (ext2_dir_entry*)&block[fDisplacement]; + + memcpy(buffer, dirEntry->name, (uint32)dirEntry->name_length); + + fDisplacement = 0; + status_t status = FindEntry(dirEntry->name); + if (status == B_ENTRY_NOT_FOUND) + return B_BAD_DATA; + if (status != B_OK) + return status; + } + + previousEntry = (ext2_dir_entry*)&block[fPreviousDisplacement]; + dirEntry = (ext2_dir_entry*)&block[fDisplacement]; + + previousEntry->SetLength(previousEntry->Length() + dirEntry->Length()); + + memset(&block[fDisplacement], 0, + fPreviousDisplacement + previousEntry->Length() - fDisplacement); + + return B_OK; +} + + +status_t +DirectoryIterator::ChangeEntry(Transaction& transaction, ino_t id, + uint8 fileType) +{ + CachedBlock cached(fVolume); + + uint8* block = cached.SetToWritable(transaction, fPhysicalBlock); + if (block == NULL) + return B_IO_ERROR; + + ext2_dir_entry* dirEntry = (ext2_dir_entry*)&block[fDisplacement]; + dirEntry->SetInodeID(id); + dirEntry->file_type = fileType; + + return B_OK; +} + + +status_t +DirectoryIterator::_AllocateBestEntryInBlock(uint8 nameLength, uint16& pos, + uint16& newLength) +{ + TRACE("DirectoryIterator::_AllocateBestEntryInBlock()\n"); + CachedBlock cached(fVolume); + const uint8* block = cached.SetTo(fPhysicalBlock); + + uint16 requiredLength = nameLength + 8; + if (requiredLength % 4 != 0) + requiredLength += 4 - requiredLength % 4; + + uint16 bestPos = fBlockSize; + uint16 bestLength = fBlockSize; + uint16 bestRealLength = fBlockSize; + ext2_dir_entry* dirEntry; + + while (pos < fBlockSize) { + dirEntry = (ext2_dir_entry*)&block[pos]; + + uint16 realLength = dirEntry->NameLength() + 8; + + if (realLength % 4 != 0) + realLength += 4 - realLength % 4; + + uint16 emptySpace = dirEntry->Length() - realLength; + if (emptySpace == requiredLength) { + // Found an exact match + TRACE("DirectoryIterator::_AllocateBestEntryInBlock(): Found an " + "exact length match\n"); + newLength = realLength; + + return B_OK; + } else if (emptySpace > requiredLength && emptySpace < bestLength) { + bestPos = pos; + bestLength = emptySpace; + bestRealLength = realLength; + } + + pos += dirEntry->Length(); + } + + if (bestPos == fBlockSize) + return B_DEVICE_FULL; + + TRACE("DirectoryIterator::_AllocateBestEntryInBlock(): Found a suitable " + "location: %lu\n", (uint32)bestPos); + pos = bestPos; + newLength = bestRealLength; + + return B_OK; +} + + +status_t +DirectoryIterator::_AddEntry(Transaction& transaction, const char* name, + uint8 nameLength, ino_t id, uint8 type, uint16 newLength, uint16 pos, + bool hasPrevious) +{ + TRACE("DirectoryIterator::_AddEntry(%s, %d, %d, %d, %d, %d, %c)\n", + name, (int)nameLength, (int)id, (int)type, (int)newLength, (int)pos, + hasPrevious ? 't' : 'f'); + CachedBlock cached(fVolume); + + uint8* block = cached.SetToWritable(transaction, fPhysicalBlock); + if (block == NULL) + return B_IO_ERROR; + + ext2_dir_entry* dirEntry = (ext2_dir_entry*)&block[pos]; + + if (hasPrevious) { + uint16 previousLength = dirEntry->Length(); + dirEntry->SetLength(newLength); + + dirEntry = (ext2_dir_entry*)&block[pos + newLength]; + newLength = previousLength - newLength; + } + + dirEntry->SetLength(newLength); + dirEntry->name_length = nameLength; + dirEntry->SetInodeID(id); + dirEntry->file_type = type; + memcpy(dirEntry->name, name, nameLength); + + TRACE("DirectoryIterator::_AddEntry(): Done\n"); + + return B_OK; +} + + +status_t +DirectoryIterator::_SplitIndexedBlock(Transaction& transaction, + const char* name, uint8 nameLength, ino_t id, uint8 type, + uint32 newBlocksPos, bool firstSplit) +{ + // Block is full, split required + TRACE("DirectoryIterator::_SplitIndexedBlock(.., %s, %u, %lu, %lu, %c)\n", + name, (unsigned int)nameLength, (uint32)id, newBlocksPos, + firstSplit ? 't' : 'f'); + + // Allocate a buffer for the entries in the block + uint8* buffer = new(std::nothrow) uint8[fBlockSize]; + if (buffer == NULL) + return B_NO_MEMORY; + ArrayDeleter bufferDeleter(buffer); + + uint32 firstPhysicalBlock = 0; + + // Prepare block to hold the first half of the entries and fill the buffer + CachedBlock cachedFirst(fVolume); + + if (firstSplit) { + // Save all entries to the buffer + status_t status = fDirectory->FindBlock(0, firstPhysicalBlock); + if (status != B_OK) + return status; + + const uint8* srcBlock = cachedFirst.SetTo(firstPhysicalBlock); + if (srcBlock == NULL) + return B_IO_ERROR; + + memcpy(buffer, srcBlock, fBlockSize); + + status = fDirectory->FindBlock(fBlockSize, fPhysicalBlock); + if (status != B_OK) + return status; + } + + uint8* firstBlock = cachedFirst.SetToWritable(transaction, fPhysicalBlock); + uint8* secondBlock = NULL; + if (firstBlock == NULL) + return B_IO_ERROR; + + status_t status; + + if (!firstSplit) { + // Save all entries to the buffer + memcpy(buffer, firstBlock, fBlockSize); + } else { + // Initialize the root node + fDirectory->Node().SetFlag(EXT2_INODE_INDEXED); + HTreeRoot* root; + + secondBlock = cachedFirst.SetToWritable(transaction, + firstPhysicalBlock, true); + if (secondBlock == NULL) + return B_IO_ERROR; + + status = fDirectory->WriteBack(transaction); + if (status != B_OK) + return status; + + memcpy(secondBlock, buffer, 2 * (sizeof(HTreeFakeDirEntry) + 4)); + + root = (HTreeRoot*)secondBlock; + + HTreeFakeDirEntry* dotdot = &root->dotdot; + dotdot->SetEntryLength(fBlockSize - (sizeof(HTreeFakeDirEntry) + 4)); + + root->hash_version = fVolume->DefaultHashVersion(); + root->root_info_length = 8; + root->indirection_levels = 0; + + root->count_limit->SetLimit((fBlockSize + - ((uint8*)root->count_limit - secondBlock)) / sizeof(HTreeEntry)); + root->count_limit->SetCount(2); + } + + // Sort entries + VectorSet entrySet; + + HTree htree(fVolume, fDirectory); + status = htree.PrepareForHash(); + if (status != B_OK) + return status; + + uint32 displacement = firstSplit ? 2 * (sizeof(HTreeFakeDirEntry) + 4) : 0; + + HashedEntry entry; + ext2_dir_entry* dirEntry = NULL; + + while (displacement < fBlockSize) { + entry.position = &buffer[displacement]; + dirEntry = (ext2_dir_entry*)entry.position; + + TRACE("DirectoryIterator::_SplitIndexedBlock(): pos: %p, name " + "length: %u, entry length: %u\n", entry.position, + (unsigned int)dirEntry->name_length, + (unsigned int)dirEntry->Length()); + + char cbuffer[256]; + memcpy(cbuffer, dirEntry->name, dirEntry->name_length); + cbuffer[dirEntry->name_length] = '\0'; + entry.hash = htree.Hash(dirEntry->name, dirEntry->name_length); + TRACE("DirectoryIterator::_SplitIndexedBlock(): %s -> %lu\n", + cbuffer, entry.hash); + + status = entrySet.Insert(entry); + if (status != B_OK) + return status; + + displacement += dirEntry->Length(); + } + + // Prepare the new entry to be included as well + ext2_dir_entry newEntry; + + uint16 newLength = (uint16)nameLength + 8; + if (newLength % 4 != 0) + newLength += 4 - newLength % 4; + + newEntry.name_length = nameLength; + newEntry.SetLength(newLength); + newEntry.SetInodeID(id); + newEntry.file_type = type; + memcpy(newEntry.name, name, nameLength); + + entry.position = (uint8*)&newEntry; + entry.hash = htree.Hash(name, nameLength); + TRACE("DirectoryIterator::_SplitIndexedBlock(): %s -> %lu\n", + name, entry.hash); + + entrySet.Insert(entry); + + // Move first half of entries to the first block + VectorSet::Iterator iterator = entrySet.Begin(); + int32 median = entrySet.Count() / 2; + displacement = 0; + TRACE("DirectoryIterator::_SplitIndexedBlock(): Count: %ld, median: %ld\n", + entrySet.Count(), median); + + uint32 previousHash = (*iterator).hash; + + for (int32 i = 0; i < median; ++i) { + dirEntry = (ext2_dir_entry*)(*iterator).position; + previousHash = (*iterator).hash; + + uint32 realLength = (uint32)dirEntry->name_length + 8; + if (realLength % 4 != 0) + realLength += 4 - realLength % 4; + + dirEntry->SetLength((uint16)realLength); + memcpy(&firstBlock[displacement], dirEntry, realLength); + + displacement += realLength; + iterator++; + } + + // Update last entry in the block + uint16 oldLength = dirEntry->Length(); + dirEntry = (ext2_dir_entry*)&firstBlock[displacement - oldLength]; + dirEntry->SetLength(fBlockSize - displacement + oldLength); + + bool collision = false; + + while (iterator != entrySet.End() && (*iterator).hash == previousHash) { + // Keep collisions on the same block + TRACE("DirectoryIterator::_SplitIndexedBlock(): Handling collisions\n"); + + // This isn't the ideal solution, but it is a rare occurance + dirEntry = (ext2_dir_entry*)(*iterator).position; + + if (displacement + dirEntry->Length() > fBlockSize) { + // Doesn't fit on the block + collision = true; + break; + } + + memcpy(&firstBlock[displacement], dirEntry, dirEntry->Length()); + + displacement += dirEntry->Length(); + iterator++; + } + + // Save the hash to store in the parent + uint32 medianHash = (*iterator).hash; + + // Update parent + if (firstSplit) { + TRACE("DirectoryIterator::_SplitIndexedBlock(): Updating root\n"); + HTreeRoot* root = (HTreeRoot*)secondBlock; + HTreeEntry* htreeEntry = (HTreeEntry*)root->count_limit; + htreeEntry->SetBlock(1); + + ++htreeEntry; + htreeEntry->SetBlock(2); + htreeEntry->SetHash(medianHash); + + off_t start = (off_t)root->root_info_length + + 2 * (sizeof(HTreeFakeDirEntry) + 4); + fParent = new(std::nothrow) HTreeEntryIterator(start, fDirectory); + if (fParent == NULL) + return B_NO_MEMORY; + + fLogicalBlock = 1; + status = fDirectory->FindBlock(fLogicalBlock * fBlockSize, + fPhysicalBlock); + + fPreviousDisplacement = fDisplacement = 0; + + status = fParent->Init(); + } + else { + status = fParent->InsertEntry(transaction, medianHash, fNumBlocks - 1, + newBlocksPos, collision); + } + if (status != B_OK) + return status; + + // Prepare last block to hold the second half of the entries + TRACE("DirectoryIterator::_SplitIndexedBlock(): Preparing second leaf " + "block\n"); + fDisplacement = 0; + + status = fDirectory->FindBlock(fDirectory->Size() - 1, fPhysicalBlock); + if (status != B_OK) + return status; + + CachedBlock cachedSecond(fVolume); + secondBlock = cachedSecond.SetToWritable(transaction, + fPhysicalBlock); + if (secondBlock == NULL) + return B_IO_ERROR; + + // Move the second half of the entries to the second block + VectorSet::Iterator end = entrySet.End(); + displacement = 0; + + while (iterator != end) { + dirEntry = (ext2_dir_entry*)(*iterator).position; + + uint32 realLength = (uint32)dirEntry->name_length + 8; + if (realLength % 4 != 0) + realLength += 4 - realLength % 4; + + dirEntry->SetLength((uint16)realLength); + memcpy(&secondBlock[displacement], dirEntry, realLength); + + displacement += realLength; + iterator++; + } + + // Update last entry in the block + oldLength = dirEntry->Length(); + dirEntry = (ext2_dir_entry*)&secondBlock[displacement - oldLength]; + dirEntry->SetLength(fBlockSize - displacement + oldLength); + + TRACE("DirectoryIterator::_SplitIndexedBlock(): Done\n"); + return B_OK; +} + + +status_t +DirectoryIterator::_NextBlock() +{ + TRACE("DirectoryIterator::_NextBlock()\n"); + if (fIndexing) { + TRACE("DirectoryIterator::_NextBlock(): Indexing\n"); + if (!fParent->HasCollision()) { + TRACE("DirectoryIterator::_NextBlock(): next block doesn't " + "contain collisions from previous block\n"); +#ifndef COLLISION_TEST + return B_ENTRY_NOT_FOUND; +#endif + } + + return fParent->GetNext(fLogicalBlock); + } + + if (++fLogicalBlock > fNumBlocks) + return B_ENTRY_NOT_FOUND; + return B_OK; } diff --git a/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.h b/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.h index 93cf4f5b2e..b65cb05777 100644 --- a/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.h +++ b/src/add-ons/kernel/file_systems/ext2/DirectoryIterator.h @@ -8,26 +8,74 @@ #include +#include "Transaction.h" + +class HTreeEntryIterator; class Inode; class DirectoryIterator { public: - DirectoryIterator(Inode* inode); - virtual ~DirectoryIterator(); + DirectoryIterator(Inode* inode, off_t start = 0, + HTreeEntryIterator* parent = NULL); + ~DirectoryIterator(); - virtual status_t GetNext(char* name, size_t* _nameLength, ino_t* id); + status_t InitCheck(); - virtual status_t Rewind(); + + status_t Next(); + status_t Get(char* name, size_t* _nameLength, ino_t* id); + + status_t Rewind(); + void Restart(); + + status_t AddEntry(Transaction& transaction, const char* name, + size_t nameLength, ino_t id, uint8 type); + status_t FindEntry(const char* name, ino_t* id = NULL); + status_t RemoveEntry(Transaction& transaction); + + status_t ChangeEntry(Transaction& transaction, ino_t id, + uint8 fileType); private: DirectoryIterator(const DirectoryIterator&); DirectoryIterator &operator=(const DirectoryIterator&); // no implementation + protected: - Inode* fInode; - off_t fOffset; + status_t _AllocateBestEntryInBlock(uint8 nameLength, uint16& pos, + uint16& newLength); + status_t _AddEntry(Transaction& transaction, const char* name, + uint8 nameLength, ino_t id, uint8 fileType, + uint16 newLength, uint16 pos, + bool hasPrevious = true); + status_t _SplitIndexedBlock(Transaction& transaction, + const char* name, uint8 nameLength, ino_t id, + uint8 type, uint32 newBlocksPos, + bool firstSplit = false); + + status_t _NextBlock(); + + + Inode* fDirectory; + Volume* fVolume; + uint32 fBlockSize; + HTreeEntryIterator* fParent; + bool fIndexing; + + uint32 fNumBlocks; + uint32 fLogicalBlock; + uint32 fPhysicalBlock; + uint32 fDisplacement; + uint32 fPreviousDisplacement; + + uint32 fStartPhysicalBlock; + uint32 fStartLogicalBlock; + uint32 fStartDisplacement; + + status_t fInitStatus; }; #endif // DIRECTORY_ITERATOR_H + diff --git a/src/add-ons/kernel/file_systems/ext2/HTree.cpp b/src/add-ons/kernel/file_systems/ext2/HTree.cpp index 3c4cb9f4e3..deb824451e 100644 --- a/src/add-ons/kernel/file_systems/ext2/HTree.cpp +++ b/src/add-ons/kernel/file_systems/ext2/HTree.cpp @@ -7,6 +7,7 @@ */ +#include "CachedBlock.h" #include "HTree.h" #include @@ -17,6 +18,8 @@ #include "Volume.h" +//#define COLLISION_TEST + //#define TRACE_EXT2 #ifdef TRACE_EXT2 # define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) @@ -78,87 +81,125 @@ HTree::~HTree() } +status_t +HTree::PrepareForHash() +{ + uint32 blockNum; + status_t status = fDirectory->FindBlock(0, blockNum); + if (status != B_OK) + return status; + + CachedBlock cached(fDirectory->GetVolume()); + const uint8* block = cached.SetTo(blockNum); + + HTreeRoot* root = (HTreeRoot*)block; + + if (root == NULL) + return B_IO_ERROR; + if (!root->IsValid()) + return B_BAD_DATA; + + fHashVersion = root->hash_version; + + return B_OK; +} + + status_t HTree::Lookup(const char* name, DirectoryIterator** iterator) { + TRACE("HTree::Lookup()\n"); if (!fIndexed || (name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '0')))) { // No HTree support or looking for trivial directories - // TODO: Does these directories get hashed? - *iterator = new(std::nothrow) DirectoryIterator(fDirectory); - - if (*iterator == NULL) - return B_NO_MEMORY; - return B_OK; + return _FallbackToLinearIteration(iterator); } - HTreeRoot root; - size_t length = sizeof(root); + uint32 blockNum; + status_t status = fDirectory->FindBlock(0, blockNum); + if (status != B_OK) + return _FallbackToLinearIteration(iterator); + + CachedBlock cached(fDirectory->GetVolume()); + const uint8* block = cached.SetTo(blockNum); + + HTreeRoot* root = (HTreeRoot*)block; - status_t status = fDirectory->ReadAt(0, (uint8*)&root, &length); - if (status < B_OK) - return status; + if (root == NULL || !root->IsValid()) + return _FallbackToLinearIteration(iterator); + + fHashVersion = root->hash_version; - if (length != sizeof(root) || !root.IsValid()) { - // Fallback to linear search - *iterator = new(std::nothrow) DirectoryIterator(fDirectory); - if (*iterator == NULL) - return B_NO_MEMORY; - - return B_OK; - } + size_t _nameLength = strlen(name); + uint8 nameLength = _nameLength >= 256 ? 255 : (uint8)_nameLength; + + uint32 hash = Hash(name, nameLength); - uint32 hash = _Hash(name, root.hash_version); - - off_t start = (off_t)root.root_info_length + off_t start = (off_t)root->root_info_length + 2 * (sizeof(HTreeFakeDirEntry) + 4); + fRootEntryDeleter.Unset(); + fRootEntry = new(std::nothrow) HTreeEntryIterator(start, fDirectory); if (fRootEntry == NULL) return B_NO_MEMORY; + + fRootEntryDeleter.SetTo(fRootEntry); - fRootDeleter.SetTo(fRootEntry); status = fRootEntry->Init(); if (status != B_OK) return status; - return fRootEntry->Lookup(hash, (uint32)root.indirection_levels, iterator); + bool detachRoot = false; + status = fRootEntry->Lookup(hash, (uint32)root->indirection_levels, + iterator, detachRoot); + TRACE("HTree::Lookup(): detach root: %c\n", detachRoot ? 't' : 'f'); + + if (detachRoot) + fRootEntryDeleter.Detach(); + + return status; } uint32 -HTree::_Hash(const char* name, uint8 version) +HTree::Hash(const char* name, uint8 length) { uint32 hash; - switch (version) { +#ifndef COLLISION_TEST + switch (fHashVersion) { case HTREE_HASH_LEGACY: - hash = _HashLegacy(name); + hash = _HashLegacy(name, length); break; case HTREE_HASH_HALF_MD4: - hash = _HashHalfMD4(name); + hash = _HashHalfMD4(name, length); break; case HTREE_HASH_TEA: - hash = _HashTEA(name); + hash = _HashTEA(name, length); break; default: panic("Hash verification succeeded but then failed?"); hash = 0; }; +#else + hash = 0; +#endif - TRACE("Filename hash: %u\n", hash); + TRACE("HTree::_Hash(): filename hash 0x%lX\n", hash); return hash & ~1; } uint32 -HTree::_HashLegacy(const char* name) +HTree::_HashLegacy(const char* name, uint8 length) { + TRACE("HTree::_HashLegacy()\n"); uint32 hash = 0x12a3fe2d; uint32 previous = 0x37abe8f9; - for (; *name != '\0'; ++name) { + for (; length > 0; --length, ++name) { uint32 next = previous + (hash ^ (*name * 7152373)); if ((next & 0x80000000) != 0) @@ -168,7 +209,7 @@ HTree::_HashLegacy(const char* name) hash = next; } - return hash; + return hash << 1; } @@ -230,8 +271,8 @@ HTree::_HalfMD4Transform(uint32 buffer[4], uint32 blocks[8]) shifts[2] = 9; shifts[3] = 13; - for (int j = 0; j < 2; ++j) { - for (int i = j; i < 4; i += 2) { + for (int j = 1; j >= 0; --j) { + for (int i = j; i < 8; i += 2) { a += _MD4G(b, c, d) + blocks[i] + 013240474631UL; uint32 shift = shifts[i / 2]; a = (a << shift) | (a >> (32 - shift)); @@ -247,13 +288,13 @@ HTree::_HalfMD4Transform(uint32 buffer[4], uint32 blocks[8]) for (int i = 0; i < 4; ++i) { a += _MD4H(b, c, d) + blocks[3 - i] + 015666365641UL; - uint32 shift = shifts[i*2]; + uint32 shift = shifts[i * 2 % 4]; a = (a << shift) | (a >> (32 - shift)); _MD4RotateVars(a, b, c, d); a += _MD4H(b, c, d) + blocks[7 - i] + 015666365641UL; - shift = shifts[i*2 + 1]; + shift = shifts[(i * 2 + 1) % 4]; a = (a << shift) | (a >> (32 - shift)); _MD4RotateVars(a, b, c, d); @@ -267,19 +308,21 @@ HTree::_HalfMD4Transform(uint32 buffer[4], uint32 blocks[8]) uint32 -HTree::_HashHalfMD4(const char* name) +HTree::_HashHalfMD4(const char* name, uint8 _length) { + TRACE("HTree::_HashHalfMD4()\n"); uint32 buffer[4]; + int32 length = (uint32)_length; buffer[0] = fHashSeed[0]; buffer[1] = fHashSeed[1]; buffer[2] = fHashSeed[2]; buffer[3] = fHashSeed[3]; - for (int length = strlen(name); length > 0; length -= 32) { + for (; length > 0; length -= 32) { uint32 blocks[8]; - _PrepareBlocksForHash(name, length, blocks, 8); + _PrepareBlocksForHash(name, (uint32)length, blocks, 8); _HalfMD4Transform(buffer, blocks); name += 32; @@ -316,19 +359,21 @@ HTree::_TEATransform(uint32 buffer[4], uint32 blocks[4]) uint32 -HTree::_HashTEA(const char* name) +HTree::_HashTEA(const char* name, uint8 _length) { + TRACE("HTree::_HashTEA()\n"); uint32 buffer[4]; + int32 length = _length; buffer[0] = fHashSeed[0]; buffer[1] = fHashSeed[1]; buffer[2] = fHashSeed[2]; buffer[3] = fHashSeed[3]; - for (int length = strlen(name); length > 0; length -= 16) { + for (; length > 0; length -= 16) { uint32 blocks[4]; - _PrepareBlocksForHash(name, length, blocks, 4); + _PrepareBlocksForHash(name, (uint32)length, blocks, 4); TRACE("_HashTEA %lx %lx %lx\n", blocks[0], blocks[1], blocks[2]); _TEATransform(buffer, blocks); @@ -340,21 +385,21 @@ HTree::_HashTEA(const char* name) void -HTree::_PrepareBlocksForHash(const char* string, int length, uint32* blocks, - int numBlocks) +HTree::_PrepareBlocksForHash(const char* string, uint32 length, uint32* blocks, + uint32 numBlocks) { uint32 padding = (uint32)length; padding = (padding << 8) | padding; padding = (padding << 16) | padding; - int numBytes = numBlocks * 4; + uint32 numBytes = numBlocks * 4; if (length > numBytes) length = numBytes; - int completeIterations = length / 4; + uint32 completeIterations = length / 4; - for (int i = 0; i < completeIterations; ++i) { - uint32 value = 0 | *(string++); + for (uint32 i = 0; i < completeIterations; ++i) { + uint32 value = (padding << 8) | *(string++); value = (value << 8) | *(string++); value = (value << 8) | *(string++); value = (value << 8) | *(string++); @@ -362,15 +407,24 @@ HTree::_PrepareBlocksForHash(const char* string, int length, uint32* blocks, } if (completeIterations < numBlocks) { - int remainingBytes = length % 4; + uint32 remainingBytes = length % 4; uint32 value = padding; - for (int i = 0; i < remainingBytes; ++i) + for (uint32 i = 0; i < remainingBytes; ++i) value = (value << 8) + *(string++); blocks[completeIterations] = value; - for (int i = completeIterations + 1; i < numBlocks; ++i) + for (uint32 i = completeIterations + 1; i < numBlocks; ++i) blocks[i] = padding; } } + + +/*inline*/ status_t +HTree::_FallbackToLinearIteration(DirectoryIterator** iterator) +{ + *iterator = new(std::nothrow) DirectoryIterator(fDirectory); + + return *iterator == NULL ? B_NO_MEMORY : B_OK; +} diff --git a/src/add-ons/kernel/file_systems/ext2/HTree.h b/src/add-ons/kernel/file_systems/ext2/HTree.h index baebbc2095..384ac52123 100644 --- a/src/add-ons/kernel/file_systems/ext2/HTree.h +++ b/src/add-ons/kernel/file_systems/ext2/HTree.h @@ -26,14 +26,18 @@ struct JournalRevokeHeader; struct HTreeFakeDirEntry { - uint32 inode_num; + uint32 inode_id; uint16 entry_length; uint8 name_length; uint8 file_type; char file_name[0]; - uint32 Inode() - { return B_LENDIAN_TO_HOST_INT32(inode_num); } + uint32 InodeID() const + { return B_LENDIAN_TO_HOST_INT32(inode_id); } + + + void SetEntryLength(uint16 entryLength) + { entry_length = B_HOST_TO_LENDIAN_INT16(entryLength); } } _PACKED; struct HTreeCountLimit { @@ -41,9 +45,17 @@ struct HTreeCountLimit { uint16 count; uint16 Limit() const - { return B_LENDIAN_TO_HOST_INT32(limit); } + { return B_LENDIAN_TO_HOST_INT16(limit); } uint16 Count() const - { return B_LENDIAN_TO_HOST_INT32(count); } + { return B_LENDIAN_TO_HOST_INT16(count); } + bool IsFull() const + { return limit == count; } + + void SetLimit(uint16 value) + { limit = B_HOST_TO_LENDIAN_INT16(value); } + + void SetCount(uint16 value) + { count = B_HOST_TO_LENDIAN_INT16(value); } } _PACKED; struct HTreeEntry { @@ -54,6 +66,12 @@ struct HTreeEntry { { return B_LENDIAN_TO_HOST_INT32(hash); } uint32 Block() const { return B_LENDIAN_TO_HOST_INT32(block); } + + void SetHash(uint32 newHash) + { hash = B_HOST_TO_LENDIAN_INT32(newHash); } + + void SetBlock(uint32 newBlock) + { block = B_HOST_TO_LENDIAN_INT32(newBlock); } } _PACKED; struct HTreeRoot { @@ -67,6 +85,8 @@ struct HTreeRoot { uint8 root_info_length; uint8 indirection_levels; uint8 flags; + + HTreeCountLimit count_limit[0]; bool IsValid() const; // Implemented in HTree.cpp @@ -94,17 +114,21 @@ public: HTree(Volume* volume, Inode* directory); ~HTree(); + status_t PrepareForHash(); + uint32 Hash(const char* name, uint8 length); + status_t Lookup(const char* name, DirectoryIterator** directory); + static status_t InitDir(Transaction& transaction, Inode* inode, + Inode* parent); + private: status_t _LookupInNode(uint32 hash, off_t& firstEntry, off_t& lastEntry, uint32 remainingIndirects); - uint32 _Hash(const char* name, uint8 version); - - uint32 _HashLegacy(const char* name); + uint32 _HashLegacy(const char* name, uint8 length); inline uint32 _MD4F(uint32 x, uint32 y, uint32 z); inline uint32 _MD4G(uint32 x, uint32 y, uint32 z); @@ -113,21 +137,25 @@ private: uint32& c, uint32& d); void _HalfMD4Transform(uint32 buffer[4], uint32 blocks[8]); - uint32 _HashHalfMD4(const char* name); + uint32 _HashHalfMD4(const char* name, uint8 length); void _TEATransform(uint32 buffer[4], uint32 blocks[4]); - uint32 _HashTEA(const char* name); + uint32 _HashTEA(const char* name, uint8 length); void _PrepareBlocksForHash(const char* string, - int length, uint32* blocks, int numBlocks); + uint32 length, uint32* blocks, uint32 numBlocks); + + inline status_t _FallbackToLinearIteration( + DirectoryIterator** iterator); bool fIndexed; uint32 fBlockSize; Inode* fDirectory; + uint8 fHashVersion; uint32 fHashSeed[4]; HTreeEntryIterator* fRootEntry; - ObjectDeleter fRootDeleter; + ObjectDeleter fRootEntryDeleter; }; #endif // HTREE_H diff --git a/src/add-ons/kernel/file_systems/ext2/HTreeEntryIterator.cpp b/src/add-ons/kernel/file_systems/ext2/HTreeEntryIterator.cpp index 05b08662cf..6b298b3c43 100644 --- a/src/add-ons/kernel/file_systems/ext2/HTreeEntryIterator.cpp +++ b/src/add-ons/kernel/file_systems/ext2/HTreeEntryIterator.cpp @@ -11,8 +11,8 @@ #include +#include "CachedBlock.h" #include "HTree.h" -#include "IndexedDirectoryIterator.h" #include "Inode.h" @@ -27,194 +27,340 @@ HTreeEntryIterator::HTreeEntryIterator(off_t offset, Inode* directory) : - fHasCollision(false), fDirectory(directory), - fOffset(offset), + fVolume(directory->GetVolume()), + fHasCollision(false), + fBlockSize(directory->GetVolume()->BlockSize()), fParent(NULL), fChild(NULL) { - fBlockSize = fDirectory->GetVolume()->BlockSize(); + fInitStatus = fDirectory->FindBlock(offset, fBlockNum); + + if (fInitStatus == B_OK) { + fFirstEntry = offset % fBlockSize / sizeof(HTreeEntry); + fCurrentEntry = fFirstEntry; + } + + TRACE("HTreeEntryIterator::HTreeEntryIterator(): created %p, block %lu, " + "entry no. %lu, parent: %p\n", this, fBlockNum, (uint32)fCurrentEntry, + fParent); } HTreeEntryIterator::HTreeEntryIterator(uint32 block, uint32 blockSize, Inode* directory, HTreeEntryIterator* parent, bool hasCollision) : - fHasCollision(hasCollision), - fBlockSize(blockSize), fDirectory(directory), - fOffset(block * blockSize + sizeof(HTreeFakeDirEntry)), + fVolume(directory->GetVolume()), + fHasCollision(hasCollision), + fFirstEntry(1), + fCurrentEntry(1), + fBlockSize(blockSize), + fBlockNum(block), fParent(parent), fChild(NULL) { - TRACE("HTreeEntryIterator::HTreeEntryIterator() block %ld offset %Lx\n", - block, fOffset); + // fCurrentEntry is initialized to 1 to skip the fake directory entry + fInitStatus = B_OK; + + TRACE("HTreeEntryIterator::HTreeEntryIterator(): created %p, block %lu, " + "parent: %p\n", this, block, fParent); } status_t HTreeEntryIterator::Init() { - size_t length = sizeof(HTreeCountLimit); - HTreeCountLimit countLimit; + TRACE("HTreeEntryIterator::Init() first entry: %lu\n", + (uint32)fFirstEntry); - status_t status = fDirectory->ReadAt(fOffset, (uint8*)&countLimit, - &length); - - if (status != B_OK) - return status; - - if (length != sizeof(HTreeCountLimit)) { - ERROR("HTreeEntryIterator::Init() bad length %ld fOffset 0x%Lx\n", - length, fOffset); + if (fInitStatus != B_OK) + return fInitStatus; + + CachedBlock cached(fVolume); + const uint8* block = cached.SetTo(fBlockNum); + if (block == NULL) { + ERROR("Failed to read htree entry block.\n"); fCount = fLimit = 0; - return B_ERROR; + return B_IO_ERROR; } - - fCount = countLimit.Count(); - fLimit = countLimit.Limit(); + + HTreeCountLimit* countLimit = (HTreeCountLimit*)( + &((HTreeEntry*)block)[fFirstEntry]); + + fCount = countLimit->Count(); + fLimit = countLimit->Limit(); if (fCount >= fLimit) { - ERROR("HTreeEntryIterator::Init() bad fCount %d fOffset 0x%Lx\n", - fCount, fOffset); + ERROR("HTreeEntryIterator::Init() bad fCount %lu\n", (uint32)fCount); fCount = fLimit = 0; return B_ERROR; } - if (fParent != NULL && - fLimit != ((fBlockSize - sizeof(HTreeFakeDirEntry)) - / sizeof(HTreeEntry)) ) { - ERROR("HTreeEntryIterator::Init() bad fLimit %d should be %ld " - "fOffset 0x%Lx\n", fLimit, (fBlockSize - sizeof(HTreeFakeDirEntry)) - / sizeof(HTreeEntry), fOffset); + if (fLimit != fBlockSize / sizeof(HTreeEntry) - fFirstEntry) { + ERROR("HTreeEntryIterator::Init() bad fLimit %lu should be %lu " + "at block %lu\n", (uint32)fLimit, fBlockSize / sizeof(HTreeEntry) + - fFirstEntry, fBlockNum); fCount = fLimit = 0; return B_ERROR; - } + } - TRACE("HTreeEntryIterator::Init() count 0x%x limit 0x%x\n", fCount, - fLimit); + TRACE("HTreeEntryIterator::Init() count %lu limit %lu\n", + (uint32)fCount, (uint32)fLimit); - fMaxOffset = fOffset + (fCount - 1) * sizeof(HTreeEntry); - return B_OK; } HTreeEntryIterator::~HTreeEntryIterator() { + TRACE("HTreeEntryIterator::~HTreeEntryIterator(): %p, parent: %p\n", this, + fParent); + delete fParent; + TRACE("HTreeEntryIterator::~HTreeEntryIterator(): Deleted the parent\n"); } status_t HTreeEntryIterator::Lookup(uint32 hash, int indirections, - DirectoryIterator** directoryIterator) + DirectoryIterator** directoryIterator, bool& detachRoot) { - off_t start = fOffset + sizeof(HTreeEntry); - off_t end = fMaxOffset; - off_t middle = start; - size_t entrySize = sizeof(HTreeEntry); - HTreeEntry entry; - - while (start <= end) { - middle = (end - start) / 2; - middle -= middle % entrySize; // Alignment - middle += start; + TRACE("HTreeEntryIterator::Lookup()\n"); - TRACE("HTreeEntryIterator::Lookup() %d 0x%Lx 0x%Lx 0x%Lx\n", - indirections, start, end, middle); - - status_t status = fDirectory->ReadAt(middle, (uint8*)&entry, - &entrySize); + if (fCount == 0) + return B_ENTRY_NOT_FOUND; - TRACE("HTreeEntryIterator::Lookup() %lx %lx\n", hash, entry.Hash()); - - if (status != B_OK) - return status; - else if (entrySize != sizeof(entry)) { - // Fallback to linear search - *directoryIterator = new(std::nothrow) - DirectoryIterator(fDirectory); - - if (*directoryIterator == NULL) - return B_NO_MEMORY; - - return B_OK; - } - - if (hash >= entry.Hash()) - start = middle + entrySize; - else - end = middle - entrySize; - } - - status_t status = fDirectory->ReadAt(start - entrySize, (uint8*)&entry, - &entrySize); - if (status != B_OK) - return status; - - if (indirections == 0) { + CachedBlock cached(fVolume); + const uint8* block = cached.SetTo(fBlockNum); + if (block == NULL) { + ERROR("Failed to read htree entry block.\n"); + // Fallback to linear search *directoryIterator = new(std::nothrow) - IndexedDirectoryIterator(entry.Block() * fBlockSize, fBlockSize, - fDirectory, this); - + DirectoryIterator(fDirectory); + if (*directoryIterator == NULL) return B_NO_MEMORY; return B_OK; } + HTreeEntry* start = (HTreeEntry*)block + fCurrentEntry + 1; + HTreeEntry* end = (HTreeEntry*)block + fCount + fFirstEntry - 1; + HTreeEntry* middle = start; + + TRACE("HTreeEntryIterator::Lookup() current entry: %lu\n", + (uint32)fCurrentEntry); + TRACE("HTreeEntryIterator::Lookup() indirections: %d s:%p m:%p e:%p\n", + indirections, start, middle, end); + + while (start <= end) { + middle = (HTreeEntry*)((end - start) / 2 + + start); + + TRACE("HTreeEntryIterator::Lookup() indirections: %d s:%p m:%p e:%p\n", + indirections, start, middle, end); + + TRACE("HTreeEntryIterator::Lookup() %lx %lx\n", hash, middle->Hash()); + + if (hash >= middle->Hash()) + start = middle + 1; + else + end = middle - 1; + } + + --start; + + fCurrentEntry = ((uint8*)start - block) / sizeof(HTreeEntry); + + if (indirections == 0) { + TRACE("HTreeEntryIterator::Lookup(): Creating an indexed directory " + "iterator starting at block: %lu, hash: 0x%lX\n", start->Block(), + start->Hash()); + *directoryIterator = new(std::nothrow) + DirectoryIterator(fDirectory, start->Block() * fBlockSize, this); + + if (*directoryIterator == NULL) + return B_NO_MEMORY; + + detachRoot = true; + return B_OK; + } + + TRACE("HTreeEntryIterator::Lookup(): Creating a HTree entry iterator " + "starting at block: %lu, hash: 0x%lX\n", start->Block(), start->Hash()); + uint32 blockNum; + status_t status = fDirectory->FindBlock(start->Block() * fBlockSize, + blockNum); + if (status != B_OK) + return status; + delete fChild; - fChild = new(std::nothrow) HTreeEntryIterator(entry.Block(), fBlockSize, - fDirectory, this, entry.Hash() & 1 == 1); - + fChild = new(std::nothrow) HTreeEntryIterator(blockNum, fBlockSize, + fDirectory, this, (start->Hash() & 1) == 1); if (fChild == NULL) return B_NO_MEMORY; - fChildDeleter.SetTo(fChild); status = fChild->Init(); if (status != B_OK) return status; - return fChild->Lookup(hash, indirections - 1, directoryIterator); + return fChild->Lookup(hash, indirections - 1, directoryIterator, + detachRoot); } status_t -HTreeEntryIterator::GetNext(off_t& childOffset) +HTreeEntryIterator::GetNext(uint32& childBlock) { - size_t entrySize = sizeof(HTreeEntry); - fOffset += entrySize; - bool firstEntry = fOffset >= fMaxOffset; - - if (firstEntry) { - if (fParent == NULL) + fCurrentEntry++; + TRACE("HTreeEntryIterator::GetNext(): current entry: %lu count: %lu, " + "limit: %lu\n", (uint32)fCurrentEntry, (uint32)fCount, (uint32)fLimit); + bool endOfBlock = fCurrentEntry >= (fCount + fFirstEntry); + + if (endOfBlock) { + TRACE("HTreeEntryIterator::GetNext(): end of entries in the block\n"); + if (fParent == NULL) { + TRACE("HTreeEntryIterator::GetNext(): block was the root block\n"); return B_ENTRY_NOT_FOUND; + } - status_t status = fParent->GetNext(fOffset); + uint32 logicalBlock; + status_t status = fParent->GetNext(logicalBlock); if (status != B_OK) return status; + TRACE("HTreeEntryIterator::GetNext(): moving to next block: %lu\n", + logicalBlock); + + status = fDirectory->FindBlock(logicalBlock * fBlockSize, fBlockNum); + if (status != B_OK) + return status; + + fFirstEntry = 1; // Skip fake directory entry + fCurrentEntry = 1; status = Init(); if (status != B_OK) return status; fHasCollision = fParent->HasCollision(); } + + CachedBlock cached(fVolume); + const uint8* block = cached.SetTo(fBlockNum); + if (block == NULL) + return B_IO_ERROR; + + HTreeEntry* entry = &((HTreeEntry*)block)[fCurrentEntry]; + + if (!endOfBlock) + fHasCollision = (entry[fCurrentEntry].Hash() & 1) == 1; - HTreeEntry entry; - status_t status = fDirectory->ReadAt(fOffset, (uint8*)&entry, &entrySize); - if (status != B_OK) - return status; - else if (entrySize != sizeof(entry)) { - // Weird error, try to skip it - return GetNext(childOffset); - } - - if (!firstEntry) - fHasCollision = (entry.Hash() & 1) == 1; - - childOffset = entry.Block() * fBlockSize; + TRACE("HTreeEntryIterator::GetNext(): next block: %lu\n", + entry->Block()); + + childBlock = entry->Block(); return B_OK; } + + +uint32 +HTreeEntryIterator::BlocksNeededForNewEntry() +{ + TRACE("HTreeEntryIterator::BlocksNeededForNewEntry(): block num: %lu, " + "volume: %p\n", fBlockNum, fVolume); + CachedBlock cached(fVolume); + + const uint8* blockData = cached.SetTo(fBlockNum); + const HTreeEntry* entries = (const HTreeEntry*)blockData; + const HTreeCountLimit* countLimit = + (const HTreeCountLimit*)&entries[fFirstEntry]; + + uint32 newBlocks = 0; + if (countLimit->IsFull()) { + newBlocks++; + + if (fParent != NULL) + newBlocks += fParent->BlocksNeededForNewEntry(); + else { + // Need a new level + HTreeRoot* root = (HTreeRoot*)entries; + + if (root->indirection_levels == 1) { + // Maximum supported indirection levels reached + return B_DEVICE_FULL; + } + + newBlocks++; + } + } + + return newBlocks; +} + + +status_t +HTreeEntryIterator::InsertEntry(Transaction& transaction, uint32 hash, + uint32 blockNum, uint32 newBlocksPos, bool hasCollision) +{ + TRACE("HTreeEntryIterator::InsertEntry(): block num: %lu\n", fBlockNum); + CachedBlock cached(fVolume); + + uint8* blockData = cached.SetToWritable(transaction, fBlockNum); + if (blockData == NULL) + return B_IO_ERROR; + + HTreeEntry* entries = (HTreeEntry*)blockData; + + HTreeCountLimit* countLimit = (HTreeCountLimit*)&entries[fFirstEntry]; + uint16 count = countLimit->Count(); + + if (count == countLimit->Limit()) { + TRACE("HTreeEntryIterator::InsertEntry(): Splitting the node\n"); + panic("Splitting a HTree node required, but isn't yet fully " + "supported\n"); + + uint32 physicalBlock; + status_t status = fDirectory->FindBlock(newBlocksPos, physicalBlock); + if (status != B_OK) + return status; + + CachedBlock secondCached(fVolume); + uint8* secondBlockData = secondCached.SetToWritable(transaction, + physicalBlock); + if (secondBlockData == NULL) + return B_IO_ERROR; + + HTreeFakeDirEntry* fakeEntry = (HTreeFakeDirEntry*)secondBlockData; + fakeEntry->inode_id = 0; // ? + fakeEntry->SetEntryLength(fBlockSize); + fakeEntry->name_length = 0; + fakeEntry->file_type = 0; // ? + + HTreeEntry* secondBlockEntries = (HTreeEntry*)secondBlockData; + memmove(&entries[fFirstEntry + count / 2], &secondBlockEntries[1], + (count - count / 2) * sizeof(HTreeEntry)); + } + + TRACE("HTreeEntryIterator::InsertEntry(): Inserting node. Count: %u, " + "current entry: %lu\n", (uint16)count, (uint32)fCurrentEntry); + + if (count > 0) { + TRACE("HTreeEntryIterator::InsertEntry(): memmove(%lu, %lu, %lu)\n", + fCurrentEntry + 2, fCurrentEntry + 1, count + fFirstEntry + - fCurrentEntry - 1); + memmove(&entries[fCurrentEntry + 2], &entries[fCurrentEntry + 1], + (count + fFirstEntry - fCurrentEntry - 1) * sizeof(HTreeEntry)); + } + + uint32 oldHash = entries[fCurrentEntry].Hash(); + entries[fCurrentEntry].SetHash(hasCollision ? oldHash | 1 : oldHash & ~1); + entries[fCurrentEntry + 1].SetHash((oldHash & 1) == 0 ? hash & ~1 + : hash | 1); + entries[fCurrentEntry + 1].SetBlock(blockNum); + + countLimit->SetCount(count + 1); + + return B_OK; +} diff --git a/src/add-ons/kernel/file_systems/ext2/HTreeEntryIterator.h b/src/add-ons/kernel/file_systems/ext2/HTreeEntryIterator.h index 6f11f5c1e8..f9f432a932 100644 --- a/src/add-ons/kernel/file_systems/ext2/HTreeEntryIterator.h +++ b/src/add-ons/kernel/file_systems/ext2/HTreeEntryIterator.h @@ -14,6 +14,9 @@ #include "DirectoryIterator.h" +class Volume; + + class HTreeEntryIterator { public: HTreeEntryIterator(off_t offset, @@ -23,10 +26,16 @@ public: status_t Init(); status_t Lookup(uint32 hash, int indirections, - DirectoryIterator** iterator); + DirectoryIterator** iterator, + bool& detachRoot); bool HasCollision() { return fHasCollision; } - status_t GetNext(off_t& offset); + status_t GetNext(uint32& offset); + + uint32 BlocksNeededForNewEntry(); + status_t InsertEntry(Transaction& transaction, + uint32 hash, uint32 block, + uint32 newBlocksPos, bool hasCollision); private: HTreeEntryIterator(uint32 block, uint32 blockSize, Inode* directory, @@ -34,17 +43,20 @@ private: bool hasCollision); private: + Inode* fDirectory; + Volume* fVolume; + status_t fInitStatus; + bool fHasCollision; uint16 fLimit, fCount; + uint16 fFirstEntry; + uint16 fCurrentEntry; uint32 fBlockSize; - Inode* fDirectory; - off_t fOffset; - off_t fMaxOffset; + uint32 fBlockNum; HTreeEntryIterator* fParent; HTreeEntryIterator* fChild; - ObjectDeleter fChildDeleter; }; #endif // HTREE_ENTRY_ITERATOR_H diff --git a/src/add-ons/kernel/file_systems/ext2/HashRevokeManager.cpp b/src/add-ons/kernel/file_systems/ext2/HashRevokeManager.cpp new file mode 100644 index 0000000000..9fdaf7a887 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/HashRevokeManager.cpp @@ -0,0 +1,170 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ + + +#include "HashRevokeManager.h" + +#include + + +//#define TRACE_EXT2 +#ifdef TRACE_EXT2 +# define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) +#else +# define TRACE(x...) ; +#endif + + +HashRevokeManager::HashRevokeManager() + : + fHash(NULL), + kInitialHashSize(128) + // TODO: Benchmark and find an optimal value +{ +} + + +HashRevokeManager::~HashRevokeManager() +{ + if (fHash != NULL) { + if (fRevokeCount != 0) { + RevokeElement *element = + (RevokeElement*)hash_remove_first(fHash, NULL); + + while (element != NULL) { + delete element; + element = (RevokeElement*)hash_remove_first(fHash, NULL); + } + } + + hash_uninit(fHash); + } +} + + +status_t +HashRevokeManager::Init() +{ + RevokeElement dummyElement; + + fHash = hash_init(kInitialHashSize, offset_of_member(dummyElement, next), + &HashRevokeManager::Compare, + &HashRevokeManager::Hash); + + if (fHash == NULL) + return B_NO_MEMORY; + + return B_OK; +} + + +status_t +HashRevokeManager::Insert(uint32 block, uint32 commitID) +{ + RevokeElement* element = (RevokeElement*)hash_lookup(fHash, &block); + + if (element != NULL) { + TRACE("HashRevokeManager::Insert(): Already has an element\n"); + if (element->commitID < commitID) { + TRACE("HashRevokeManager::Insert(): Deleting previous element\n"); + status_t retValue = hash_remove(fHash, element); + + if (retValue != B_OK) + return retValue; + + delete element; + } + else { + return B_OK; + // We already have a newer version of the block + } + } + + return _ForceInsert(block, commitID); +} + + +status_t +HashRevokeManager::Remove(uint32 block) +{ + RevokeElement* element = (RevokeElement*)hash_lookup(fHash, &block); + + if (element == NULL) + return B_ERROR; // TODO: Perhaps we should just ignore? + + status_t retValue = hash_remove(fHash, element); + + if (retValue == B_OK) + delete element; + + return retValue; +} + + +bool +HashRevokeManager::Lookup(uint32 block, uint32 commitID) +{ + RevokeElement* element = (RevokeElement*)hash_lookup(fHash, &block); + + if (element == NULL) + return false; + + return element->commitID >= commitID; +} + + +/*static*/ int +HashRevokeManager::Compare(void* _revoked, const void *_block) +{ + RevokeElement* revoked = (RevokeElement*)_revoked; + uint32 block = *(uint32*)_block; + + if (revoked->block == block) + return 0; + + return (revoked->block > block) ? 1 : -1; +} + + +/*static*/ uint32 +HashRevokeManager::Hash(void* _revoked, const void* _block, uint32 range) +{ + TRACE("HashRevokeManager::Hash(): revoked: %p, block: %p, range: %lu\n", + _revoked, _block, range); + RevokeElement* revoked = (RevokeElement*)_revoked; + + if (revoked != NULL) + return revoked->block % range; + + uint32 block = *(uint32*)_block; + return block % range; +} + + +status_t +HashRevokeManager::_ForceInsert(uint32 block, uint32 commitID) +{ + RevokeElement* element = new(std::nothrow) RevokeElement; + + if (element == NULL) + return B_NO_MEMORY; + + element->block = block; + element->commitID = commitID; + + status_t retValue = hash_insert_grow(fHash, element); + + if (retValue == B_OK) { + fRevokeCount++; + TRACE("HashRevokeManager::_ForceInsert(): revoke count: %lu\n", + fRevokeCount); + } + + return retValue; +} + diff --git a/src/add-ons/kernel/file_systems/ext2/HashRevokeManager.h b/src/add-ons/kernel/file_systems/ext2/HashRevokeManager.h new file mode 100644 index 0000000000..ab1f3c4158 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/HashRevokeManager.h @@ -0,0 +1,47 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ +#ifndef HASHREVOKEMANAGER_H +#define HASHREVOKEMANAGER_H + +#include + +#include "RevokeManager.h" + + +struct RevokeElement { + RevokeElement* next; // Next in hash + uint32 block; + uint32 commitID; +}; + + +class HashRevokeManager : public RevokeManager { +public: + HashRevokeManager(); + virtual ~HashRevokeManager(); + + status_t Init(); + + virtual status_t Insert(uint32 block, uint32 commitID); + virtual status_t Remove(uint32 block); + virtual bool Lookup(uint32 block, uint32 commitID); + + static int Compare(void* element, const void* key); + static uint32 Hash(void* element, const void* key, uint32 range); + +protected: + status_t _ForceInsert(uint32 block, uint32 commitID); + +private: + hash_table* fHash; + + const int kInitialHashSize; +}; + +#endif // HASHREVOKEMANAGER_H + diff --git a/src/add-ons/kernel/file_systems/ext2/IndexedDirectoryIterator.cpp b/src/add-ons/kernel/file_systems/ext2/IndexedDirectoryIterator.cpp index 035d0f1869..e69de29bb2 100644 --- a/src/add-ons/kernel/file_systems/ext2/IndexedDirectoryIterator.cpp +++ b/src/add-ons/kernel/file_systems/ext2/IndexedDirectoryIterator.cpp @@ -1,75 +0,0 @@ -/* - * Copyright 2010, Haiku Inc. All rights reserved. - * This file may be used under the terms of the MIT License. - * - * Authors: - * Janito V. Ferreira Filho - */ - - -#include "IndexedDirectoryIterator.h" - -#include "ext2.h" -#include "HTree.h" -#include "HTreeEntryIterator.h" -#include "Inode.h" - - -//#define TRACE_EXT2 -#ifdef TRACE_EXT2 -# define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) -#else -# define TRACE(x...) ; -#endif - - -IndexedDirectoryIterator::IndexedDirectoryIterator(off_t start, - uint32 blockSize, Inode* directory, HTreeEntryIterator* parent) - : - DirectoryIterator(directory), - fIndexing(true), - - fParent(parent), - fMaxOffset(start + blockSize), - fBlockSize(blockSize), - fMaxAttempts(0) -{ - fOffset = start; -} - - -IndexedDirectoryIterator::~IndexedDirectoryIterator() -{ -} - - -status_t -IndexedDirectoryIterator::GetNext(char* name, size_t* nameLength, ino_t* id) -{ - if (fIndexing && fOffset + sizeof(HTreeFakeDirEntry) >= fMaxOffset) { - TRACE("IndexedDirectoryIterator::GetNext() calling next block\n"); - status_t status = fParent->GetNext(fOffset); - if (status != B_OK) - return status; - - if (fMaxAttempts++ > 4) - return B_ERROR; - - fMaxOffset = fOffset + fBlockSize; - } - - return DirectoryIterator::GetNext(name, nameLength, id); -} - - -status_t -IndexedDirectoryIterator::Rewind() -{ - // The only way to rewind it is too loose indexing - - fOffset = 0; - fMaxOffset = fInode->Size(); - fIndexing = false; - - return B_OK; -} diff --git a/src/add-ons/kernel/file_systems/ext2/IndexedDirectoryIterator.h b/src/add-ons/kernel/file_systems/ext2/IndexedDirectoryIterator.h index 8a4dabd3ca..e69de29bb2 100644 --- a/src/add-ons/kernel/file_systems/ext2/IndexedDirectoryIterator.h +++ b/src/add-ons/kernel/file_systems/ext2/IndexedDirectoryIterator.h @@ -1,36 +0,0 @@ -/* - * Copyright 2010, Haiku Inc. All rights reserved. - * This file may be used under the terms of the MIT License. - * - * Authors: - * Janito V. Ferreira Filho - */ -#ifndef INDEXED_DIRECTORY_ITERATOR_H -#define INDEXED_DIRECTORY_ITERATOR_H - - -#include "DirectoryIterator.h" - - -class HTreeEntryIterator; - -class IndexedDirectoryIterator : public DirectoryIterator { -public: - IndexedDirectoryIterator(off_t start, - uint32 blockSize, Inode* directory, - HTreeEntryIterator* parent); - virtual ~IndexedDirectoryIterator(); - - status_t GetNext(char* name, size_t* nameLength, - ino_t* id); - - status_t Rewind(); -private: - bool fIndexing; - HTreeEntryIterator* fParent; - off_t fMaxOffset; - uint32 fBlockSize; - uint32 fMaxAttempts; -}; - -#endif // INDEXED_DIRECTORY_ITERATOR_H diff --git a/src/add-ons/kernel/file_systems/ext2/Inode.cpp b/src/add-ons/kernel/file_systems/ext2/Inode.cpp index bf80136705..199e640a40 100644 --- a/src/add-ons/kernel/file_systems/ext2/Inode.cpp +++ b/src/add-ons/kernel/file_systems/ext2/Inode.cpp @@ -8,8 +8,13 @@ #include #include +#include #include "CachedBlock.h" +#include "DataStream.h" +#include "DirectoryIterator.h" +#include "HTree.h" +#include "Utility.h" //#define TRACE_EXT2 @@ -26,52 +31,147 @@ Inode::Inode(Volume* volume, ino_t id) fID(id), fCache(NULL), fMap(NULL), - fNode(NULL), + fCached(false), fAttributesBlock(NULL) { rw_lock_init(&fLock, "ext2 inode"); - uint32 block; - if (volume->GetInodeBlock(id, block) == B_OK) { - TRACE("inode %Ld at block %lu\n", ID(), block); - uint8* inodeBlock = (uint8*)block_cache_get(volume->BlockCache(), - block); - if (inodeBlock != NULL) { - fNode = (ext2_inode*)(inodeBlock + volume->InodeBlockIndex(id) - * volume->InodeSize()); - } - } + TRACE("Inode::Inode(): ext2_inode: %lu, disk inode: %lu\n", + sizeof(ext2_inode), fVolume->InodeSize()); + fNodeSize = sizeof(ext2_inode) > fVolume->InodeSize() + ? fVolume->InodeSize() : sizeof(ext2_inode); - if (fNode != NULL) { - // TODO: we don't need a cache for short symlinks - fCache = file_cache_create(fVolume->ID(), ID(), Size()); - fMap = file_map_create(fVolume->ID(), ID(), Size()); - } + fInitStatus = UpdateNodeFromDisk(); + if (fInitStatus == B_OK) { + if (IsDirectory() || (IsSymLink() && Size() < 60)) { + TRACE("Inode::Inode(): Not creating the file cache\n"); + fCached = false; + + fInitStatus = B_OK; + } else + fInitStatus = EnableFileCache(); + } else + TRACE("Inode: Failed initialization\n"); +} + + +Inode::Inode(Volume* volume) + : + fVolume(volume), + fID(0), + fCache(NULL), + fMap(NULL), + fCached(false), + fAttributesBlock(NULL), + fInitStatus(B_NO_INIT) +{ + rw_lock_init(&fLock, "ext2 inode"); + + TRACE("Inode::Inode(): ext2_inode: %lu, disk inode: %lu\n", + sizeof(ext2_inode), fVolume->InodeSize()); + fNodeSize = sizeof(ext2_inode) > fVolume->InodeSize() + ? fVolume->InodeSize() : sizeof(ext2_inode); } Inode::~Inode() { - file_cache_delete(FileCache()); - file_map_delete(Map()); + TRACE("Inode destructor\n"); + + if (fCached) { + TRACE("Deleting the file cache and file map\n"); + file_cache_delete(FileCache()); + file_map_delete(Map()); + } if (fAttributesBlock) { + TRACE("Returning the attributes block\n"); uint32 block = B_LENDIAN_TO_HOST_INT32(Node().file_access_control); block_cache_put(fVolume->BlockCache(), block); } - if (fNode != NULL) { - uint32 block; - if (fVolume->GetInodeBlock(ID(), block) == B_OK) - block_cache_put(fVolume->BlockCache(), block); - } + TRACE("Inode destructor: Done\n"); } status_t Inode::InitCheck() { - return fNode != NULL ? B_OK : B_ERROR; + return fInitStatus; +} + + +void +Inode::WriteLockInTransaction(Transaction& transaction) +{ + acquire_vnode(fVolume->FSVolume(), ID()); + + TRACE("Inode::WriteLockInTransaction(): Locking\n"); + rw_lock_write_lock(&fLock); + + transaction.AddListener(this); +} + + +status_t +Inode::WriteBack(Transaction& transaction) +{ + uint32 inodeBlock; + + status_t status = fVolume->GetInodeBlock(fID, inodeBlock); + if (status != B_OK) + return status; + + CachedBlock cached(fVolume); + uint8* inodeBlockData = cached.SetToWritable(transaction, inodeBlock); + if (inodeBlockData == NULL) + return B_IO_ERROR; + + TRACE("Inode::WriteBack(): Inode ID: %d, inode block: %lu, data: %p, " + "index: %lu, inode size: %lu, node size: %lu, this: %p, node: %p\n", + (int)fID, inodeBlock, inodeBlockData, fVolume->InodeBlockIndex(fID), + fVolume->InodeSize(), fNodeSize, this, &fNode); + memcpy(inodeBlockData + + fVolume->InodeBlockIndex(fID) * fVolume->InodeSize(), + (uint8*)&fNode, fNodeSize); + + TRACE("Inode::WriteBack() finished\n"); + + return B_OK; +} + + +status_t +Inode::UpdateNodeFromDisk() +{ + uint32 block; + + status_t status = fVolume->GetInodeBlock(fID, block); + if (status != B_OK) + return status; + + TRACE("inode %Ld at block %lu\n", fID, block); + + CachedBlock cached(fVolume); + const uint8* inodeBlock = cached.SetTo(block); + + if (inodeBlock == NULL) + return B_IO_ERROR; + + TRACE("Inode size: %lu, inode index: %lu\n", fVolume->InodeSize(), + fVolume->InodeBlockIndex(fID)); + ext2_inode* inode = (ext2_inode*)(inodeBlock + + fVolume->InodeBlockIndex(fID) * fVolume->InodeSize()); + + TRACE("Attempting to copy inode data from %p to %p, ext2_inode " + "size: %lu\n", inode, &fNode, fNodeSize); + + memcpy(&fNode, inode, fNodeSize); + + uint32 numLinks = fNode.NumLinks(); + fUnlinked = numLinks == 0 || (IsDirectory() && numLinks == 1); + + return B_OK; } @@ -95,9 +195,9 @@ Inode::CheckPermissions(int accessMode) const // shift mode bits, to check directly against accessMode mode_t mode = Mode(); - if (user == (uid_t)fNode->UserID()) + if (user == (uid_t)fNode.UserID()) mode >>= 6; - else if (group == (gid_t)fNode->GroupID()) + else if (group == (gid_t)fNode.GroupID()) mode >>= 3; if (accessMode & ~(mode & S_IRWXO)) @@ -114,8 +214,10 @@ Inode::FindBlock(off_t offset, uint32& block) uint32 perIndirectBlock = perBlock * perBlock; uint32 index = offset >> fVolume->BlockShift(); - if (offset >= Size()) + if (offset >= Size()) { + TRACE("FindBlock: offset larger than inode size\n"); return B_ENTRY_NOT_FOUND; + } // TODO: we could return the size of the sparse range, as this might be more // than just a block @@ -186,7 +288,7 @@ Inode::FindBlock(off_t offset, uint32& block) } } } else { - // outside of the possible data stream + // Outside of the possible data stream dprintf("ext2: block outside datastream!\n"); return B_ERROR; } @@ -203,7 +305,8 @@ Inode::ReadAt(off_t pos, uint8* buffer, size_t* _length) // set/check boundaries for pos/length if (pos < 0) { - TRACE("inode %Ld: ReadAt failed(pos %Ld, length %lu)\n", ID(), pos, length); + TRACE("inode %Ld: ReadAt failed(pos %Ld, length %lu)\n", ID(), pos, + length); return B_BAD_VALUE; } @@ -217,6 +320,149 @@ Inode::ReadAt(off_t pos, uint8* buffer, size_t* _length) } +status_t +Inode::WriteAt(Transaction& transaction, off_t pos, const uint8* buffer, + size_t* _length) +{ + TRACE("Inode::WriteAt(%lld, %p, *(%p) = %ld)\n", (long long)pos, buffer, + _length, (long)*_length); + ReadLocker readLocker(fLock); + + if (IsFileCacheDisabled()) + return B_BAD_VALUE; + + if (pos < 0) + return B_BAD_VALUE; + + readLocker.Unlock(); + + TRACE("Inode::WriteAt(): Starting transaction\n"); + transaction.Start(fVolume->GetJournal()); + + WriteLocker writeLocker(fLock); + + TRACE("Inode::WriteAt(): Updating modification time\n"); + fNode.SetModificationTime(real_time_clock()); + + // NOTE: Debugging info to find why sometimes resize doesn't happen + size_t length = *_length; + off_t oldEnd = pos + length; + TRACE("Inode::WriteAt(): Old calc for end? %x:%x\n", + (int)(oldEnd >> 32), (int)(oldEnd & 0xFFFFFFFF)); + + off_t end = pos + (off_t)length; + off_t oldSize = Size(); + + TRACE("Inode::WriteAt(): Old size: %x:%x, new size: %x:%x\n", + (int)(oldSize >> 32), (int)(oldSize & 0xFFFFFFFF), + (int)(end >> 32), (int)(end & 0xFFFFFFFF)); + + if (end > oldSize) { + status_t status = Resize(transaction, end); + if (status != B_OK) { + *_length = 0; + WriteLockInTransaction(transaction); + return status; + } + + status = WriteBack(transaction); + if (status != B_OK) { + *_length = 0; + WriteLockInTransaction(transaction); + return status; + } + } + + writeLocker.Unlock(); + + if (oldSize < pos) + FillGapWithZeros(oldSize, pos); + + if (length == 0) { + // Probably just changed the file size with the pos parameter + return B_OK; + } + + TRACE("Inode::WriteAt(): Performing write: %p, %d, %p, %d\n", + FileCache(), (int)pos, buffer, (int)*_length); + status_t status = file_cache_write(FileCache(), NULL, pos, buffer, _length); + + WriteLockInTransaction(transaction); + + TRACE("Inode::WriteAt(): Done\n"); + + return status; +} + + +status_t +Inode::FillGapWithZeros(off_t start, off_t end) +{ + TRACE("Inode::FileGapWithZeros(%ld - %ld)\n", (long)start, (long)end); + + while (start < end) { + size_t size; + + if (end > start + 1024 * 1024 * 1024) + size = 1024 * 1024 * 1024; + else + size = end - start; + + TRACE("Inode::FillGapWithZeros(): Calling file_cache_write(%p, NULL, " + "%ld, NULL, &(%ld) = %p)\n", fCache, (long)start, (long)size, + &size); + status_t status = file_cache_write(fCache, NULL, start, NULL, + &size); + if (status != B_OK) + return status; + + start += size; + } + + return B_OK; +} + + +status_t +Inode::Resize(Transaction& transaction, off_t size) +{ + TRACE("Inode::Resize(): size: %ld\n", (long)size); + if (size < 0) + return B_BAD_VALUE; + + off_t oldSize = Size(); + + if (size == oldSize) + return B_OK; + + TRACE("Inode::Resize(): old size: %ld, new size: %ld\n", (long)oldSize, + (long)size); + + status_t status; + if (size > oldSize) { + status = _EnlargeDataStream(transaction, size); + if (status != B_OK) { + // Restore original size + _ShrinkDataStream(transaction, oldSize); + } + } else + status = _ShrinkDataStream(transaction, size); + + TRACE("Inode::Resize(): Updating file map and cache\n"); + + if (status != B_OK) + return status; + + file_cache_set_size(FileCache(), size); + file_map_set_size(Map(), size); + + TRACE("Inode::Resize(): Writing back inode changes. Size: %ld\n", + (long)Size()); + + return WriteBack(transaction); +} + + status_t Inode::AttributeBlockReadAt(off_t pos, uint8* buffer, size_t* _length) { @@ -245,3 +491,445 @@ Inode::AttributeBlockReadAt(off_t pos, uint8* buffer, size_t* _length) *_length = length; return B_NO_ERROR; } + + +status_t +Inode::InitDirectory(Transaction& transaction, Inode* parent) +{ + TRACE("Inode::InitDirectory()\n"); + uint32 blockSize = fVolume->BlockSize(); + + status_t status = Resize(transaction, blockSize); + if (status != B_OK) + return status; + + uint32 blockNum; + status = FindBlock(0, blockNum); + if (status != B_OK) + return status; + + CachedBlock cached(fVolume); + uint8* block = cached.SetToWritable(transaction, blockNum, true); + + HTreeRoot* root = (HTreeRoot*)block; + root->dot.inode_id = fID; + root->dot.entry_length = 12; + root->dot.name_length = 1; + root->dot.file_type = EXT2_TYPE_DIRECTORY; + root->dot_entry_name[0] = '.'; + + root->dotdot.inode_id = parent == NULL ? fID : parent->ID(); + root->dotdot.entry_length = blockSize - 12; + root->dotdot.name_length = 2; + root->dotdot.file_type = EXT2_TYPE_DIRECTORY; + root->dotdot_entry_name[0] = '.'; + root->dotdot_entry_name[1] = '.'; + + parent->Node().SetNumLinks(parent->Node().NumLinks() + 1); + + return parent->WriteBack(transaction); +} + + +status_t +Inode::Unlink(Transaction& transaction) +{ + uint32 numLinks = fNode.NumLinks(); + TRACE("Inode::Unlink(): Current links: %lu\n", numLinks); + + if (numLinks == 0) + return B_BAD_VALUE; + + if ((IsDirectory() && numLinks == 2) || (numLinks == 1)) { + fUnlinked = true; + + TRACE("Inode::Unlink(): Putting inode in orphan list\n"); + ino_t firstOrphanID; + status_t status = fVolume->SaveOrphan(transaction, fID, firstOrphanID); + if (status != B_OK) + return status; + + if (firstOrphanID != 0) { + Vnode firstOrphan(fVolume, firstOrphanID); + Inode* nextOrphan; + + status = firstOrphan.Get(&nextOrphan); + if (status != B_OK) + return status; + + fNode.SetNextOrphan(nextOrphan->ID()); + } else { + // Next orphan link is stored in deletion time + fNode.deletion_time = 0; + } + + fNode.num_links = 0; + + status = remove_vnode(fVolume->FSVolume(), fID); + if (status != B_OK) + return status; + } else + fNode.SetNumLinks(--numLinks); + + return WriteBack(transaction); +} + + +/*static*/ status_t +Inode::Create(Transaction& transaction, Inode* parent, const char* name, + int32 mode, int openMode, uint8 type, bool* _created, ino_t* _id, + Inode** _inode, fs_vnode_ops* vnodeOps, uint32 publishFlags) +{ + TRACE("Inode::Create()\n"); + Volume* volume = transaction.GetVolume(); + + DirectoryIterator* entries = NULL; + ObjectDeleter entriesDeleter; + + if (parent != NULL) { + parent->WriteLockInTransaction(transaction); + + TRACE("Inode::Create(): Looking up entry destination\n"); + HTree htree(volume, parent); + + status_t status = htree.Lookup(name, &entries); + if (status == B_ENTRY_NOT_FOUND) { + panic("We need to add the first node.\n"); + return B_ERROR; + } + if (status != B_OK) + return status; + entriesDeleter.SetTo(entries); + + TRACE("Inode::Create(): Looking up to see if file already exists\n"); + ino_t entryID; + + status = entries->FindEntry(name, &entryID); + if (status == B_OK) { + // File already exists + TRACE("Inode::Create(): File already exists\n"); + if (S_ISDIR(mode) || S_ISLNK(mode) || (openMode & O_EXCL) != 0) + return B_FILE_EXISTS; + + Vnode vnode(volume, entryID); + Inode* inode; + + status = vnode.Get(&inode); + if (status != B_OK) { + TRACE("Inode::Create() Failed to get the inode from the " + "vnode\n"); + return B_ENTRY_NOT_FOUND; + } + + if (inode->IsDirectory() && (openMode & O_RWMASK) != O_RDONLY) + return B_IS_A_DIRECTORY; + if ((openMode & O_DIRECTORY) != 0 && !inode->IsDirectory()) + return B_NOT_A_DIRECTORY; + + if (inode->CheckPermissions(open_mode_to_access(openMode) + | ((openMode & O_TRUNC) != 0 ? W_OK : 0)) != B_OK) + return B_NOT_ALLOWED; + + if ((openMode & O_TRUNC) != 0) { + // Truncate requested + TRACE("Inode::Create(): Truncating file\n"); + inode->WriteLockInTransaction(transaction); + + status = inode->Resize(transaction, 0); + if (status != B_OK) + return status; + } + + if (_created != NULL) + *_created = false; + if (_id != NULL) + *_id = inode->ID(); + if (_inode != NULL) + *_inode = inode; + + if (_id != NULL || _inode != NULL) + vnode.Keep(); + + TRACE("Inode::Create(): Done opening file\n"); + return B_OK; + /*} else if ((mode & S_ATTR_DIR) == 0) { + TRACE("Inode::Create(): (mode & S_ATTR_DIR) == 0\n"); + return B_BAD_VALUE;*/ + } else if ((openMode & O_DIRECTORY) != 0) { + TRACE("Inode::Create(): (openMode & O_DIRECTORY) != 0\n"); + return B_ENTRY_NOT_FOUND; + } + + // Return to initial position + TRACE("Inode::Create(): Restarting iterator\n"); + entries->Restart(); + } + + status_t status; + if (parent != NULL) { + status = parent->CheckPermissions(W_OK); + if (status != B_OK) + return status; + } + + TRACE("Inode::Create(): Allocating inode\n"); + ino_t id; + status = volume->AllocateInode(transaction, parent, mode, id); + if (status != B_OK) + return status; + + if (entries != NULL) { + size_t nameLength = strlen(name); + status = entries->AddEntry(transaction, name, nameLength, id, type); + if (status != B_OK) + return status; + } + + TRACE("Inode::Create(): Creating inode\n"); + Inode* inode = new(std::nothrow) Inode(volume); + if (inode == NULL) + return B_NO_MEMORY; + + TRACE("Inode::Create(): Getting node structure\n"); + ext2_inode& node = inode->Node(); + TRACE("Inode::Create(): Initializing inode data\n"); + memset(&node, 0, sizeof(ext2_inode)); + node.SetMode(mode); + node.SetUserID(geteuid()); + node.SetGroupID(parent != NULL ? parent->Node().GroupID() : getegid()); + node.SetNumLinks(inode->IsDirectory() ? 2 : 1); + TRACE("Inode::Create(): Updating time\n"); + time_t creationTime = real_time_clock(); + node.SetAccessTime(creationTime); + node.SetCreationTime(creationTime); + node.SetModificationTime(creationTime); + + TRACE("Inode::Create(): Updating ID\n"); + inode->fID = id; + + if (inode->IsDirectory()) { + TRACE("Inode::Create(): Initializing directory\n"); + status = inode->InitDirectory(transaction, parent); + if (status != B_OK) + return status; + } + + // TODO: Maybe it can be better + /*if (volume->HasExtendedAttributes()) { + TRACE("Inode::Create(): Initializing extended attributes\n"); + uint32 blockGroup = 0; + uint32 pos = 0; + uint32 allocated; + + status = volume->AllocateBlocks(transaction, 1, 1, blockGroup, pos, + allocated); + if (status != B_OK) + return status; + + // Clear the new block + uint32 blockNum = volume->FirstDataBlock() + pos + + volume->BlocksPerGroup() * blockGroup; + CachedBlock cached(volume); + cached.SetToWritable(transaction, blockNum, true); + + node.SetExtendedAttributesBlock(blockNum); + }*/ + + TRACE("Inode::Create(): Saving inode\n"); + status = inode->WriteBack(transaction); + if (status != B_OK) + return status; + + TRACE("Inode::Create(): Creating vnode\n"); + + Vnode vnode; + status = vnode.Publish(transaction, inode, vnodeOps, publishFlags); + if (status != B_OK) + return status; + + if (!inode->IsSymLink()) { + // Vnode::Publish doesn't publish symlinks + if (!inode->IsDirectory()) { + status = inode->EnableFileCache(); + if (status != B_OK) + return status; + } + + inode->WriteLockInTransaction(transaction); + } + + if (_created) + *_created = true; + if (_id != NULL) + *_id = id; + if (_inode != NULL) + *_inode = inode; + + if (_id != NULL || _inode != NULL) + vnode.Keep(); + + TRACE("Inode::Create(): Deleting entries iterator\n"); + DirectoryIterator* iterator = entriesDeleter.Detach(); + TRACE("Inode::Create(): Entries iterator: %p\n", entries); + delete iterator; + TRACE("Inode::Create(): Done\n"); + + return B_OK; +} + + +status_t +Inode::EnableFileCache() +{ + TRACE("Inode::EnableFileCache()\n"); + + if (fCached) + return B_OK; + if (fCache != NULL) { + fCached = true; + return B_OK; + } + + TRACE("Inode::EnableFileCache(): Creating the file cache: %d, %d, %d\n", + (int)fVolume->ID(), (int)ID(), (int)Size()); + fCache = file_cache_create(fVolume->ID(), ID(), Size()); + fMap = file_map_create(fVolume->ID(), ID(), Size()); + + if (fCache == NULL) { + TRACE("Inode::EnableFileCache(): Failed to create file cache\n"); + fCached = false; + return B_ERROR; + } + + fCached = true; + TRACE("Inode::EnableFileCache(): Done\n"); + + return B_OK; +} + + +status_t +Inode::DisableFileCache() +{ + TRACE("Inode::DisableFileCache()\n"); + + if (!fCached) + return B_OK; + + file_cache_delete(FileCache()); + file_map_delete(Map()); + + fCached = false; + + return B_OK; +} + + +status_t +Inode::Sync() +{ + if (!IsFileCacheDisabled()) + return file_cache_sync(fCache); + + return B_OK; +} + + +void +Inode::TransactionDone(bool success) +{ + if (!success) { + // Revert any changes to the inode + if (fInitStatus == B_OK && UpdateNodeFromDisk() != B_OK) + panic("Failed to reload inode from disk!\n"); + else if (fInitStatus == B_NO_INIT) { + // TODO: Unpublish vnode? + panic("Failed to finish creating inode\n"); + } + } else { + if (fInitStatus == B_NO_INIT) { + TRACE("Inode::TransactionDone(): Inode creation succeeded\n"); + fInitStatus = B_OK; + } + } +} + + +void +Inode::RemovedFromTransaction() +{ + TRACE("Inode::RemovedFromTransaction(): Unlocking\n"); + rw_lock_write_unlock(&fLock); + + put_vnode(fVolume->FSVolume(), ID()); +} + + +status_t +Inode::_EnlargeDataStream(Transaction& transaction, off_t size) +{ + // TODO: Update fNode.num_blocks + if (size < 0) + return B_BAD_DATA; + + TRACE("Inode::_EnlargeDataStream()\n"); + + uint32 blockSize = fVolume->BlockSize(); + off_t oldSize = Size(); + off_t maxSize = oldSize; + if (maxSize % blockSize != 0) + maxSize += blockSize - maxSize % blockSize; + + if (size <= maxSize) { + // No need to allocate more blocks + TRACE("Inode::_EnlargeDataStream(): No need to allocate more blocks\n"); + TRACE("Inode::_EnlargeDataStream(): Setting size to %ld\n", (long)size); + fNode.SetSize(size); + return B_OK; + } + + uint32 end = size == 0 ? 0 : (size - 1) / fVolume->BlockSize() + 1; + DataStream stream(fVolume, &fNode.stream, oldSize); + stream.Enlarge(transaction, end); + + TRACE("Inode::_EnlargeDataStream(): Setting size to %ld\n", (long)size); + fNode.SetSize(size); + TRACE("Inode::_EnlargeDataStream(): Setting allocated block count to %lu\n", + end); + fNode.SetNumBlocks(fNode.NumBlocks() + end * (fVolume->BlockSize() / 512)); + + return B_OK; +} + + +status_t +Inode::_ShrinkDataStream(Transaction& transaction, off_t size) +{ + TRACE("Inode::_ShrinkDataStream()\n"); + + if (size < 0) + return B_BAD_DATA; + + uint32 blockSize = fVolume->BlockSize(); + off_t oldSize = Size(); + off_t lastByte = oldSize == 0 ? 0 : oldSize - 1; + off_t minSize = (lastByte / blockSize + 1) * blockSize; + // Minimum size that doesn't require freeing blocks + + if (size > minSize) { + // No need to allocate more blocks + TRACE("Inode::_ShrinkDataStream(): No need to allocate more blocks\n"); + TRACE("Inode::_ShrinkDataStream(): Setting size to %ld\n", (long)size); + fNode.SetSize(size); + return B_OK; + } + + uint32 end = size == 0 ? 0 : (size - 1) / fVolume->BlockSize() + 1; + DataStream stream(fVolume, &fNode.stream, oldSize); + stream.Shrink(transaction, end); + + fNode.SetSize(size); + fNode.SetNumBlocks(fNode.NumBlocks() - end * (fVolume->BlockSize() / 512)); + + return B_OK; +} diff --git a/src/add-ons/kernel/file_systems/ext2/Inode.h b/src/add-ons/kernel/file_systems/ext2/Inode.h index 52db832f81..d2eaa99361 100644 --- a/src/add-ons/kernel/file_systems/ext2/Inode.h +++ b/src/add-ons/kernel/file_systems/ext2/Inode.h @@ -6,13 +6,15 @@ #define INODE_H +#include #include +#include #include "ext2.h" #include "Volume.h" -class Inode { +class Inode : public TransactionListener { public: Inode(Volume* volume, ino_t id); ~Inode(); @@ -22,6 +24,10 @@ public: ino_t ID() const { return fID; } rw_lock* Lock() { return &fLock; } + void WriteLockInTransaction(Transaction& transaction); + + status_t UpdateNodeFromDisk(); + status_t WriteBack(Transaction& transaction); bool IsDirectory() const { return S_ISDIR(Mode()); } @@ -29,47 +35,193 @@ public: { return S_ISREG(Mode()); } bool IsSymLink() const { return S_ISLNK(Mode()); } - status_t CheckPermissions(int accessMode) const; - mode_t Mode() const { return fNode->Mode(); } - int32 Flags() const { return fNode->Flags(); } + bool IsDeleted() const { return fUnlinked; } - off_t Size() const { return fNode->Size(); } + mode_t Mode() const { return fNode.Mode(); } + int32 Flags() const { return fNode.Flags(); } + + off_t Size() const { return fNode.Size(); } time_t ModificationTime() const - { return fNode->ModificationTime(); } + { return fNode.ModificationTime(); } time_t CreationTime() const - { return fNode->CreationTime(); } + { return fNode.CreationTime(); } time_t AccessTime() const - { return fNode->AccessTime(); } + { return fNode.AccessTime(); } //::Volume* _Volume() const { return fVolume; } Volume* GetVolume() const { return fVolume; } status_t FindBlock(off_t offset, uint32& block); status_t ReadAt(off_t pos, uint8 *buffer, size_t *length); + status_t WriteAt(Transaction& transaction, off_t pos, + const uint8* buffer, size_t* length); + status_t FillGapWithZeros(off_t start, off_t end); + + status_t Resize(Transaction& transaction, off_t size); status_t AttributeBlockReadAt(off_t pos, uint8 *buffer, size_t *length); - ext2_inode& Node() { return *fNode; } + ext2_inode& Node() { return fNode; } + + status_t InitDirectory(Transaction& transaction, Inode* parent); + + status_t Unlink(Transaction& transaction); + + static status_t Create(Transaction& transaction, Inode* parent, + const char* name, int32 mode, int openMode, + uint8 type, bool* _created = NULL, + ino_t* _id = NULL, Inode** _inode = NULL, + fs_vnode_ops* vnodeOps = NULL, + uint32 publishFlags = 0); void* FileCache() const { return fCache; } void* Map() const { return fMap; } + status_t EnableFileCache(); + status_t DisableFileCache(); + bool IsFileCacheDisabled() const { return !fCached; } + + status_t Sync(); + +protected: + virtual void TransactionDone(bool success); + virtual void RemovedFromTransaction(); + private: + Inode(Volume* volume); Inode(const Inode&); Inode &operator=(const Inode&); // no implementation + status_t _EnlargeDataStream(Transaction& transaction, + off_t size); + status_t _ShrinkDataStream(Transaction& transaction, off_t size); + + + rw_lock fLock; + ::Volume* fVolume; + ino_t fID; + void* fCache; + void* fMap; + bool fCached; + bool fUnlinked; + ext2_inode fNode; + uint32 fNodeSize; + // Inodes have a varible size, but the important + // information is always the same size (except in ext4) + ext2_xattr_header* fAttributesBlock; + status_t fInitStatus; +}; + + +// The Vnode class provides a convenience layer upon get_vnode(), so that +// you don't have to call put_vnode() anymore, which may make code more +// readable in some cases + +class Vnode { +public: + Vnode(Volume* volume, ino_t id) + : + fInode(NULL) + { + SetTo(volume, id); + } + + Vnode() + : + fStatus(B_NO_INIT), + fInode(NULL) + { + } + + ~Vnode() + { + Unset(); + } + + status_t InitCheck() + { + return fStatus; + } + + void Unset() + { + if (fInode != NULL) { + put_vnode(fInode->GetVolume()->FSVolume(), fInode->ID()); + fInode = NULL; + fStatus = B_NO_INIT; + } + } + + status_t SetTo(Volume* volume, ino_t id) + { + Unset(); + + return fStatus = get_vnode(volume->FSVolume(), id, (void**)&fInode); + } + + status_t Get(Inode** _inode) + { + *_inode = fInode; + return fStatus; + } + + void Keep() + { + dprintf("Vnode::Keep()\n"); + fInode = NULL; + } + + status_t Publish(Transaction& transaction, Inode* inode, + fs_vnode_ops* vnodeOps, uint32 publishFlags) + { + dprintf("Vnode::Publish()\n"); + Volume* volume = transaction.GetVolume(); + + status_t status = B_OK; + + if (!inode->IsSymLink() && volume->ID() >= 0) { + dprintf("Vnode::Publish(): Publishing vnode: %d, %d, %p, %p, %x, " + "%x\n", (int)volume->FSVolume(), (int)inode->ID(), inode, + vnodeOps != NULL ? vnodeOps : &gExt2VnodeOps, (int)inode->Mode(), + (int)publishFlags); + status = publish_vnode(volume->FSVolume(), inode->ID(), inode, + vnodeOps != NULL ? vnodeOps : &gExt2VnodeOps, inode->Mode(), + publishFlags); + dprintf("Vnode::Publish(): Result: %s\n", strerror(status)); + } + + if (status == B_OK) { + dprintf("Vnode::Publish(): Preparing internal data\n"); + fInode = inode; + fStatus = B_OK; + + cache_add_transaction_listener(volume->BlockCache(), + transaction.ID(), TRANSACTION_ABORTED, &_TransactionListener, + inode); + } + + return status; + } + private: - rw_lock fLock; - ::Volume* fVolume; - ino_t fID; - void* fCache; - void* fMap; - ext2_inode* fNode; - ext2_xattr_header* fAttributesBlock; + status_t fStatus; + Inode* fInode; + + // TODO: How to apply coding style here? + static void _TransactionListener(int32 id, int32 event, void* _inode) + { + Inode* inode = (Inode*)_inode; + + if (event == TRANSACTION_ABORTED) { + // TODO: Unpublish? + panic("Transaction %d aborted, inode %p still exists!\n", (int)id, + inode); + } + } }; #endif // INODE_H diff --git a/src/add-ons/kernel/file_systems/ext2/InodeAllocator.cpp b/src/add-ons/kernel/file_systems/ext2/InodeAllocator.cpp new file mode 100644 index 0000000000..4fa5fb076e --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/InodeAllocator.cpp @@ -0,0 +1,193 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ + + +#include "InodeAllocator.h" + +#include + +#include "BitmapBlock.h" +#include "Inode.h" +#include "Volume.h" + + +//#define TRACE_EXT2 +#ifdef TRACE_EXT2 +# define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) +#else +# define TRACE(x...) ; +#endif + + +InodeAllocator::InodeAllocator(Volume* volume) + : + fVolume(volume) +{ + mutex_init(&fLock, "ext2 inode allocator"); +} + + +InodeAllocator::~InodeAllocator() +{ + mutex_destroy(&fLock); +} + + +/*virtual*/ status_t +InodeAllocator::New(Transaction& transaction, Inode* parent, int32 mode, + ino_t& id) +{ + // Apply allocation policy + uint32 preferredBlockGroup = parent == NULL ? parent->ID() + / parent->GetVolume()->InodesPerGroup() : 0; + + return _Allocate(transaction, preferredBlockGroup, S_ISDIR(mode), id); +} + + +/*virtual*/ status_t +InodeAllocator::Free(Transaction& transaction, ino_t id, bool isDirectory) +{ + TRACE("InodeAllocator::Free(%d, %c)\n", (int)id, isDirectory ? 't' : 'f'); + MutexLocker lock(fLock); + + uint32 numInodes = fVolume->InodesPerGroup(); + uint32 blockGroup = (id - 1) / numInodes; + ext2_block_group* group; + + status_t status = fVolume->GetBlockGroup(blockGroup, &group); + if (status != B_OK) + return status; + + if (blockGroup == fVolume->NumGroups() - 1) + numInodes = fVolume->NumInodes() - blockGroup * numInodes; + + TRACE("InodeAllocator::Free(): Updating block group data\n"); + group->SetFreeInodes(group->FreeInodes() + 1); + if (isDirectory) + group->SetUsedDirectories(group->UsedDirectories() - 1); + + status = fVolume->WriteBlockGroup(transaction, blockGroup); + if (status != B_OK) + return status; + + return _UnmarkInBitmap(transaction, group->InodeBitmap(), numInodes, id); +} + + +status_t +InodeAllocator::_Allocate(Transaction& transaction, uint32 preferredBlockGroup, + bool isDirectory, ino_t& id) +{ + MutexLocker lock(fLock); + + uint32 blockGroup = preferredBlockGroup; + uint32 lastBlockGroup = fVolume->NumGroups() - 1; + + for (int i = 0; i < 2; ++i) { + for (; blockGroup < lastBlockGroup; ++blockGroup) { + ext2_block_group* group; + + status_t status = fVolume->GetBlockGroup(blockGroup, &group); + if (status != B_OK) + return status; + + uint32 freeInodes = group->FreeInodes(); + if (freeInodes != 0) { + group->SetFreeInodes(freeInodes - 1); + if (isDirectory) + group->SetUsedDirectories(group->UsedDirectories() + 1); + + status = fVolume->WriteBlockGroup(transaction, blockGroup); + if (status != B_OK) + return status; + + return _MarkInBitmap(transaction, group->InodeBitmap(), + blockGroup, fVolume->InodesPerGroup(), id); + } + } + + if (i == 0) { + ext2_block_group* group; + + status_t status = fVolume->GetBlockGroup(blockGroup, &group); + if (status != B_OK) + return status; + + uint32 freeInodes = group->FreeInodes(); + if (group->FreeInodes() != 0) { + group->SetFreeInodes(freeInodes - 1); + + return _MarkInBitmap(transaction, group->InodeBitmap(), + blockGroup, fVolume->NumInodes() + - blockGroup * fVolume->InodesPerGroup(), id); + } + } + + blockGroup = 0; + lastBlockGroup = preferredBlockGroup; + } + + return B_DEVICE_FULL; +} + + +status_t +InodeAllocator::_MarkInBitmap(Transaction& transaction, uint32 bitmapBlock, + uint32 blockGroup, uint32 numInodes, ino_t& id) +{ + BitmapBlock inodeBitmap(fVolume, numInodes); + + if (!inodeBitmap.SetToWritable(transaction, bitmapBlock)) { + TRACE("Unable to open inode bitmap (block number: %lu) for block group " + "%lu\n", bitmapBlock, blockGroup); + return B_IO_ERROR; + } + + uint32 pos = 0; + inodeBitmap.FindNextUnmarked(pos); + + if (pos == inodeBitmap.NumBits()) { + TRACE("Even though the block group %lu indicates there are free " + "inodes, no unmarked bit was found in the inode bitmap at block " + "%lu.", blockGroup, bitmapBlock); + return B_ERROR; + } + + if (!inodeBitmap.Mark(pos, 1)) { + TRACE("Failed to mark bit %lu at bitmap block %lu\n", pos, + bitmapBlock); + return B_BAD_DATA; + } + + id = pos + blockGroup * fVolume->InodesPerGroup() + 1; + + return B_OK; +} + + +status_t +InodeAllocator::_UnmarkInBitmap(Transaction& transaction, uint32 bitmapBlock, + uint32 numInodes, ino_t id) +{ + BitmapBlock inodeBitmap(fVolume, numInodes); + + if (!inodeBitmap.SetToWritable(transaction, bitmapBlock)) { + TRACE("Unable to open inode bitmap at block %lu\n", bitmapBlock); + return B_IO_ERROR; + } + + uint32 pos = (id - 1) % fVolume->InodesPerGroup(); + if (!inodeBitmap.Unmark(pos, 1)) { + TRACE("Unable to unmark bit %lu in inode bitmap block %lu\n", pos, + bitmapBlock); + return B_BAD_DATA; + } + + return B_OK; +} diff --git a/src/add-ons/kernel/file_systems/ext2/InodeAllocator.h b/src/add-ons/kernel/file_systems/ext2/InodeAllocator.h new file mode 100644 index 0000000000..160cd69bd2 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/InodeAllocator.h @@ -0,0 +1,45 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ +#ifndef INODEALLOCATOR_H +#define INODEALLOCATOR_H + +#include + +#include "Transaction.h" + + +class Inode; +class Volume; + + +class InodeAllocator { +public: + InodeAllocator(Volume* volume); + virtual ~InodeAllocator(); + + virtual status_t New(Transaction& transaction, Inode* parent, + int32 mode, ino_t& id); + virtual status_t Free(Transaction& transaction, ino_t id, + bool isDirectory); + +private: + status_t _Allocate(Transaction& transaction, + uint32 prefferedBlockGroup, bool isDirectory, + ino_t& id); + status_t _MarkInBitmap(Transaction& transaction, + uint32 bitmapBlock, uint32 blockGroup, + uint32 numInodes, ino_t& id); + status_t _UnmarkInBitmap(Transaction& transaction, + uint32 bitmapBlock, uint32 numInodes, ino_t id); + + + Volume* fVolume; + mutex fLock; +}; + +#endif // INODEALLOCATOR_H diff --git a/src/add-ons/kernel/file_systems/ext2/InodeJournal.cpp b/src/add-ons/kernel/file_systems/ext2/InodeJournal.cpp new file mode 100644 index 0000000000..657d3d2d33 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/InodeJournal.cpp @@ -0,0 +1,91 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ + + +#include "InodeJournal.h" + +#include + +#include + +#include "HashRevokeManager.h" + + +//#define TRACE_EXT2 +#ifdef TRACE_EXT2 +# define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) +#else +# define TRACE(x...) ; +#endif + + +InodeJournal::InodeJournal(Inode* inode) + : + Journal(), + fInode(inode) +{ + if (inode == NULL) + fInitStatus = B_BAD_DATA; + else { + Volume* volume = inode->GetVolume(); + + fFilesystemVolume = volume; + fFilesystemBlockCache = volume->BlockCache(); + fJournalVolume = volume; + fJournalBlockCache = volume->BlockCache(); + + if (!inode->IsFileCacheDisabled()) + fInitStatus = inode->DisableFileCache(); + else + fInitStatus = B_OK; + + if (fInitStatus == B_OK) { + TRACE("InodeJournal::InodeJournal(): Inode's file cache disabled " + "successfully\n"); + HashRevokeManager* revokeManager = new(std::nothrow) + HashRevokeManager; + TRACE("InodeJournal::InodeJournal(): Allocated a hash revoke " + "manager at %p\n", revokeManager); + + if (revokeManager == NULL) { + TRACE("InodeJournal::InodeJournal(): Insufficient memory to " + "create the hash revoke manager\n"); + fInitStatus = B_NO_MEMORY; + } else { + fInitStatus = revokeManager->Init(); + + if (fInitStatus == B_OK) { + fRevokeManager = revokeManager; + fInitStatus = _LoadSuperBlock(); + } + } + } + } +} + + +InodeJournal::~InodeJournal() +{ +} + + +status_t +InodeJournal::InitCheck() +{ + if (fInitStatus != B_OK) + TRACE("InodeJournal: Initialization error\n"); + return fInitStatus; +} + + +status_t +InodeJournal::MapBlock(uint32 logical, uint32& physical) +{ + TRACE("InodeJournal::MapBlock()\n"); + return fInode->FindBlock(logical * fBlockSize, physical); +} diff --git a/src/add-ons/kernel/file_systems/ext2/InodeJournal.h b/src/add-ons/kernel/file_systems/ext2/InodeJournal.h new file mode 100644 index 0000000000..a2abb05abb --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/InodeJournal.h @@ -0,0 +1,29 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ +#ifndef INODEJOURNAL_H +#define INODEJOURNAL_H + + +#include "Inode.h" +#include "Journal.h" + + +class InodeJournal : public Journal { +public: + InodeJournal(Inode* inode); + ~InodeJournal(); + + status_t InitCheck(); + + status_t MapBlock(uint32 logical, uint32& physical); +private: + Inode* fInode; + status_t fInitStatus; +}; + +#endif // INODEJOURNAL_H diff --git a/src/add-ons/kernel/file_systems/ext2/Jamfile b/src/add-ons/kernel/file_systems/ext2/Jamfile index 56f4d7148d..8c4639881d 100644 --- a/src/add-ons/kernel/file_systems/ext2/Jamfile +++ b/src/add-ons/kernel/file_systems/ext2/Jamfile @@ -7,17 +7,27 @@ SubDir HAIKU_TOP src add-ons kernel file_systems ext2 ; } #UsePrivateHeaders [ FDirName kernel disk_device_manager ] ; +UsePrivateHeaders [ FDirName kernel util ] ; UsePrivateHeaders shared storage ; UsePrivateKernelHeaders ; KernelAddon ext2 : Volume.cpp + DataStream.cpp Inode.cpp AttributeIterator.cpp DirectoryIterator.cpp - IndexedDirectoryIterator.cpp HTree.cpp HTreeEntryIterator.cpp + RevokeManager.cpp + HashRevokeManager.cpp + Journal.cpp + NoJournal.cpp + InodeJournal.cpp + Transaction.cpp + BitmapBlock.cpp + BlockAllocator.cpp + InodeAllocator.cpp kernel_interface.cpp ; diff --git a/src/add-ons/kernel/file_systems/ext2/Journal.cpp b/src/add-ons/kernel/file_systems/ext2/Journal.cpp new file mode 100644 index 0000000000..6557d14b49 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/Journal.cpp @@ -0,0 +1,1262 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ + + +#include "Journal.h" + +#include +#include +#include + +#include + +#include "CachedBlock.h" +#include "HashRevokeManager.h" + + +//#define TRACE_EXT2 +#ifdef TRACE_EXT2 +# define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) +#else +# define TRACE(x...) ; +#endif + + +class LogEntry : public DoublyLinkedListLinkImpl { +public: + LogEntry(Journal* journal, uint32 logStart, + uint32 length); + ~LogEntry(); + + uint32 Start() const { return fStart; } + uint32 CommitID() const { return fCommitID; } + + Journal* GetJournal() { return fJournal; } + +private: + Journal* fJournal; + uint32 fStart; + uint32 fCommitID; +}; + + +LogEntry::LogEntry(Journal* journal, uint32 logStart, uint32 commitID) + : + fJournal(journal), + fStart(logStart), + fCommitID(commitID) +{ +} + + +LogEntry::~LogEntry() +{ +} + + +void +JournalHeader::MakeDescriptor(uint32 sequence) +{ + this->magic = B_HOST_TO_BENDIAN_INT32(JOURNAL_MAGIC); + this->sequence = B_HOST_TO_BENDIAN_INT32(sequence); + this->block_type = B_HOST_TO_BENDIAN_INT32(JOURNAL_DESCRIPTOR_BLOCK); +} + + +void +JournalHeader::MakeCommit(uint32 sequence) +{ + this->magic = B_HOST_TO_BENDIAN_INT32(JOURNAL_MAGIC); + this->sequence = B_HOST_TO_BENDIAN_INT32(sequence); + this->block_type = B_HOST_TO_BENDIAN_INT32(JOURNAL_COMMIT_BLOCK); +} + + +Journal::Journal(Volume* fsVolume, Volume* jVolume) + : + fJournalVolume(jVolume), + fJournalBlockCache(jVolume->BlockCache()), + fFilesystemVolume(fsVolume), + fFilesystemBlockCache(fsVolume->BlockCache()), + fRevokeManager(NULL), + fInitStatus(B_OK), + fBlockSize(sizeof(JournalSuperBlock)), + fFirstCommitID(0), + fFirstCacheCommitID(0), + fFirstLogBlock(1), + fLogSize(0), + fVersion(0), + fLogStart(0), + fLogEnd(0), + fFreeBlocks(0), + fMaxTransactionSize(0), + fCurrentCommitID(0), + fHasSubTransaction(false), + fSeparateSubTransactions(false), + fUnwrittenTransactions(0), + fTransactionID(0) +{ + recursive_lock_init(&fLock, "ext2 journal"); + mutex_init(&fLogEntriesLock, "ext2 journal log entries"); + + HashRevokeManager* revokeManager = new(std::nothrow) HashRevokeManager; + TRACE("Journal::Journal(): Allocated a hash revoke manager at %p\n", + revokeManager); + + if (revokeManager == NULL) + fInitStatus = B_NO_MEMORY; + else { + fInitStatus = revokeManager->Init(); + + if (fInitStatus == B_OK) { + fRevokeManager = revokeManager; + fInitStatus = _LoadSuperBlock(); + } + } +} + + +Journal::Journal() + : + fJournalVolume(NULL), + fJournalBlockCache(NULL), + fFilesystemVolume(NULL), + fFilesystemBlockCache(NULL), + fRevokeManager(NULL), + fInitStatus(B_OK), + fBlockSize(sizeof(JournalSuperBlock)), + fFirstCommitID(0), + fFirstCacheCommitID(0), + fFirstLogBlock(1), + fLogSize(0), + fVersion(0), + fLogStart(0), + fLogEnd(0), + fFreeBlocks(0), + fMaxTransactionSize(0), + fCurrentCommitID(0), + fHasSubTransaction(false), + fSeparateSubTransactions(false), + fUnwrittenTransactions(0), + fTransactionID(0) +{ + recursive_lock_init(&fLock, "ext2 journal"); + mutex_init(&fLogEntriesLock, "ext2 journal log entries"); +} + + +Journal::~Journal() +{ + TRACE("Journal destructor.\n"); + + TRACE("Journal::~Journal(): Attempting to delete revoke manager at %p\n", + fRevokeManager); + delete fRevokeManager; + + recursive_lock_destroy(&fLock); + mutex_destroy(&fLogEntriesLock); +} + + +status_t +Journal::InitCheck() +{ + return fInitStatus; +} + + +status_t +Journal::Uninit() +{ + status_t status = FlushLogAndBlocks(); + + if (status == B_OK) { + // Mark journal as clean + fLogStart = 0; + status = _SaveSuperBlock(); + } + + return status; +} + + +/*virtual*/ status_t +Journal::StartLog() +{ + fLogStart = fFirstLogBlock; + fLogEnd = fFirstLogBlock; + fFreeBlocks = 0; + + fCurrentCommitID = fFirstCommitID; + + return _SaveSuperBlock(); +} + + +status_t +Journal::RestartLog() +{ + fFirstCommitID = 1; + + return B_OK; +} + + +/*virtual*/ status_t +Journal::Lock(Transaction* owner, bool separateSubTransactions) +{ + TRACE("Journal::Lock()\n"); + status_t status = recursive_lock_lock(&fLock); + if (status != B_OK) + return status; + + TRACE("Journal::Lock(): Aquired lock\n"); + + if (!fSeparateSubTransactions && recursive_lock_get_recursion(&fLock) > 1) { + // reuse current transaction + TRACE("Journal::Lock(): Reusing current transaction\n"); + return B_OK; + } + + if(separateSubTransactions) + fSeparateSubTransactions = true; + + if (owner != NULL) + owner->SetParent(fOwner); + + fOwner = owner; + + if (fOwner != NULL) { + if (fUnwrittenTransactions > 0) { + // start a sub transaction + TRACE("Journal::Lock(): Starting sub transaction\n"); + cache_start_sub_transaction(fFilesystemBlockCache, fTransactionID); + fHasSubTransaction = true; + } else { + TRACE("Journal::Lock(): Starting new transaction\n"); + fTransactionID = cache_start_transaction(fFilesystemBlockCache); + } + + if (fTransactionID < B_OK) { + recursive_lock_unlock(&fLock); + return fTransactionID; + } + + cache_add_transaction_listener(fFilesystemBlockCache, fTransactionID, + TRANSACTION_IDLE, _TransactionIdle, this); + } + + return B_OK; +} + + +/*virtual*/ status_t +Journal::Unlock(Transaction* owner, bool success) +{ + TRACE("Journal::Unlock(): Lock recursion: %ld\n", + recursive_lock_get_recursion(&fLock)); + if (fSeparateSubTransactions + || recursive_lock_get_recursion(&fLock) == 1) { + // we only end the transaction if we unlock it + if (owner != NULL) { + TRACE("Journal::Unlock(): Calling _TransactionDone\n"); + status_t status = _TransactionDone(success); + if (status != B_OK) + return status; + + TRACE("Journal::Unlock(): Returned from _TransactionDone\n"); + bool separateSubTransactions = fSeparateSubTransactions; + fSeparateSubTransactions = true; + TRACE("Journal::Unlock(): Notifying listeners for: %p\n", owner); + owner->NotifyListeners(success); + TRACE("Journal::Unlock(): Done notifying listeners\n"); + fSeparateSubTransactions = separateSubTransactions; + + fOwner = owner->Parent(); + } else + fOwner = NULL; + + if (fSeparateSubTransactions + && recursive_lock_get_recursion(&fLock) == 1) + fSeparateSubTransactions = false; + } else + owner->MoveListenersTo(fOwner); + + TRACE("Journal::Unlock(): Unlocking the lock\n"); + + recursive_lock_unlock(&fLock); + return B_OK; +} + + +status_t +Journal::MapBlock(uint32 logical, uint32& physical) +{ + TRACE("Journal::MapBlock()\n"); + physical = logical; + + return B_OK; +} + + +inline uint32 +Journal::FreeLogBlocks() const +{ + TRACE("Journal::FreeLogBlocks(): start: %lu, end: %lu, size: %lu\n", + fLogStart, fLogEnd, fLogSize); + return fLogStart <= fLogEnd + ? fLogSize - fLogEnd + fLogStart - 1 + : fLogStart - fLogEnd; +} + + +status_t +Journal::FlushLogAndBlocks() +{ + return _FlushLog(true, true); +} + + +int32 +Journal::TransactionID() const +{ + return fTransactionID; +} + + +status_t +Journal::_WritePartialTransactionToLog(JournalHeader* descriptorBlock, + bool detached, uint8** _escapedData, uint32 &logBlock, off_t& blockNumber, + long& cookie, ArrayDeleter& escapedDataDeleter, uint32& blockCount, + bool& finished) +{ + TRACE("Journal::_WritePartialTransactionToLog()\n"); + + uint32 descriptorBlockPos = logBlock; + uint8* escapedData = *_escapedData; + + JournalBlockTag* tag = (JournalBlockTag*)descriptorBlock->data; + JournalBlockTag* lastTag = (JournalBlockTag*)((uint8*)descriptorBlock + + fBlockSize - sizeof(JournalHeader)); + + finished = false; + status_t status = B_OK; + + while (tag < lastTag && status == B_OK) { + tag->SetBlockNumber(blockNumber); + tag->SetFlags(0); + + CachedBlock data(fFilesystemVolume); + const JournalHeader* blockData = (JournalHeader*)data.SetTo( + blockNumber); + if (blockData == NULL) { + panic("Got a NULL pointer while iterating through transaction " + "blocks.\n"); + return B_ERROR; + } + + void* finalData; + + if (blockData->CheckMagic()) { + // The journaled block starts with the magic value + // We must remove it to prevent confusion + TRACE("Journal::_WritePartialTransactionToLog(): Block starts with " + "magic number. Escaping it\n"); + tag->SetEscapedFlag(); + + if (escapedData == NULL) { + TRACE("Journal::_WritePartialTransactionToLog(): Allocating " + "space for escaped block (%lu)\n", fBlockSize); + escapedData = new(std::nothrow) uint8[fBlockSize]; + if (escapedData == NULL) { + TRACE("Journal::_WritePartialTransactionToLof(): Failed to " + "allocate buffer for escaped data block\n"); + return B_NO_MEMORY; + } + escapedDataDeleter.SetTo(escapedData); + *_escapedData = escapedData; + + ((int32*)escapedData)[0] = 0; // Remove magic + } + + memcpy(escapedData + 4, blockData->data, fBlockSize - 4); + finalData = escapedData; + } else + finalData = (void*)blockData; + + // TODO: use iovecs? + + logBlock = _WrapAroundLog(logBlock + 1); + + uint32 physicalBlock; + status = MapBlock(logBlock, physicalBlock); + if (status != B_OK) + return status; + + off_t logOffset = physicalBlock * fBlockSize; + + TRACE("Journal::_WritePartialTransactionToLog(): Writing from memory: " + "%p, to disk: %ld\n", finalData, (long)logOffset); + size_t written = write_pos(fJournalVolume->Device(), logOffset, + finalData, fBlockSize); + if (written != fBlockSize) { + TRACE("Failed to write journal block.\n"); + return B_IO_ERROR; + } + + TRACE("Journal::_WritePartialTransactionToLog(): Wrote a journal block " + "at: %lu\n", logBlock); + + blockCount++; + tag++; + + status = cache_next_block_in_transaction(fFilesystemBlockCache, + fTransactionID, detached, &cookie, &blockNumber, NULL, NULL); + } + + finished = status != B_OK; + + // Write descriptor block + --tag; + tag->SetLastTagFlag(); + + uint32 physicalBlock; + status = MapBlock(descriptorBlockPos, physicalBlock); + if (status != B_OK) + return status; + + off_t descriptorBlockOffset = physicalBlock * fBlockSize; + + TRACE("Journal::_WritePartialTransactionToLog(): Writing to: %ld\n", + (long)descriptorBlockOffset); + size_t written = write_pos(fJournalVolume->Device(), + descriptorBlockOffset, descriptorBlock, fBlockSize); + if (written != fBlockSize) { + TRACE("Failed to write journal descriptor block.\n"); + return B_IO_ERROR; + } + + blockCount++; + logBlock = _WrapAroundLog(logBlock + 1); + + return B_OK; +} + + +status_t +Journal::_WriteTransactionToLog() +{ + TRACE("Journal::_WriteTransactionToLog()\n"); + // Transaction enters the Flush state + bool detached = false; + TRACE("Journal::_WriteTransactionToLog(): Attempting to get transaction " + "size\n"); + size_t size = _FullTransactionSize(); + TRACE("Journal::_WriteTransactionToLog(): transaction size: %lu\n", size); + + if (size > fMaxTransactionSize) { + TRACE("Journal::_WriteTransactionToLog(): not enough free space " + "for the transaction. Attempting to free some space.\n"); + size = _MainTransactionSize(); + TRACE("Journal::_WriteTransactionToLog(): main transaction size: %lu\n", + size); + + if(fHasSubTransaction && size < fMaxTransactionSize) { + TRACE("Journal::_WriteTransactionToLog(): transaction doesn't fit, " + "but it can be separated\n"); + detached = true; + } else { + // Error: transaction can't fit in log + panic("transaction too large (size: %lu, max size: %lu, log size: " + "%lu)\n", size, fMaxTransactionSize, fLogSize); + return B_BUFFER_OVERFLOW; + } + } + + TRACE("Journal::_WriteTransactionToLog(): free log blocks: %lu\n", + FreeLogBlocks()); + if (size > FreeLogBlocks()) { + TRACE("Journal::_WriteTransactionToLog(): Syncing block cache\n"); + cache_sync_transaction(fFilesystemBlockCache, fTransactionID); + + if (size > FreeLogBlocks()) { + panic("Transaction fits, but sync didn't result in enough" + "free space.\n\tGot %ld when at least %ld was expected.", + (long)FreeLogBlocks(), (long)size); + } + } + + TRACE("Journal::_WriteTransactionToLog(): finished managing space for " + "the transaction\n"); + + fHasSubTransaction = false; + + // Prepare Descriptor block + TRACE("Journal::_WriteTransactionToLog(): attempting to allocate space for " + "the descriptor block, block size %lu\n", fBlockSize); + JournalHeader* descriptorBlock = + (JournalHeader*)new(std::nothrow) uint8[fBlockSize]; + if (descriptorBlock == NULL) { + TRACE("Journal::_WriteTransactionToLog(): Failed to allocate a buffer " + "for the descriptor block\n"); + return B_NO_MEMORY; + } + ArrayDeleter descriptorBlockDeleter((uint8*)descriptorBlock); + + descriptorBlock->MakeDescriptor(fCurrentCommitID); + + // Prepare Commit block + TRACE("Journal::_WriteTransactionToLog(): attempting to allocate space for " + "the commit block, block size %lu\n", fBlockSize); + JournalHeader* commitBlock = + (JournalHeader*)new(std::nothrow) uint8[fBlockSize]; + if (descriptorBlock == NULL) { + TRACE("Journal::_WriteTransactionToLog(): Failed to allocate a buffer " + "for the commit block\n"); + return B_NO_MEMORY; + } + ArrayDeleter commitBlockDeleter((uint8*)commitBlock); + + commitBlock->MakeCommit(fCurrentCommitID + 1); + memset(commitBlock->data, 0, fBlockSize - sizeof(JournalHeader)); + // TODO: This probably isn't necessary + + uint8* escapedData = NULL; + ArrayDeleter escapedDataDeleter; + + off_t blockNumber; + long cookie = 0; + + status_t status = cache_next_block_in_transaction(fFilesystemBlockCache, + fTransactionID, detached, &cookie, &blockNumber, NULL, NULL); + if (status != B_OK) { + TRACE("Journal::_WriteTransactionToLog(): Transaction has no blocks to " + "write\n"); + return B_OK; + } + + uint32 blockCount = 0; + + uint32 logBlock = _WrapAroundLog(fLogEnd); + + bool finished = false; + + status = _WritePartialTransactionToLog(descriptorBlock, detached, + &escapedData, logBlock, blockNumber, cookie, escapedDataDeleter, + blockCount, finished); + if (!finished && status != B_OK) + return status; + + uint32 commitBlockPos = logBlock; + + while (!finished) { + descriptorBlock->IncrementSequence(); + + status = _WritePartialTransactionToLog(descriptorBlock, detached, + &escapedData, logBlock, blockNumber, cookie, escapedDataDeleter, + blockCount, finished); + if (!finished && status != B_OK) + return status; + + // It is okay to write the commit blocks of the partial transactions + // as long as the commit block of the first partial transaction isn't + // written. When it recovery reaches where the first commit should be + // and doesn't find it, it considers it found the end of the log. + + uint32 physicalBlock; + status = MapBlock(logBlock, physicalBlock); + if (status != B_OK) + return status; + + off_t logOffset = physicalBlock * fBlockSize; + + TRACE("Journal::_WriteTransactionToLog(): Writting commit block to " + "%ld\n", (long)logOffset); + off_t written = write_pos(fJournalVolume->Device(), logOffset, + commitBlock, fBlockSize); + if (written != fBlockSize) { + TRACE("Failed to write journal commit block.\n"); + return B_IO_ERROR; + } + + commitBlock->IncrementSequence(); + blockCount++; + + logBlock = _WrapAroundLog(logBlock + 1); + } + + // Transaction will enter the Commit state + uint32 physicalBlock; + status = MapBlock(commitBlockPos, physicalBlock); + if (status != B_OK) + return status; + + off_t logOffset = physicalBlock * fBlockSize; + + TRACE("Journal::_WriteTansactionToLog(): Writing to: %ld\n", + (long)logOffset); + off_t written = write_pos(fJournalVolume->Device(), logOffset, commitBlock, + fBlockSize); + if (written != fBlockSize) { + TRACE("Failed to write journal commit block.\n"); + return B_IO_ERROR; + } + + blockCount++; + fLogEnd = _WrapAroundLog(fLogEnd + blockCount); + + status = _SaveSuperBlock(); + + // Transaction will enter Finished state + LogEntry *logEntry = new LogEntry(this, fLogEnd, fCurrentCommitID++); + TRACE("Journal::_WriteTransactionToLog(): Allocating log entry at %p\n", + logEntry); + if (logEntry == NULL) { + panic("no memory to allocate log entries!"); + return B_NO_MEMORY; + } + + mutex_lock(&fLogEntriesLock); + fLogEntries.Add(logEntry); + mutex_unlock(&fLogEntriesLock); + + if (detached) { + fTransactionID = cache_detach_sub_transaction(fFilesystemBlockCache, + fTransactionID, _TransactionWritten, logEntry); + fUnwrittenTransactions = 1; + + if (status == B_OK && _FullTransactionSize() > fLogSize) { + // If the transaction is too large after writing, there is no way to + // recover, so let this transaction fail. + dprintf("transaction too large (%d blocks, log size %d)!\n", + (int)_FullTransactionSize(), (int)fLogSize); + return B_BUFFER_OVERFLOW; + } + } else { + cache_end_transaction(fFilesystemBlockCache, fTransactionID, + _TransactionWritten, logEntry); + fUnwrittenTransactions = 0; + } + + return B_OK; +} + + +status_t +Journal::_SaveSuperBlock() +{ + TRACE("Journal::_SaveSuperBlock()\n"); + uint32 physicalBlock; + status_t status = MapBlock(0, physicalBlock); + if (status != B_OK) + return status; + + off_t superblockPos = physicalBlock * fBlockSize; + + JournalSuperBlock superblock; + size_t bytesRead = read_pos(fJournalVolume->Device(), superblockPos, + &superblock, sizeof(superblock)); + + if (bytesRead != sizeof(superblock)) + return B_IO_ERROR; + + superblock.SetFirstCommitID(fFirstCommitID); + superblock.SetLogStart(fLogStart); + + TRACE("Journal::SaveSuperBlock(): Write to %ld\n", (long)superblockPos); + size_t bytesWritten = write_pos(fJournalVolume->Device(), superblockPos, + &superblock, sizeof(superblock)); + + if (bytesWritten != sizeof(superblock)) + return B_IO_ERROR; + + TRACE("Journal::_SaveSuperBlock(): Done\n"); + + return B_OK; +} + + +status_t +Journal::_LoadSuperBlock() +{ + TRACE("Journal::_LoadSuperBlock()\n"); + uint32 superblockPos; + + status_t status = MapBlock(0, superblockPos); + if (status != B_OK) + return status; + + TRACE("Journal::_LoadSuperBlock(): super block physical block: %lu\n", + superblockPos); + + JournalSuperBlock superblock; + size_t bytesRead = read_pos(fJournalVolume->Device(), superblockPos + * fJournalVolume->BlockSize(), &superblock, sizeof(superblock)); + + if (bytesRead != sizeof(superblock)) { + TRACE("Journal::_LoadSuperBlock(): failed to read superblock\n"); + return B_IO_ERROR; + } + + if (!superblock.header.CheckMagic()) { + TRACE("Journal::_LoadSuperBlock(): Invalid superblock magic %lX\n", + superblock.header.Magic()); + return B_BAD_VALUE; + } + + if (superblock.header.BlockType() == JOURNAL_SUPERBLOCK_V1) { + TRACE("Journal::_LoadSuperBlock(): Journal superblock version 1\n"); + fVersion = 1; + } else if (superblock.header.BlockType() == JOURNAL_SUPERBLOCK_V2) { + TRACE("Journal::_LoadSuperBlock(): Journal superblock version 2\n"); + fVersion = 2; + } else { + TRACE("Journal::_LoadSuperBlock(): Invalid superblock version\n"); + return B_BAD_VALUE; + } + + if (fVersion >= 2) { + status = _CheckFeatures(&superblock); + + if (status != B_OK) { + TRACE("Journal::_LoadSuperBlock(): Unsupported features\n"); + return status; + } + } + + fBlockSize = superblock.BlockSize(); + fFirstCommitID = superblock.FirstCommitID(); + fFirstLogBlock = superblock.FirstLogBlock(); + fLogStart = superblock.LogStart(); + fLogSize = superblock.NumBlocks(); + + uint32 descriptorTags = (fBlockSize - sizeof(JournalHeader)) + / sizeof(JournalBlockTag); + // Maximum tags per descriptor block + uint32 maxDescriptors = (fLogSize - 1) / (descriptorTags + 2); + // Maximum number of full journal transactions + fMaxTransactionSize = maxDescriptors * descriptorTags; + fMaxTransactionSize += (fLogSize - 1) - fMaxTransactionSize - 2; + // Maximum size of a "logical" transaction + // TODO: Why is "superblock.MaxTransactionBlocks();" zero? + //fFirstCacheCommitID = fFirstCommitID - fTransactionID /*+ 1*/; + + TRACE("Journal::_LoadSuperBlock(): block size: %lu, first commit id: %lu, " + "first log block: %lu, log start: %lu, log size: %lu, max transaction " + "size: %lu\n", fBlockSize, fFirstCommitID, fFirstLogBlock, fLogStart, + fLogSize, fMaxTransactionSize); + + return B_OK; +} + + +status_t +Journal::_CheckFeatures(JournalSuperBlock* superblock) +{ + if ((superblock->ReadOnlyCompatibleFeatures() + & ~JOURNAL_KNOWN_READ_ONLY_COMPATIBLE_FEATURES) != 0 + || (superblock->IncompatibleFeatures() + & ~JOURNAL_KNOWN_INCOMPATIBLE_FEATURES) != 0) + return B_NOT_SUPPORTED; + + return B_OK; +} + + +uint32 +Journal::_CountTags(JournalHeader* descriptorBlock) +{ + uint32 count = 0; + + JournalBlockTag* tags = (JournalBlockTag*)descriptorBlock->data; + // Skip the header + JournalBlockTag* lastTag = (JournalBlockTag*) + (descriptorBlock + fBlockSize - sizeof(JournalBlockTag)); + + while (tags < lastTag && (tags->Flags() & JOURNAL_FLAG_LAST_TAG) == 0) { + if ((tags->Flags() & JOURNAL_FLAG_SAME_UUID) == 0) { + // sizeof(UUID) = 16 = 2*sizeof(JournalBlockTag) + tags += 2; // Skip new UUID + } + + TRACE("Journal::_CountTags(): Tag block: %lu\n", tags->BlockNumber()); + + tags++; // Go to next tag + count++; + } + + if ((tags->Flags() & JOURNAL_FLAG_LAST_TAG) != 0) + count++; + + TRACE("Journal::_CountTags(): counted tags: %lu\n", count); + + return count; +} + + +/*virtual*/ status_t +Journal::Recover() +{ + TRACE("Journal::Recover()\n"); + if (fLogStart == 0) // Journal was cleanly unmounted + return B_OK; + + TRACE("Journal::Recover(): Journal needs recovery\n"); + + uint32 lastCommitID; + + status_t status = _RecoverPassScan(lastCommitID); + if (status != B_OK) + return status; + + status = _RecoverPassRevoke(lastCommitID); + if (status != B_OK) + return status; + + return _RecoverPassReplay(lastCommitID); +} + + +// First pass: Find the end of the log +status_t +Journal::_RecoverPassScan(uint32& lastCommitID) +{ + TRACE("Journal Recover: 1st Pass: Scan\n"); + + CachedBlock cached(fJournalVolume); + JournalHeader* header; + uint32 nextCommitID = fFirstCommitID; + uint32 nextBlock = fLogStart; + uint32 nextBlockPos; + + status_t status = MapBlock(nextBlock, nextBlockPos); + if (status != B_OK) + return status; + + header = (JournalHeader*)cached.SetTo(nextBlockPos); + + while (header->CheckMagic() && header->Sequence() == nextCommitID) { + uint32 blockType = header->BlockType(); + + if (blockType == JOURNAL_DESCRIPTOR_BLOCK) { + uint32 tags = _CountTags(header); + nextBlock += tags; + TRACE("Journal recover pass scan: Found a descriptor block with " + "%lu tags\n", tags); + } else if (blockType == JOURNAL_COMMIT_BLOCK) { + nextCommitID++; + TRACE("Journal recover pass scan: Found a commit block. Next " + "commit ID: %lu\n", nextCommitID); + } else if (blockType != JOURNAL_REVOKE_BLOCK) { + TRACE("Journal recover pass scan: Reached an unrecognized block, " + "assuming as log's end.\n"); + break; + } else { + TRACE("Journal recover pass scan: Found a revoke block, " + "skipping it\n"); + } + + nextBlock = _WrapAroundLog(nextBlock + 1); + + status = MapBlock(nextBlock, nextBlockPos); + if (status != B_OK) + return status; + + header = (JournalHeader*)cached.SetTo(nextBlockPos); + } + + TRACE("Journal Recovery pass scan: Last detected transaction ID: %lu\n", + nextCommitID); + + lastCommitID = nextCommitID; + return B_OK; +} + + +// Second pass: Collect all revoked blocks +status_t +Journal::_RecoverPassRevoke(uint32 lastCommitID) +{ + TRACE("Journal Recover: 2nd Pass: Revoke\n"); + + CachedBlock cached(fJournalVolume); + JournalHeader* header; + uint32 nextCommitID = fFirstCommitID; + uint32 nextBlock = fLogStart; + uint32 nextBlockPos; + + status_t status = MapBlock(nextBlock, nextBlockPos); + if (status != B_OK) + return status; + + header = (JournalHeader*)cached.SetTo(nextBlockPos); + + while (nextCommitID < lastCommitID) { + if (!header->CheckMagic() || header->Sequence() != nextCommitID) { + // Somehow the log is different than the expexted + return B_ERROR; + } + + uint32 blockType = header->BlockType(); + + if (blockType == JOURNAL_DESCRIPTOR_BLOCK) + nextBlock += _CountTags(header); + else if (blockType == JOURNAL_COMMIT_BLOCK) + nextCommitID++; + else if (blockType == JOURNAL_REVOKE_BLOCK) { + TRACE("Journal::_RecoverPassRevoke(): Found a revoke block\n"); + status = fRevokeManager->ScanRevokeBlock( + (JournalRevokeHeader*)header, nextCommitID); + + if (status != B_OK) + return status; + } else { + // TODO: Warn that we found an unrecognized block + break; + } + + nextBlock = _WrapAroundLog(nextBlock + 1); + + status = MapBlock(nextBlock, nextBlockPos); + if (status != B_OK) + return status; + + header = (JournalHeader*)cached.SetTo(nextBlockPos); + } + + if (nextCommitID != lastCommitID) { + // Possibly because of some sort of IO error + TRACE("Journal::_RecoverPassRevoke(): Incompatible commit IDs\n"); + return B_ERROR; + } + + TRACE("Journal recovery pass revoke: Revoked blocks: %lu\n", + fRevokeManager->NumRevokes()); + + return B_OK; +} + + +// Third pass: Replay log +status_t +Journal::_RecoverPassReplay(uint32 lastCommitID) +{ + TRACE("Journal Recover: 3rd Pass: Replay\n"); + + uint32 nextCommitID = fFirstCommitID; + uint32 nextBlock = fLogStart; + uint32 nextBlockPos; + + status_t status = MapBlock(nextBlock, nextBlockPos); + if (status != B_OK) + return status; + + CachedBlock cached(fJournalVolume); + JournalHeader* header = (JournalHeader*)cached.SetTo(nextBlockPos); + + int count = 0; + + uint8* data = new(std::nothrow) uint8[fBlockSize]; + if (data == NULL) { + TRACE("Journal::_RecoverPassReplay(): Failed to allocate memory for " + "data\n"); + return B_NO_MEMORY; + } + + ArrayDeleter dataDeleter(data); + + while (nextCommitID < lastCommitID) { + if (!header->CheckMagic() || header->Sequence() != nextCommitID) { + // Somehow the log is different than the expexted + TRACE("Journal::_RecoverPassReplay(): Wierd problem with block\n"); + return B_ERROR; + } + + uint32 blockType = header->BlockType(); + + if (blockType == JOURNAL_DESCRIPTOR_BLOCK) { + JournalBlockTag* last_tag = (JournalBlockTag*)((uint8*)header + + fBlockSize - sizeof(JournalBlockTag)); + + for (JournalBlockTag* tag = (JournalBlockTag*)header->data; + tag <= last_tag; ++tag) { + nextBlock = _WrapAroundLog(nextBlock + 1); + + status = MapBlock(nextBlock, nextBlockPos); + if (status != B_OK) + return status; + + if (!fRevokeManager->Lookup(tag->BlockNumber(), + nextCommitID)) { + // Block isn't revoked + size_t read = read_pos(fJournalVolume->Device(), + nextBlockPos * fBlockSize, data, fBlockSize); + if (read != fBlockSize) + return B_IO_ERROR; + + if ((tag->Flags() & JOURNAL_FLAG_ESCAPED) != 0) { + // Block is escaped + ((int32*)data)[0] + = B_HOST_TO_BENDIAN_INT32(JOURNAL_MAGIC); + } + + TRACE("Journal::_RevoverPassReplay(): Write to %lu\n", + tag->BlockNumber() * fBlockSize); + size_t written = write_pos(fFilesystemVolume->Device(), + tag->BlockNumber() * fBlockSize, data, fBlockSize); + + if (written != fBlockSize) + return B_IO_ERROR; + + ++count; + } + + if ((tag->Flags() & JOURNAL_FLAG_LAST_TAG) != 0) + break; + if ((tag->Flags() & JOURNAL_FLAG_SAME_UUID) == 0) { + // TODO: Check new UUID with file system UUID + tag += 2; + // sizeof(JournalBlockTag) = 8 + // sizeof(UUID) = 16 + } + } + } else if (blockType == JOURNAL_COMMIT_BLOCK) + nextCommitID++; + else if (blockType != JOURNAL_REVOKE_BLOCK) { + // TODO: Warn that we found an unrecognized block + break; + } // If blockType == JOURNAL_REVOKE_BLOCK we just skip it + + nextBlock = _WrapAroundLog(nextBlock + 1); + + status = MapBlock(nextBlock, nextBlockPos); + if (status != B_OK) + return status; + + header = (JournalHeader*)cached.SetTo(nextBlockPos); + } + + if (nextCommitID != lastCommitID) { + // Possibly because of some sort of IO error + return B_ERROR; + } + + TRACE("Journal recovery pass replay: Replayed blocks: %u\n", count); + + return B_OK; +} + + +status_t +Journal::_FlushLog(bool canWait, bool flushBlocks) +{ + TRACE("Journal::_FlushLog()\n"); + status_t status = canWait ? recursive_lock_lock(&fLock) + : recursive_lock_trylock(&fLock); + + TRACE("Journal::_FlushLog(): Acquired fLock, recursion: %ld\n", + recursive_lock_get_recursion(&fLock)); + if (status != B_OK) + return status; + + if (recursive_lock_get_recursion(&fLock) > 1) { + // Called from inside a transaction + recursive_lock_unlock(&fLock); + TRACE("Journal::_FlushLog(): Called from a transaction. Leaving...\n"); + return B_OK; + } + + if (fUnwrittenTransactions != 0 && _FullTransactionSize() != 0) { + status = _WriteTransactionToLog(); + if (status < B_OK) + panic("Failed flushing transaction: %s\n", strerror(status)); + } + + TRACE("Journal::_FlushLog(): Attempting to flush journal volume at %p\n", + fJournalVolume); + + // TODO: Not sure this is correct. Need to review... + // NOTE: Not correct. Causes double lock of a block cache mutex + // TODO: Need some other way to synchronize the journal... + /*status = fJournalVolume->FlushDevice(); + if (status != B_OK) + return status;*/ + + TRACE("Journal::_FlushLog(): Flushed journal volume\n"); + + if (flushBlocks) { + TRACE("Journal::_FlushLog(): Attempting to flush file system volume " + "at %p\n", fFilesystemVolume); + status = fFilesystemVolume->FlushDevice(); + if (status == B_OK) + TRACE("Journal::_FlushLog(): Flushed file system volume\n"); + } + + TRACE("Journal::_FlushLog(): Finished. Releasing lock\n"); + + recursive_lock_unlock(&fLock); + + TRACE("Journal::_FlushLog(): Done, final status: %s\n", strerror(status)); + return status; +} + + +inline uint32 +Journal::_WrapAroundLog(uint32 block) +{ + TRACE("Journal::_WrapAroundLog()\n"); + if (block >= fLogSize) + return block - fLogSize + fFirstLogBlock; + else + return block; +} + + +size_t +Journal::_CurrentTransactionSize() const +{ + TRACE("Journal::_CurrentTransactionSize(): transaction %ld\n", + fTransactionID); + + size_t count; + + if (fHasSubTransaction) { + count = cache_blocks_in_sub_transaction(fFilesystemBlockCache, + fTransactionID); + + TRACE("\tSub transaction size: %ld\n", count); + } else { + count = cache_blocks_in_transaction(fFilesystemBlockCache, + fTransactionID); + + TRACE("\tTransaction size: %ld\n", count); + } + + return count; +} + + +size_t +Journal::_FullTransactionSize() const +{ + TRACE("Journal::_FullTransactionSize(): transaction %ld\n", fTransactionID); + TRACE("\tFile sytem block cache: %p\n", fFilesystemBlockCache); + + size_t count = cache_blocks_in_transaction(fFilesystemBlockCache, + fTransactionID); + + TRACE("\tFull transaction size: %ld\n", count); + + return count; +} + + +size_t +Journal::_MainTransactionSize() const +{ + TRACE("Journal::_MainTransactionSize(): transaction %ld\n", fTransactionID); + + size_t count = cache_blocks_in_main_transaction(fFilesystemBlockCache, + fTransactionID); + + TRACE("\tMain transaction size: %ld\n", count); + + return count; +} + + +status_t +Journal::_TransactionDone(bool success) +{ + if (!success) { + if (fHasSubTransaction) { + TRACE("Journal::_TransactionDone(): transaction %ld failed, " + "aborting subtransaction\n", fTransactionID); + cache_abort_sub_transaction(fFilesystemBlockCache, fTransactionID); + // parent is unaffected + } else { + TRACE("Journal::_TransactionDone(): transaction %ld failed," + " aborting\n", fTransactionID); + cache_abort_transaction(fFilesystemBlockCache, fTransactionID); + fUnwrittenTransactions = 0; + } + + TRACE("Journal::_TransactionDone(): returning B_OK\n"); + return B_OK; + } + + // If possible, delay flushing the transaction + uint32 size = _FullTransactionSize(); + TRACE("Journal::_TransactionDone(): full transaction size: %lu, max " + "transaction size: %lu, free log blocks: %lu\n", size, + fMaxTransactionSize, FreeLogBlocks()); + if (fMaxTransactionSize > 0 && size < fMaxTransactionSize) { + TRACE("Journal::_TransactionDone(): delaying flush of transaction " + "%ld\n", fTransactionID); + + // Make sure the transaction fits in the log + if (size < FreeLogBlocks()) + cache_sync_transaction(fFilesystemBlockCache, fTransactionID); + + fUnwrittenTransactions++; + TRACE("Journal::_TransactionDone(): returning B_OK\n"); + return B_OK; + } + + return _WriteTransactionToLog(); +} + + +/*static*/ void +Journal::_TransactionWritten(int32 transactionID, int32 event, void* _logEntry) +{ + LogEntry* logEntry = (LogEntry*)_logEntry; + + TRACE("Journal::_TransactionWritten(): Transaction %ld checkpointed\n", + transactionID); + + Journal* journal = logEntry->GetJournal(); + + TRACE("Journal::_TransactionWritten(): log entry: %p, journal: %p\n", + logEntry, journal); + TRACE("Journal::_TransactionWritten(): log entries: %p\n", + &journal->fLogEntries); + + mutex_lock(&journal->fLogEntriesLock); + + TRACE("Journal::_TransactionWritten(): first log entry: %p\n", + journal->fLogEntries.First()); + if (logEntry == journal->fLogEntries.First()) { + TRACE("Journal::_TransactionWritten(): Moving start of log to %lu\n", + logEntry->Start()); + journal->fLogStart = logEntry->Start(); + journal->fFirstCommitID = logEntry->CommitID(); + TRACE("Journal::_TransactionWritten(): Setting commit ID to %lu\n", + logEntry->CommitID()); + + if (journal->_SaveSuperBlock() != B_OK) + panic("ext2: Failed to write journal superblock\n"); + } + + TRACE("Journal::_TransactionWritten(): Removing log entry\n"); + journal->fLogEntries.Remove(logEntry); + + TRACE("Journal::_TransactionWritten(): Unlocking entries list\n"); + mutex_unlock(&journal->fLogEntriesLock); + + TRACE("Journal::_TransactionWritten(): Deleting log entry at %p\n", logEntry); + delete logEntry; +} + + +/*static*/ void +Journal::_TransactionIdle(int32 transactionID, int32 event, void* _journal) +{ + Journal* journal = (Journal*)_journal; + journal->_FlushLog(false, false); +} diff --git a/src/add-ons/kernel/file_systems/ext2/Journal.h b/src/add-ons/kernel/file_systems/ext2/Journal.h new file mode 100644 index 0000000000..45e6cbe546 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/Journal.h @@ -0,0 +1,257 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ +#ifndef JOURNAL_H +#define JOURNAL_H + + +#define JOURNAL_MAGIC 0xc03b3998U + +#define JOURNAL_DESCRIPTOR_BLOCK 1 +#define JOURNAL_COMMIT_BLOCK 2 +#define JOURNAL_SUPERBLOCK_V1 3 +#define JOURNAL_SUPERBLOCK_V2 4 +#define JOURNAL_REVOKE_BLOCK 5 + +#define JOURNAL_FLAG_ESCAPED 1 +#define JOURNAL_FLAG_SAME_UUID 2 +#define JOURNAL_FLAG_DELETED 4 +#define JOURNAL_FLAG_LAST_TAG 8 + +#define JOURNAL_FEATURE_INCOMPATIBLE_REVOKE 1 + +#define JOURNAL_KNOWN_READ_ONLY_COMPATIBLE_FEATURES 0 +#define JOURNAL_KNOWN_INCOMPATIBLE_FEATURES \ + JOURNAL_FEATURE_INCOMPATIBLE_REVOKE + + +#include "Volume.h" + +#include +#include + +#include "Transaction.h" + + +class RevokeManager; + + +struct JournalHeader { + uint32 magic; + uint32 block_type; + uint32 sequence; + char data[0]; + + uint32 Magic() const + { return B_BENDIAN_TO_HOST_INT32(magic); } + uint32 BlockType() const + { return B_BENDIAN_TO_HOST_INT32(block_type); } + uint32 Sequence() const + { return B_BENDIAN_TO_HOST_INT32(sequence); } + + bool CheckMagic() const + { return Magic() == JOURNAL_MAGIC; } + + void IncrementSequence() + { sequence = B_HOST_TO_BENDIAN_INT32(Sequence() + 1); } + void DecrementSequence() + { sequence = B_HOST_TO_BENDIAN_INT32(Sequence() - 1); } + void MakeDescriptor(uint32 sequence); + void MakeCommit(uint32 sequence); +} _PACKED; + + +struct JournalBlockTag { + uint32 block_number; + uint32 flags; + + uint32 BlockNumber() const + { return B_BENDIAN_TO_HOST_INT32(block_number); } + uint32 Flags() const + { return B_BENDIAN_TO_HOST_INT32(flags); } + + void SetBlockNumber(uint32 block) + { block_number = B_HOST_TO_BENDIAN_INT32(block); } + void SetFlags(uint32 new_flags) + { flags = B_HOST_TO_BENDIAN_INT32(new_flags); } + void SetLastTagFlag() + { flags |= B_HOST_TO_BENDIAN_INT32(JOURNAL_FLAG_LAST_TAG); } + void SetEscapedFlag() + { flags |= B_HOST_TO_BENDIAN_INT32(JOURNAL_FLAG_ESCAPED); } +} _PACKED; + + +struct JournalRevokeHeader { + JournalHeader header; + uint32 num_bytes; + + uint32 revoke_blocks[0]; + + uint32 NumBytes() const + { return B_BENDIAN_TO_HOST_INT32(num_bytes); } + uint32 RevokeBlock(int offset) const + { return B_BENDIAN_TO_HOST_INT32(revoke_blocks[offset]); } +} _PACKED; + + +struct JournalSuperBlock { + JournalHeader header; + + uint32 block_size; + uint32 num_blocks; + uint32 first_log_block; + + uint32 first_commit_id; + uint32 log_start; + + uint32 error; + + uint32 compatible_features; + uint32 incompatible_features; + uint32 read_only_compatible_features; + + uint8 uuid[16]; + + uint32 num_users; + uint32 dynamic_superblock; + + uint32 max_transaction_blocks; + uint32 max_transaction_data; + + uint32 padding[44]; + + uint8 user_ids[16*48]; + + uint32 BlockSize() const + { return B_BENDIAN_TO_HOST_INT32(block_size); } + uint32 NumBlocks() const + { return B_BENDIAN_TO_HOST_INT32(num_blocks); } + uint32 FirstLogBlock() const + { return B_BENDIAN_TO_HOST_INT32(first_log_block); } + uint32 FirstCommitID() const + { return B_BENDIAN_TO_HOST_INT32(first_commit_id); } + uint32 LogStart() const + { return B_BENDIAN_TO_HOST_INT32(log_start); } + uint32 IncompatibleFeatures() const + { return B_BENDIAN_TO_HOST_INT32(incompatible_features); } + uint32 ReadOnlyCompatibleFeatures() const + { return B_BENDIAN_TO_HOST_INT32(read_only_compatible_features); } + uint32 MaxTransactionBlocks() const + { return B_BENDIAN_TO_HOST_INT32(max_transaction_blocks); } + uint32 MaxTransactionData() const + { return B_BENDIAN_TO_HOST_INT32(max_transaction_data); } + + void SetLogStart(uint32 logStart) + { log_start = B_HOST_TO_BENDIAN_INT32(logStart); } + void SetFirstCommitID(uint32 firstCommitID) + { first_commit_id = B_HOST_TO_BENDIAN_INT32(firstCommitID); } +} _PACKED; + +class LogEntry; +class Transaction; +typedef DoublyLinkedList LogEntryList; + + +class Journal { +public: + Journal(Volume *fsVolume, Volume *jVolume); + virtual ~Journal(); + + virtual status_t InitCheck(); + virtual status_t Uninit(); + + virtual status_t Recover(); + virtual status_t StartLog(); + status_t RestartLog(); + + virtual status_t Lock(Transaction* owner, + bool separateSubTransactions); + virtual status_t Unlock(Transaction* owner, bool success); + + virtual status_t MapBlock(uint32 logical, uint32& physical); + inline uint32 FreeLogBlocks() const; + + status_t FlushLogAndBlocks(); + + int32 TransactionID() const; + + Volume* GetFilesystemVolume() + { return fFilesystemVolume; } +protected: + Journal(); + + status_t _WritePartialTransactionToLog( + JournalHeader* descriptorBlock, + bool detached, uint8** escapedBlock, + uint32& logBlock, off_t& blockNumber, + long& cookie, + ArrayDeleter& escapedDataDeleter, + uint32& blockCount, bool& finished); + virtual status_t _WriteTransactionToLog(); + + status_t _SaveSuperBlock(); + status_t _LoadSuperBlock(); + + + Volume* fJournalVolume; + void* fJournalBlockCache; + Volume* fFilesystemVolume; + void* fFilesystemBlockCache; + + recursive_lock fLock; + Transaction* fOwner; + + RevokeManager* fRevokeManager; + + status_t fInitStatus; + uint32 fBlockSize; + uint32 fFirstCommitID; + uint32 fFirstCacheCommitID; + uint32 fFirstLogBlock; + uint32 fLogSize; + uint32 fVersion; + + uint32 fLogStart; + uint32 fLogEnd; + uint32 fFreeBlocks; + uint32 fMaxTransactionSize; + + uint32 fCurrentCommitID; + + LogEntryList fLogEntries; + mutex fLogEntriesLock; + bool fHasSubTransaction; + bool fSeparateSubTransactions; + int32 fUnwrittenTransactions; + int32 fTransactionID; + +private: + status_t _CheckFeatures(JournalSuperBlock* superblock); + + uint32 _CountTags(JournalHeader *descriptorBlock); + status_t _RecoverPassScan(uint32& lastCommitID); + status_t _RecoverPassRevoke(uint32 lastCommitID); + status_t _RecoverPassReplay(uint32 lastCommitID); + + status_t _FlushLog(bool canWait, bool flushBlocks); + + inline uint32 _WrapAroundLog(uint32 block); + + size_t _CurrentTransactionSize() const; + size_t _FullTransactionSize() const; + size_t _MainTransactionSize() const; + + virtual status_t _TransactionDone(bool success); + + static void _TransactionWritten(int32 transactionID, + int32 event, void* _logEntry); + static void _TransactionIdle(int32 transactionID, + int32 event, void* _journal); +}; + +#endif // JOURNAL_H + diff --git a/src/add-ons/kernel/file_systems/ext2/NoJournal.cpp b/src/add-ons/kernel/file_systems/ext2/NoJournal.cpp new file mode 100644 index 0000000000..d6230376eb --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/NoJournal.cpp @@ -0,0 +1,101 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ + + +#include "NoJournal.h" + +#include + +#include + + +//#define TRACE_EXT2 +#ifdef TRACE_EXT2 +# define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) +#else +# define TRACE(x...) ; +#endif + + +NoJournal::NoJournal(Volume* volume) + : + Journal() +{ + fFilesystemVolume = volume; + fFilesystemBlockCache = volume->BlockCache(); + fJournalVolume = volume; + fHasSubTransaction = false; + fSeparateSubTransactions = false; +} + + +NoJournal::~NoJournal() +{ +} + + +status_t +NoJournal::InitCheck() +{ + return B_OK; +} + + +status_t +NoJournal::Recover() +{ + return B_OK; +} + + +status_t +NoJournal::StartLog() +{ + return B_OK; +} + + +status_t +NoJournal::Lock(Transaction* owner, bool separateSubTransactions) +{ + status_t status = block_cache_sync(fFilesystemBlockCache); + TRACE("NoJournal::Lock(): block_cache_sync: %s\n", strerror(status)); + + if (status == B_OK) + status = Journal::Lock(owner, separateSubTransactions); + + return status; +} + + +status_t +NoJournal::Unlock(Transaction* owner, bool success) +{ + TRACE("NoJournal::Unlock\n"); + return Journal::Unlock(owner, success); +} + + +status_t +NoJournal::_WriteTransactionToLog() +{ + TRACE("NoJournal::_WriteTransactionToLog(): Ending transaction %ld\n", + fTransactionID); + + fTransactionID = cache_end_transaction(fFilesystemBlockCache, + fTransactionID, _TransactionWritten, NULL); + + return B_OK; +} + + +/*static*/ void +NoJournal::_TransactionWritten(int32 transactionID, int32 event, void* param) +{ + TRACE("Transaction %ld checkpointed\n", transactionID); +} diff --git a/src/add-ons/kernel/file_systems/ext2/NoJournal.h b/src/add-ons/kernel/file_systems/ext2/NoJournal.h new file mode 100644 index 0000000000..a29c545b39 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/NoJournal.h @@ -0,0 +1,34 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ +#ifndef NOJOURNAL_H +#define NOJOURNAL_H + + +#include "Journal.h" + + +class NoJournal : public Journal { +public: + NoJournal(Volume* volume); + ~NoJournal(); + + status_t InitCheck(); + status_t Recover(); + status_t StartLog(); + + status_t Lock(Transaction* owner, bool separateSubTransactions); + status_t Unlock(Transaction* owner, bool success); + +private: + status_t _WriteTransactionToLog(); + + static void _TransactionWritten(int32 transactionID, + int32 event, void* param); +}; + +#endif // NOJOURNAL_H diff --git a/src/add-ons/kernel/file_systems/ext2/RevokeManager.cpp b/src/add-ons/kernel/file_systems/ext2/RevokeManager.cpp new file mode 100644 index 0000000000..1e15f652bf --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/RevokeManager.cpp @@ -0,0 +1,52 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ + + +#include "RevokeManager.h" + + +//#define TRACE_EXT2 +#ifdef TRACE_EXT2 +# define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) +#else +# define TRACE(x...) ; +#endif + + +RevokeManager::RevokeManager() + : + fRevokeCount(0) +{ +} + + +RevokeManager::~RevokeManager() +{ +} + + +status_t +RevokeManager::ScanRevokeBlock(JournalRevokeHeader* revokeBlock, uint32 commitID) +{ + TRACE("RevokeManager::ScanRevokeBlock(): Commit ID: %lu\n", commitID); + int count = revokeBlock->NumBytes() / 4; + + for (int i = 0; i < count; ++i) { + TRACE("RevokeManager::ScanRevokeBlock(): Found a revoked block: %lu\n", + revokeBlock->RevokeBlock(i)); + status_t status = Insert(revokeBlock->RevokeBlock(i), commitID); + + if (status != B_OK) { + TRACE("RevokeManager::ScanRevokeBlock(): Error inserting\n"); + return status; + } + } + + return B_OK; +} + diff --git a/src/add-ons/kernel/file_systems/ext2/RevokeManager.h b/src/add-ons/kernel/file_systems/ext2/RevokeManager.h new file mode 100644 index 0000000000..796f7fe947 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/RevokeManager.h @@ -0,0 +1,35 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ +#ifndef REVOKEMANAGER_H +#define REVOKEMANAGER_H + +#include "Journal.h" + + +struct JournalRevokeHeader; + +class RevokeManager { +public: + RevokeManager(); + virtual ~RevokeManager() = 0; + + virtual status_t Insert(uint32 block, uint32 commitID) = 0; + virtual status_t Remove(uint32 block) = 0; + virtual bool Lookup(uint32 block, uint32 commitID) = 0; + + uint32 NumRevokes() { return fRevokeCount; } + + status_t ScanRevokeBlock(JournalRevokeHeader* revokeBlock, + uint32 commitID); + +protected: + uint32 fRevokeCount; +}; + +#endif // REVOKEMANAGER_H + diff --git a/src/add-ons/kernel/file_systems/ext2/Transaction.cpp b/src/add-ons/kernel/file_systems/ext2/Transaction.cpp new file mode 100644 index 0000000000..32469e718e --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/Transaction.cpp @@ -0,0 +1,224 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ + + +#include "Transaction.h" + +#include + +#include + +#include "Journal.h" + + +//#define TRACE_EXT2 +#ifdef TRACE_EXT2 +# define TRACE(x...) dprintf("\33[34mext2:\33[0m " x) +#else +# define TRACE(x...) ; +#endif + + +TransactionListener::TransactionListener() +{ +} + + +TransactionListener::~TransactionListener() +{ +} + + +Transaction::Transaction() + : + fJournal(NULL), + fParent(NULL) +{ +} + + +Transaction::Transaction(Journal* journal) + : + fJournal(NULL), + fParent(NULL) +{ + Start(journal); +} + + +Transaction::~Transaction() +{ + if (IsStarted()) + fJournal->Unlock(this, false); +} + +status_t +Transaction::Start(Journal* journal) +{ + if (IsStarted()) + return B_OK; + + fJournal = journal; + if (fJournal == NULL) + return B_ERROR; + + status_t status = fJournal->Lock(this, false); + if (status != B_OK) + fJournal = NULL; + + return status; +} + + +status_t +Transaction::Done(bool success) +{ + if (!IsStarted()) + return B_OK; + + status_t status = fJournal->Unlock(this, success); + + if (status == B_OK) + fJournal = NULL; + + return status; +} + + +int32 +Transaction::ID() const +{ + if (!IsStarted()) + return -1; + + return fJournal->TransactionID(); +} + + +bool +Transaction::IsStarted() const +{ + return fJournal != NULL; +} + + +bool +Transaction::HasParent() const +{ + return fParent != NULL; +} + + +status_t +Transaction::WriteBlocks(off_t blockNumber, const uint8* buffer, + size_t numBlocks) +{ + if (!IsStarted()) + return B_NO_INIT; + + void* cache = GetVolume()->BlockCache(); + size_t blockSize = GetVolume()->BlockSize(); + + for (size_t i = 0; i < numBlocks; ++i) { + void* block = block_cache_get_empty(cache, blockNumber + i, ID()); + if (block == NULL) + return B_ERROR; + + memcpy(block, buffer, blockSize); + buffer += blockSize; + + block_cache_put(cache, blockNumber + i); + } + + return B_OK; +} + + +void +Transaction::Split() +{ + cache_start_sub_transaction(fJournal->GetFilesystemVolume()->BlockCache(), + ID()); +} + + +Volume* +Transaction::GetVolume() const +{ + if (!IsStarted()) + return NULL; + + return fJournal->GetFilesystemVolume(); +} + + +void +Transaction::AddListener(TransactionListener* listener) +{ + TRACE("Transaction::AddListener()\n"); + if (!IsStarted()) + panic("Transaction is not running!"); + + fListeners.Add(listener); +} + + +void +Transaction::RemoveListener(TransactionListener* listener) +{ + TRACE("Transaction::RemoveListener()\n"); + if (!IsStarted()) + panic("Transaction is not running!"); + + fListeners.Remove(listener); + listener->RemovedFromTransaction(); +} + + +void +Transaction::NotifyListeners(bool success) +{ + TRACE("Transaction::NotifyListeners(): fListeners.First(): %p\n", + fListeners.First()); + if (success) { + TRACE("Transaction::NotifyListeners(true): Number of listeners: %ld\n", + fListeners.Count()); + } else { + TRACE("Transaction::NotifyListeners(false): Number of listeners: %ld\n", + fListeners.Count()); + } + TRACE("Transaction::NotifyListeners(): Finished counting\n"); + + while (TransactionListener* listener = fListeners.RemoveHead()) { + listener->TransactionDone(success); + listener->RemovedFromTransaction(); + } +} + + +void +Transaction::MoveListenersTo(Transaction* transaction) +{ + TRACE("Transaction::MoveListenersTo()\n"); + while (TransactionListener* listener = fListeners.RemoveHead()) + transaction->fListeners.Add(listener); +} + + +void +Transaction::SetParent(Transaction* transaction) +{ + fParent = transaction; +} + + +Transaction* +Transaction::Parent() const +{ + return fParent; +} diff --git a/src/add-ons/kernel/file_systems/ext2/Transaction.h b/src/add-ons/kernel/file_systems/ext2/Transaction.h new file mode 100644 index 0000000000..a85b105dc0 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/Transaction.h @@ -0,0 +1,72 @@ +/* + * Copyright 2001-2010, Haiku Inc. All rights reserved. + * This file may be used under the terms of the MIT License. + * + * Authors: + * Janito V. Ferreira Filho + */ +#ifndef TRANSACTION_H +#define TRANSACTION_H + + +#include + + +class Journal; +class Volume; + + +class TransactionListener + : public DoublyLinkedListLinkImpl { +public: + TransactionListener(); + virtual ~TransactionListener(); + + virtual void TransactionDone(bool success) = 0; + virtual void RemovedFromTransaction() = 0; +}; + +typedef DoublyLinkedList TransactionListeners; + + +class Transaction { +public: + Transaction(); + Transaction(Journal* journal); + ~Transaction(); + + status_t Start(Journal* journal); + status_t Done(bool success = true); + + bool IsStarted() const; + bool HasParent() const; + + status_t WriteBlocks(off_t blockNumber, + const uint8* buffer, + size_t numBlocks = 1); + + void Split(); + + Volume* GetVolume() const; + int32 ID() const; + + void AddListener(TransactionListener* listener); + void RemoveListener( + TransactionListener* listener); + + void NotifyListeners(bool success); + void MoveListenersTo(Transaction* transaction); + + void SetParent(Transaction* transaction); + Transaction* Parent() const; +private: + Transaction(const Transaction& other); + Transaction& operator=(const Transaction& other); + // no implementation + + Journal* fJournal; + TransactionListeners fListeners; + Transaction* fParent; +}; + +#endif // TRANSACTION_H diff --git a/src/add-ons/kernel/file_systems/ext2/Utility.h b/src/add-ons/kernel/file_systems/ext2/Utility.h new file mode 100644 index 0000000000..4d947d4173 --- /dev/null +++ b/src/add-ons/kernel/file_systems/ext2/Utility.h @@ -0,0 +1,39 @@ +/* + * Copyright 2001-2009, Axel Dörfler, axeld@pinc-software.de. + * This file may be used under the terms of the MIT License. + */ +#ifndef UTILITY_H +#define UTILITY_H + +#include "ext2.h" + +enum inode_type { + S_DIRECTORY = S_IFDIR, + S_FILE = S_IFREG, + S_SYMLINK = S_IFLNK, + + S_INDEX_TYPES = (S_STR_INDEX | S_INT_INDEX | S_UINT_INDEX + | S_LONG_LONG_INDEX | S_ULONG_LONG_INDEX + | S_FLOAT_INDEX | S_DOUBLE_INDEX), + + S_EXTENDED_TYPES = (S_ATTR_DIR | S_ATTR | S_INDEX_DIR) +}; + + +/*! Converts the open mode, the open flags given to bfs_open(), into + access modes, e.g. since O_RDONLY requires read access to the + file, it will be converted to R_OK. +*/ +inline int +open_mode_to_access(int openMode) +{ + openMode &= O_RWMASK; + if (openMode == O_RDONLY) + return R_OK; + if (openMode == O_WRONLY) + return W_OK; + + return R_OK | W_OK; +} + +#endif // UTILITY_H diff --git a/src/add-ons/kernel/file_systems/ext2/Volume.cpp b/src/add-ons/kernel/file_systems/ext2/Volume.cpp index 1911dbb183..85b98521a0 100644 --- a/src/add-ons/kernel/file_systems/ext2/Volume.cpp +++ b/src/add-ons/kernel/file_systems/ext2/Volume.cpp @@ -20,7 +20,10 @@ #include +#include "CachedBlock.h" #include "Inode.h" +#include "InodeJournal.h" +#include "NoJournal.h" //#define TRACE_EXT2 @@ -199,7 +202,7 @@ ext2_super_block::IsValid() // TODO: check some more values! if (Magic() != (uint32)EXT2_SUPER_BLOCK_MAGIC) return false; - + return true; } @@ -210,6 +213,9 @@ ext2_super_block::IsValid() Volume::Volume(fs_volume* volume) : fFSVolume(volume), + fBlockAllocator(this), + fInodeAllocator(this), + fJournalInode(NULL), fFlags(0), fGroupBlocks(NULL), fRootNode(NULL) @@ -220,6 +226,7 @@ Volume::Volume(fs_volume* volume) Volume::~Volume() { + TRACE("Volume destructor.\n"); if (fGroupBlocks != NULL) { uint32 blockCount = (fNumGroups + fGroupsPerBlock - 1) / fGroupsPerBlock; @@ -239,6 +246,13 @@ Volume::IsValidSuperBlock() } +bool +Volume::HasExtendedAttributes() const +{ + return (fSuperBlock.CompatibleFeatures() & EXT2_FEATURE_EXT_ATTR) != 0; +} + + const char* Volume::Name() const { @@ -252,8 +266,14 @@ Volume::Name() const status_t Volume::Mount(const char* deviceName, uint32 flags) { - flags |= B_MOUNT_READ_ONLY; + // flags |= B_MOUNT_READ_ONLY; // we only support read-only for now + + if ((flags & B_MOUNT_READ_ONLY) != 0) { + TRACE("Volume::Mount(): Read only\n"); + } else { + TRACE("Volume::Mount(): Read write\n"); + } DeviceOpener opener(deviceName, (flags & B_MOUNT_READ_ONLY) != 0 ? O_RDONLY : O_RDWR); @@ -278,10 +298,17 @@ Volume::Mount(const char* deviceName, uint32 flags) fBlockSize = 1UL << fSuperBlock.BlockShift(); fFirstDataBlock = fSuperBlock.FirstDataBlock(); - fNumInodes = fSuperBlock.NumInodes(); - fNumGroups = (fSuperBlock.NumBlocks() - fFirstDataBlock - 1) - / fSuperBlock.BlocksPerGroup() + 1; + fFreeBlocks = fSuperBlock.FreeBlocks(); + fFreeInodes = fSuperBlock.FreeInodes(); + + uint32 numBlocks = fSuperBlock.NumBlocks() - fFirstDataBlock; + uint32 blocksPerGroup = fSuperBlock.BlocksPerGroup(); + fNumGroups = numBlocks / blocksPerGroup; + if (numBlocks % blocksPerGroup != 0) + fNumGroups++; + fGroupsPerBlock = fBlockSize / sizeof(ext2_block_group); + fNumInodes = fSuperBlock.NumInodes(); TRACE("block size %ld, num groups %ld, groups per block %ld, first %lu\n", fBlockSize, fNumGroups, fGroupsPerBlock, fFirstDataBlock); @@ -309,7 +336,59 @@ Volume::Mount(const char* deviceName, uint32 flags) fBlockCache = opener.InitCache(NumBlocks(), fBlockSize); if (fBlockCache == NULL) return B_ERROR; + + TRACE("Volume::Mount(): Initialized block cache: %p\n", fBlockCache); + // initialize journal + if ((fSuperBlock.CompatibleFeatures() & EXT2_FEATURE_HAS_JOURNAL) != 0) { + // TODO: There should be a mount option to ignore the existent journal + if (fSuperBlock.JournalInode() != 0) { + fJournalInode = new(std::nothrow) Inode(this, + fSuperBlock.JournalInode()); + + if (fJournalInode == NULL) + return B_NO_MEMORY; + + TRACE("Opening an on disk, inode mapped journal.\n"); + fJournal = new(std::nothrow) InodeJournal(fJournalInode); + } else { + // TODO: external journal + TRACE("Can not open an external journal.\n"); + return B_NOT_SUPPORTED; + } + } else { + TRACE("Opening a fake journal (NoJournal).\n"); + fJournal = new(std::nothrow) NoJournal(this); + } + + if (fJournal == NULL) { + TRACE("No memory to create the journal\n"); + return B_NO_MEMORY; + } + + TRACE("Volume::Mount(): Checking if journal was initialized\n"); + status = fJournal->InitCheck(); + if (status != B_OK) + return status; + + // TODO: Only recover if asked to + TRACE("Volume::Mount(): Asking journal to recover\n"); + status = fJournal->Recover(); + if (status != B_OK) + return status; + + TRACE("Volume::Mount(): Restart journal log\n"); + status = fJournal->StartLog(); + if (status != B_OK) + return status; + + // Initialize allocators + TRACE("Volume::Mount(): Initialize block allocator\n"); + status = fBlockAllocator.Initialize(); + if (status != B_OK) + return status; + + // ready status = get_vnode(fFSVolume, EXT2_ROOT_NODE, (void**)&fRootNode); if (status != B_OK) { TRACE("could not create root node: get_vnode() failed!\n"); @@ -342,11 +421,22 @@ Volume::Mount(const char* deviceName, uint32 flags) status_t Volume::Unmount() { + TRACE("Volume::Unmount()\n"); + + status_t status = fJournal->Uninit(); + + delete fJournal; + delete fJournalInode; + + TRACE("Volume::Unmount(): Putting root node\n"); put_vnode(fFSVolume, RootNode()->ID()); + TRACE("Volume::Unmount(): Deleting the block cache\n"); block_cache_delete(fBlockCache, !IsReadOnly()); + TRACE("Volume::Unmount(): Closing device\n"); close(fDevice); - return B_OK; + TRACE("Volume::Unmount(): Done\n"); + return status; } @@ -391,13 +481,13 @@ Volume::_UnsupportedIncompatibleFeatures(ext2_super_block& superBlock) } -off_t -Volume::_GroupBlockOffset(uint32 blockIndex) +uint32 +Volume::_GroupDescriptorBlock(uint32 blockIndex) { if ((fSuperBlock.IncompatibleFeatures() & EXT2_INCOMPATIBLE_FEATURE_META_GROUP) == 0 || blockIndex < fSuperBlock.FirstMetaBlockGroup()) - return off_t(fFirstDataBlock + blockIndex + 1) << fBlockShift; + return fFirstDataBlock + blockIndex + 1; panic("meta block"); return 0; @@ -419,18 +509,16 @@ Volume::GetBlockGroup(int32 index, ext2_block_group** _group) MutexLocker _(fLock); if (fGroupBlocks[blockIndex] == NULL) { + CachedBlock cached(this); + const uint8* block = cached.SetTo(_GroupDescriptorBlock(blockIndex)); + if (block == NULL) + return B_IO_ERROR; + ext2_block_group* groupBlock = (ext2_block_group*)malloc(fBlockSize); if (groupBlock == NULL) return B_NO_MEMORY; - ssize_t bytesRead = read_pos(fDevice, _GroupBlockOffset(blockIndex), - groupBlock, fBlockSize); - if (bytesRead >= B_OK && (uint32)bytesRead != fBlockSize) - bytesRead = B_IO_ERROR; - if (bytesRead < B_OK) { - free(groupBlock); - return bytesRead; - } + memcpy((uint8*)groupBlock, block, fBlockSize); fGroupBlocks[blockIndex] = groupBlock; @@ -443,6 +531,261 @@ Volume::GetBlockGroup(int32 index, ext2_block_group** _group) } +status_t +Volume::WriteBlockGroup(Transaction& transaction, int32 index) +{ + if (index < 0 || (uint32)index > fNumGroups) + return B_BAD_VALUE; + + TRACE("Volume::WriteBlockGroup()\n"); + + int32 blockIndex = index / fGroupsPerBlock; + + MutexLocker _(fLock); + + if (fGroupBlocks[blockIndex] == NULL) + return B_BAD_VALUE; + + CachedBlock cached(this); + uint8* block = cached.SetToWritable(transaction, + _GroupDescriptorBlock(blockIndex)); + if (block == NULL) + return B_IO_ERROR; + + memcpy(block, (const uint8*)fGroupBlocks[blockIndex], fBlockSize); + + // TODO: Write copies + + return B_OK; +} + + +status_t +Volume::SaveOrphan(Transaction& transaction, ino_t newID, ino_t& oldID) +{ + oldID = fSuperBlock.LastOrphan(); + TRACE("Volume::SaveOrphan(): Old: %d, New: %d\n", (int)oldID, (int)newID); + fSuperBlock.SetLastOrphan(newID); + + return WriteSuperBlock(transaction); +} + + +status_t +Volume::RemoveOrphan(Transaction& transaction, ino_t id) +{ + ino_t currentID = fSuperBlock.LastOrphan(); + TRACE("Volume::RemoveOrphan(): ID: %d\n", (int)id); + if (currentID == 0) + return B_OK; + + CachedBlock cached(this); + + uint32 blockNum; + status_t status = GetInodeBlock(currentID, blockNum); + if (status != B_OK) + return status; + + uint8* block = cached.SetToWritable(transaction, blockNum); + if (block == NULL) + return B_IO_ERROR; + + ext2_inode* inode = (ext2_inode*)(block + + InodeBlockIndex(currentID) * InodeSize()); + + if (currentID == id) { + TRACE("Volume::RemoveOrphan(): First entry. Updating head to: %d\n", + (int)inode->NextOrphan()); + fSuperBlock.SetLastOrphan(inode->NextOrphan()); + + return WriteSuperBlock(transaction); + } + + currentID = inode->NextOrphan(); + if (currentID == 0) + return B_OK; + + do { + uint32 lastBlockNum = blockNum; + status = GetInodeBlock(currentID, blockNum); + if (status != B_OK) + return status; + + if (blockNum != lastBlockNum) { + block = cached.SetToWritable(transaction, blockNum); + if (block == NULL) + return B_IO_ERROR; + } + + ext2_inode* inode = (ext2_inode*)(block + + InodeBlockIndex(currentID) * InodeSize()); + + currentID = inode->NextOrphan(); + if (currentID == 0) + return B_OK; + } while(currentID != id); + + CachedBlock cachedRemoved(this); + + status = GetInodeBlock(id, blockNum); + if (status != B_OK) + return status; + + uint8* removedBlock = cachedRemoved.SetToWritable(transaction, blockNum); + if (removedBlock == NULL) + return B_IO_ERROR; + + ext2_inode* removedInode = (ext2_inode*)(removedBlock + + InodeBlockIndex(id) * InodeSize()); + + // Next orphan is stored inside deletion time + inode->deletion_time = removedInode->deletion_time; + TRACE("Volume::RemoveOrphan(): Updated pointer to %d\n", + (int)inode->NextOrphan()); + + return status; +} + + +status_t +Volume::AllocateInode(Transaction& transaction, Inode* parent, int32 mode, + ino_t& id) +{ + status_t status = fInodeAllocator.New(transaction, parent, mode, id); + if (status != B_OK) + return status; + + --fFreeInodes; + + return WriteSuperBlock(transaction); +} + + +status_t +Volume::FreeInode(Transaction& transaction, ino_t id, bool isDirectory) +{ + status_t status = fInodeAllocator.Free(transaction, id, isDirectory); + if (status != B_OK) + return status; + + ++fFreeInodes; + + return WriteSuperBlock(transaction); +} + + +status_t +Volume::AllocateBlocks(Transaction& transaction, uint32 minimum, uint32 maximum, + uint32& blockGroup, uint32& start, uint32& length) +{ + TRACE("Volume::AllocateBlocks()\n"); + if (IsReadOnly()) + return B_READ_ONLY_DEVICE; + + TRACE("Volume::AllocateBlocks(): Calling the block allocator\n"); + + status_t status = fBlockAllocator.AllocateBlocks(transaction, minimum, + maximum, blockGroup, start, length); + if (status != B_OK) + return status; + + TRACE("Volume::AllocateBlocks(): Allocated %lu blocks\n", length); + + fFreeBlocks -= length; + + return WriteSuperBlock(transaction); +} + + +status_t +Volume::FreeBlocks(Transaction& transaction, uint32 start, uint32 length) +{ + TRACE("Volume::FreeBlocks(%lu, %lu)\n", start, length); + if (IsReadOnly()) + return B_READ_ONLY_DEVICE; + + status_t status = fBlockAllocator.Free(transaction, start, length); + if (status != B_OK) + return status; + + TRACE("Volume::FreeBlocks(): number of free blocks (before): %lu\n", + fFreeBlocks); + fFreeBlocks += length; + TRACE("Volume::FreeBlocks(): number of free blocks (after): %lu\n", + fFreeBlocks); + + return WriteSuperBlock(transaction); +} + + +status_t +Volume::LoadSuperBlock() +{ + CachedBlock cached(this); + const uint8* block = cached.SetTo(fFirstDataBlock); + + if (block == NULL) + return B_IO_ERROR; + + if (fFirstDataBlock == 0) + memcpy(&fSuperBlock, block + 1024, sizeof(fSuperBlock)); + else + memcpy(&fSuperBlock, block, sizeof(fSuperBlock)); + + fFreeBlocks = fSuperBlock.FreeBlocks(); + fFreeInodes = fSuperBlock.FreeInodes(); + + return B_OK; +} + + +status_t +Volume::WriteSuperBlock(Transaction& transaction) +{ + TRACE("Volume::WriteSuperBlock()\n"); + fSuperBlock.SetFreeBlocks(fFreeBlocks); + fSuperBlock.SetFreeInodes(fFreeInodes); + // TODO: Rest of fields that can be modified + + TRACE("Volume::WriteSuperBlock(): free blocks: %lu, free inodes: %lu\n", + fSuperBlock.FreeBlocks(), fSuperBlock.FreeInodes()); + + CachedBlock cached(this); + uint8* block = cached.SetToWritable(transaction, fFirstDataBlock); + + if (block == NULL) + return B_IO_ERROR; + + TRACE("Volume::WriteSuperBlock(): first data block: %lu, block: %p, " + "superblock: %p\n", fFirstDataBlock, block, &fSuperBlock); + + if (fFirstDataBlock == 0) + memcpy(block + 1024, &fSuperBlock, sizeof(fSuperBlock)); + else + memcpy(block, &fSuperBlock, sizeof(fSuperBlock)); + + TRACE("Volume::WriteSuperBlock(): Done\n"); + + return B_OK; +} + + +status_t +Volume::FlushDevice() +{ + TRACE("Volume::FlushDevice(): %p, %p\n", this, fBlockCache); + return block_cache_sync(fBlockCache); +} + + +status_t +Volume::Sync() +{ + TRACE("Volume::Sync()\n"); + return fJournal->FlushLogAndBlocks(); +} + + // #pragma mark - Disk scanning and initialization @@ -460,3 +803,20 @@ Volume::Identify(int fd, ext2_super_block* superBlock) ? B_OK : B_NOT_SUPPORTED; } + +void +Volume::TransactionDone(bool success) +{ + if (!success) { + status_t status = LoadSuperBlock(); + if (status != B_OK) + panic("Failed to reload ext2 superblock.\n"); + } +} + + +void +Volume::RemovedFromTransaction() +{ + // TODO: Does it make a difference? +} diff --git a/src/add-ons/kernel/file_systems/ext2/Volume.h b/src/add-ons/kernel/file_systems/ext2/Volume.h index 1945afbb86..7f97750d3d 100644 --- a/src/add-ons/kernel/file_systems/ext2/Volume.h +++ b/src/add-ons/kernel/file_systems/ext2/Volume.h @@ -9,8 +9,12 @@ #include #include "ext2.h" +#include "BlockAllocator.h" +#include "InodeAllocator.h" +#include "Transaction.h" class Inode; +class Journal; enum volume_flags { @@ -18,7 +22,7 @@ enum volume_flags { }; -class Volume { +class Volume : public TransactionListener { public: Volume(fs_volume* volume); ~Volume(); @@ -30,6 +34,8 @@ public: bool IsReadOnly() const { return (fFlags & VOLUME_READ_ONLY) != 0; } + bool HasExtendedAttributes() const; + Inode* RootNode() const { return fRootNode; } int Device() const { return fDevice; } @@ -40,35 +46,76 @@ public: uint32 NumInodes() const { return fNumInodes; } + uint32 NumGroups() const + { return fNumGroups; } off_t NumBlocks() const { return fSuperBlock.NumBlocks(); } - off_t FreeBlocks() const - { return fSuperBlock.FreeBlocks(); } + off_t NumFreeBlocks() const + { return fFreeBlocks; } + uint32 FirstDataBlock() const + { return fFirstDataBlock; } uint32 BlockSize() const { return fBlockSize; } uint32 BlockShift() const { return fBlockShift; } + uint32 BlocksPerGroup() const + { return fSuperBlock.BlocksPerGroup(); } uint32 InodeSize() const { return fSuperBlock.InodeSize(); } + uint32 InodesPerGroup() const + { return fSuperBlock.InodesPerGroup(); } ext2_super_block& SuperBlock() { return fSuperBlock; } status_t GetInodeBlock(ino_t id, uint32& block); uint32 InodeBlockIndex(ino_t id) const; status_t GetBlockGroup(int32 index, ext2_block_group** _group); - + status_t WriteBlockGroup(Transaction& transaction, + int32 index); + + Journal* GetJournal() { return fJournal; } + bool IndexedDirectories() const { return (fSuperBlock.CompatibleFeatures() & EXT2_FEATURE_DIRECTORY_INDEX) != 0; } + uint8 DefaultHashVersion() const + { return fSuperBlock.default_hash_version; } + + status_t SaveOrphan(Transaction& transaction, + ino_t newID, ino_t &oldID); + status_t RemoveOrphan(Transaction& transaction, + ino_t id); + + status_t AllocateInode(Transaction& transaction, + Inode* parent, int32 mode, ino_t& id); + status_t FreeInode(Transaction& transaction, ino_t id, + bool isDirectory); + + status_t AllocateBlocks(Transaction& transaction, + uint32 minimum, uint32 maximum, + uint32& blockGroup, uint32& start, + uint32& length); + status_t FreeBlocks(Transaction& transaction, + uint32 start, uint32 length); + + status_t LoadSuperBlock(); + status_t WriteSuperBlock(Transaction& transaction); // cache access void* BlockCache() { return fBlockCache; } + status_t FlushDevice(); + status_t Sync(); + static status_t Identify(int fd, ext2_super_block* superBlock); + // TransactionListener functions + void TransactionDone(bool success); + void RemovedFromTransaction(); + private: static uint32 _UnsupportedIncompatibleFeatures( ext2_super_block& superBlock); - off_t _GroupBlockOffset(uint32 blockIndex); + uint32 _GroupDescriptorBlock(uint32 blockIndex); private: mutex fLock; @@ -76,12 +123,21 @@ private: int fDevice; ext2_super_block fSuperBlock; char fName[32]; + + BlockAllocator fBlockAllocator; + InodeAllocator fInodeAllocator; + Journal* fJournal; + Inode* fJournalInode; + uint32 fFlags; uint32 fBlockSize; uint32 fBlockShift; uint32 fFirstDataBlock; + uint32 fNumInodes; uint32 fNumGroups; + uint32 fFreeBlocks; + uint32 fFreeInodes; uint32 fGroupsPerBlock; ext2_block_group** fGroupBlocks; uint32 fInodesPerBlock; diff --git a/src/add-ons/kernel/file_systems/ext2/ext2.h b/src/add-ons/kernel/file_systems/ext2/ext2.h index 4e267ab9b7..3839349be7 100644 --- a/src/add-ons/kernel/file_systems/ext2/ext2.h +++ b/src/add-ons/kernel/file_systems/ext2/ext2.h @@ -13,6 +13,8 @@ #include +//#define TRACE_EXT2 + #define EXT2_SUPER_BLOCK_OFFSET 1024 struct ext2_super_block { @@ -108,9 +110,20 @@ struct ext2_super_block { { return B_LENDIAN_TO_HOST_INT32(read_only_features); } uint32 IncompatibleFeatures() const { return B_LENDIAN_TO_HOST_INT32(incompatible_features); } + ino_t JournalInode() const + { return B_LENDIAN_TO_HOST_INT32(journal_inode); } + ino_t LastOrphan() const + { return (ino_t)B_LENDIAN_TO_HOST_INT32(last_orphan); } uint32 HashSeed(uint8 i) const { return B_LENDIAN_TO_HOST_INT32(hash_seed[i]); } + void SetFreeInodes(uint32 freeInodes) + { free_inodes = B_HOST_TO_LENDIAN_INT32(freeInodes); } + void SetFreeBlocks(uint32 freeBlocks) + { free_blocks = B_HOST_TO_LENDIAN_INT32(freeBlocks); } + void SetLastOrphan(ino_t id) + { last_orphan = B_HOST_TO_LENDIAN_INT32((uint32)id); } + bool IsValid(); // implemented in Volume.cpp } _PACKED; @@ -162,8 +175,27 @@ struct ext2_block_group { uint16 _padding; uint32 _reserved[3]; - uint32 InodeTable() const + uint32 BlockBitmap() const + { return B_LENDIAN_TO_HOST_INT32(block_bitmap); } + uint32 InodeBitmap() const + { return B_LENDIAN_TO_HOST_INT32(inode_bitmap); } + uint32 InodeTable() const { return B_LENDIAN_TO_HOST_INT32(inode_table); } + uint16 FreeBlocks() const + { return B_LENDIAN_TO_HOST_INT16(free_blocks); } + uint16 FreeInodes() const + { return B_LENDIAN_TO_HOST_INT16(free_inodes); } + uint16 UsedDirectories() const + { return B_LENDIAN_TO_HOST_INT16(used_directories); } + + void SetFreeBlocks(uint16 freeBlocks) + { free_blocks = B_HOST_TO_LENDIAN_INT16(freeBlocks); } + + void SetFreeInodes(uint16 freeInodes) + { free_inodes = B_HOST_TO_LENDIAN_INT16(freeInodes); } + + void SetUsedDirectories(uint16 usedDirectories) + { used_directories = B_HOST_TO_LENDIAN_INT16(usedDirectories); } } _PACKED; #define EXT2_DIRECT_BLOCKS 12 @@ -214,11 +246,13 @@ struct ext2_inode { uint16 Mode() const { return B_LENDIAN_TO_HOST_INT16(mode); } uint32 Flags() const { return B_LENDIAN_TO_HOST_INT32(flags); } uint16 NumLinks() const { return B_LENDIAN_TO_HOST_INT16(num_links); } + uint32 NumBlocks() const { return B_LENDIAN_TO_HOST_INT32(num_blocks); } time_t AccessTime() const { return B_LENDIAN_TO_HOST_INT32(access_time); } time_t CreationTime() const { return B_LENDIAN_TO_HOST_INT32(creation_time); } time_t ModificationTime() const { return B_LENDIAN_TO_HOST_INT32(modification_time); } time_t DeletionTime() const { return B_LENDIAN_TO_HOST_INT32(deletion_time); } + ino_t NextOrphan() const { return (ino_t)DeletionTime(); } off_t Size() const { @@ -241,6 +275,85 @@ struct ext2_inode { return B_LENDIAN_TO_HOST_INT16(gid) | (B_LENDIAN_TO_HOST_INT16(gid_high) << 16); } + + void SetMode(uint16 newMode) + { + mode = B_LENDIAN_TO_HOST_INT16(newMode); + } + + void UpdateMode(uint16 newMode, uint16 mask) + { + SetMode((Mode() & ~mask) | (newMode & mask)); + } + + void SetFlag(uint32 mask) + { + flags |= B_HOST_TO_LENDIAN_INT32(mask); + } + + void SetFlags(uint32 newFlags) + { + flags = B_HOST_TO_LENDIAN_INT32(newFlags); + } + + void SetNumLinks(uint16 numLinks) + { + num_links = B_HOST_TO_LENDIAN_INT16(numLinks); + } + + void SetNumBlocks(uint32 numBlocks) + { + num_blocks = B_HOST_TO_LENDIAN_INT32(numBlocks); + } + + void SetAccessTime(time_t accessTime) + { + access_time = B_HOST_TO_LENDIAN_INT32((uint32)accessTime); + } + + void SetCreationTime(time_t creationTime) + { + creation_time = B_HOST_TO_LENDIAN_INT32((uint32)creationTime); + } + + void SetModificationTime(time_t modificationTime) + { + modification_time = B_HOST_TO_LENDIAN_INT32((uint32)modificationTime); + } + + void SetDeletionTime(time_t deletionTime) + { + deletion_time = B_HOST_TO_LENDIAN_INT32((uint32)deletionTime); + } + + void SetNextOrphan(ino_t id) + { + deletion_time = B_HOST_TO_LENDIAN_INT32((uint32)id); + } + + void SetSize(off_t newSize) + { + size = B_HOST_TO_LENDIAN_INT32(newSize & 0xFFFFFFFF); + if (S_ISREG(Mode())) + size_high = B_HOST_TO_LENDIAN_INT32(newSize >> 32); + } + + void SetUserID(uint32 newUID) + { + uid = B_HOST_TO_LENDIAN_INT16(newUID & 0xFFFF); + uid_high = B_HOST_TO_LENDIAN_INT16(newUID >> 16); + } + + void SetGroupID(uint32 newGID) + { + gid = B_HOST_TO_LENDIAN_INT16(newGID & 0xFFFF); + gid_high = B_HOST_TO_LENDIAN_INT16(newGID >> 16); + } + + void SetExtendedAttributesBlock(uint32 block) + { + file_access_control = B_HOST_TO_LENDIAN_INT32(block); + } } _PACKED; #define EXT2_SUPER_BLOCK_MAGIC 0xef53 @@ -270,10 +383,26 @@ struct ext2_dir_entry { uint8 file_type; char name[EXT2_NAME_LENGTH]; - uint32 InodeID() const { return B_LENDIAN_TO_HOST_INT32(inode_id); } - uint16 Length() const { return B_LENDIAN_TO_HOST_INT16(length); } - uint8 NameLength() const { return name_length; } - uint8 FileType() const { return file_type; } + uint32 InodeID() const { return B_LENDIAN_TO_HOST_INT32(inode_id); } + uint16 Length() const { return B_LENDIAN_TO_HOST_INT16(length); } + uint8 NameLength() const { return name_length; } + uint8 FileType() const { return file_type; } + + void SetInodeID(uint32 id) { inode_id = B_HOST_TO_LENDIAN_INT32(id); } + + void SetLength(uint16 newLength/*uint8 nameLength*/) + { + length = B_HOST_TO_LENDIAN_INT16(newLength); + /*name_length = nameLength; + + if (nameLength % 4 == 0) { + length = B_HOST_TO_LENDIAN_INT16( + (short)(nameLength + MinimumSize())); + } else { + length = B_HOST_TO_LENDIAN_INT16( + (short)(nameLength % 4 + 1 + MinimumSize())); + }*/ + } bool IsValid() const { @@ -370,6 +499,17 @@ struct ext2_xattr_entry { } _PACKED; +struct file_cookie { + bigtime_t last_notification; + off_t last_size; + int open_mode; +}; + +#define EXT2_OPEN_MODE_USER_MASK 0x7fffffff + +#define INODE_NOTIFICATION_INTERVAL 10000000LL + + extern fs_volume_ops gExt2VolumeOps; extern fs_vnode_ops gExt2VnodeOps; diff --git a/src/add-ons/kernel/file_systems/ext2/kernel_interface.cpp b/src/add-ons/kernel/file_systems/ext2/kernel_interface.cpp index 99d7fa6e47..a03e1e52e0 100644 --- a/src/add-ons/kernel/file_systems/ext2/kernel_interface.cpp +++ b/src/add-ons/kernel/file_systems/ext2/kernel_interface.cpp @@ -11,12 +11,18 @@ #include #include #include +#include +#include +#include #include "AttributeIterator.h" +#include "CachedBlock.h" #include "DirectoryIterator.h" #include "ext2.h" #include "HTree.h" #include "Inode.h" +#include "Journal.h" +#include "Utility.h" //#define TRACE_EXT2 @@ -35,20 +41,26 @@ struct identify_cookie { }; -/*! Converts the open mode, the open flags given to bfs_open(), into - access modes, e.g. since O_RDONLY requires read access to the - file, it will be converted to R_OK. -*/ -int -open_mode_to_access(int openMode) +//! ext2_io() callback hook +static status_t +iterative_io_get_vecs_hook(void* cookie, io_request* request, off_t offset, + size_t size, struct file_io_vec* vecs, size_t* _count) { - openMode &= O_RWMASK; - if (openMode == O_RDONLY) - return R_OK; - else if (openMode == O_WRONLY) - return W_OK; + Inode* inode = (Inode*)cookie; - return R_OK | W_OK; + return file_map_translate(inode->Map(), offset, size, vecs, _count, + inode->GetVolume()->BlockSize()); +} + + +//! ext2_io() callback hook +static status_t +iterative_io_finished_hook(void* cookie, io_request* request, status_t status, + bool partialTransfer, size_t bytesTransferred) +{ + Inode* inode = (Inode*)cookie; + rw_lock_read_unlock(inode->Lock()); + return B_OK; } @@ -116,6 +128,7 @@ ext2_mount(fs_volume* _volume, const char* device, uint32 flags, status_t status = volume->Mount(device, flags); if (status != B_OK) { + TRACE("Failed mounting the volume. Error: %s\n", strerror(status)); delete volume; return status; } @@ -148,7 +161,7 @@ ext2_read_fs_info(fs_volume* _volume, struct fs_info* info) info->io_size = EXT2_IO_SIZE; info->block_size = volume->BlockSize(); info->total_blocks = volume->NumBlocks(); - info->free_blocks = volume->FreeBlocks(); + info->free_blocks = volume->NumFreeBlocks(); // Volume name strlcpy(info->volume_name, volume->Name(), sizeof(info->volume_name)); @@ -201,6 +214,50 @@ ext2_put_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter) } +static status_t +ext2_remove_vnode(fs_volume* _volume, fs_vnode* _node, bool reenter) +{ + TRACE("ext2_remove_vnode()\n"); + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + ObjectDeleter inodeDeleter(inode); + + if (!inode->IsDeleted()) + return B_OK; + + TRACE("ext2_remove_vnode(): Starting transaction\n"); + Transaction transaction(volume->GetJournal()); + + if (!inode->IsSymLink() || inode->Size() >= EXT2_SHORT_SYMLINK_LENGTH) { + TRACE("ext2_remove_vnode(): Truncating\n"); + status_t status = inode->Resize(transaction, 0); + if (status != B_OK) + return status; + } + + TRACE("ext2_remove_vnode(): Removing from orphan list\n"); + status_t status = volume->RemoveOrphan(transaction, inode->ID()); + if (status != B_OK) + return status; + + TRACE("ext2_remove_vnode(): Setting deletion time\n"); + inode->Node().SetDeletionTime(real_time_clock()); + + status = inode->WriteBack(transaction); + if (status != B_OK) + return status; + + TRACE("ext2_remove_vnode(): Freeing inode\n"); + status = volume->FreeInode(transaction, inode->ID(), inode->IsDirectory()); + + // TODO: When Transaction::Done() fails, do we have to re-add the vnode? + if (status == B_OK) + status = transaction.Done(); + + return status; +} + + static bool ext2_can_page(fs_volume* _volume, fs_vnode* _node, void* _cookie) { @@ -252,10 +309,86 @@ ext2_read_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie, } +static status_t +ext2_write_pages(fs_volume* _volume, fs_vnode* _node, void* _cookie, + off_t pos, const iovec* vecs, size_t count, size_t* _numBytes) +{ + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + + if (volume->IsReadOnly()) + return B_READ_ONLY_DEVICE; + + if (inode->FileCache() == NULL) + return B_BAD_VALUE; + + rw_lock_read_lock(inode->Lock()); + + uint32 vecIndex = 0; + size_t vecOffset = 0; + size_t bytesLeft = *_numBytes; + status_t status; + + while (true) { + file_io_vec fileVecs[8]; + size_t fileVecCount = 8; + + status = file_map_translate(inode->Map(), pos, bytesLeft, fileVecs, + &fileVecCount, 0); + if (status != B_OK && status != B_BUFFER_OVERFLOW) + break; + + bool bufferOverflow = status == B_BUFFER_OVERFLOW; + + size_t bytes = bytesLeft; + status = write_file_io_vec_pages(volume->Device(), fileVecs, + fileVecCount, vecs, count, &vecIndex, &vecOffset, &bytes); + if (status != B_OK || !bufferOverflow) + break; + + pos += bytes; + bytesLeft -= bytes; + } + + rw_lock_read_unlock(inode->Lock()); + + return status; +} + + +static status_t +ext2_io(fs_volume* _volume, fs_vnode* _node, void* _cookie, io_request* request) +{ + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + +#ifndef EXT2_SHELL + if (io_request_is_write(request) && volume->IsReadOnly()) { + notify_io_request(request, B_READ_ONLY_DEVICE); + return B_READ_ONLY_DEVICE; + } +#endif + + if (inode->FileCache() == NULL) { +#ifndef EXT2_SHELL + notify_io_request(request, B_BAD_VALUE); +#endif + return B_BAD_VALUE; + } + + // We lock the node here and will unlock it in the "finished" hook. + rw_lock_read_lock(inode->Lock()); + + return do_iterative_fd_io(volume->Device(), request, + iterative_io_get_vecs_hook, iterative_io_finished_hook, inode); +} + + static status_t ext2_get_file_map(fs_volume* _volume, fs_vnode* _node, off_t offset, size_t size, struct file_io_vec* vecs, size_t* _count) { + TRACE("ext2_get_file_map()\n"); Volume* volume = (Volume*)_volume->private_volume; Inode* inode = (Inode*)_node->private_node; size_t index = 0, max = *_count; @@ -294,7 +427,7 @@ ext2_get_file_map(fs_volume* _volume, fs_vnode* _node, off_t offset, if (size <= vecs[index - 1].length || offset >= inode->Size()) { // We're done! *_count = index; - TRACE("ext2_get_file_map for inode %ld\n", inode->ID()); + TRACE("ext2_get_file_map for inode %ld\n", (long)inode->ID()); return B_OK; } } @@ -311,6 +444,8 @@ static status_t ext2_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name, ino_t* _vnodeID) { + TRACE("ext2_lookup: name address: %p\n", name); + TRACE("ext2_lookup: name: %s\n", name); Volume* volume = (Volume*)_volume->private_volume; Inode* directory = (Inode*)_directory->private_node; @@ -328,19 +463,74 @@ ext2_lookup(fs_volume* _volume, fs_vnode* _directory, const char* name, ObjectDeleter iteratorDeleter(iterator); - while (true) { - char buffer[B_FILE_NAME_LENGTH]; - size_t length = sizeof(buffer); - status = iterator->GetNext(buffer, &length, _vnodeID); - if (status != B_OK) - return status; - TRACE("ext2_lookup() %s\n", buffer); + status = iterator->FindEntry(name, _vnodeID); + if (status != B_OK) + return status; + + return get_vnode(volume->FSVolume(), *_vnodeID, NULL); +} - if (!strcmp(buffer, name)) - break; + +static status_t +ext2_ioctl(fs_volume* _volume, fs_vnode* _node, void* _cookie, uint32 cmd, + void* buffer, size_t bufferLength) +{ + TRACE("ioctl: %lu\n", cmd); + + Volume* volume = (Volume*)_volume->private_volume; + switch (cmd) { + case 56742: + { + TRACE("ioctl: Test the block allocator\n"); + // Test the block allocator + TRACE("ioctl: Creating transaction\n"); + Transaction transaction(volume->GetJournal()); + TRACE("ioctl: Creating cached block\n"); + CachedBlock cached(volume); + uint32 blocksPerGroup = volume->BlocksPerGroup(); + uint32 blockSize = volume->BlockSize(); + uint32 firstBlock = volume->FirstDataBlock(); + uint32 start = 0; + uint32 group = 0; + uint32 length; + + TRACE("ioctl: blocks per group: %lu, block size: %lu, " + "first block: %lu, start: %lu, group: %lu\n", blocksPerGroup, + blockSize, firstBlock, start, group); + + while (volume->AllocateBlocks(transaction, 1, 2048, group, start, + length) == B_OK) { + TRACE("ioctl: Allocated blocks in group %lu: %lu-%lu\n", group, + start, start + length); + uint32 blockNum = start + group * blocksPerGroup - firstBlock; + + for (uint32 i = 0; i < length; ++i) { + uint8* block = cached.SetToWritable(transaction, blockNum); + memset(block, 0, blockSize); + blockNum++; + } + + TRACE("ioctl: Blocks cleared\n"); + + transaction.Done(); + transaction.Start(volume->GetJournal()); + } + + TRACE("ioctl: Done\n"); + + return B_OK; + } } - return get_vnode(volume->FSVolume(), *_vnodeID, NULL); + return B_OK; +} + + +static status_t +ext2_fsync(fs_volume* _volume, fs_vnode* _node) +{ + Inode* inode = (Inode*)_node->private_node; + return inode->Sync(); } @@ -371,9 +561,492 @@ ext2_read_stat(fs_volume* _volume, fs_vnode* _node, struct stat* stat) } +status_t +ext2_write_stat(fs_volume* _volume, fs_vnode* _node, const struct stat* stat, + uint32 mask) +{ + TRACE("ext2_write_stat\n"); + Volume* volume = (Volume*)_volume->private_volume; + + if (volume->IsReadOnly()) + return B_READ_ONLY_DEVICE; + + Inode* inode = (Inode*)_node->private_node; + + status_t status = inode->CheckPermissions(W_OK); + if (status < B_OK) + return status; + + TRACE("ext2_write_stat: Starting transaction\n"); + Transaction transaction(volume->GetJournal()); + inode->WriteLockInTransaction(transaction); + + bool updateTime = false; + + if ((mask & B_STAT_SIZE) != 0) { + if (inode->IsDirectory()) + return B_IS_A_DIRECTORY; + if (!inode->IsFile()) + return B_BAD_VALUE; + + TRACE("ext2_write_stat: Old size: %ld, new size: %ld\n", + (long)inode->Size(), (long)stat->st_size); + if (inode->Size() != stat->st_size) { + off_t oldSize = inode->Size(); + + status = inode->Resize(transaction, stat->st_size); + if(status != B_OK) + return status; + + if ((mask & B_STAT_SIZE_INSECURE) == 0) { + rw_lock_write_unlock(inode->Lock()); + inode->FillGapWithZeros(oldSize, inode->Size()); + rw_lock_write_lock(inode->Lock()); + } + + updateTime = true; + } + } + + ext2_inode& node = inode->Node(); + + if ((mask & B_STAT_MODE) != 0) { + node.UpdateMode(stat->st_mode, S_IUMSK); + updateTime = true; + } + + if ((mask & B_STAT_UID) != 0) { + node.SetUserID(stat->st_uid); + updateTime = true; + } + if ((mask & B_STAT_GID) != 0) { + node.SetGroupID(stat->st_gid); + updateTime = true; + } + + if ((mask & B_STAT_MODIFICATION_TIME) != 0 || updateTime + || (mask & B_STAT_CHANGE_TIME) != 0) { + time_t newTime = 0; + + if ((mask & B_STAT_MODIFICATION_TIME) != 0) + newTime = stat->st_mtim.tv_sec; + + if ((mask & B_STAT_CHANGE_TIME) != 0) + newTime = newTime > stat->st_ctim.tv_sec ? newTime + : stat->st_ctim.tv_sec; + + if (newTime == 0) + newTime = real_time_clock(); + + node.SetModificationTime(newTime); + } + if ((mask & B_STAT_CREATION_TIME) != 0) + node.SetCreationTime(stat->st_crtim.tv_sec); + + status = inode->WriteBack(transaction); + if (status == B_OK) + status = transaction.Done(); + if (status == B_OK) + notify_stat_changed(volume->ID(), inode->ID(), mask); + + return status; +} + + +static status_t +ext2_create(fs_volume* _volume, fs_vnode* _directory, const char* name, + int openMode, int mode, void** _cookie, ino_t* _vnodeID) +{ + Volume* volume = (Volume*)_volume->private_volume; + Inode* directory = (Inode*)_directory->private_node; + + TRACE("ext2_create()\n"); + + if (volume->IsReadOnly()) + return B_READ_ONLY_DEVICE; + + if (!directory->IsDirectory()) + return B_BAD_TYPE; + + TRACE("ext2_create(): Creating cookie\n"); + + // Allocate cookie + file_cookie* cookie = new(std::nothrow) file_cookie; + if (cookie == NULL) + return B_NO_MEMORY; + ObjectDeleter cookieDeleter(cookie); + + cookie->open_mode = openMode; + cookie->last_size = 0; + cookie->last_notification = system_time(); + + TRACE("ext2_create(): Starting transaction\n"); + + Transaction transaction(volume->GetJournal()); + + TRACE("ext2_create(): Creating inode\n"); + + Inode* inode; + bool created; + status_t status = Inode::Create(transaction, directory, name, + S_FILE | (mode & S_IUMSK), openMode, EXT2_TYPE_FILE, &created, _vnodeID, + &inode, &gExt2VnodeOps); + if (status != B_OK) + return status; + + TRACE("ext2_create(): Created inode\n"); + + if ((openMode & O_NOCACHE) != 0 && !inode->IsFileCacheDisabled()) { + status = inode->DisableFileCache(); + if (status != B_OK) + return status; + } + + entry_cache_add(volume->ID(), directory->ID(), name, *_vnodeID); + + status = transaction.Done(); + if (status != B_OK) { + entry_cache_remove(volume->ID(), directory->ID(), name); + return status; + } + + *_cookie = cookie; + cookieDeleter.Detach(); + + if (created) + notify_entry_created(volume->ID(), directory->ID(), name, *_vnodeID); + + return B_OK; +} + + +static status_t +ext2_create_symlink(fs_volume* _volume, fs_vnode* _directory, const char* name, + const char* path, int mode) +{ + TRACE("ext2_create_symlink()\n"); + + Volume* volume = (Volume*)_volume->private_volume; + Inode* directory = (Inode*)_directory->private_node; + + if (volume->IsReadOnly()) + return B_READ_ONLY_DEVICE; + + if (!directory->IsDirectory()) + return B_BAD_TYPE; + + status_t status = directory->CheckPermissions(W_OK); + if (status != B_OK) + return status; + + TRACE("ext2_create_symlink(): Starting transaction\n"); + Transaction transaction(volume->GetJournal()); + + Inode* link; + ino_t id; + status = Inode::Create(transaction, directory, name, S_SYMLINK | 0777, + 0, (uint8)EXT2_TYPE_SYMLINK, NULL, &id, &link); + if (status < B_OK) + return status; + + // TODO: We have to prepare the link before publishing? + + size_t length = strlen(path); + TRACE("ext2_create_symlink(): Path (%s) length: %d\n", path, (int)length); + if (length < EXT2_SHORT_SYMLINK_LENGTH) { + strcpy(link->Node().symlink, path); + link->Node().SetSize((uint32)length); + + TRACE("ext2_create_symlink(): Publishing vnode\n"); + publish_vnode(volume->FSVolume(), id, link, &gExt2VnodeOps, + link->Mode(), 0); + put_vnode(volume->FSVolume(), id); + } else { + TRACE("ext2_create_symlink(): Publishing vnode\n"); + publish_vnode(volume->FSVolume(), id, link, &gExt2VnodeOps, + link->Mode(), 0); + put_vnode(volume->FSVolume(), id); + + if (link->IsFileCacheDisabled()) { + status = link->EnableFileCache(); + if (status != B_OK) + return status; + } + + size_t written = length; + status = link->WriteAt(transaction, 0, (const uint8*)path, &written); + if (status == B_OK && written != length) + status = B_IO_ERROR; + } + + if (status == B_OK) + status = link->WriteBack(transaction); + + entry_cache_add(volume->ID(), directory->ID(), name, id); + + status = transaction.Done(); + if (status != B_OK) { + entry_cache_remove(volume->ID(), directory->ID(), name); + return status; + } + + notify_entry_created(volume->ID(), directory->ID(), name, id); + + TRACE("ext2_create_symlink(): Done\n"); + + return status; +} + + +static status_t +ext2_link(fs_volume* volume, fs_vnode* dir, const char* name, fs_vnode* node) +{ + // TODO + + return B_NOT_SUPPORTED; +} + + +static status_t +ext2_unlink(fs_volume* _volume, fs_vnode* _directory, const char* name) +{ + TRACE("ext2_unlink()\n"); + if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) + return B_NOT_ALLOWED; + + Volume* volume = (Volume*)_volume->private_volume; + Inode* directory = (Inode*)_directory->private_node; + + status_t status = directory->CheckPermissions(W_OK); + if (status != B_OK) + return status; + + TRACE("ext2_unlink(): Starting transaction\n"); + Transaction transaction(volume->GetJournal()); + + directory->WriteLockInTransaction(transaction); + + TRACE("ext2_unlink(): Looking up for directory entry\n"); + HTree htree(volume, directory); + DirectoryIterator* directoryIterator; + + status = htree.Lookup(name, &directoryIterator); + if (status != B_OK) + return status; + + ino_t id; + status = directoryIterator->FindEntry(name, &id); + if (status != B_OK) + return status; + + Vnode vnode(volume, id); + Inode* inode; + status = vnode.Get(&inode); + if (status != B_OK) + return status; + + inode->WriteLockInTransaction(transaction); + + status = inode->Unlink(transaction); + if (status != B_OK) + return status; + + status = directoryIterator->RemoveEntry(transaction); + if (status != B_OK) + return status; + + entry_cache_remove(volume->ID(), directory->ID(), name); + + status = transaction.Done(); + if (status != B_OK) + entry_cache_add(volume->ID(), directory->ID(), name, id); + else + notify_entry_removed(volume->ID(), directory->ID(), name, id); + + return status; +} + + +static status_t +ext2_rename(fs_volume* _volume, fs_vnode* _oldDir, const char* oldName, + fs_vnode* _newDir, const char* newName) +{ + TRACE("ext2_rename()\n"); + + Volume* volume = (Volume*)_volume->private_volume; + Inode* oldDirectory = (Inode*)_oldDir->private_node; + Inode* newDirectory = (Inode*)_newDir->private_node; + + if (oldDirectory == newDirectory && strcmp(oldName, newName) == 0) + return B_OK; + + Transaction transaction(volume->GetJournal()); + + oldDirectory->WriteLockInTransaction(transaction); + if (oldDirectory != newDirectory) + newDirectory->WriteLockInTransaction(transaction); + + status_t status = oldDirectory->CheckPermissions(W_OK); + if (status == B_OK) + status = newDirectory->CheckPermissions(W_OK); + if (status != B_OK) + return status; + + HTree oldHTree(volume, oldDirectory); + DirectoryIterator* oldIterator; + + status = oldHTree.Lookup(oldName, &oldIterator); + if (status != B_OK) + return status; + + ObjectDeleter oldIteratorDeleter(oldIterator); + + ino_t oldID; + status = oldIterator->FindEntry(oldName, &oldID); + if (status != B_OK) + return status; + + if (oldDirectory != newDirectory) { + TRACE("ext2_rename(): Different parent directories\n"); + CachedBlock cached(volume); + + ino_t parentID = newDirectory->ID(); + ino_t oldDirID = oldDirectory->ID(); + + do { + Vnode vnode(volume, parentID); + Inode* parent; + + status = vnode.Get(&parent); + if (status != B_OK) + return B_IO_ERROR; + + uint32 blockNum; + status = parent->FindBlock(0, blockNum); + if (status != B_OK) + return status; + + const HTreeRoot* data = (const HTreeRoot*)cached.SetTo(blockNum); + parentID = data->dotdot.InodeID(); + } while (parentID != oldID && parentID != oldDirID + && parentID != EXT2_ROOT_NODE); + + if (parentID == oldID) + return B_BAD_VALUE; + } + + HTree newHTree(volume, newDirectory); + DirectoryIterator* newIterator; + + status = newHTree.Lookup(newName, &newIterator); + if (status != B_OK) + return status; + + ObjectDeleter newIteratorDeleter(newIterator); + + Vnode vnode(volume, oldID); + Inode* inode; + + status = vnode.Get(&inode); + if (status != B_OK) + return status; + + uint8 fileType; + + // TODO: Support all file types? + if (inode->IsDirectory()) + fileType = EXT2_TYPE_DIRECTORY; + else if (inode->IsSymLink()) + fileType = EXT2_TYPE_SYMLINK; + else + fileType = EXT2_TYPE_FILE; + + // Add entry in destination directory + ino_t existentID; + status = newIterator->FindEntry(newName, &existentID); + if (status == B_OK) { + if (existentID == oldID) { + // Remove entry in oldID + // return inode->Unlink(); + return B_BAD_VALUE; + } + + Vnode vnodeExistent(volume, existentID); + Inode* existent; + + if (vnodeExistent.Get(&existent) != B_OK) + return B_NAME_IN_USE; + + if (existent->IsDirectory() != inode->IsDirectory()) { + return existent->IsDirectory() ? B_IS_A_DIRECTORY + : B_NOT_A_DIRECTORY; + } + + // TODO: Perhaps we have to revert this in case of error? + status = newIterator->ChangeEntry(transaction, oldID, fileType); + if (status != B_OK) + return status; + + notify_entry_removed(volume->ID(), newDirectory->ID(), newName, + existentID); + } else if (status == B_ENTRY_NOT_FOUND) { + newIterator->Restart(); + + status = newIterator->AddEntry(transaction, newName, strlen(newName), + oldID, fileType); + if (status != B_OK) + return status; + } else + return status; + + // Remove entry from source folder + status = oldIterator->RemoveEntry(transaction); + if (status != B_OK) + return status; + + inode->WriteLockInTransaction(transaction); + + if (oldDirectory != newDirectory && inode->IsDirectory()) { + DirectoryIterator inodeIterator(inode); + + status = inodeIterator.FindEntry(".."); + if (status == B_ENTRY_NOT_FOUND) { + TRACE("Corrupt file sytem. Missing \"..\" in directory %ld\n", + (int32)inode->ID()); + return B_BAD_DATA; + } else if (status != B_OK) + return status; + + inodeIterator.ChangeEntry(transaction, newDirectory->ID(), + (uint8)EXT2_TYPE_DIRECTORY); + } + + status = inode->WriteBack(transaction); + if (status != B_OK) + return status; + + entry_cache_remove(volume->ID(), oldDirectory->ID(), oldName); + entry_cache_add(volume->ID(), newDirectory->ID(), newName, oldID); + + status = transaction.Done(); + if (status != B_OK) { + entry_cache_remove(volume->ID(), oldDirectory->ID(), newName); + entry_cache_add(volume->ID(), newDirectory->ID(), oldName, oldID); + + return status; + } + + notify_entry_moved(volume->ID(), oldDirectory->ID(), oldName, + newDirectory->ID(), newName, oldID); + + return B_OK; +} + + static status_t ext2_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie) { + Volume* volume = (Volume*)_volume->private_volume; Inode* inode = (Inode*)_node->private_node; // opening a directory read-only is allowed, although you can't read @@ -381,26 +1054,115 @@ ext2_open(fs_volume* _volume, fs_vnode* _node, int openMode, void** _cookie) if (inode->IsDirectory() && (openMode & O_RWMASK) != 0) return B_IS_A_DIRECTORY; - if ((openMode & O_TRUNC) != 0) - return B_READ_ONLY_DEVICE; - - return inode->CheckPermissions(open_mode_to_access(openMode) + status_t status = inode->CheckPermissions(open_mode_to_access(openMode) | (openMode & O_TRUNC ? W_OK : 0)); + if (status != B_OK) + return status; + + // Prepare the cookie + file_cookie* cookie = new(std::nothrow) file_cookie; + if (cookie == NULL) + return B_NO_MEMORY; + ObjectDeleter cookieDeleter(cookie); + + cookie->open_mode = openMode & EXT2_OPEN_MODE_USER_MASK; + cookie->last_size = inode->Size(); + cookie->last_notification = system_time(); + + if ((openMode & O_NOCACHE) != 0) { + status = inode->DisableFileCache(); + if (status != B_OK) + return status; + } + + // Should we truncate the file? + if ((openMode & O_TRUNC) != 0) { + if ((openMode & O_RWMASK) == O_RDONLY) + return B_NOT_ALLOWED; + + Transaction transaction(volume->GetJournal()); + inode->WriteLockInTransaction(transaction); + + status_t status = inode->Resize(transaction, 0); + if (status == B_OK) + status = inode->WriteBack(transaction); + if (status == B_OK) + status = transaction.Done(); + if (status != B_OK) + return status; + + // TODO: No need to notify file size changed? + } + + cookieDeleter.Detach(); + *_cookie = cookie; + + return B_OK; } static status_t -ext2_read(fs_volume *_volume, fs_vnode *_node, void *_cookie, off_t pos, - void *buffer, size_t *_length) +ext2_read(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos, + void* buffer, size_t* _length) { - Inode *inode = (Inode *)_node->private_node; + Inode* inode = (Inode*)_node->private_node; if (!inode->IsFile()) { *_length = 0; return inode->IsDirectory() ? B_IS_A_DIRECTORY : B_BAD_VALUE; } - return inode->ReadAt(pos, (uint8 *)buffer, _length); + return inode->ReadAt(pos, (uint8*)buffer, _length); +} + + +static status_t +ext2_write(fs_volume* _volume, fs_vnode* _node, void* _cookie, off_t pos, + const void* buffer, size_t* _length) +{ + TRACE("ext2_write()\n"); + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + + if (volume->IsReadOnly()) + return B_READ_ONLY_DEVICE; + + if (inode->IsDirectory()) { + *_length = 0; + return B_IS_A_DIRECTORY; + } + + TRACE("ext2_write(): Preparing cookie\n"); + + file_cookie* cookie = (file_cookie*)_cookie; + + if ((cookie->open_mode & O_APPEND) != 0) + pos = inode->Size(); + + TRACE("ext2_write(): Creating transaction\n"); + Transaction transaction; + + status_t status = inode->WriteAt(transaction, pos, (const uint8*)buffer, + _length); + if (status == B_OK) + status = transaction.Done(); + if (status == B_OK) { + TRACE("ext2_write(): Finalizing\n"); + ReadLocker lock(*inode->Lock()); + + if (cookie->last_size != inode->Size() + && system_time() > cookie->last_notification + + INODE_NOTIFICATION_INTERVAL) { + notify_stat_changed(volume->ID(), inode->ID(), + B_STAT_MODIFICATION_TIME | B_STAT_SIZE | B_STAT_INTERIM_UPDATE); + cookie->last_size = inode->Size(); + cookie->last_notification = system_time(); + } + } + + TRACE("ext2_write(): Done\n"); + + return status; } @@ -412,8 +1174,16 @@ ext2_close(fs_volume *_volume, fs_vnode *_node, void *_cookie) static status_t -ext2_free_cookie(fs_volume* _volume, fs_vnode* _node, void* cookie) +ext2_free_cookie(fs_volume* _volume, fs_vnode* _node, void* _cookie) { + file_cookie* cookie = (file_cookie*)_cookie; + Volume* volume = (Volume*)_volume->private_volume; + Inode* inode = (Inode*)_node->private_node; + + if (inode->Size() != cookie->last_size) + notify_stat_changed(volume->ID(), inode->ID(), B_STAT_SIZE); + + delete cookie; return B_OK; } @@ -450,9 +1220,118 @@ ext2_read_link(fs_volume *_volume, fs_vnode *_node, char *buffer, static status_t -ext2_open_dir(fs_volume *_volume, fs_vnode *_node, void **_cookie) +ext2_create_dir(fs_volume* _volume, fs_vnode* _directory, const char* name, + int mode) +{ + TRACE("ext2_create_dir()\n"); + Volume* volume = (Volume*)_volume->private_volume; + Inode* directory = (Inode*)_directory->private_node; + + if (volume->IsReadOnly()) + return B_READ_ONLY_DEVICE; + + if (!directory->IsDirectory()) + return B_BAD_TYPE; + + status_t status = directory->CheckPermissions(W_OK); + if (status != B_OK) + return status; + + TRACE("ext2_create_dir(): Starting transaction\n"); + Transaction transaction(volume->GetJournal()); + + ino_t id; + status = Inode::Create(transaction, directory, name, + S_DIRECTORY | (mode & S_IUMSK), 0, EXT2_TYPE_DIRECTORY, NULL, &id); + if (status != B_OK) + return status; + + put_vnode(volume->FSVolume(), id); + + entry_cache_add(volume->ID(), directory->ID(), name, id); + + status = transaction.Done(); + if (status != B_OK) { + entry_cache_remove(volume->ID(), directory->ID(), name); + return status; + } + + notify_entry_created(volume->ID(), directory->ID(), name, id); + + TRACE("ext2_create_dir(): Done\n"); + + return B_OK; +} + + +static status_t +ext2_remove_dir(fs_volume* _volume, fs_vnode* _directory, const char* name) { - Inode *inode = (Inode *)_node->private_node; + TRACE("ext2_remove_dir()\n"); + + Volume* volume = (Volume*)_volume->private_volume; + Inode* directory = (Inode*)_directory->private_node; + + status_t status = directory->CheckPermissions(W_OK); + if (status != B_OK) + return status; + + TRACE("ext2_remove_dir(): Starting transaction\n"); + Transaction transaction(volume->GetJournal()); + + directory->WriteLockInTransaction(transaction); + + TRACE("ext2_remove_dir(): Looking up for directory entry\n"); + HTree htree(volume, directory); + DirectoryIterator* directoryIterator; + + status = htree.Lookup(name, &directoryIterator); + if (status != B_OK) + return status; + + ino_t id; + status = directoryIterator->FindEntry(name, &id); + if (status != B_OK) + return status; + + Vnode vnode(volume, id); + Inode* inode; + status = vnode.Get(&inode); + if (status != B_OK) + return status; + + inode->WriteLockInTransaction(transaction); + + status = inode->Unlink(transaction); + if (status != B_OK) + return status; + + status = directory->Unlink(transaction); + if (status != B_OK) + return status; + + status = directoryIterator->RemoveEntry(transaction); + if (status != B_OK) + return status; + + entry_cache_remove(volume->ID(), directory->ID(), name); + entry_cache_remove(volume->ID(), id, ".."); + + status = transaction.Done(); + if (status != B_OK) { + entry_cache_add(volume->ID(), directory->ID(), name, id); + entry_cache_add(volume->ID(), id, "..", id); + } else + notify_entry_removed(volume->ID(), directory->ID(), name, id); + + return status; +} + + +static status_t +ext2_open_dir(fs_volume* _volume, fs_vnode* _node, void** _cookie) +{ + Inode* inode = (Inode*)_node->private_node; status_t status = inode->CheckPermissions(R_OK); if (status < B_OK) return status; @@ -477,13 +1356,17 @@ ext2_read_dir(fs_volume *_volume, fs_vnode *_node, void *_cookie, size_t length = bufferSize; ino_t id; - status_t status = iterator->GetNext(dirent->d_name, &length, &id); + status_t status = iterator->Get(dirent->d_name, &length, &id); if (status == B_ENTRY_NOT_FOUND) { *_num = 0; return B_OK; } else if (status != B_OK) return status; + status = iterator->Next(); + if (status != B_OK && status != B_ENTRY_NOT_FOUND) + return status; + Volume* volume = (Volume*)_volume->private_volume; dirent->d_dev = volume->ID(); @@ -513,7 +1396,7 @@ ext2_close_dir(fs_volume * /*_volume*/, fs_vnode * /*node*/, void * /*_cookie*/) static status_t ext2_free_dir_cookie(fs_volume *_volume, fs_vnode *_node, void *_cookie) { - delete (DirectoryIterator *)_cookie; + delete (DirectoryIterator*)_cookie; return B_OK; } @@ -662,7 +1545,7 @@ ext2_read_attr(fs_volume* _volume, fs_vnode* _node, void* cookie, TRACE("%s()\n", __FUNCTION__); Inode* inode = (Inode*)_node->private_node; - Volume* volume = (Volume*)_volume->private_volume; + //Volume* volume = (Volume*)_volume->private_volume; ext2_xattr_entry *entry = (ext2_xattr_entry *)cookie; if (!entry->IsValid()) @@ -736,46 +1619,46 @@ fs_vnode_ops gExt2VnodeOps = { &ext2_lookup, NULL, &ext2_put_vnode, - NULL, + &ext2_remove_vnode, /* VM file access */ &ext2_can_page, &ext2_read_pages, - NULL, + &ext2_write_pages, NULL, // io() NULL, // cancel_io() &ext2_get_file_map, - NULL, + &ext2_ioctl, NULL, NULL, // fs_select NULL, // fs_deselect - NULL, + &ext2_fsync, &ext2_read_link, - NULL, + &ext2_create_symlink, - NULL, - NULL, - NULL, + &ext2_link, + &ext2_unlink, + &ext2_rename, &ext2_access, &ext2_read_stat, - NULL, + &ext2_write_stat, /* file operations */ - NULL, + &ext2_create, &ext2_open, &ext2_close, &ext2_free_cookie, &ext2_read, - NULL, + &ext2_write, /* directory operations */ - NULL, - NULL, + &ext2_create_dir, + &ext2_remove_dir, &ext2_open_dir, &ext2_close_dir, &ext2_free_dir_cookie,