From a1b0ec30977df3c4f608eaac97ddc03bf9d3b255 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Duval?= Date: Tue, 7 Sep 2010 23:25:42 +0000 Subject: [PATCH] Patch from Janito Ferreira Filho (aka jjvf): this is the result of his GSoC assignment "Implement ext2/3 Read and Write Support for Haiku". * Tested and checked most features and fs operations, while passing successfully the Linux fsck. Though the implementation still needs more testing and is to be used with caution, it's better in my mind to have the code committed now given the size of the patch. * Code style isn't extensively checked but is mostly OK. Code review is welcome. Some notes from Janito: * Sparse files aren't supported and hard links aren't supported. Write attributes methods aren't activated nor tested. * Journaling needs more testing to make sure it behaves in a compatible way to Ext3, and support for the different modes hasn't been implemented (due to the block and file cache incompatibility). Correct revoke management is also lacking, as is proper management of the superblock state and copies and block group copies. * The code is partly based and inspired by the BFS implementation. Author information might need to be fixed. I'd like to congratulate and thank Janito for his hard work to bring the implementation to the current state. I hope he'll keep on maintaining it and become a regular contributor/committer. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@38573 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- .../kernel/file_systems/ext2/BitmapBlock.cpp | 664 +++++++++ .../kernel/file_systems/ext2/BitmapBlock.h | 48 + .../file_systems/ext2/BlockAllocator.cpp | 714 ++++++++++ .../kernel/file_systems/ext2/BlockAllocator.h | 53 + .../kernel/file_systems/ext2/CachedBlock.h | 67 +- .../kernel/file_systems/ext2/DataStream.cpp | 650 +++++++++ .../kernel/file_systems/ext2/DataStream.h | 94 ++ .../file_systems/ext2/DirectoryIterator.cpp | 755 +++++++++- .../file_systems/ext2/DirectoryIterator.h | 60 +- .../kernel/file_systems/ext2/HTree.cpp | 158 ++- src/add-ons/kernel/file_systems/ext2/HTree.h | 52 +- .../file_systems/ext2/HTreeEntryIterator.cpp | 364 +++-- .../file_systems/ext2/HTreeEntryIterator.h | 24 +- .../file_systems/ext2/HashRevokeManager.cpp | 170 +++ .../file_systems/ext2/HashRevokeManager.h | 47 + .../ext2/IndexedDirectoryIterator.cpp | 75 - .../ext2/IndexedDirectoryIterator.h | 36 - .../kernel/file_systems/ext2/Inode.cpp | 746 +++++++++- src/add-ons/kernel/file_systems/ext2/Inode.h | 184 ++- .../file_systems/ext2/InodeAllocator.cpp | 193 +++ .../kernel/file_systems/ext2/InodeAllocator.h | 45 + .../kernel/file_systems/ext2/InodeJournal.cpp | 91 ++ .../kernel/file_systems/ext2/InodeJournal.h | 29 + src/add-ons/kernel/file_systems/ext2/Jamfile | 12 +- .../kernel/file_systems/ext2/Journal.cpp | 1262 +++++++++++++++++ .../kernel/file_systems/ext2/Journal.h | 257 ++++ .../kernel/file_systems/ext2/NoJournal.cpp | 101 ++ .../kernel/file_systems/ext2/NoJournal.h | 34 + .../file_systems/ext2/RevokeManager.cpp | 52 + .../kernel/file_systems/ext2/RevokeManager.h | 35 + .../kernel/file_systems/ext2/Transaction.cpp | 224 +++ .../kernel/file_systems/ext2/Transaction.h | 72 + .../kernel/file_systems/ext2/Utility.h | 39 + .../kernel/file_systems/ext2/Volume.cpp | 394 ++++- src/add-ons/kernel/file_systems/ext2/Volume.h | 66 +- src/add-ons/kernel/file_systems/ext2/ext2.h | 150 +- .../file_systems/ext2/kernel_interface.cpp | 985 ++++++++++++- 37 files changed, 8530 insertions(+), 472 deletions(-) create mode 100644 src/add-ons/kernel/file_systems/ext2/BitmapBlock.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/BitmapBlock.h create mode 100644 src/add-ons/kernel/file_systems/ext2/BlockAllocator.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/BlockAllocator.h create mode 100644 src/add-ons/kernel/file_systems/ext2/DataStream.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/DataStream.h create mode 100644 src/add-ons/kernel/file_systems/ext2/HashRevokeManager.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/HashRevokeManager.h create mode 100644 src/add-ons/kernel/file_systems/ext2/InodeAllocator.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/InodeAllocator.h create mode 100644 src/add-ons/kernel/file_systems/ext2/InodeJournal.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/InodeJournal.h create mode 100644 src/add-ons/kernel/file_systems/ext2/Journal.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/Journal.h create mode 100644 src/add-ons/kernel/file_systems/ext2/NoJournal.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/NoJournal.h create mode 100644 src/add-ons/kernel/file_systems/ext2/RevokeManager.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/RevokeManager.h create mode 100644 src/add-ons/kernel/file_systems/ext2/Transaction.cpp create mode 100644 src/add-ons/kernel/file_systems/ext2/Transaction.h create mode 100644 src/add-ons/kernel/file_systems/ext2/Utility.h 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,