/*
 * Copyright 2010, Haiku Inc. All rights reserved.
 * Copyright 2001-2010, Axel Dörfler, axeld@pinc-software.de.
 * This file may be used under the terms of the MIT License.
 *
 * Authors:
 *		Janito V. Ferreira Filho
 */
 
 
#include "Journal.h"
 
#include <new>
#include <string.h>
#include <unistd.h>
 
#include <fs_cache.h>
 
#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
#define ERROR(x...) dprintf("\33[34mext2:\33[0m " x)
#define WARN(x...) dprintf("\33[34mext2:\33[0m " x)
 
 
class LogEntry : public DoublyLinkedListLinkImpl<LogEntry> {
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();
		} else
			delete revokeManager;
	}
}
 
 
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),
	fIsStarted(false),
	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()
{
	if (!fIsStarted)
		return B_OK;
 
	status_t status = FlushLogAndBlocks();
 
	if (status == B_OK) {
		// Mark journal as clean
		fLogStart = 0;
		status = _SaveSuperBlock();
	}
 
	fIsStarted = false;
 
	return status;
}
 
 
/*virtual*/ status_t
Journal::StartLog()
{
	fLogStart = fFirstLogBlock;
	fLogEnd = fFirstLogBlock;
	fFreeBlocks = 0;
	fIsStarted = true;
 
	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: %" B_PRId32 "\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(off_t logical, fsblock_t& physical)
{
	TRACE("Journal::MapBlock()\n");
	physical = logical;
 
	return B_OK;
}
 
 
inline uint32
Journal::FreeLogBlocks() const
{
	TRACE("Journal::FreeLogBlocks(): start: %" B_PRIu32 ", end: %" B_PRIu32
		", size: %" B_PRIu32 "\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<uint8>& 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 (%" B_PRIu32 ")\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);
 
		fsblock_t 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: %" B_PRIdOFF "\n", finalData, 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: %" B_PRIu32 "\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();
 
	fsblock_t physicalBlock;
	status = MapBlock(descriptorBlockPos, physicalBlock);
	if (status != B_OK)
		return status;
 
	off_t descriptorBlockOffset = physicalBlock * fBlockSize;
 
	TRACE("Journal::_WritePartialTransactionToLog(): Writing to: %" B_PRIdOFF
		"\n", 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: %" B_PRIuSIZE
		"\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: %"
			B_PRIuSIZE "\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: %" B_PRIuSIZE ", max size: %"
				B_PRIu32 ", log size: %" B_PRIu32 ")\n", size,
				fMaxTransactionSize, fLogSize);
			return B_BUFFER_OVERFLOW;
		}
	}
 
	TRACE("Journal::_WriteTransactionToLog(): free log blocks: %" B_PRIu32
		"\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 %" B_PRIu32 " when at least %" B_PRIuSIZE
				" was expected.", FreeLogBlocks(), size);
		}
	}
 
	TRACE("Journal::_WriteTransactionToLog(): finished managing space for "
		"the transaction\n");
 
	fHasSubTransaction = false;
	if (!fIsStarted)
		StartLog();
 
	// Prepare Descriptor block
	TRACE("Journal::_WriteTransactionToLog(): attempting to allocate space for "
		"the descriptor block, block size %" B_PRIu32 "\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<uint8> descriptorBlockDeleter((uint8*)descriptorBlock);
 
	descriptorBlock->MakeDescriptor(fCurrentCommitID);
 
	// Prepare Commit block
	TRACE("Journal::_WriteTransactionToLog(): attempting to allocate space for "
		"the commit block, block size %" B_PRIu32 "\n", fBlockSize);
	JournalHeader* commitBlock =
		(JournalHeader*)new(std::nothrow) uint8[fBlockSize];
	if (commitBlock == NULL) {
		TRACE("Journal::_WriteTransactionToLog(): Failed to allocate a buffer "
			"for the commit block\n");
		return B_NO_MEMORY;
	}
	ArrayDeleter<uint8> commitBlockDeleter((uint8*)commitBlock);
 
	commitBlock->MakeCommit(fCurrentCommitID + 1);
	memset(commitBlock->data, 0, fBlockSize - sizeof(JournalHeader));
		// TODO: This probably isn't necessary
 
	uint8* escapedData = NULL;
	ArrayDeleter<uint8> 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.
 
		fsblock_t physicalBlock;
		status = MapBlock(logBlock, physicalBlock);
		if (status != B_OK)
			return status;
 
		off_t logOffset = physicalBlock * fBlockSize;
 
		TRACE("Journal::_WriteTransactionToLog(): Writting commit block to "
			"%" B_PRIdOFF "\n", 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
	fsblock_t physicalBlock;
	status = MapBlock(commitBlockPos, physicalBlock);
	if (status != B_OK)
		return status;
 
	off_t logOffset = physicalBlock * fBlockSize;
 
	TRACE("Journal::_WriteTransactionToLog(): Writing to: %" B_PRIdOFF "\n",
		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.
			ERROR("transaction too large (%" B_PRIuSIZE " blocks, log size %"
				B_PRIu32 ")!\n", _FullTransactionSize(), 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");
	fsblock_t 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 %" B_PRIdOFF "\n",
		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");
	fsblock_t superblockPos;
 
	status_t status = MapBlock(0, superblockPos);
	if (status != B_OK)
		return status;
 
	TRACE("Journal::_LoadSuperBlock(): superblock physical block: %" B_PRIu64
		"\n", superblockPos);
 
	JournalSuperBlock superblock;
	size_t bytesRead = read_pos(fJournalVolume->Device(), superblockPos
		* fJournalVolume->BlockSize(), &superblock, sizeof(superblock));
 
	if (bytesRead != sizeof(superblock)) {
		ERROR("Journal::_LoadSuperBlock(): failed to read superblock\n");
		return B_IO_ERROR;
	}
 
	if (!superblock.header.CheckMagic()) {
		ERROR("Journal::_LoadSuperBlock(): Invalid superblock magic %" B_PRIx32
			"\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 {
		ERROR("Journal::_LoadSuperBlock(): Invalid superblock version\n");
		return B_BAD_VALUE;
	}
 
	if (fVersion >= 2) {
		status = _CheckFeatures(&superblock);
 
		if (status != B_OK) {
			ERROR("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: %" B_PRIu32 ", first commit"
		" id: %" B_PRIu32 ", first log block: %" B_PRIu32 ", log start: %"
		B_PRIu32 ", log size: %" B_PRIu32 ", max transaction size: %" B_PRIu32
		"\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_UNSUPPORTED;
 
	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: %" B_PRIu32 "\n",
			tags->BlockNumber());
 
		tags++; // Go to next tag
		count++;
	}
 
	if ((tags->Flags() & JOURNAL_FLAG_LAST_TAG) != 0)
		count++;
 
	TRACE("Journal::_CountTags(): counted tags: %" B_PRIu32 "\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;
	fsblock_t 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 "
				"%" B_PRIu32 " tags\n", tags);
		} else if (blockType == JOURNAL_COMMIT_BLOCK) {
			nextCommitID++;
			TRACE("Journal recover pass scan: Found a commit block. Next "
				"commit ID: %" B_PRIu32 "\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: %"
		B_PRIu32 "\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;
	fsblock_t 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 {
			WARN("Journal::_RecoverPassRevoke(): Found an unrecognized block\n");
			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: %" B_PRIu32 "\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;
	fsblock_t 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<uint8> dataDeleter(data);
 
	while (nextCommitID < lastCommitID) {
		if (!header->CheckMagic() || header->Sequence() != nextCommitID) {
			// Somehow the log is different than the expected
			ERROR("Journal::_RecoverPassReplay(): Weird 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 %" B_PRIu32
						"\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) {
			WARN("Journal::_RecoverPassReplay(): Found an unrecognized block\n");
			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: %" B_PRId32 "\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 %" B_PRIu32 "\n",
		fTransactionID);
 
	size_t count;
 
	if (fHasSubTransaction) {
		count = cache_blocks_in_sub_transaction(fFilesystemBlockCache,
			fTransactionID);
 
		TRACE("\tSub transaction size: %" B_PRIuSIZE "\n", count);
	} else {
		count =  cache_blocks_in_transaction(fFilesystemBlockCache,
			fTransactionID);
 
		TRACE("\tTransaction size: %" B_PRIuSIZE "\n", count);
	}
 
	return count;
}
 
 
size_t
Journal::_FullTransactionSize() const
{
	TRACE("Journal::_FullTransactionSize(): transaction %" B_PRIu32 "\n",
		fTransactionID);
	TRACE("\tFile sytem block cache: %p\n", fFilesystemBlockCache);
 
	size_t count = cache_blocks_in_transaction(fFilesystemBlockCache,
		 fTransactionID);
 
	TRACE("\tFull transaction size: %" B_PRIuSIZE "\n", count);
 
	return count;
}
 
 
size_t
Journal::_MainTransactionSize() const
{
	TRACE("Journal::_MainTransactionSize(): transaction %" B_PRIu32 "\n",
		fTransactionID);
 
	size_t count =  cache_blocks_in_main_transaction(fFilesystemBlockCache,
		fTransactionID);
 
	TRACE("\tMain transaction size: %" B_PRIuSIZE "\n", count);
 
	return count;
}
 
 
status_t
Journal::_TransactionDone(bool success)
{
	if (!success) {
		if (fHasSubTransaction) {
			TRACE("Journal::_TransactionDone(): transaction %" B_PRIu32
				" failed, aborting subtransaction\n", fTransactionID);
			cache_abort_sub_transaction(fFilesystemBlockCache, fTransactionID);
			// parent is unaffected
		} else {
			TRACE("Journal::_TransactionDone(): transaction %" B_PRIu32
				" 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: %" B_PRIu32
		", max transaction size: %" B_PRIu32 ", free log blocks: %" B_PRIu32
		"\n", size, fMaxTransactionSize, FreeLogBlocks());
	if (fMaxTransactionSize > 0 && size < fMaxTransactionSize) {
		TRACE("Journal::_TransactionDone(): delaying flush of transaction "
			"%" B_PRIu32 "\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 %" B_PRIu32
		" 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 %"
			B_PRIu32 "\n", logEntry->Start());
		journal->fLogStart = logEntry->Start();
		journal->fFirstCommitID = logEntry->CommitID();
		TRACE("Journal::_TransactionWritten(): Setting commit ID to %" B_PRIu32
			"\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);
}

V730 Not all members of a class are initialized inside the constructor. Consider inspecting: fOwner.