#include "Volume.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <new>
#include <fs_cache.h>
#include <AutoDeleter.h>
#include <util/AutoLock.h>
#include "Block.h"
#include "BlockAllocator.h"
#include "checksumfs.h"
#include "checksumfs_private.h"
#include "DebugSupport.h"
#include "Directory.h"
#include "File.h"
#include "SuperBlock.h"
#include "SymLink.h"
#include "Transaction.h"
Volume::Volume(uint32 flags)
:
fFSVolume(NULL),
fFD(-1),
fFlags(B_FS_IS_PERSISTENT | B_FS_HAS_ATTR | B_FS_HAS_MIME
| ((flags & B_FS_IS_READONLY) != 0 ? B_FS_IS_READONLY : 0)),
fBlockCache(NULL),
fTotalBlocks(0),
fName(NULL),
fBlockAllocator(NULL),
fRootDirectory(NULL)
{
mutex_init(&fLock, "checksumfs volume");
mutex_init(&fTransactionLock, "checksumfs transaction");
}
Volume::~Volume()
{
delete fRootDirectory;
delete fBlockAllocator;
if (fBlockCache != NULL)
block_cache_delete(fBlockCache, false);
if (fFD >= 0)
close(fFD);
free(fName);
mutex_destroy(&fTransactionLock);
mutex_destroy(&fLock);
}
status_t
Volume::Init(const char* device)
{
if (!IsReadOnly()) {
fFD = open(device, O_RDWR);
if (fFD < 0)
fFlags |= B_FS_IS_READONLY;
}
if (IsReadOnly())
fFD = open(device, O_RDONLY);
if (fFD < 0)
return errno;
struct stat st;
if (fstat(fFD, &st) < 0)
return errno;
off_t size;
switch (st.st_mode & S_IFMT) {
case S_IFREG:
size = st.st_size;
break;
case S_IFCHR:
case S_IFBLK:
{
device_geometry geometry;
if (ioctl(fFD, B_GET_GEOMETRY, &geometry, sizeof(geometry)) < 0)
return errno;
size = (off_t)geometry.bytes_per_sector * geometry.sectors_per_track
* geometry.cylinder_count * geometry.head_count;
break;
}
default:
return B_BAD_VALUE;
}
return _Init(size / B_PAGE_SIZE);
}
status_t
Volume::Init(int fd, uint64 totalBlocks)
{
fFD = dup(fd);
if (fFD < 0)
RETURN_ERROR(errno);
return _Init(totalBlocks);
}
status_t
Volume::Mount(fs_volume* fsVolume)
{
fFSVolume = fsVolume;
Block block;
if (!block.GetReadable(this, kCheckSumFSSuperBlockOffset / B_PAGE_SIZE))
RETURN_ERROR(B_ERROR);
SuperBlock* superBlock = (SuperBlock*)block.Data();
if (!superBlock->Check(fTotalBlocks))
RETURN_ERROR(B_BAD_DATA);
fName = strdup(superBlock->Name());
if (fName == NULL)
RETURN_ERROR(B_NO_MEMORY);
status_t error = fBlockAllocator->Init(superBlock->BlockBitmap(),
superBlock->FreeBlocks());
if (error != B_OK)
RETURN_ERROR(error);
Node* rootNode;
error = ReadNode(superBlock->RootDirectory(), rootNode);
if (error != B_OK)
RETURN_ERROR(error);
fRootDirectory = dynamic_cast<Directory*>(rootNode);
if (fRootDirectory == NULL) {
delete rootNode;
RETURN_ERROR(B_BAD_DATA);
}
error = PublishNode(fRootDirectory, 0);
if (error != B_OK) {
delete fRootDirectory;
fRootDirectory = NULL;
return error;
}
return B_OK;
}
void
Volume::Unmount()
{
status_t error = block_cache_sync(fBlockCache);
if (error != B_OK) {
dprintf("checksumfs: Error: Failed to sync block cache when "
"unmounting!\n");
}
}
status_t
Volume::Initialize(const char* name)
{
fName = strdup(name);
if (fName == NULL)
RETURN_ERROR(B_NO_MEMORY);
Transaction transaction(this);
status_t error = transaction.Start();
if (error != B_OK)
RETURN_ERROR(error);
error = fBlockAllocator->Initialize(transaction);
if (error != B_OK)
RETURN_ERROR(error);
error = CreateDirectory(S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH,
transaction, fRootDirectory);
if (error != B_OK)
RETURN_ERROR(error);
transaction.KeepNode(fRootDirectory);
fRootDirectory->SetHardLinks(1);
Block block;
if (!block.GetZero(this, kCheckSumFSSuperBlockOffset / B_PAGE_SIZE,
transaction)) {
RETURN_ERROR(B_ERROR);
}
SuperBlock* superBlock = (SuperBlock*)block.Data();
superBlock->Initialize(this);
block.Put();
error = transaction.Commit();
if (error != B_OK)
RETURN_ERROR(error);
return block_cache_sync(fBlockCache);
}
void
Volume::GetInfo(fs_info& info)
{
MutexLocker locker(fLock);
info.flags = fFlags;
info.block_size = B_PAGE_SIZE;
info.io_size = B_PAGE_SIZE * 16;
info.total_blocks = fTotalBlocks;
info.free_blocks = fBlockAllocator->FreeBlocks();
info.total_nodes = 1;
info.free_nodes = info.free_blocks;
strlcpy(info.volume_name, fName, sizeof(info.volume_name));
}
status_t
Volume::NewNode(Node* node)
{
return new_vnode(fFSVolume, node->BlockIndex(), node, &gCheckSumFSVnodeOps);
}
status_t
Volume::PublishNode(Node* node, uint32 flags)
{
return publish_vnode(fFSVolume, node->BlockIndex(), node,
&gCheckSumFSVnodeOps, node->Mode(), flags);
}
status_t
Volume::GetNode(uint64 blockIndex, Node*& _node)
{
return get_vnode(fFSVolume, blockIndex, (void**)&_node);
}
status_t
Volume::PutNode(Node* node)
{
return put_vnode(fFSVolume, node->BlockIndex());
}
status_t
Volume::RemoveNode(Node* node)
{
return remove_vnode(fFSVolume, node->BlockIndex());
}
status_t
Volume::UnremoveNode(Node* node)
{
return unremove_vnode(fFSVolume, node->BlockIndex());
}
status_t
Volume::ReadNode(uint64 blockIndex, Node*& _node)
{
if (blockIndex == 0 || blockIndex >= fTotalBlocks)
return B_BAD_VALUE;
Block block;
if (!block.GetReadable(this, blockIndex))
return B_ERROR;
checksumfs_node* nodeData = (checksumfs_node*)block.Data();
Node* node;
switch (nodeData->mode & S_IFMT) {
case S_IFDIR:
node = new(std::nothrow) Directory(this, blockIndex, *nodeData);
break;
case S_IFREG:
node = new(std::nothrow) File(this, blockIndex, *nodeData);
break;
case S_IFLNK:
node = new(std::nothrow) SymLink(this, blockIndex, *nodeData);
break;
default:
node = new(std::nothrow) Node(this, blockIndex, *nodeData);
break;
}
if (node == NULL)
return B_NO_MEMORY;
_node = node;
return B_OK;
}
status_t
Volume::CreateDirectory(mode_t mode, Transaction& transaction,
Directory*& _directory)
{
Directory* directory = new(std::nothrow) Directory(this,
(mode & S_IUMSK) | S_IFDIR);
status_t error = _CreateNode(directory, transaction);
if (error != B_OK)
return error;
_directory = directory;
return B_OK;
}
status_t
Volume::CreateFile(mode_t mode, Transaction& transaction, File*& _file)
{
File* file = new(std::nothrow) File(this, (mode & S_IUMSK) | S_IFREG);
status_t error = _CreateNode(file, transaction);
if (error != B_OK)
return error;
_file = file;
return B_OK;
}
status_t
Volume::CreateSymLink(mode_t mode, Transaction& transaction, SymLink*& _symLink)
{
SymLink* symLink = new(std::nothrow) SymLink(this,
(mode & S_IUMSK) | S_IFLNK);
status_t error = _CreateNode(symLink, transaction);
if (error != B_OK)
return error;
_symLink = symLink;
return B_OK;
}
status_t
Volume::DeleteNode(Node* node)
{
node->DeletingNode();
uint64 blockIndex = node->BlockIndex();
Transaction transaction(this);
status_t error = transaction.Start();
if (error == B_OK) {
error = fBlockAllocator->Free(node->BlockIndex(), 1, transaction);
if (error == B_OK) {
error = transaction.Commit();
if (error != B_OK) {
ERROR("Failed to commit transaction for deleting node at %"
B_PRIu64 "\n", blockIndex);
}
} else {
ERROR("Failed to free block for node at %" B_PRIu64 "\n",
blockIndex);
}
} else {
ERROR("Failed to start transaction for deleting node at %" B_PRIu64
"\n", blockIndex);
}
transaction.Abort();
delete node;
return error;
}
status_t
Volume::SetName(const char* name)
{
if (name == NULL || strlen(name) > kCheckSumFSNameLength)
return B_BAD_VALUE;
char* newName = strdup(name);
if (newName == NULL)
return B_NO_MEMORY;
MemoryDeleter newNameDeleter(newName);
Transaction transaction(this);
status_t error = transaction.Start();
if (error != B_OK)
return error;
MutexLocker locker(fLock);
Block block;
if (!block.GetWritable(this, kCheckSumFSSuperBlockOffset / B_PAGE_SIZE,
transaction)) {
return B_ERROR;
}
SuperBlock* superBlock = (SuperBlock*)block.Data();
superBlock->SetName(newName);
block.Put();
error = transaction.Commit();
if (error != B_OK)
return error;
free(fName);
fName = (char*)newNameDeleter.Detach();
return B_OK;
}
status_t
Volume::_Init(uint64 totalBlocks)
{
fTotalBlocks = totalBlocks;
if (fTotalBlocks * B_PAGE_SIZE < kCheckSumFSMinSize)
RETURN_ERROR(B_BAD_VALUE);
fBlockCache = block_cache_create(fFD, fTotalBlocks, B_PAGE_SIZE,
IsReadOnly());
if (fBlockCache == NULL)
RETURN_ERROR(B_NO_MEMORY);
fBlockAllocator = new(std::nothrow) BlockAllocator(this);
if (fBlockAllocator == NULL)
RETURN_ERROR(B_NO_MEMORY);
return B_OK;
}
status_t
Volume::_CreateNode(Node* node, Transaction& transaction)
{
if (node == NULL)
return B_NO_MEMORY;
ObjectDeleter<Node> nodeDeleter(node);
AllocatedBlock allocatedBlock(fBlockAllocator, transaction);
status_t error = allocatedBlock.Allocate();
if (error != B_OK)
return error;
{
Block block;
if (!block.GetZero(this, allocatedBlock.Index(), transaction))
return B_ERROR;
}
node->SetBlockIndex(allocatedBlock.Index());
error = transaction.AddNode(node, TRANSACTION_DELETE_NODE);
if (error != B_OK)
return error;
allocatedBlock.Detach();
nodeDeleter.Detach();
return B_OK;
}