/* * Copyright 2010, Ingo Weinhold, ingo_weinhold@gmx.de. * Distributed under the terms of the MIT License. */ #include "Volume.h" #include #include #include #include #include #include #include #include #include #include #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) { // open the device if (!IsReadOnly()) { fFD = open(device, O_RDWR); // If opening read-write fails, we retry read-only. if (fFD < 0) fFlags |= B_FS_IS_READONLY; } if (IsReadOnly()) fFD = open(device, O_RDONLY); if (fFD < 0) return errno; // get the size 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; // load the superblock 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); // copy the volume name fName = strdup(superBlock->Name()); if (fName == NULL) RETURN_ERROR(B_NO_MEMORY); // init the block allocator status_t error = fBlockAllocator->Init(superBlock->BlockBitmap(), superBlock->FreeBlocks()); if (error != B_OK) RETURN_ERROR(error); // load the root directory Node* rootNode; error = ReadNode(superBlock->RootDirectory(), rootNode); if (error != B_OK) RETURN_ERROR(error); fRootDirectory = dynamic_cast(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); // create the root directory 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); // write the superblock 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(); // commit the transaction and flush the block cache 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; // random value info.total_blocks = fTotalBlocks; info.free_blocks = fBlockAllocator->FreeBlocks(); info.total_nodes = 1; // phew, who cares? 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; // load the node's block Block block; if (!block.GetReadable(this, blockIndex)) return B_ERROR; checksumfs_node* nodeData = (checksumfs_node*)block.Data(); // create the Node object Node* node; switch (nodeData->mode & S_IFMT) { // TODO: Don't directly access mode! 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; // TODO: Sanity check the node! _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) { // let the node delete data associated with it node->DeletingNode(); uint64 blockIndex = node->BlockIndex(); // delete the node itself 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; // clone the name char* newName = strdup(name); if (newName == NULL) return B_NO_MEMORY; MemoryDeleter newNameDeleter(newName); // start a transaction Transaction transaction(this); status_t error = transaction.Start(); if (error != B_OK) return error; // we lock the volume now, to keep the locking order (transaction -> volume) MutexLocker locker(fLock); // update the superblock Block block; if (!block.GetWritable(this, kCheckSumFSSuperBlockOffset / B_PAGE_SIZE, transaction)) { return B_ERROR; } SuperBlock* superBlock = (SuperBlock*)block.Data(); superBlock->SetName(newName); block.Put(); // commit the transaction error = transaction.Commit(); if (error != B_OK) return error; // Everything went fine. We can replace the name. Since we still have the // volume lock, there's no race condition. 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); // create a block cache fBlockCache = block_cache_create(fFD, fTotalBlocks, B_PAGE_SIZE, IsReadOnly()); if (fBlockCache == NULL) RETURN_ERROR(B_NO_MEMORY); // create the block allocator 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 nodeDeleter(node); // allocate a free block AllocatedBlock allocatedBlock(fBlockAllocator, transaction); status_t error = allocatedBlock.Allocate(); if (error != B_OK) return error; // clear the block { Block block; if (!block.GetZero(this, allocatedBlock.Index(), transaction)) return B_ERROR; } node->SetBlockIndex(allocatedBlock.Index()); // attach the node to the transaction error = transaction.AddNode(node, TRANSACTION_DELETE_NODE); if (error != B_OK) return error; allocatedBlock.Detach(); nodeDeleter.Detach(); return B_OK; }