⛏️ index : haiku.git

/*
 * Copyright 2011, Jérôme Duval, korli@users.berlios.de.
 * Copyright 2008-2010, Axel Dörfler, axeld@pinc-software.de.
 * This file may be used under the terms of the MIT License.
 */


//! Superblock, mounting, etc.


#include "Volume.h"

#include <errno.h>
#include <new>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <fs_cache.h>
#include <fs_volume.h>

#include <util/AutoLock.h>

#include "CachedBlock.h"
#include "CRCTable.h"
#include "DeviceOpener.h"
#include "Inode.h"
#include "InodeJournal.h"
#include "NoJournal.h"


//#define TRACE_EXT2
#ifdef TRACE_EXT2
#	define TRACE(x...) dprintf("\33[34mext2:\33[0m " x)
#else
#	define TRACE(x...) ;
#endif
#	define FATAL(x...) dprintf("\33[34mext2:\33[0m " x)


//	#pragma mark -


bool
ext2_super_block::IsMagicValid()
{
	return Magic() == (uint32)EXT2_SUPER_BLOCK_MAGIC;
}


bool
ext2_super_block::IsValid()
{
	if (!IsMagicValid()
			|| BlockShift() > 16
			|| BlocksPerGroup() != (1UL << BlockShift()) * 8
			|| InodeSize() > (1UL << BlockShift())
			|| RevisionLevel() > EXT2_MAX_REVISION
			|| ReservedGDTBlocks() > (1UL << BlockShift()) / 4
			|| NumInodes() == 0
			|| InodeSize() == 0
			|| FreeInodes() > NumInodes()) {
		return false;
	}

	return true;
}


//	#pragma mark -


Volume::Volume(fs_volume* volume)
	:
	fFSVolume(volume),
	fBlockAllocator(NULL),
	fInodeAllocator(this),
	fJournalInode(NULL),
	fFlags(0),
	fGroupBlocks(NULL),
	fRootNode(NULL)
{
	mutex_init(&fLock, "ext2 volume");
}


Volume::~Volume()
{
	TRACE("Volume destructor.\n");
	delete fBlockAllocator;
	if (fGroupBlocks != NULL) {
		uint32 blockCount = (fNumGroups + fGroupsPerBlock - 1)
			/ fGroupsPerBlock;
		for (uint32 i = 0; i < blockCount; i++)
			free(fGroupBlocks[i]);

		free(fGroupBlocks);
	}
}


bool
Volume::IsValidSuperBlock()
{
	return fSuperBlock.IsValid();
}


bool
Volume::HasExtendedAttributes() const
{
	return (fSuperBlock.CompatibleFeatures() & EXT2_FEATURE_EXT_ATTR) != 0;
}


const char*
Volume::Name() const
{
	// The name may be empty, in that case, userspace will generate one.
	return fSuperBlock.name;
}


void
Volume::SetName(const char* name)
{
	strlcpy(fSuperBlock.name, name, sizeof(fSuperBlock.name));
}


status_t
Volume::Mount(const char* deviceName, uint32 flags)
{
	// 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);
	fDevice = opener.Device();
	if (fDevice < B_OK) {
		FATAL("Volume::Mount(): couldn't open device\n");
		return fDevice;
	}

	if (opener.IsReadOnly())
		fFlags |= VOLUME_READ_ONLY;

	// read the superblock
	status_t status = Identify(fDevice, &fSuperBlock);
	if (status != B_OK) {
		FATAL("Volume::Mount(): Identify() failed\n");
		return status;
	}

	TRACE("features %" B_PRIx32 ", incompatible features %" B_PRIx32
		", read-only features %" B_PRIx32 "\n",
		fSuperBlock.CompatibleFeatures(), fSuperBlock.IncompatibleFeatures(),
		fSuperBlock.ReadOnlyFeatures());

	// check read-only features if mounting read-write
	if (!IsReadOnly() && _UnsupportedReadOnlyFeatures(fSuperBlock) != 0)
		return B_UNSUPPORTED;

	if (HasMetaGroupChecksumFeature() && HasChecksumFeature())
		return B_ERROR;

	if (!_VerifySuperBlock())
		return B_ERROR;

	if ((fSuperBlock.State() & EXT2_FS_STATE_VALID) == 0
		|| (fSuperBlock.State() & EXT2_FS_STATE_ERROR) != 0) {
		if (!IsReadOnly()) {
			FATAL("Volume::Mount(): can't mount R/W, volume not clean\n");
			return B_NOT_ALLOWED;
		} else {
			FATAL("Volume::Mount(): warning: volume not clean\n");
		}
	}

	// initialize short hands to the superblock (to save byte swapping)
	fBlockShift = fSuperBlock.BlockShift();
	if (fBlockShift < 10 || fBlockShift > 16)
		return B_ERROR;
	if (fBlockShift > 12) {
		FATAL("blocksize not supported!\n");
		return B_UNSUPPORTED;
	}
	fBlockSize = 1UL << fBlockShift;
	fFirstDataBlock = fSuperBlock.FirstDataBlock();

	fFreeBlocks = fSuperBlock.FreeBlocks(Has64bitFeature());
	fFreeInodes = fSuperBlock.FreeInodes();

	if (fFirstDataBlock > fSuperBlock.NumBlocks(Has64bitFeature()))
		return B_ERROR;

	off_t numBlocks = fSuperBlock.NumBlocks(Has64bitFeature()) - fFirstDataBlock;
	uint32 blocksPerGroup = fSuperBlock.BlocksPerGroup();
	fNumGroups = numBlocks / blocksPerGroup;
	if (numBlocks % blocksPerGroup != 0)
		fNumGroups++;

	if (blocksPerGroup == 0 || fSuperBlock.FragmentsPerGroup() == 0) {
		FATAL("zero blocksPerGroup or fragmentsPerGroup!\n");
		return B_UNSUPPORTED;
	}
	if (blocksPerGroup != fSuperBlock.FragmentsPerGroup()) {
		FATAL("blocksPerGroup is not equal to fragmentsPerGroup!\n");
		return B_UNSUPPORTED;
	}

	if (Has64bitFeature()) {
		TRACE("64bits\n");
		fGroupDescriptorSize = fSuperBlock.GroupDescriptorSize();
		if (fGroupDescriptorSize < sizeof(ext2_block_group))
			return B_ERROR;
		if (fGroupDescriptorSize != sizeof(ext2_block_group))
			return B_UNSUPPORTED;
	} else
		fGroupDescriptorSize = EXT2_BLOCK_GROUP_NORMAL_SIZE;
	fGroupsPerBlock = fBlockSize / fGroupDescriptorSize;
	fNumInodes = fSuperBlock.NumInodes();

	TRACE("block size %" B_PRIu32 ", num groups %" B_PRIu32 ", groups per "
		"block %" B_PRIu32 ", first %" B_PRIu32 ", group_descriptor_size %"
		B_PRIu32 "\n", fBlockSize, fNumGroups,
		fGroupsPerBlock, fFirstDataBlock, fGroupDescriptorSize);

	uint32 blockCount = (fNumGroups + fGroupsPerBlock - 1) / fGroupsPerBlock;

	fGroupBlocks = (uint8**)malloc(blockCount * sizeof(uint8*));
	if (fGroupBlocks == NULL)
		return B_NO_MEMORY;

	memset(fGroupBlocks, 0, blockCount * sizeof(uint8*));
	fInodesPerBlock = fBlockSize / InodeSize();

	_SuperBlockChecksumSeed();

	// check if the device size is large enough to hold the file system
	off_t diskSize;
	status = opener.GetSize(&diskSize);
	if (status != B_OK)
		return status;
	if ((diskSize + fBlockSize) <= ((off_t)NumBlocks() << BlockShift())) {
		FATAL("diskSize is too small for the number of blocks!\n");
		return B_BAD_VALUE;
	}

	fBlockCache = opener.InitCache(NumBlocks(), fBlockSize);
	if (fBlockCache == NULL)
		return B_ERROR;

	TRACE("Volume::Mount(): Initialized block cache: %p\n", fBlockCache);

	// initialize journal if mounted read-write
	if (!IsReadOnly() && HasJournalFeature()) {
		// 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_UNSUPPORTED;
		}
	} 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) {
		FATAL("could not initialize journal!\n");
		return status;
	}

	// TODO: Only recover if asked to
	TRACE("Volume::Mount(): Asking journal to recover\n");
	status = fJournal->Recover();
	if (status != B_OK) {
		FATAL("could not recover journal!\n");
		return status;
	}

	TRACE("Volume::Mount(): Restart journal log\n");
	status = fJournal->StartLog();
	if (status != B_OK) {
		FATAL("could not initialize start journal!\n");
		return status;
	}

	if (!IsReadOnly()) {
		// Initialize allocators
		fBlockAllocator = new(std::nothrow) BlockAllocator(this);
		if (fBlockAllocator != NULL) {
			TRACE("Volume::Mount(): Initialize block allocator\n");
			status = fBlockAllocator->Initialize();
		}
		if (fBlockAllocator == NULL || status != B_OK) {
			delete fBlockAllocator;
			fBlockAllocator = NULL;
			FATAL("could not initialize block allocator, going read-only!\n");
			fFlags |= VOLUME_READ_ONLY;
			fJournal->Uninit();
			delete fJournal;
			delete fJournalInode;
			fJournalInode = NULL;
			fJournal = new(std::nothrow) NoJournal(this);
		}
	}

	// ready
	status = get_vnode(fFSVolume, EXT2_ROOT_NODE, (void**)&fRootNode);
	if (status != B_OK) {
		FATAL("could not create root node: get_vnode() failed!\n");
		return status;
	}

	// all went fine
	opener.Keep();

	return B_OK;
}


status_t
Volume::Unmount()
{
	TRACE("Volume::Unmount()\n");

	status_t status = fJournal->Uninit();

	// this will wait on the block notifier/writer thread
	FlushDevice();
	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);

	TRACE("Volume::Unmount(): Done\n");
	return status;
}


status_t
Volume::GetInodeBlock(ino_t id, off_t& block)
{
	ext2_block_group* group;
	status_t status = GetBlockGroup((id - 1) / fSuperBlock.InodesPerGroup(),
		&group);
	if (status != B_OK)
		return status;

	block = group->InodeTable(Has64bitFeature())
		+ ((id - 1) % fSuperBlock.InodesPerGroup()) / fInodesPerBlock;
	if (block < 0)
		return B_BAD_DATA;
	return B_OK;
}


uint32
Volume::InodeBlockIndex(ino_t id) const
{
	return ((id - 1) % fSuperBlock.InodesPerGroup()) % fInodesPerBlock;
}


/*static*/ uint32
Volume::_UnsupportedIncompatibleFeatures(ext2_super_block& superBlock)
{
	uint32 supportedIncompatible = EXT2_INCOMPATIBLE_FEATURE_FILE_TYPE
		| EXT2_INCOMPATIBLE_FEATURE_RECOVER
		| EXT2_INCOMPATIBLE_FEATURE_JOURNAL
		| EXT2_INCOMPATIBLE_FEATURE_EXTENTS
		| EXT2_INCOMPATIBLE_FEATURE_FLEX_GROUP
		| EXT2_INCOMPATIBLE_FEATURE_64BIT
		| EXT2_INCOMPATIBLE_FEATURE_CSUM_SEED;
		/*| EXT2_INCOMPATIBLE_FEATURE_META_GROUP*/;
	uint32 unsupported = superBlock.IncompatibleFeatures()
		& ~supportedIncompatible;

	if (unsupported != 0) {
		FATAL("ext2: incompatible features not supported: %" B_PRIx32
			" (extents %x)\n", unsupported, EXT2_INCOMPATIBLE_FEATURE_EXTENTS);
	}

	return unsupported;
}


/*static*/ uint32
Volume::_UnsupportedReadOnlyFeatures(ext2_super_block& superBlock)
{
	uint32 supportedReadOnly = EXT2_READ_ONLY_FEATURE_SPARSE_SUPER
		| EXT2_READ_ONLY_FEATURE_LARGE_FILE
		| EXT2_READ_ONLY_FEATURE_HUGE_FILE
		| EXT2_READ_ONLY_FEATURE_EXTRA_ISIZE
		| EXT2_READ_ONLY_FEATURE_DIR_NLINK
		| EXT2_READ_ONLY_FEATURE_GDT_CSUM
		| EXT4_READ_ONLY_FEATURE_METADATA_CSUM;
	// TODO actually implement EXT2_READ_ONLY_FEATURE_SPARSE_SUPER when
	// implementing superblock backup copies

	uint32 unsupported = superBlock.ReadOnlyFeatures() & ~supportedReadOnly;

	if (unsupported != 0) {
		FATAL("ext2: readonly features not supported: %" B_PRIx32 "\n",
			unsupported);
	}

	return unsupported;
}


uint32
Volume::_GroupDescriptorBlock(uint32 blockIndex)
{
	if ((fSuperBlock.IncompatibleFeatures()
			& EXT2_INCOMPATIBLE_FEATURE_META_GROUP) == 0
		|| blockIndex < fSuperBlock.FirstMetaBlockGroup())
		return fFirstDataBlock + blockIndex + 1;

	panic("meta block");
	return 0;
}


uint16
Volume::_GroupCheckSum(ext2_block_group *group, int32 index)
{
	int32 number = B_HOST_TO_LENDIAN_INT32(index);
	size_t offset = offsetof(ext2_block_group, checksum);
	size_t offsetExt4 = offsetof(ext2_block_group, block_bitmap_high);
	if (HasMetaGroupChecksumFeature()) {
		uint16 dummy = 0;
		uint32 checksum = calculate_crc32c(fChecksumSeed, (uint8*)&number,
			sizeof(number));
		checksum = calculate_crc32c(checksum, (uint8*)group, offset);
		checksum = calculate_crc32c(checksum, (uint8*)&dummy, sizeof(dummy));
		if (fGroupDescriptorSize > offset + sizeof(dummy)) {
			checksum = calculate_crc32c(checksum, (uint8*)group + offsetExt4,
				fGroupDescriptorSize - offsetExt4);
		}
		return checksum & 0xffff;
	} else if (HasChecksumFeature()) {
		uint16 checksum = calculate_crc(0xffff, fSuperBlock.uuid,
			sizeof(fSuperBlock.uuid));
		checksum = calculate_crc(checksum, (uint8*)&number, sizeof(number));
		checksum = calculate_crc(checksum, (uint8*)group, offset);
		if (Has64bitFeature() && fGroupDescriptorSize > offsetExt4) {
			checksum = calculate_crc(checksum, (uint8*)group + offsetExt4,
				fGroupDescriptorSize - offsetExt4);
		}
		return checksum;
	}
	return 0;
}


/*!	Makes the requested block group available.
	The block groups are loaded on demand, but are kept in memory until the
	volume is unmounted; therefore we don't use the block cache.
*/
status_t
Volume::GetBlockGroup(int32 index, ext2_block_group** _group)
{
	if (index < 0 || (uint32)index > fNumGroups)
		return B_BAD_VALUE;

	int32 blockIndex = index / fGroupsPerBlock;
	int32 blockOffset = index % fGroupsPerBlock;

	MutexLocker _(fLock);

	if (fGroupBlocks[blockIndex] == NULL) {
		CachedBlock cached(this);
		const uint8* block = cached.SetTo(_GroupDescriptorBlock(blockIndex));
		if (block == NULL)
			return B_IO_ERROR;

		fGroupBlocks[blockIndex] = (uint8*)malloc(fBlockSize);
		if (fGroupBlocks[blockIndex] == NULL)
			return B_NO_MEMORY;

		memcpy(fGroupBlocks[blockIndex], block, fBlockSize);

		TRACE("group [%" B_PRId32 "]: inode table %" B_PRIu64 "\n", index,
			((ext2_block_group*)(fGroupBlocks[blockIndex] + blockOffset
				* fGroupDescriptorSize))->InodeTable(Has64bitFeature()));
	}

	*_group = (ext2_block_group*)(fGroupBlocks[blockIndex]
		+ blockOffset * fGroupDescriptorSize);
	if (HasChecksumFeature()
		&& (*_group)->checksum != _GroupCheckSum(*_group, index)) {
		return B_BAD_DATA;
	}
	return B_OK;
}


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;
	int32 blockOffset = index % fGroupsPerBlock;

	MutexLocker _(fLock);

	if (fGroupBlocks[blockIndex] == NULL)
		return B_BAD_VALUE;

	ext2_block_group *group = (ext2_block_group*)(fGroupBlocks[blockIndex]
		+ blockOffset * fGroupDescriptorSize);

	group->checksum = _GroupCheckSum(group, index);
	TRACE("Volume::WriteBlockGroup() checksum 0x%x for group %" B_PRId32 " "
		"(free inodes %" B_PRIu32 ", unused %" B_PRIu32 ")\n", group->checksum,
		index, group->FreeInodes(Has64bitFeature()),
		group->UnusedInodes(Has64bitFeature()));

	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::ActivateLargeFiles(Transaction& transaction)
{
	if ((fSuperBlock.ReadOnlyFeatures()
		& EXT2_READ_ONLY_FEATURE_LARGE_FILE) != 0)
		return B_OK;

	fSuperBlock.SetReadOnlyFeatures(fSuperBlock.ReadOnlyFeatures()
		| EXT2_READ_ONLY_FEATURE_LARGE_FILE);

	return WriteSuperBlock(transaction);
}


status_t
Volume::ActivateDirNLink(Transaction& transaction)
{
	if ((fSuperBlock.ReadOnlyFeatures()
		& EXT2_READ_ONLY_FEATURE_DIR_NLINK) != 0)
		return B_OK;

	fSuperBlock.SetReadOnlyFeatures(fSuperBlock.ReadOnlyFeatures()
		| EXT2_READ_ONLY_FEATURE_DIR_NLINK);

	return WriteSuperBlock(transaction);
}


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, fsblock_t& 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 %" B_PRIu32 " blocks\n",
		length);

	fFreeBlocks -= length;

	return WriteSuperBlock(transaction);
}


status_t
Volume::FreeBlocks(Transaction& transaction, fsblock_t start, uint32 length)
{
	TRACE("Volume::FreeBlocks(%" B_PRIu64 ", %" B_PRIu32 ")\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): %" B_PRIdOFF
		"\n", fFreeBlocks);
	fFreeBlocks += length;
	TRACE("Volume::FreeBlocks(): number of free blocks (after): %" B_PRIdOFF
		"\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(Has64bitFeature());
	fFreeInodes = fSuperBlock.FreeInodes();

	return B_OK;
}


status_t
Volume::WriteSuperBlock(Transaction& transaction)
{
	TRACE("Volume::WriteSuperBlock()\n");
	fSuperBlock.SetFreeBlocks(fFreeBlocks, Has64bitFeature());
	fSuperBlock.SetFreeInodes(fFreeInodes);
	// TODO: Rest of fields that can be modified

	if (HasMetaGroupChecksumFeature()) {
		fSuperBlock.checksum = calculate_crc32c(0xffffffff, (uint8*)&fSuperBlock,
			offsetof(struct ext2_super_block, checksum));
	}

	TRACE("Volume::WriteSuperBlock(): free blocks: %" B_PRIu64 ", free inodes:"
		" %" B_PRIu32 "\n", fSuperBlock.FreeBlocks(Has64bitFeature()),
		fSuperBlock.FreeInodes());

	CachedBlock cached(this);
	uint8* block = cached.SetToWritable(transaction, fFirstDataBlock);

	if (block == NULL)
		return B_IO_ERROR;

	TRACE("Volume::WriteSuperBlock(): first data block: %" B_PRIu32 ", 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;
}


void
Volume::_SuperBlockChecksumSeed()
{
	if (HasChecksumSeedFeature()) {
		fChecksumSeed = fSuperBlock.checksum_seed;
	} else if (HasMetaGroupChecksumFeature()) {
		fChecksumSeed = calculate_crc32c(0xffffffff, (uint8*)fSuperBlock.uuid,
			sizeof(fSuperBlock.uuid));
	} else
		fChecksumSeed = 0;
}


bool
Volume::_VerifySuperBlock()
{
	if (!HasMetaGroupChecksumFeature())
		return true;

	uint32 checksum = calculate_crc32c(0xffffffff, (uint8*)&fSuperBlock,
		offsetof(struct ext2_super_block, checksum));
	return checksum == fSuperBlock.checksum;
}


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->Uninit();
}


//	#pragma mark - Disk scanning and initialization


/*static*/ status_t
Volume::Identify(int fd, ext2_super_block* superBlock)
{
	if (read_pos(fd, EXT2_SUPER_BLOCK_OFFSET, superBlock,
			sizeof(ext2_super_block)) != sizeof(ext2_super_block))
		return B_IO_ERROR;

	if (!superBlock->IsMagicValid())
		return B_BAD_VALUE;

	if (!superBlock->IsValid()) {
		FATAL("invalid superblock!\n");
		return B_BAD_VALUE;
	}

	if (_UnsupportedIncompatibleFeatures(*superBlock) != 0)
		return B_UNSUPPORTED;

	return B_OK;
}


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?
}