/*
 * Copyright 2002-2012, Axel Dörfler, axeld@pinc-software.de.
 * Copyright 2012, Andreas Henriksson, sausageboy@gmail.com
 * This file may be used under the terms of the MIT License.
 */
 
 
//! File system error checking
 
 
#include "CheckVisitor.h"
 
#include "BlockAllocator.h"
#include "BPlusTree.h"
#include "Inode.h"
#include "Volume.h"
 
 
struct check_index {
	check_index()
		:
		inode(NULL)
	{
	}
 
	char				name[B_FILE_NAME_LENGTH];
	block_run			run;
	Inode*				inode;
};
 
 
CheckVisitor::CheckVisitor(Volume* volume)
	:
	FileSystemVisitor(volume),
	fCheckBitmap(NULL)
{
}
 
 
CheckVisitor::~CheckVisitor()
{
	free(fCheckBitmap);
}
 
 
status_t
CheckVisitor::StartBitmapPass()
{
	if (!_ControlValid())
		return B_BAD_VALUE;
 
	// Lock the volume's journal and block allocator
	GetVolume()->GetJournal(0)->Lock(NULL, true);
	recursive_lock_lock(&GetVolume()->Allocator().Lock());
 
	size_t size = _BitmapSize();
	fCheckBitmap = (uint32*)malloc(size);
	if (fCheckBitmap == NULL) {
		recursive_lock_unlock(&GetVolume()->Allocator().Lock());
		GetVolume()->GetJournal(0)->Unlock(NULL, true);
		return B_NO_MEMORY;
	}
 
	memset(&Control().stats, 0, sizeof(check_control::stats));
 
	// initialize bitmap
	memset(fCheckBitmap, 0, size);
	for (int32 block = GetVolume()->Log().Start() + GetVolume()->Log().Length();
			block-- > 0;) {
		_SetCheckBitmapAt(block);
	}
 
	Control().pass = BFS_CHECK_PASS_BITMAP;
	Control().stats.block_size = GetVolume()->BlockSize();
 
	// TODO: check reserved area in bitmap!
 
	Start(VISIT_REGULAR | VISIT_INDICES | VISIT_REMOVED
		| VISIT_ATTRIBUTE_DIRECTORIES);
 
	return B_OK;
}
 
 
status_t
CheckVisitor::WriteBackCheckBitmap()
{
	if (GetVolume()->IsReadOnly())
		return B_OK;
 
	// calculate the number of used blocks in the check bitmap
	size_t size = _BitmapSize();
	off_t usedBlocks = 0LL;
 
	// TODO: update the allocation groups used blocks info
	for (uint32 i = size >> 2; i-- > 0;) {
		uint32 compare = 1;
		// Count the number of bits set
		for (int16 j = 0; j < 32; j++, compare <<= 1) {
			if ((compare & fCheckBitmap[i]) != 0)
				usedBlocks++;
		}
	}
 
	Control().stats.freed = GetVolume()->UsedBlocks() - usedBlocks
		+ Control().stats.missing;
	if (Control().stats.freed < 0)
		Control().stats.freed = 0;
 
	// Should we fix errors? Were there any errors we can fix?
	if ((Control().flags & BFS_FIX_BITMAP_ERRORS) != 0
		&& (Control().stats.freed != 0 || Control().stats.missing != 0)) {
		// If so, write the check bitmap back over the original one,
		// and use transactions here to play safe - we even use several
		// transactions, so that we don't blow the maximum log size
		// on large disks, since we don't need to make this atomic.
#if 0
		// prints the blocks that differ
		off_t block = 0;
		for (int32 i = 0; i < fNumGroups; i++) {
			AllocationBlock cached(fVolume);
			for (uint32 j = 0; j < fGroups[i].NumBlocks(); j++) {
				cached.SetTo(fGroups[i], j);
				for (uint32 k = 0; k < cached.NumBlockBits(); k++) {
					if (cached.IsUsed(k) != _CheckBitmapIsUsedAt(block)) {
						dprintf("differ block %lld (should be %d)\n", block,
							_CheckBitmapIsUsedAt(block));
					}
					block++;
				}
			}
		}
#endif
 
		GetVolume()->SuperBlock().used_blocks
			= HOST_ENDIAN_TO_BFS_INT64(usedBlocks);
 
		size_t blockSize = GetVolume()->BlockSize();
		off_t numBitmapBlocks = GetVolume()->NumBitmapBlocks();
 
		for (uint32 i = 0; i < numBitmapBlocks; i += 512) {
			Transaction transaction(GetVolume(), 1 + i);
 
			uint32 blocksToWrite = 512;
			if (blocksToWrite + i > numBitmapBlocks)
				blocksToWrite = numBitmapBlocks - i;
 
			status_t status = transaction.WriteBlocks(1 + i,
				(uint8*)fCheckBitmap + i * blockSize, blocksToWrite);
			if (status < B_OK) {
				FATAL(("error writing bitmap: %s\n", strerror(status)));
				return status;
			}
			transaction.Done();
		}
	}
 
	return B_OK;
}
 
 
status_t
CheckVisitor::StartIndexPass()
{
	// if we don't have indices to rebuild, this pass is done
	if (indices.IsEmpty())
		return B_ENTRY_NOT_FOUND;
 
	Control().pass = BFS_CHECK_PASS_INDEX;
 
	status_t status = _PrepareIndices();
	if (status != B_OK) {
		Control().status = status;
		return status;
	}
 
	Start(VISIT_REGULAR);
	return Next();
}
 
 
status_t
CheckVisitor::StopChecking()
{
	if (GetVolume()->IsReadOnly()) {
		// We can't fix errors on this volume
		Control().flags = 0;
	}
 
	if (Control().status != B_ENTRY_NOT_FOUND)
		FATAL(("CheckVisitor didn't run through\n"));
 
	_FreeIndices();
 
	recursive_lock_unlock(&GetVolume()->Allocator().Lock());
	GetVolume()->GetJournal(0)->Unlock(NULL, true);
	return B_OK;
}
 
 
status_t
CheckVisitor::VisitDirectoryEntry(Inode* inode, Inode* parent,
	const char* treeName)
{
	Control().inode = inode->ID();
	Control().mode = inode->Mode();
 
	if (Pass() != BFS_CHECK_PASS_BITMAP)
		return B_OK;
 
	// check if the inode's name is the same as in the b+tree
	if (inode->IsRegularNode()) {
		RecursiveLocker locker(inode->SmallDataLock());
		NodeGetter node(GetVolume(), inode);
		if (node.Node() == NULL) {
			Control().errors |= BFS_COULD_NOT_OPEN;
			Control().status = B_IO_ERROR;
			return B_OK;
		}
 
		const char* localName = inode->Name(node.Node());
		if (localName == NULL || strcmp(localName, treeName)) {
			Control().errors |= BFS_NAMES_DONT_MATCH;
			FATAL(("Names differ: tree \"%s\", inode \"%s\"\n", treeName,
				localName));
 
			if ((Control().flags & BFS_FIX_NAME_MISMATCHES) != 0) {
				// Rename the inode
				Transaction transaction(GetVolume(), inode->BlockNumber());
 
				// Note, this may need extra blocks, but the inode will
				// only be checked afterwards, so that it won't be lost
				status_t status = inode->SetName(transaction, treeName);
				if (status == B_OK)
					status = inode->WriteBack(transaction);
				if (status == B_OK)
					status = transaction.Done();
				if (status != B_OK) {
					Control().status = status;
					return B_OK;
				}
			}
		}
	}
 
	// Check for the correct mode of the node (if the mode of the
	// file don't fit to its parent, there is a serious problem)
	if (((parent->Mode() & S_ATTR_DIR) != 0
			&& !inode->IsAttribute())
		|| ((parent->Mode() & S_INDEX_DIR) != 0
			&& !inode->IsIndex())
		|| (is_directory(parent->Mode())
			&& !inode->IsRegularNode())) {
		FATAL(("inode at %" B_PRIdOFF " is of wrong type: %o (parent "
			"%o at %" B_PRIdOFF ")!\n", inode->BlockNumber(),
			inode->Mode(), parent->Mode(), parent->BlockNumber()));
 
		// if we are allowed to fix errors, we should remove the file
		if ((Control().flags & BFS_REMOVE_WRONG_TYPES) != 0
			&& (Control().flags & BFS_FIX_BITMAP_ERRORS) != 0) {
			Control().status = _RemoveInvalidNode(parent, NULL, inode,
				treeName);
		} else
			Control().status = B_ERROR;
 
		Control().errors |= BFS_WRONG_TYPE;
	}
	return B_OK;
}
 
 
status_t
CheckVisitor::VisitInode(Inode* inode, const char* treeName)
{
	Control().inode = inode->ID();
	Control().mode = inode->Mode();
		// (we might have set these in VisitDirectoryEntry already)
 
	// set name
	if (treeName == NULL) {
		if (inode->GetName(Control().name) < B_OK) {
			if (inode->IsContainer())
				strcpy(Control().name, "(dir has no name)");
			else
				strcpy(Control().name, "(node has no name)");
		}
	} else
		strcpy(Control().name, treeName);
 
	status_t status = B_OK;
 
	switch (Pass()) {
		case BFS_CHECK_PASS_BITMAP:
		{
			status = _CheckInodeBlocks(inode, NULL);
			if (status != B_OK)
				return status;
 
			// Check the B+tree as well
			if (inode->IsContainer()) {
				bool repairErrors = (Control().flags & BFS_FIX_BPLUSTREES) != 0;
				bool errorsFound = false;
 
				status = inode->Tree()->Validate(repairErrors, errorsFound);
 
				if (errorsFound) {
					Control().errors |= BFS_INVALID_BPLUSTREE;
					if (inode->IsIndex() && treeName != NULL && repairErrors) {
						// We completely rebuild corrupt indices
						check_index* index = new(std::nothrow) check_index;
						if (index == NULL)
							return B_NO_MEMORY;
 
						strlcpy(index->name, treeName, sizeof(index->name));
						index->run = inode->BlockRun();
						Indices().Push(index);
					}
				}
			}
 
			break;
		}
 
		case BFS_CHECK_PASS_INDEX:
			status = _AddInodeToIndex(inode);
			break;
	}
 
	Control().status = status;
	return B_OK;
}
 
 
status_t
CheckVisitor::OpenInodeFailed(status_t reason, ino_t id, Inode* parent,
		char* treeName, TreeIterator* iterator)
{
	FATAL(("Could not open inode at %" B_PRIdOFF "\n", id));
 
	if (treeName != NULL)
		strlcpy(Control().name, treeName, B_FILE_NAME_LENGTH);
	else
		strcpy(Control().name, "(node has no name)");
 
	Control().inode = id;
	Control().errors = BFS_COULD_NOT_OPEN;
 
	// remove inode from the tree if we can
	if (parent != NULL && iterator != NULL
		&& (Control().flags & BFS_REMOVE_INVALID) != 0) {
		Control().status = _RemoveInvalidNode(parent, iterator->Tree(), NULL,
			treeName);
	} else
		Control().status = B_ERROR;
 
	return B_OK;
}
 
 
status_t
CheckVisitor::OpenBPlusTreeFailed(Inode* inode)
{
	FATAL(("Could not open b+tree from inode at %" B_PRIdOFF "\n",
		inode->ID()));
	return B_OK;
}
 
 
status_t
CheckVisitor::TreeIterationFailed(status_t reason, Inode* parent)
{
	// Iterating over the B+tree failed - we let the checkfs run
	// fail completely, as we would delete all files we cannot
	// access.
	// TODO: maybe have a force parameter that actually does that.
	// TODO: we also need to be able to repair broken B+trees!
	return reason;
}
 
 
status_t
CheckVisitor::_RemoveInvalidNode(Inode* parent, BPlusTree* tree,
	Inode* inode, const char* name)
{
	// It's safe to start a transaction, because Inode::Remove()
	// won't touch the block bitmap (which we hold the lock for)
	// if we set the INODE_DONT_FREE_SPACE flag - since we fix
	// the bitmap anyway.
	Transaction transaction(GetVolume(), parent->BlockNumber());
	status_t status;
 
	if (inode != NULL) {
		inode->Node().flags |= HOST_ENDIAN_TO_BFS_INT32(INODE_DONT_FREE_SPACE);
 
		status = parent->Remove(transaction, name, NULL, false, true);
	} else {
		parent->WriteLockInTransaction(transaction);
 
		// does the file even exist?
		off_t id;
		status = tree->Find((uint8*)name, (uint16)strlen(name), &id);
		if (status == B_OK)
			status = tree->Remove(transaction, name, id);
	}
 
	if (status == B_OK) {
		entry_cache_remove(GetVolume()->ID(), parent->ID(), name);
		transaction.Done();
	}
 
	return status;
}
 
 
bool
CheckVisitor::_ControlValid()
{
	if (Control().magic != BFS_IOCTL_CHECK_MAGIC) {
		FATAL(("invalid check_control!\n"));
		return false;
	}
 
	return true;
}
 
 
bool
CheckVisitor::_CheckBitmapIsUsedAt(off_t block) const
{
	size_t size = _BitmapSize();
	uint32 index = block / 32;	// 32bit resolution
	if (index > size / 4)
		return false;
 
	return BFS_ENDIAN_TO_HOST_INT32(fCheckBitmap[index])
		& (1UL << (block & 0x1f));
}
 
 
void
CheckVisitor::_SetCheckBitmapAt(off_t block)
{
	size_t size = _BitmapSize();
	uint32 index = block / 32;	// 32bit resolution
	if (index > size / 4)
		return;
 
	fCheckBitmap[index] |= HOST_ENDIAN_TO_BFS_INT32(1UL << (block & 0x1f));
}
 
 
size_t
CheckVisitor::_BitmapSize() const
{
	return GetVolume()->BlockSize() * GetVolume()->NumBitmapBlocks();
}
 
 
status_t
CheckVisitor::_CheckInodeBlocks(Inode* inode, const char* name)
{
	status_t status = _CheckAllocated(inode->BlockRun(), "inode");
	if (status != B_OK)
		return status;
 
	if (inode->IsSymLink() && (inode->Flags() & INODE_LONG_SYMLINK) == 0) {
		// symlinks may not have a valid data stream
		if (strlen(inode->Node().short_symlink) >= SHORT_SYMLINK_NAME_LENGTH)
			return B_BAD_DATA;
 
		return B_OK;
	}
 
	data_stream* data = &inode->Node().data;
 
	// check the direct range
 
	if (data->max_direct_range) {
		for (int32 i = 0; i < NUM_DIRECT_BLOCKS; i++) {
			if (data->direct[i].IsZero())
				break;
 
			status = _CheckAllocated(data->direct[i], "direct");
			if (status < B_OK)
				return status;
 
			Control().stats.direct_block_runs++;
			Control().stats.blocks_in_direct
				+= data->direct[i].Length();
		}
	}
 
	CachedBlock cached(GetVolume());
 
	// check the indirect range
 
	if (data->max_indirect_range) {
		status = _CheckAllocated(data->indirect, "indirect");
		if (status < B_OK)
			return status;
 
		off_t block = GetVolume()->ToBlock(data->indirect);
 
		for (int32 i = 0; i < data->indirect.Length(); i++) {
			block_run* runs = (block_run*)cached.SetTo(block + i);
			if (runs == NULL)
				RETURN_ERROR(B_IO_ERROR);
 
			int32 runsPerBlock = GetVolume()->BlockSize() / sizeof(block_run);
			int32 index = 0;
			for (; index < runsPerBlock; index++) {
				if (runs[index].IsZero())
					break;
 
				status = _CheckAllocated(runs[index], "indirect->run");
				if (status < B_OK)
					return status;
 
				Control().stats.indirect_block_runs++;
				Control().stats.blocks_in_indirect
					+= runs[index].Length();
			}
			Control().stats.indirect_array_blocks++;
 
			if (index < runsPerBlock)
				break;
		}
	}
 
	// check the double indirect range
 
	if (data->max_double_indirect_range) {
		status = _CheckAllocated(data->double_indirect, "double indirect");
		if (status != B_OK)
			return status;
 
		int32 runsPerBlock = runs_per_block(GetVolume()->BlockSize());
		int32 runsPerArray = runsPerBlock * data->double_indirect.Length();
 
		CachedBlock cachedDirect(GetVolume());
 
		for (int32 indirectIndex = 0; indirectIndex < runsPerArray;
				indirectIndex++) {
			// get the indirect array block
			block_run* array = (block_run*)cached.SetTo(
				GetVolume()->ToBlock(data->double_indirect)
					+ indirectIndex / runsPerBlock);
			if (array == NULL)
				return B_IO_ERROR;
 
			block_run indirect = array[indirectIndex % runsPerBlock];
			// are we finished yet?
			if (indirect.IsZero())
				return B_OK;
 
			status = _CheckAllocated(indirect, "double indirect->runs");
			if (status != B_OK)
				return status;
 
			int32 maxIndex
				= ((uint32)indirect.Length() << GetVolume()->BlockShift())
					/ sizeof(block_run);
 
			for (int32 index = 0; index < maxIndex; ) {
				block_run* runs = (block_run*)cachedDirect.SetTo(
					GetVolume()->ToBlock(indirect) + index / runsPerBlock);
				if (runs == NULL)
					return B_IO_ERROR;
 
				do {
					// are we finished yet?
					if (runs[index % runsPerBlock].IsZero())
						return B_OK;
 
					status = _CheckAllocated(runs[index % runsPerBlock],
						"double indirect->runs->run");
					if (status != B_OK)
						return status;
 
					Control().stats.double_indirect_block_runs++;
					Control().stats.blocks_in_double_indirect
						+= runs[index % runsPerBlock].Length();
				} while ((++index % runsPerBlock) != 0);
			}
 
			Control().stats.double_indirect_array_blocks++;
		}
	}
 
	return B_OK;
}
 
 
status_t
CheckVisitor::_CheckAllocated(block_run run, const char* type)
{
	BlockAllocator& allocator = GetVolume()->Allocator();
 
	// make sure the block run is valid
	if (!allocator.IsValidBlockRun(run)) {
		Control().errors |= BFS_INVALID_BLOCK_RUN;
		return B_OK;
	}
 
	status_t status;
 
	off_t start = GetVolume()->ToBlock(run);
	off_t end = start + run.Length();
 
	// check if the run is allocated in the block bitmap on disk
	off_t block = start;
 
	while (block < end) {
		off_t firstMissing;
		status = allocator.CheckBlocks(block, end - block, true, &firstMissing);
		if (status == B_OK)
			break;
		else if (status != B_BAD_DATA)
			return status;
 
		off_t afterLastMissing;
		status = allocator.CheckBlocks(firstMissing, end - firstMissing, false,
			&afterLastMissing);
		if (status == B_OK)
			afterLastMissing = end;
		else if (status != B_BAD_DATA)
			return status;
 
		PRINT(("%s: block_run(%ld, %u, %u): blocks %Ld - %Ld are "
			"not allocated!\n", type, run.AllocationGroup(), run.Start(),
			run.Length(), firstMissing, afterLastMissing - 1));
 
		Control().stats.missing += afterLastMissing - firstMissing;
 
		block = afterLastMissing;
	}
 
	// set bits in check bitmap, while checking if they're already set
	off_t firstSet = -1;
 
	for (block = start; block < end; block++) {
		if (_CheckBitmapIsUsedAt(block)) {
			if (firstSet == -1) {
				firstSet = block;
				Control().errors |= BFS_BLOCKS_ALREADY_SET;
			}
			Control().stats.already_set++;
		} else {
			if (firstSet != -1) {
				FATAL(("%s: block_run(%d, %u, %u): blocks %" B_PRIdOFF
					" - %" B_PRIdOFF " are already set!\n", type,
					(int)run.AllocationGroup(), run.Start(), run.Length(),
					firstSet, block - 1));
				firstSet = -1;
			}
			_SetCheckBitmapAt(block);
		}
	}
 
	return B_OK;
}
 
 
status_t
CheckVisitor::_PrepareIndices()
{
	int32 count = 0;
 
	for (int32 i = 0; i < Indices().CountItems(); i++) {
		check_index* index = Indices().Array()[i];
		Vnode vnode(GetVolume(), index->run);
		Inode* inode;
		status_t status = vnode.Get(&inode);
		if (status != B_OK) {
			FATAL(("check: Could not open index at %" B_PRIdOFF "\n",
				GetVolume()->ToBlock(index->run)));
			return status;
		}
 
		BPlusTree* tree = inode->Tree();
		if (tree == NULL) {
			// TODO: We can't yet repair those
			continue;
		}
 
		status = tree->MakeEmpty();
		if (status != B_OK)
			return status;
 
		index->inode = inode;
		vnode.Keep();
		count++;
	}
 
	return count == 0 ? B_ENTRY_NOT_FOUND : B_OK;
}
 
 
void
CheckVisitor::_FreeIndices()
{
	for (int32 i = 0; i < Indices().CountItems(); i++) {
		check_index* index = Indices().Array()[i];
		if (index->inode != NULL) {
			put_vnode(GetVolume()->FSVolume(),
				GetVolume()->ToVnode(index->inode->BlockRun()));
		}
		delete index;
	}
	Indices().MakeEmpty();
}
 
 
status_t
CheckVisitor::_AddInodeToIndex(Inode* inode)
{
	Transaction transaction(GetVolume(), inode->BlockNumber());
 
	for (int32 i = 0; i < Indices().CountItems(); i++) {
		check_index* index = Indices().Array()[i];
		if (index->inode == NULL)
			continue;
 
		index->inode->WriteLockInTransaction(transaction);
 
		BPlusTree* tree = index->inode->Tree();
		if (tree == NULL)
			return B_ERROR;
 
		status_t status = B_OK;
 
		if (!strcmp(index->name, "name")) {
			if (inode->InNameIndex()) {
				char name[B_FILE_NAME_LENGTH];
				if (inode->GetName(name, B_FILE_NAME_LENGTH) != B_OK)
					return B_ERROR;
 
				status = tree->Insert(transaction, name, inode->ID());
			}
		} else if (!strcmp(index->name, "last_modified")) {
			if (inode->InLastModifiedIndex()) {
				status = tree->Insert(transaction, inode->OldLastModified(),
					inode->ID());
			}
		} else if (!strcmp(index->name, "size")) {
			if (inode->InSizeIndex())
				status = tree->Insert(transaction, inode->Size(), inode->ID());
		} else {
			uint8 key[MAX_INDEX_KEY_LENGTH];
			size_t keyLength = sizeof(key);
			if (inode->ReadAttribute(index->name, B_ANY_TYPE, 0, key,
					&keyLength) == B_OK) {
				status = tree->Insert(transaction, key, keyLength, inode->ID());
			}
		}
 
		if (status != B_OK)
			return status;
	}
 
	return transaction.Done();
}

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

V547 Expression is always false.

V576 Incorrect format. Consider checking the second actual argument of the 'fssh_dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the fifth actual argument of the 'fssh_dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'fssh_dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'fssh_dprintf' function. The memsize type argument is expected.

V547 Expression 'Control().stats.freed < 0' is always false. Unsigned type value is never < 0.

V576 Incorrect format. Consider checking the seventh actual argument of the 'fssh_dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the second actual argument of the 'fssh_dprintf' function. The memsize type argument is expected.

V576 Incorrect format. Consider checking the sixth actual argument of the 'fssh_dprintf' function. The memsize type argument is expected.