/* * Copyright (c) 2013, 2014, 2015 Jonas 'Sortie' Termansen. * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * * filesystem.cpp * Filesystem. */ #include #include #include #include #include #include #include #include "ext-constants.h" #include "ext-structs.h" #include "block.h" #include "blockgroup.h" #include "device.h" #include "filesystem.h" #include "inode.h" #include "util.h" Filesystem::Filesystem(Device* device, const char* mount_path) { uint64_t sb_offset = 1024; uint32_t sb_block_id = sb_offset / device->block_size; this->sb_block = device->GetBlock(sb_block_id); assert(sb_block); // TODO: This can fail. this->sb = (struct ext_superblock*) (sb_block->block_data + sb_offset % device->block_size); this->device = device; this->block_groups = NULL; this->mount_path = mount_path; this->block_size = device->block_size; this->inode_size = this->sb->s_inode_size; this->num_blocks = this->sb->s_blocks_count; this->num_groups = divup(this->sb->s_blocks_count, this->sb->s_blocks_per_group); this->num_inodes = this->sb->s_inodes_count; this->mru_inode = NULL; this->lru_inode = NULL; this->dirty_inode = NULL; for ( size_t i = 0; i < INODE_HASH_LENGTH; i++ ) this->hash_inodes[i] = NULL; struct timespec now_realtime, now_monotonic; clock_gettime(CLOCK_REALTIME, &now_realtime); clock_gettime(CLOCK_MONOTONIC, &now_monotonic); this->mtime_realtime = now_realtime.tv_sec; this->mtime_monotonic = now_monotonic.tv_sec; this->dirty = false; if ( device->write ) { BeginWrite(); sb->s_mtime = mtime_realtime; sb->s_mnt_count++; sb->s_state = EXT2_ERROR_FS; // TODO: Remove this temporarily compatibility when this driver is moved // into the kernel and the FUSE frontend is removed. #ifdef __GLIBC__ strncpy(sb->s_last_mounted, mount_path, sizeof(sb->s_last_mounted)); #else strlcpy(sb->s_last_mounted, mount_path, sizeof(sb->s_last_mounted)); #endif FinishWrite(); Sync(); } } Filesystem::~Filesystem() { Sync(); while ( mru_inode ) delete mru_inode; for ( size_t i = 0; i < num_groups; i++ ) delete block_groups[i]; delete[] block_groups; if ( device->write ) { BeginWrite(); sb->s_state = EXT2_VALID_FS; FinishWrite(); Sync(); } sb_block->Unref(); } void Filesystem::BeginWrite() { sb_block->BeginWrite(); } void Filesystem::FinishWrite() { dirty = true; sb_block->FinishWrite(); } void Filesystem::Sync() { while ( dirty_inode ) dirty_inode->Sync(); // TODO: This can be made faster by maintaining a linked list of dirty block // groups. for ( size_t i = 0; i < num_groups; i++ ) if ( block_groups && block_groups[i] ) block_groups[i]->Sync(); if ( dirty ) { // The correct real-time might not have been known when the filesystem // was mounted (perhaps during early system boot), so find out what time // it is now, how long ago we were mounted, and subtract to get the // correct mount time. struct timespec now_realtime, now_monotonic; clock_gettime(CLOCK_REALTIME, &now_realtime); clock_gettime(CLOCK_MONOTONIC, &now_monotonic); time_t since_boot = now_monotonic.tv_sec - mtime_monotonic; mtime_realtime = now_realtime.tv_sec - since_boot; sb->s_wtime = now_realtime.tv_sec; sb->s_mtime = mtime_realtime; sb_block->Sync(); dirty = false; } device->Sync(); } BlockGroup* Filesystem::GetBlockGroup(uint32_t group_id) { assert(group_id < num_groups); if ( block_groups[group_id] ) return block_groups[group_id]->Refer(), block_groups[group_id]; size_t group_size = sizeof(ext_blockgrpdesc); uint32_t first_block_id = sb->s_first_data_block + 1 /* superblock */; uint32_t block_id = first_block_id + (group_id * group_size) / block_size; uint32_t offset = (group_id * group_size) % block_size; Block* block = device->GetBlock(block_id); if ( !block ) return (BlockGroup*) NULL; BlockGroup* group = new BlockGroup(this, group_id); if ( !group ) // TODO: Use operator new nothrow! return block->Unref(), (BlockGroup*) NULL; group->data_block = block; uint8_t* buf = group->data_block->block_data + offset; group->data = (struct ext_blockgrpdesc*) buf; return block_groups[group_id] = group; } Inode* Filesystem::GetInode(uint32_t inode_id) { if ( !inode_id || num_inodes <= inode_id ) return errno = EBADF, (Inode*) NULL; if ( !inode_id ) return errno = EBADF, (Inode*) NULL; size_t bin = inode_id % INODE_HASH_LENGTH; for ( Inode* iter = hash_inodes[bin]; iter; iter = iter->next_hashed ) if ( iter->inode_id == inode_id ) return iter->Refer(), iter; uint32_t group_id = (inode_id-1) / sb->s_inodes_per_group; uint32_t tabel_index = (inode_id-1) % sb->s_inodes_per_group; assert(group_id < num_groups); BlockGroup* group = GetBlockGroup(group_id); if ( !group ) return (Inode*) NULL; uint32_t tabel_block = group->data->bg_inode_table; group->Unref(); uint32_t block_id = tabel_block + (tabel_index * inode_size) / block_size; uint32_t offset = (tabel_index * inode_size) % block_size; Block* block = device->GetBlock(block_id); if ( !block ) return (Inode*) NULL; Inode* inode = new Inode(this, inode_id); if ( !inode ) return block->Unref(), (Inode*) NULL; inode->data_block = block; uint8_t* buf = inode->data_block->block_data + offset; inode->data = (struct ext_inode*) buf; inode->Prelink(); return inode; } uint32_t Filesystem::AllocateBlock(BlockGroup* preferred) { if ( !device->write ) return errno = EROFS, 0; if ( !sb->s_free_blocks_count ) return errno = ENOSPC, 0; if ( preferred ) if ( uint32_t block_id = preferred->AllocateBlock() ) return block_id; // TODO: This can be made faster by maintaining a linked list of block // groups that definitely have free blocks. for ( uint32_t group_id = 0; group_id < num_groups; group_id++ ) if ( uint32_t block_id = GetBlockGroup(group_id)->AllocateBlock() ) return block_id; // TODO: This case should only be fit in the event of corruption. We should // rebuild all these values upon filesystem mount instead so we know // this can't happen. That also allows us to make the linked list // requested above. BeginWrite(); sb->s_free_blocks_count = 0; FinishWrite(); return errno = ENOSPC, 0; } uint32_t Filesystem::AllocateInode(BlockGroup* preferred) { if ( !device->write ) return errno = EROFS, 0; if ( !sb->s_free_inodes_count ) return errno = ENOSPC, 0; if ( preferred ) if ( uint32_t inode_id = preferred->AllocateInode() ) return inode_id; // TODO: This can be made faster by maintaining a linked list of block // groups that definitely have free inodes. for ( uint32_t group_id = 0; group_id < num_groups; group_id++ ) if ( uint32_t inode_id = GetBlockGroup(group_id)->AllocateInode() ) return inode_id; // TODO: This case should only be fit in the event of corruption. We should // rebuild all these values upon filesystem mount instead so we know // this can't happen. That also allows us to make the linked list // requested above. BeginWrite(); sb->s_free_inodes_count = 0; FinishWrite(); return errno = ENOSPC, 0; } void Filesystem::FreeBlock(uint32_t block_id) { assert(device->write); assert(block_id); assert(block_id < num_blocks); uint32_t group_id = (block_id - sb->s_first_data_block) / sb->s_blocks_per_group; assert(group_id < num_groups); BlockGroup* group = GetBlockGroup(group_id); if ( !group ) return; group->FreeBlock(block_id); group->Unref(); } void Filesystem::FreeInode(uint32_t inode_id) { assert(device->write); assert(inode_id); assert(inode_id < num_inodes); uint32_t group_id = (inode_id-1) / sb->s_inodes_per_group; assert(group_id < num_groups); BlockGroup* group = GetBlockGroup(group_id); if ( !group ) return; group->FreeInode(inode_id); group->Unref(); }