sortix-mirror/ext/filesystem.cpp
Jonas 'Sortie' Termansen e2202b2ddb Fix extfs unhandled allocation failures.
This is not sufficient. The operator new calls are dangerous right now
because they throw exceptions (not handled) on error instead of returning
NULL. This needs to be changed to operator new nothrow instead.
2015-08-27 22:12:11 +02:00

261 lines
7.9 KiB
C++

/*******************************************************************************
Copyright(C) Jonas 'Sortie' Termansen 2013, 2014, 2015.
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.
This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
more details.
You should have received a copy of the GNU General Public License along with
this program. If not, see <http://www.gnu.org/licenses/>.
filesystem.cpp
Filesystem.
*******************************************************************************/
#include <sys/types.h>
#include <assert.h>
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#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;
BeginWrite();
sb->s_mtime = mtime_realtime;
sb->s_mnt_count++;
sb->s_state = EXT2_ERROR_FS;
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;
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 ( !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 ( !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(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(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();
}