diff --git a/Makefile b/Makefile index 1e7628fa..0893ad60 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ MAKEFILE_NOT_MEANT_FOR_SORTIX=1 include compiler.mak include version.mak -MODULES=libc libm dispd games mkinitrd mxmpp utils bench sortix +MODULES=libc libm dispd games mkinitrd mxmpp utils bench ext sortix ifndef SYSROOT SYSROOT:=$(shell pwd)/sysroot diff --git a/ext/.gitignore b/ext/.gitignore new file mode 100644 index 00000000..b15cc768 --- /dev/null +++ b/ext/.gitignore @@ -0,0 +1,2 @@ +extfs +*.o diff --git a/ext/Makefile b/ext/Makefile new file mode 100644 index 00000000..a3edf9f5 --- /dev/null +++ b/ext/Makefile @@ -0,0 +1,38 @@ +include ../compiler.mak +include ../version.mak +include ../dirs.mak + +OPTLEVEL?=-g -O2 +CXXFLAGS?=$(OPTLEVEL) + +CPPFLAGS:=$(CPPFLAGS) +CXXFLAGS:=$(CXXFLAGS) -Wall -Wextra -fno-exceptions -fno-rtti + +LIBS:=$(LIBS) + +ifeq ($(HOST_IS_SORTIX),0) + LIBS:=$(LIBS) -lfuse + CPPFLAGS:=$(CPPFLAGS) -D_FILE_OFFSET_BITS=64 +endif + +BINARIES:=extfs + +INSTALLBINARIES:=$(addprefix $(DESTDIR)$(BINDIR)/,$(BINARIES)) + +all: $(BINARIES) + +.PHONY: all install uninstall clean + +install: all + mkdir -p $(DESTDIR)$(BINDIR) + install $(BINARIES) $(DESTDIR)$(BINDIR) + +uninstall: + rm -f $(DESTDIR)$(INSTALLBINARIES) + +extfs: *.cpp *.h + $(CXX) -std=gnu++11 $(CPPFLAGS) $(CXXFLAGS) $(wildcard *.cpp) -o $@ $(LIBS) + +clean: + rm -f $(BINARIES) *.o + diff --git a/ext/block.cpp b/ext/block.cpp new file mode 100644 index 00000000..a577f085 --- /dev/null +++ b/ext/block.cpp @@ -0,0 +1,115 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + block.cpp + Blocks in the filesystem. + +*******************************************************************************/ + +#include + +#include +#include + +#include "block.h" +#include "device.h" +#include "ioleast.h" + +Block::Block(Device* device, uint32_t block_id) +{ + this->prev_block = NULL; + this->next_block = NULL; + this->prev_hashed = NULL; + this->next_hashed = NULL; + this->device = device; + this->reference_count = 1; + this->block_id = block_id; + this->dirty = false; + this->block_data = 0; +} + +Block::~Block() +{ + Sync(); + Unlink(); + delete[] block_data; +} + +void Block::Refer() +{ + reference_count++; +} + +void Block::Unref() +{ + if ( !--reference_count ) +#if 0 + delete this; +#else + {}; +#endif +} + +void Block::Sync() +{ + if ( !dirty ) + return; + dirty = false; + if ( !device->write ) + return; + off_t file_offset = (off_t) device->block_size * (off_t) block_id; + pwriteall(device->fd, block_data, device->block_size, file_offset); + +} + +void Block::Dirty() +{ + dirty = true; + Use(); +} + +void Block::Use() +{ + Unlink(); + Prelink(); +} + +void Block::Unlink() +{ + (prev_block ? prev_block->next_block : device->mru_block) = next_block; + (next_block ? next_block->prev_block : device->lru_block) = prev_block; + size_t bin = block_id % DEVICE_HASH_LENGTH; + (prev_hashed ? prev_hashed->next_hashed : device->hash_blocks[bin]) = next_hashed; + if ( next_hashed ) next_hashed->prev_hashed = prev_hashed; +} + +void Block::Prelink() +{ + prev_block = NULL; + next_block = device->mru_block; + if ( device->mru_block ) + device->mru_block->prev_block = this; + device->mru_block = this; + if ( !device->lru_block ) + device->lru_block = this; + size_t bin = block_id % DEVICE_HASH_LENGTH; + prev_hashed = NULL; + next_hashed = device->hash_blocks[bin]; + device->hash_blocks[bin] = this; + if ( next_hashed ) + next_hashed->prev_hashed = this; +} diff --git a/ext/block.h b/ext/block.h new file mode 100644 index 00000000..94cec14f --- /dev/null +++ b/ext/block.h @@ -0,0 +1,56 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + block.h + Blocks in the filesystem. + +*******************************************************************************/ + +#ifndef BLOCK_H +#define BLOCK_H + +class Device; + +class Block +{ +public: + Block(Device* device, uint32_t block_id); + ~Block(); + +public: + Block* prev_block; + Block* next_block; + Block* prev_hashed; + Block* next_hashed; + Device* device; + size_t reference_count; + uint32_t block_id; + bool dirty; + uint8_t* block_data; + +public: + void Refer(); + void Unref(); + void Sync(); + void Dirty(); + void Use(); + void Unlink(); + void Prelink(); + +}; + +#endif diff --git a/ext/blockgroup.cpp b/ext/blockgroup.cpp new file mode 100644 index 00000000..36a118bf --- /dev/null +++ b/ext/blockgroup.cpp @@ -0,0 +1,238 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + blockgroup.cpp + Filesystem block group. + +*******************************************************************************/ + +#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 "util.h" + +BlockGroup::BlockGroup(Filesystem* filesystem, uint32_t group_id) +{ + this->data_block = NULL; + this->data = NULL; + this->filesystem = filesystem; + this->block_bitmap_chunk = NULL; + this->inode_bitmap_chunk = NULL; + this->reference_count = 1; + this->group_id = group_id; + this->block_alloc_chunk = 0; + this->inode_alloc_chunk = 0; + this->block_bitmap_chunk_i = 0; + // TODO: inode_bitmap_chunk_i + this->first_block_id = filesystem->sb->s_first_data_block + + filesystem->sb->s_blocks_per_group * group_id; + this->first_inode_id = 1 + + filesystem->sb->s_inodes_per_group * group_id; + this->num_blocks = group_id+1== filesystem->num_groups ? + filesystem->num_blocks - first_block_id : + filesystem->sb->s_blocks_per_group; + this->num_inodes = group_id+1== filesystem->num_groups ? + filesystem->num_inodes - first_inode_id : + filesystem->sb->s_inodes_per_group; + size_t num_chunk_bits = filesystem->block_size * 8UL; + this->num_block_bitmap_chunks = divup(num_blocks, (uint32_t) num_chunk_bits); + this->num_inode_bitmap_chunks = divup(num_inodes, (uint32_t) num_chunk_bits); + this->dirty = false; +} + +BlockGroup::~BlockGroup() +{ + Sync(); + if ( data_block ) + data_block->Unref(); + filesystem->block_groups[group_id] = NULL; +} + +uint32_t BlockGroup::AllocateBlock() +{ + if ( !data->bg_free_blocks_count ) + return errno = ENOSPC, 0; + size_t num_chunk_bits = filesystem->block_size * 8UL; + uint32_t begun_chunk = block_alloc_chunk; + for ( uint32_t i = 0; i < num_block_bitmap_chunks; i++ ) + { + block_alloc_chunk = (begun_chunk + i) % num_block_bitmap_chunks; + bool last = block_alloc_chunk + 1 == num_block_bitmap_chunks; + if ( !block_bitmap_chunk ) + { + uint32_t block_id = data->bg_block_bitmap + block_alloc_chunk; + block_bitmap_chunk = filesystem->device->GetBlock(block_id); + block_bitmap_chunk_i = 0; + } + uint32_t chunk_offset = block_alloc_chunk * num_chunk_bits; + uint8_t* chunk_bits = block_bitmap_chunk->block_data; + size_t num_bits = last ? num_blocks - chunk_offset : num_chunk_bits; + for ( ; block_bitmap_chunk_i < num_bits; block_bitmap_chunk_i++ ) + if ( !checkbit(chunk_bits, block_bitmap_chunk_i) ) + { + setbit(chunk_bits, block_bitmap_chunk_i); + block_bitmap_chunk->Dirty(); + data->bg_free_blocks_count--; + Dirty(); + filesystem->sb->s_free_blocks_count--; + filesystem->Dirty(); + uint32_t group_block_id = chunk_offset + block_bitmap_chunk_i++; + uint32_t block_id = first_block_id + group_block_id; + return block_id; + } + block_bitmap_chunk->Sync(); + block_bitmap_chunk->Unref(); + block_bitmap_chunk = NULL; + } + data->bg_free_blocks_count = 0; + Dirty(); + return errno = ENOSPC, 0; +} + +uint32_t BlockGroup::AllocateInode() +{ + if ( !data->bg_free_inodes_count ) + return errno = ENOSPC, 0; + size_t num_chunk_bits = filesystem->block_size * 8UL; + uint32_t begun_chunk = inode_alloc_chunk; + for ( uint32_t i = 0; i < num_inode_bitmap_chunks; i++ ) + { + inode_alloc_chunk = (begun_chunk + i) % num_inode_bitmap_chunks; + bool last = inode_alloc_chunk + 1 == num_inode_bitmap_chunks; + if ( !inode_bitmap_chunk ) + { + uint32_t block_id = data->bg_inode_bitmap + inode_alloc_chunk; + inode_bitmap_chunk = filesystem->device->GetBlock(block_id); + inode_bitmap_chunk_i = 0; + } + uint32_t chunk_offset = inode_alloc_chunk * num_chunk_bits; + uint8_t* chunk_bits = inode_bitmap_chunk->block_data; + size_t num_bits = last ? num_inodes - chunk_offset : num_chunk_bits; + for ( ; inode_bitmap_chunk_i < num_bits; inode_bitmap_chunk_i++ ) + if ( !checkbit(chunk_bits, inode_bitmap_chunk_i) ) + { + setbit(chunk_bits, inode_bitmap_chunk_i); + inode_bitmap_chunk->Dirty(); + data->bg_free_inodes_count--; + Dirty(); + filesystem->sb->s_free_inodes_count--; + filesystem->Dirty(); + uint32_t group_inode_id = chunk_offset + inode_bitmap_chunk_i++; + uint32_t inode_id = first_inode_id + group_inode_id; + return inode_id; + } + inode_bitmap_chunk->Sync(); + inode_bitmap_chunk->Unref(); + inode_bitmap_chunk = NULL; + } + data->bg_free_inodes_count = 0; + Dirty(); + return errno = ENOSPC, 0; +} + +void BlockGroup::FreeBlock(uint32_t block_id) +{ + block_id -= first_block_id; + size_t num_chunk_bits = filesystem->block_size * 8UL; + uint32_t chunk_id = block_id / num_chunk_bits; + uint32_t chunk_bit = block_id % num_chunk_bits; + if ( !block_bitmap_chunk || chunk_id != block_alloc_chunk ) + { + if ( block_bitmap_chunk ) + block_bitmap_chunk->Sync(), + block_bitmap_chunk->Unref(); + block_alloc_chunk = chunk_id; + uint32_t block_id = data->bg_block_bitmap + block_alloc_chunk; + block_bitmap_chunk = filesystem->device->GetBlock(block_id); + block_bitmap_chunk_i = 0; + } + + uint8_t* chunk_bits = block_bitmap_chunk->block_data; + clearbit(chunk_bits, chunk_bit); + block_bitmap_chunk->Dirty(); + data->bg_free_blocks_count++; + Dirty(); + filesystem->sb->s_free_blocks_count++; + filesystem->Dirty(); +} + +void BlockGroup::FreeInode(uint32_t inode_id) +{ + inode_id -= first_inode_id; + size_t num_chunk_bits = filesystem->block_size * 8UL; + uint32_t chunk_id = inode_id / num_chunk_bits; + uint32_t chunk_bit = inode_id % num_chunk_bits; + if ( !inode_bitmap_chunk || chunk_id != inode_alloc_chunk ) + { + if ( inode_bitmap_chunk ) + inode_bitmap_chunk->Sync(), + inode_bitmap_chunk->Unref(); + inode_alloc_chunk = chunk_id; + uint32_t block_id = data->bg_inode_bitmap + inode_alloc_chunk; + inode_bitmap_chunk = filesystem->device->GetBlock(block_id); + inode_bitmap_chunk_i = 0; + } + + uint8_t* chunk_bits = inode_bitmap_chunk->block_data; + clearbit(chunk_bits, chunk_bit); + inode_bitmap_chunk->Dirty(); + data->bg_free_inodes_count++; + Dirty(); + filesystem->sb->s_free_inodes_count++; + filesystem->Dirty(); +} + +void BlockGroup::Refer() +{ + // TODO +} + +void BlockGroup::Unref() +{ + // TODO +} + +void BlockGroup::Sync() +{ + if ( block_bitmap_chunk ) + block_bitmap_chunk->Sync(); + if ( dirty ) + data_block->Sync(); + dirty = false; +} + +void BlockGroup::Dirty() +{ + dirty = true; + data_block->Dirty(); + Use(); +} + +void BlockGroup::Use() +{ +} diff --git a/ext/blockgroup.h b/ext/blockgroup.h new file mode 100644 index 00000000..4038f29f --- /dev/null +++ b/ext/blockgroup.h @@ -0,0 +1,70 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + blockgroup.h + Filesystem block group. + +*******************************************************************************/ + +#ifndef BLOCKGROUP_H +#define BLOCKGROUP_H + +class Block; +class Filesystem; + +class BlockGroup +{ +public: + BlockGroup(Filesystem* filesystem, uint32_t group_id); + ~BlockGroup(); + +public: + Block* data_block; + struct ext_blockgrpdesc* data; + Filesystem* filesystem; + Block* block_bitmap_chunk; + Block* inode_bitmap_chunk; + size_t reference_count; + uint32_t group_id; + uint32_t block_alloc_chunk; + uint32_t inode_alloc_chunk; + uint32_t num_block_bitmap_chunks; + uint32_t num_inode_bitmap_chunks; + uint32_t block_bitmap_chunk_i; + uint32_t inode_bitmap_chunk_i; + uint32_t num_blocks; + uint32_t num_inodes; + uint32_t first_block_id; + uint32_t first_inode_id; + bool dirty; + +public: + uint32_t AllocateBlock(); + uint32_t AllocateInode(); + void FreeBlock(uint32_t block_id); + void FreeInode(uint32_t inode_id); + void Refer(); + void Unref(); + void Sync(); + void Dirty(); + void Use(); + void Unlink(); + void Prelink(); + +}; + +#endif diff --git a/ext/device.cpp b/ext/device.cpp new file mode 100644 index 00000000..13d742fe --- /dev/null +++ b/ext/device.cpp @@ -0,0 +1,96 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + device.cpp + Block device. + +*******************************************************************************/ + +#include +#include + +#include +#include +#include + +#include "block.h" +#include "device.h" +#include "ioleast.h" + +Device::Device(int fd, uint32_t block_size, bool write) +{ + this->write = write; + this->fd = fd; + this->block_size = block_size; + struct stat st; + fstat(fd, &st); + this->device_size = st.st_size; + this->mru_block = NULL; + this->lru_block = NULL; + for ( size_t i = 0; i < DEVICE_HASH_LENGTH; i++ ) + hash_blocks[i] = NULL; +} + +Device::~Device() +{ + Sync(); + while ( mru_block ) + delete mru_block; +} + +Block* Device::GetBlock(uint32_t block_id) +{ + if ( Block* block = GetCachedBlock(block_id) ) + return block; + Block* block = new Block(this, block_id); + block->block_data = new uint8_t[block_size]; + off_t file_offset = (off_t) block_size * (off_t) block_id; + preadall(fd, block->block_data, block_size, file_offset); + block->Prelink(); + return block; +} + +Block* Device::GetBlockZeroed(uint32_t block_id) +{ + if ( Block* block = GetCachedBlock(block_id) ) + { + memset(block->block_data, 0, block_size); + block->Dirty(); + return block; + } + Block* block = new Block(this, block_id); + block->block_data = new uint8_t[block_size]; + memset(block->block_data, 0, block_size); + block->Prelink(); + block->Dirty(); + return block; +} + +Block* Device::GetCachedBlock(uint32_t block_id) +{ + size_t bin = block_id % DEVICE_HASH_LENGTH; + for ( Block* iter = hash_blocks[bin]; iter; iter = iter->next_hashed ) + if ( iter->block_id == block_id ) + return iter->Refer(), iter; + return NULL; +} + +void Device::Sync() +{ + for ( Block* iter = mru_block; iter; iter = iter->next_block ) + iter->Sync(); +} diff --git a/ext/device.h b/ext/device.h new file mode 100644 index 00000000..9a33e5eb --- /dev/null +++ b/ext/device.h @@ -0,0 +1,53 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + device.h + Block device. + +*******************************************************************************/ + +#ifndef DEVICE_H +#define DEVICE_H + +class Block; + +const size_t DEVICE_HASH_LENGTH = 1 << 16; + +class Device +{ +public: + Device(int fd, uint32_t block_size, bool write); + ~Device(); + +public: + Block* mru_block; + Block* lru_block; + Block* hash_blocks[DEVICE_HASH_LENGTH]; + off_t device_size; + uint32_t block_size; + int fd; + bool write; + +public: + Block* GetBlock(uint32_t block_id); + Block* GetBlockZeroed(uint32_t block_id); + Block* GetCachedBlock(uint32_t block_id); + void Sync(); + +}; + +#endif diff --git a/ext/ext-constants.h b/ext/ext-constants.h new file mode 100644 index 00000000..e2808619 --- /dev/null +++ b/ext/ext-constants.h @@ -0,0 +1,135 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + ext-constants.h + Constants for the extended filesystem. + +*******************************************************************************/ + +#ifndef EXT_CONSTANTS_H +#define EXT_CONSTANTS_H + +const uint16_t EXT2_SUPER_MAGIC = 0xEF53; +const uint16_t EXT2_VALID_FS = 1; +const uint16_t EXT2_ERROR_FS = 2; +const uint16_t EXT2_ERRORS_CONTINUE = 1; +const uint16_t EXT2_ERRORS_RO = 2; +const uint16_t EXT2_ERRORS_PANIC = 3; +const uint32_t EXT2_OS_LINUX = 0; +const uint32_t EXT2_OS_HURD = 1; +const uint32_t EXT2_OS_MASIX = 2; +const uint32_t EXT2_OS_FREEBSD = 3; +const uint32_t EXT2_OS_LITES = 4; +const uint32_t EXT2_GOOD_OLD_REV = 0; +const uint32_t EXT2_DYNAMIC_REV = 1; +const uint16_t EXT2_DEF_RESUID = 0; +const uint16_t EXT2_DEF_RESGID = 0; +const uint16_t EXT2_GOOD_OLD_FIRST_INO = 11; +const uint16_t EXT2_GOOD_OLD_INODE_SIZE = 128; +const uint32_t EXT2_FEATURE_COMPAT_DIR_PREALLOC = 1U << 0U; +const uint32_t EXT2_FEATURE_COMPAT_IMAGIC_INODES = 1U << 1U; +const uint32_t EXT3_FEATURE_COMPAT_HAS_JOURNAL = 1U << 2U; +const uint32_t EXT2_FEATURE_COMPAT_EXT_ATTR = 1U << 3U; +const uint32_t EXT2_FEATURE_COMPAT_RESIZE_INO = 1U << 4U; +const uint32_t EXT2_FEATURE_COMPAT_DIR_INDEX = 1U << 5U; +const uint32_t EXT2_FEATURE_INCOMPAT_COMPRESSION = 1U << 0U; +const uint32_t EXT2_FEATURE_INCOMPAT_FILETYPE = 1U << 1U; +const uint32_t EXT2_FEATURE_INCOMPAT_RECOVER = 1U << 2U; +const uint32_t EXT2_FEATURE_INCOMPAT_JOURNAL_DEV = 1U << 3U; +const uint32_t EXT2_FEATURE_INCOMPAT_META_BG = 1U << 4U; +const uint32_t EXT2_FEATURE_RO_COMPAT_SPARSE_SUPER = 1U << 0U; +const uint32_t EXT2_FEATURE_RO_COMPAT_LARGE_FILE = 1U << 1U; +const uint32_t EXT2_FEATURE_RO_COMPAT_BTREE_DIR = 1U << 2U; +const uint32_t EXT2_LZV1_ALG = 1U << 0U; +const uint32_t EXT2_LZRW3A_ALG = 1U << 1U; +const uint32_t EXT2_GZIP_ALG = 1U << 2U; +const uint32_t EXT2_BZIP2_ALG = 1U << 3U; +const uint32_t EXT2_LZO_ALG = 1U << 4U; +const uint16_t EXT2_S_IFMT = 0xF000; +const uint16_t EXT2_S_IFSOCK = 0xC000; +const uint16_t EXT2_S_IFLNK = 0xA000; +const uint16_t EXT2_S_IFREG = 0x8000; +const uint16_t EXT2_S_IFBLK = 0x6000; +const uint16_t EXT2_S_IFDIR = 0x4000; +const uint16_t EXT2_S_IFCHR = 0x2000; +const uint16_t EXT2_S_IFIFO = 0x1000; +const uint16_t EXT2_S_ISUID = 0x0800; +const uint16_t EXT2_S_ISGID = 0x0400; +const uint16_t EXT2_S_ISVTX = 0x0200; +const uint16_t EXT2_S_IRUSR = 0x0100; +const uint16_t EXT2_S_IWUSR = 0x0080; +const uint16_t EXT2_S_IXUSR = 0x0040; +const uint16_t EXT2_S_IRGRP = 0x0020; +const uint16_t EXT2_S_IWGRP = 0x0010; +const uint16_t EXT2_S_IXGRP = 0x0008; +const uint16_t EXT2_S_IROTH = 0x0004; +const uint16_t EXT2_S_IWOTH = 0x0002; +const uint16_t EXT2_S_IXOTH = 0x0001; +#define EXT2_S_ISSOCK(mode) (((mode) & EXT2_S_IFMT) == EXT2_S_IFSOCK) +#define EXT2_S_ISLNK(mode) (((mode) & EXT2_S_IFMT) == EXT2_S_IFLNK) +#define EXT2_S_ISREG(mode) (((mode) & EXT2_S_IFMT) == EXT2_S_IFREG) +#define EXT2_S_ISBLK(mode) (((mode) & EXT2_S_IFMT) == EXT2_S_IFBLK) +#define EXT2_S_ISDIR(mode) (((mode) & EXT2_S_IFMT) == EXT2_S_IFDIR) +#define EXT2_S_ISCHR(mode) (((mode) & EXT2_S_IFMT) == EXT2_S_IFCHR) +#define EXT2_S_ISFIFO(mode) (((mode) & EXT2_S_IFMT) == EXT2_S_IFIFO) +const uint32_t EXT2_SECRM_FL = 0x00000001U; +const uint32_t EXT2_UNRM_FL = 0x00000002U; +const uint32_t EXT2_COMPR_FL = 0x00000004U; +const uint32_t EXT2_SYNC_FL = 0x00000008U; +const uint32_t EXT2_IMMUTABLE_FL = 0x00000010U; +const uint32_t EXT2_APPEND_FL = 0x00000020U; +const uint32_t EXT2_NODUMP_FL = 0x00000040U; +const uint32_t EXT2_NOATIME_FL = 0x00000080U; +const uint32_t EXT2_DIRTY_FL = 0x00000100U; +const uint32_t EXT2_COMPRBLK_FL = 0x00000200U; +const uint32_t EXT2_NOCOMPR_FL = 0x00000400U; +const uint32_t EXT2_ECOMPR_FL = 0x00000800U; +const uint32_t EXT2_BTREE_FL = 0x00001000U; +const uint32_t EXT2_INDEX_FL = 0x00001000U; +const uint32_t EXT2_IMAGIC_FL = 0x00002000U; +const uint32_t EXT3_JOURNAL_DATA_FL = 0x00004000U; +const uint32_t EXT2_RESERVED_FL = 0x80000000U; +const uint32_t EXT2_ROOT_INO = 2; +const uint8_t EXT2_FT_UNKNOWN = 0; +const uint8_t EXT2_FT_REG_FILE = 1; +const uint8_t EXT2_FT_DIR = 2; +const uint8_t EXT2_FT_CHRDEV = 3; +const uint8_t EXT2_FT_BLKDEV = 4; +const uint8_t EXT2_FT_FIFO = 5; +const uint8_t EXT2_FT_SOCK = 6; +const uint8_t EXT2_FT_SYMLINK = 7; + +static inline uint8_t EXT2_FT_OF_MODE(mode_t mode) +{ + if ( EXT2_S_ISREG(mode) ) + return EXT2_FT_REG_FILE; + if ( EXT2_S_ISDIR(mode) ) + return EXT2_FT_DIR; + if ( EXT2_S_ISCHR(mode) ) + return EXT2_FT_CHRDEV; + if ( EXT2_S_ISBLK(mode) ) + return EXT2_FT_BLKDEV; + if ( EXT2_S_ISFIFO(mode) ) + return EXT2_FT_FIFO; + if ( EXT2_S_ISSOCK(mode) ) + return EXT2_FT_SOCK; + if ( EXT2_S_ISLNK(mode) ) + return EXT2_FT_SYMLINK; + return EXT2_FT_UNKNOWN; +} + +#endif diff --git a/ext/ext-structs.h b/ext/ext-structs.h new file mode 100644 index 00000000..bde7ea4b --- /dev/null +++ b/ext/ext-structs.h @@ -0,0 +1,131 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + ext-structs.h + Data structures for the extended filesystem. + +*******************************************************************************/ + +#ifndef EXT_STRUCTS_H +#define EXT_STRUCTS_H + +struct ext_superblock +{ + uint32_t s_inodes_count; + uint32_t s_blocks_count; + uint32_t s_r_blocks_count; + uint32_t s_free_blocks_count; + uint32_t s_free_inodes_count; + uint32_t s_first_data_block; + uint32_t s_log_block_size; + int32_t s_log_frag_size; + uint32_t s_blocks_per_group; + uint32_t s_frags_per_group; + uint32_t s_inodes_per_group; + uint32_t s_mtime; + uint32_t s_wtime; + uint16_t s_mnt_count; + uint16_t s_max_mnt_count; + uint16_t s_magic; + uint16_t s_state; + uint16_t s_errors; + uint16_t s_minor_rev_level; + uint32_t s_lastcheck; + uint32_t s_checkinterval; + uint32_t s_creator_os; + uint32_t s_rev_level; + uint16_t s_def_resuid; + uint16_t s_def_resgid; +// EXT2_DYNAMIC_REV + uint32_t s_first_ino; + uint16_t s_inode_size; + uint16_t s_block_group_nr; + uint32_t s_feature_compat; + uint32_t s_feature_incompat; + uint32_t s_feature_ro_compat; + uint8_t s_uuid[16]; + /*uint8_t*/ char s_volume_name[16]; + /*uint8_t*/ char s_last_mounted[64]; + uint32_t s_algo_bitmap; +// Performance Hints + uint8_t s_prealloc_blocks; + uint8_t s_prealloc_dir_blocks; + uint16_t alignment0; +// Journaling Support + uint8_t s_journal_uuid[16]; + uint32_t s_journal_inum; + uint32_t s_journal_dev; + uint32_t s_last_orphan; +// Directory Indexing Support + uint32_t s_hash_seed[4]; + uint8_t s_def_hash_version; + uint8_t alignment1[3]; +// Other options + uint32_t s_default_mount_options; + uint32_t s_first_meta_bg; + uint8_t alignment2[760]; +}; + +struct ext_blockgrpdesc +{ + uint32_t bg_block_bitmap; + uint32_t bg_inode_bitmap; + uint32_t bg_inode_table; + uint16_t bg_free_blocks_count; + uint16_t bg_free_inodes_count; + uint16_t bg_used_dirs_count; + uint16_t alignment0; + uint8_t alignment1[12]; +}; + +struct ext_inode +{ + uint16_t i_mode; + uint16_t i_uid; + uint32_t i_size; + uint32_t i_atime; + uint32_t i_ctime; + uint32_t i_mtime; + uint32_t i_dtime; + uint16_t i_gid; + uint16_t i_links_count; + uint32_t i_blocks; + uint32_t i_flags; + uint32_t i_osd1; + uint32_t i_block[15]; + uint32_t i_generation; + uint32_t i_file_acl; + uint32_t i_dir_acl; + uint32_t i_faddr; + uint8_t i_frag; + uint8_t i_fsize; + uint16_t i_mode_high; + uint16_t i_uid_high; + uint16_t i_gid_high; + uint32_t i_osd2_alignment0; +}; + +struct ext_dirent +{ + uint32_t inode; + uint16_t reclen; + uint8_t name_len; + uint8_t file_type; + char name[0]; +}; + +#endif diff --git a/ext/extfs.cpp b/ext/extfs.cpp new file mode 100644 index 00000000..03e028d8 --- /dev/null +++ b/ext/extfs.cpp @@ -0,0 +1,1481 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + extfs.cpp + Implementation of the extended filesystem. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__OFF_MAX) && !defined(OFF_MAX) +#define OFF_MAX __OFF_MAX +#endif + +#if defined(__sortix__) +#include +#endif + +#if defined(__sortix__) +#include +#else +struct timespec timespec_make(time_t sec, long nsec) +{ + struct timespec ret; + ret.tv_sec = sec; + ret.tv_nsec = nsec; + return ret; +} +#endif + +#if defined(__linux__) +#define FUSE_USE_VERSION 26 +#include +#endif + +#if defined(__sortix__) +#include +#endif + +#include "ext-constants.h" +#include "ext-structs.h" + +#include "blockgroup.h" +#include "block.h" +#include "device.h" +#include "filesystem.h" +#include "inode.h" +#include "ioleast.h" +#include "util.h" + +const uint32_t EXT2_FEATURE_COMPAT_SUPPORTED = 0; +const uint32_t EXT2_FEATURE_INCOMPAT_SUPPORTED = \ + EXT2_FEATURE_INCOMPAT_FILETYPE; +const uint32_t EXT2_FEATURE_RO_COMPAT_SUPPORTED = \ + EXT2_FEATURE_RO_COMPAT_LARGE_FILE; + +// TODO: Inode 0 is not legal, but a lot of functions here accept it! + +mode_t HostModeFromExtMode(uint32_t extmode) +{ + mode_t hostmode = extmode & 0777; + if ( extmode & EXT2_S_ISVTX ) hostmode |= S_ISVTX; +#if defined(S_ISGID) // Not on Sortix. + if ( extmode & EXT2_S_ISGID ) hostmode |= S_ISGID; +#endif +#if defined(S_ISUID) // Not on Sortix. + if ( extmode & EXT2_S_ISUID ) hostmode |= S_ISUID; +#endif + if ( EXT2_S_ISSOCK(extmode) ) hostmode |= S_IFSOCK; + if ( EXT2_S_ISLNK(extmode) ) hostmode |= S_IFLNK; + if ( EXT2_S_ISREG(extmode) ) hostmode |= S_IFREG; + if ( EXT2_S_ISBLK(extmode) ) hostmode |= S_IFBLK; + if ( EXT2_S_ISDIR(extmode) ) hostmode |= S_IFDIR; + if ( EXT2_S_ISCHR(extmode) ) hostmode |= S_IFCHR; + if ( EXT2_S_ISFIFO(extmode) ) hostmode |= S_IFIFO; + return hostmode; +} + +uint32_t ExtModeFromHostMode(mode_t hostmode) +{ + uint32_t extmode = hostmode & 0777; + if ( hostmode & S_ISVTX ) extmode |= EXT2_S_ISVTX; +#if defined(S_ISGID) // Not on Sortix. + if ( hostmode & S_ISGID ) extmode |= EXT2_S_ISGID; +#endif +#if defined(S_ISUID) // Not on Sortix. + if ( hostmode & EXT2_S_ISUID ) extmode |= EXT2_S_ISUID; +#endif + if ( S_ISSOCK(hostmode) ) extmode |= EXT2_S_IFSOCK; + if ( S_ISLNK(hostmode) ) extmode |= EXT2_S_IFLNK; + if ( S_ISREG(hostmode) ) extmode |= EXT2_S_IFREG; + if ( S_ISBLK(hostmode) ) extmode |= EXT2_S_IFBLK; + if ( S_ISDIR(hostmode) ) extmode |= EXT2_S_IFDIR; + if ( S_ISCHR(hostmode) ) extmode |= EXT2_S_IFCHR; + if ( S_ISFIFO(hostmode) ) extmode |= EXT2_S_IFIFO; + return extmode; +} + +void StatInode(Inode* inode, struct stat* st) +{ + memset(st, 0, sizeof(*st)); + st->st_ino = inode->inode_id; + st->st_mode = HostModeFromExtMode(inode->Mode()); + st->st_nlink = inode->data->i_links_count; + st->st_uid = inode->UserId(); + st->st_gid = inode->GroupId(); + st->st_size = inode->Size(); + st->st_atim = timespec_make(inode->data->i_atime, 0); + st->st_ctim = timespec_make(inode->data->i_ctime, 0); + st->st_mtim = timespec_make(inode->data->i_mtime, 0); + st->st_blksize = inode->filesystem->block_size; + st->st_blocks = inode->data->i_blocks; +} + +#if defined(__sortix__) + +bool RespondData(int svr, int chl, const void* ptr, size_t count) +{ + return fsm_send(svr, chl, ptr, count) == (ssize_t) count; +} + +bool RespondHeader(int svr, int chl, size_t type, size_t size) +{ + struct fsm_msg_header hdr; + hdr.msgtype = type; + hdr.msgsize = size; + return RespondData(svr, chl, &hdr, sizeof(hdr)); +} + +bool RespondMessage(int svr, int chl, unsigned int type, const void* ptr, + size_t count) +{ + return RespondHeader(svr, chl, type, count) && + RespondData(svr, chl, ptr, count); +} + +bool RespondError(int svr, int chl, int errnum) +{ + struct fsm_resp_error body; + body.errnum = errnum; + //fprintf(stderr, "extfs: sending error %i (%s)\n", errnum, strerror(errnum)); + return RespondMessage(svr, chl, FSM_RESP_ERROR, &body, sizeof(body)); +} + +bool RespondSuccess(int svr, int chl) +{ + struct fsm_resp_success body; + return RespondMessage(svr, chl, FSM_RESP_SUCCESS, &body, sizeof(body)); +} + +bool RespondStat(int svr, int chl, struct stat* st) +{ + struct fsm_resp_stat body; + body.st = *st; + return RespondMessage(svr, chl, FSM_RESP_STAT, &body, sizeof(body)); +} + +bool RespondSeek(int svr, int chl, off_t offset) +{ + struct fsm_resp_lseek body; + body.offset = offset; + return RespondMessage(svr, chl, FSM_RESP_LSEEK, &body, sizeof(body)); +} + +bool RespondRead(int svr, int chl, const uint8_t* buf, size_t count) +{ + struct fsm_resp_read body; + body.count = count; + return RespondMessage(svr, chl, FSM_RESP_READ, &body, sizeof(body)) && + RespondData(svr, chl, buf, count); +} + +bool RespondWrite(int svr, int chl, size_t count) +{ + struct fsm_resp_write body; + body.count = count; + return RespondMessage(svr, chl, FSM_RESP_WRITE, &body, sizeof(body)); +} + +bool RespondOpen(int svr, int chl, ino_t ino, mode_t type) +{ + struct fsm_resp_open body; + body.ino = ino; + body.type = type; + return RespondMessage(svr, chl, FSM_RESP_OPEN, &body, sizeof(body)); +} + +bool RespondMakeDir(int svr, int chl, ino_t ino) +{ + struct fsm_resp_mkdir body; + body.ino = ino; + return RespondMessage(svr, chl, FSM_RESP_MKDIR, &body, sizeof(body)); +} + +bool RespondReadDir(int svr, int chl, struct kernel_dirent* dirent) +{ + struct fsm_resp_readdirents body; + body.ino = dirent->d_ino; + body.type = dirent->d_type; + body.namelen = dirent->d_namelen; + return RespondMessage(svr, chl, FSM_RESP_READDIRENTS, &body, sizeof(body)) && + RespondData(svr, chl, dirent->d_name, dirent->d_namelen); +} + +void HandleRefer(int svr, int chl, struct fsm_req_refer* msg, Filesystem* fs) +{ + (void) svr; + (void) chl; + if ( fs->num_inodes <= msg->ino ) + return; + if ( Inode* inode = fs->GetInode((uint32_t) msg->ino) ) + inode->RemoteRefer(); +} + +void HandleUnref(int svr, int chl, struct fsm_req_unref* msg, Filesystem* fs) +{ + (void) svr; + (void) chl; + if ( fs->num_inodes <= msg->ino ) + return; + if ( Inode* inode = fs->GetInode((uint32_t) msg->ino) ) + inode->RemoteUnref(); +} + +void HandleSync(int svr, int chl, struct fsm_req_sync* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + inode->Sync(); + inode->Unref(); + RespondSuccess(svr, chl); +} + +void HandleStat(int svr, int chl, struct fsm_req_stat* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + struct stat st; + StatInode(inode, &st); + inode->Unref(); + RespondStat(svr, chl, &st); +} + +void HandleChangeMode(int svr, int chl, struct fsm_req_chmod* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + uint32_t req_mode = ExtModeFromHostMode(msg->mode); + uint32_t old_mode = inode->Mode(); + uint32_t new_mode = (old_mode & ~S_SETABLE) | (req_mode & S_SETABLE); + inode->SetMode(new_mode); + inode->Unref(); +} + +void HandleChangeOwner(int svr, int chl, struct fsm_req_chown* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + inode->SetUserId((uint32_t) msg->uid); + inode->SetGroupId((uint32_t) msg->gid); + inode->Unref(); + RespondSuccess(svr, chl); +} + +void HandleUTimens(int svr, int chl, struct fsm_req_utimens* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + inode->data->i_atime = msg->times[0].tv_sec; + inode->data->i_mtime = msg->times[1].tv_sec; + inode->Dirty(); + inode->Unref(); + RespondSuccess(svr, chl); +} + +void HandleTruncate(int svr, int chl, struct fsm_req_truncate* msg, Filesystem* fs) +{ + if( msg->size < 0 ) { RespondError(svr, chl, EINVAL); return; } + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + inode->Truncate((uint64_t) msg->size); + inode->Unref(); + RespondSuccess(svr, chl); +} + +void HandleSeek(int svr, int chl, struct fsm_req_lseek* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + if ( msg->whence == SEEK_SET ) + RespondSeek(svr, chl, msg->offset); + else if ( msg->whence == SEEK_END ) + { + off_t inode_size = inode->Size(); + if ( (msg->offset < 0 && inode_size + msg->offset < 0) || + (0 <= msg->offset && OFF_MAX - inode_size < msg->offset) ) + RespondError(svr, chl, EOVERFLOW); + else + RespondSeek(svr, chl, msg->offset + inode_size); + } + else + RespondError(svr, chl, EINVAL); + inode->Unref(); +} + +void HandleReadAt(int svr, int chl, struct fsm_req_pread* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + uint8_t* buf = (uint8_t*) malloc(msg->count); + if ( !buf ) { inode->Unref(); RespondError(svr, chl, errno); return; } + ssize_t amount = inode->ReadAt(buf, msg->count, msg->offset); + RespondRead(svr, chl, buf, amount); + inode->Unref(); + free(buf); +} + +void HandleWriteAt(int svr, int chl, struct fsm_req_pread* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + const uint8_t* buf = (const uint8_t*) &msg[1]; + ssize_t amount = inode->WriteAt(buf, msg->count, msg->offset); + RespondWrite(svr, chl, amount); + inode->Unref(); +} + +void HandleOpen(int svr, int chl, struct fsm_req_open* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->dirino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->dirino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(svr, chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + Inode* result = inode->Open(path, msg->flags, ExtModeFromHostMode(msg->mode)); + + free(path); + inode->Unref(); + + if ( !result ) { RespondError(svr, chl, errno); return; } + + RespondOpen(svr, chl, result->inode_id, result->Mode() & S_IFMT); + result->Unref(); +} + +void HandleMakeDir(int svr, int chl, struct fsm_req_open* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->dirino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->dirino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(svr, chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + Inode* result = inode->CreateDirectory(path, ExtModeFromHostMode(msg->mode)); + + free(path); + inode->Unref(); + + if ( !result ) { RespondError(svr, chl, errno); return; } + + RespondMakeDir(svr, chl, result->inode_id); + result->Unref(); +} + +void HandleReadDir(int svr, int chl, struct fsm_req_readdirents* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + if ( !S_ISDIR(inode->Mode()) ) + { + inode->Unref(); + RespondError(svr, chl, ENOTDIR); + return; + } + union + { + struct kernel_dirent kernel_entry; + uint8_t padding[sizeof(struct kernel_dirent) + 256]; + }; + memset(&kernel_entry, 0, sizeof(kernel_entry)); + + uint64_t file_size = inode->Size(); + uint64_t offset = 0; + Block* block = NULL; + uint64_t block_id = 0; + while ( offset < file_size ) + { + uint64_t entry_block_id = offset / fs->block_size; + uint64_t entry_block_offset = offset % fs->block_size; + if ( block && block_id != entry_block_id ) + block->Unref(), + block = NULL; + if ( !block && !(block = inode->GetBlock(block_id = entry_block_id)) ) + { + inode->Unref(); + RespondError(svr, chl, errno); + return; + } + const uint8_t* block_data = block->block_data + entry_block_offset; + const struct ext_dirent* entry = (const struct ext_dirent*) block_data; + if ( entry->inode && entry->name_len && !(msg->rec_num--) ) + { + kernel_entry.d_reclen = sizeof(kernel_entry) + entry->name_len; + kernel_entry.d_off = 0; + kernel_entry.d_ino = entry->inode; + kernel_entry.d_dev = 0; + kernel_entry.d_type = 0; // TODO: Support this! + kernel_entry.d_namelen = entry->name_len; + memcpy(kernel_entry.d_name, entry->name, entry->name_len); + size_t dname_offset = offsetof(struct kernel_dirent, d_name); + padding[dname_offset + kernel_entry.d_namelen] = '\0'; + block->Unref(); + inode->Unref(); + RespondReadDir(svr, chl, &kernel_entry); + return; + } + offset += entry->reclen; + } + if ( block ) + block->Unref(); + + kernel_entry.d_reclen = sizeof(kernel_entry); + RespondReadDir(svr, chl, &kernel_entry); +} + +void HandleIsATTY(int svr, int chl, struct fsm_req_isatty* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->ino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->ino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + RespondError(svr, chl, ENOTTY); + inode->Unref(); +} + +void HandleUnlink(int svr, int chl, struct fsm_req_unlink* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->dirino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->dirino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(svr, chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + Inode* result = inode->Unlink(path, false); + free(path); + inode->Unref(); + + if ( !result ) { RespondError(svr, chl, errno); return; } + + result->Unref(); + + RespondSuccess(svr, chl); +} + +void HandleRemoveDir(int svr, int chl, struct fsm_req_unlink* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->dirino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->dirino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(svr, chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + if ( inode->RemoveDirectory(path) ) + RespondSuccess(svr, chl); + else + RespondError(svr, chl, errno); + + free(path); + inode->Unref(); +} + +void HandleLink(int svr, int chl, struct fsm_req_link* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->dirino ) { RespondError(svr, chl, EBADF); return; } + if ( fs->num_inodes <= msg->linkino ) { RespondError(svr, chl, EBADF); return; } + Inode* inode = fs->GetInode((uint32_t) msg->dirino); + if ( !inode ) { RespondError(svr, chl, errno); return; } + Inode* dest = fs->GetInode((uint32_t) msg->linkino); + if ( !dest ) { inode->Unref(); RespondError(svr, chl, errno); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->namelen+1); + if ( !path ) + { + RespondError(svr, chl, errno); + inode->Unref(); + return; + } + memcpy(path, pathraw, msg->namelen); + path[msg->namelen] = '\0'; + + if ( inode->Link(path, dest, false) ) + RespondSuccess(svr, chl); + else + RespondError(svr, chl, errno); + + free(path); + dest->Unref(); + inode->Unref(); +} + +void HandleRename(int svr, int chl, struct fsm_req_rename* msg, Filesystem* fs) +{ + if ( fs->num_inodes <= msg->olddirino ) { RespondError(svr, chl, EBADF); return; } + if ( fs->num_inodes <= msg->newdirino ) { RespondError(svr, chl, EBADF); return; } + + char* pathraw = (char*) &(msg[1]); + char* path = (char*) malloc(msg->oldnamelen+1 + msg->newnamelen+1); + if ( !path ) { RespondError(svr, chl, errno); return; } + memcpy(path, pathraw, msg->oldnamelen); + path[msg->oldnamelen] = '\0'; + memcpy(path + msg->oldnamelen + 1, pathraw + msg->oldnamelen, msg->newnamelen); + path[msg->oldnamelen + 1 + msg->newnamelen] = '\0'; + + const char* oldname = path; + const char* newname = path + msg->oldnamelen + 1; + + Inode* olddir = fs->GetInode((uint32_t) msg->newdirino); + if ( !olddir ) { free(path); RespondError(svr, chl, errno); return; } + Inode* newdir = fs->GetInode((uint32_t) msg->newdirino); + if ( !newdir ) { olddir->Unref(); free(path); RespondError(svr, chl, errno); return; } + + if ( newdir->Rename(olddir, oldname, newname) ) + RespondSuccess(svr, chl); + else + RespondError(svr, chl, errno); + + newdir->Unref(); + olddir->Unref(); + free(path); +} + +void HandleIncomingMessage(int svr, int chl, struct fsm_msg_header* hdr, + Filesystem* fs) +{ + typedef void (*handler_t)(int, int, void*, Filesystem*); + handler_t handlers[FSM_MSG_NUM] = { NULL }; + handlers[FSM_REQ_SYNC] = (handler_t) HandleSync; + handlers[FSM_REQ_STAT] = (handler_t) HandleStat; + handlers[FSM_REQ_CHMOD] = (handler_t) HandleChangeMode; + handlers[FSM_REQ_CHOWN] = (handler_t) HandleChangeOwner; + handlers[FSM_REQ_TRUNCATE] = (handler_t) HandleTruncate; + handlers[FSM_REQ_LSEEK] = (handler_t) HandleSeek; + handlers[FSM_REQ_PREAD] = (handler_t) HandleReadAt; + handlers[FSM_REQ_OPEN] = (handler_t) HandleOpen; + handlers[FSM_REQ_READDIRENTS] = (handler_t) HandleReadDir; + handlers[FSM_REQ_PWRITE] = (handler_t) HandleWriteAt; + handlers[FSM_REQ_ISATTY] = (handler_t) HandleIsATTY; + handlers[FSM_REQ_UTIMENS] = (handler_t) HandleUTimens; + handlers[FSM_REQ_MKDIR] = (handler_t) HandleMakeDir; + handlers[FSM_REQ_RMDIR] = (handler_t) HandleRemoveDir; + handlers[FSM_REQ_UNLINK] = (handler_t) HandleUnlink; + handlers[FSM_REQ_LINK] = (handler_t) HandleLink; + handlers[FSM_REQ_RENAME] = (handler_t) HandleRename; + // TODO: symlink + // TODO: readlink + handlers[FSM_REQ_REFER] = (handler_t) HandleRefer; + handlers[FSM_REQ_UNREF] = (handler_t) HandleUnref; + if ( FSM_MSG_NUM <= hdr->msgtype || !handlers[hdr->msgtype] ) + { + fprintf(stderr, "extfs: message %zu not supported!\n", hdr->msgtype); + RespondError(svr, chl, ENOTSUP); + return; + } + uint8_t* body = (uint8_t*) malloc(hdr->msgsize); + if ( !body ) + { + fprintf(stderr, "extfs: message too large: %zu bytes\n", hdr->msgsize); + RespondError(svr, chl, errno); + return; + } + if ( fsm_recv(svr, chl, body, hdr->msgsize) == (ssize_t) hdr->msgsize ) + handlers[hdr->msgtype](svr, chl, body, fs); + else + { + fprintf(stderr, "extfs: incomplete message: expected %zu bytes\n", hdr->msgsize); + RespondError(svr, chl, errno); + } + free(body); +} + +void AlarmHandler(int) +{ +} + +static volatile bool should_terminate = false; +void TerminationHandler(int) +{ + should_terminate = true; +} + +#endif + +#if defined(__linux__) + +struct ext2_fuse_ctx +{ + Device* dev; + Filesystem* fs; +}; + +#ifndef S_SETABLE +#define S_SETABLE 02777 +#endif + +#define FUSE_FS (((struct ext2_fuse_ctx*) (fuse_get_context()->private_data))->fs) + +void* ext2_fuse_init(struct fuse_conn_info* /*conn*/) +{ + return fuse_get_context()->private_data; +} + +void ext2_fuse_destroy(void* fs_private) +{ + struct ext2_fuse_ctx* ext2_fuse_ctx = (struct ext2_fuse_ctx*) fs_private; + ext2_fuse_ctx->fs->Sync(); + ext2_fuse_ctx->dev->Sync(); + delete ext2_fuse_ctx->fs; ext2_fuse_ctx->fs = NULL; + delete ext2_fuse_ctx->dev; ext2_fuse_ctx->dev = NULL; +} + +Inode* ext2_fuse_resolve_path(const char* path) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode(EXT2_ROOT_INO); + assert(inode); + while ( path[0] ) + { + if ( *path == '/' ) + { + if ( !EXT2_S_ISDIR(inode->Mode()) ) + return errno = ENOTDIR, (Inode*) NULL; + path++; + continue; + } + size_t elem_len = strcspn(path, "/"); + char* elem = new char[elem_len+1]; + memcpy(elem, path, elem_len); + elem[elem_len] = '\0'; + path += elem_len; + Inode* next = inode->Open(elem, O_RDONLY, 0); + delete[] elem; + inode->Unref(); + if ( !next ) + return NULL; + inode = next; + } + return inode; +} + +// Assumes that the path doesn't end with / unless it's the root directory. +Inode* ext2_fuse_parent_dir(const char** path_ptr) +{ + const char* path = *path_ptr; + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode(EXT2_ROOT_INO); + assert(inode); + while ( strchr(path, '/') ) + { + if ( *path == '/' ) + { + if ( !EXT2_S_ISDIR(inode->Mode()) ) + return errno = ENOTDIR, (Inode*) NULL; + path++; + continue; + } + size_t elem_len = strcspn(path, "/"); + char* elem = new char[elem_len+1]; + memcpy(elem, path, elem_len); + elem[elem_len] = '\0'; + path += elem_len; + Inode* next = inode->Open(elem, O_RDONLY, 0); + delete[] elem; + inode->Unref(); + if ( !next ) + return NULL; + inode = next; + } + *path_ptr = *path ? path : "."; + assert(!strchr(*path_ptr, '/')); + return inode; +} + +int ext2_fuse_getattr(const char* path, struct stat* st) +{ + Inode* inode = ext2_fuse_resolve_path(path); + if ( !inode ) + return -errno; + StatInode(inode, st); + inode->Unref(); + return 0; +} + +int ext2_fuse_fgetattr(const char* /*path*/, struct stat* st, + struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + StatInode(inode, st); + inode->Unref(); + return 0; +} + +int ext2_fuse_readlink(const char* path, char* buf, size_t bufsize) +{ + (void) path; + (void) buf; + (void) bufsize; + return -(errno = ENOSYS); +} + +int ext2_fuse_mknod(const char* path, mode_t mode, dev_t dev) +{ + (void) path; + (void) mode; + (void) dev; + return -(errno = ENOSYS); +} + +int ext2_fuse_mkdir(const char* path, mode_t mode) +{ + Inode* inode = ext2_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + Inode* newdir = inode->CreateDirectory(path, ExtModeFromHostMode(mode)); + inode->Unref(); + if ( !newdir ) + return -errno; + newdir->Unref(); + return 0; +} + +int ext2_fuse_unlink(const char* path) +{ + Inode* inode = ext2_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + Inode* result = inode->Unlink(path, false); + inode->Unref(); + if ( !result ) + return -errno; + result->Unref(); + return 0; +} + +int ext2_fuse_rmdir(const char* path) +{ + Inode* inode = ext2_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + bool success = inode->RemoveDirectory(path); + inode->Unref(); + return success ? 0 : -errno; +} + +int ext2_fuse_symlink(const char* oldname, const char* newname) +{ + (void) oldname; + (void) newname; + return -(errno = ENOSYS); +} + +int ext2_fuse_rename(const char* oldname, const char* newname) +{ + Inode* olddir = ext2_fuse_parent_dir(&oldname); + if ( !olddir ) + return -errno; + Inode* newdir = ext2_fuse_parent_dir(&newname); + if ( !newdir ) + return olddir->Unref(), -errno; + bool success = newdir->Rename(olddir, oldname, newname); + newdir->Unref(); + olddir->Unref(); + return success ? 0 : -errno; +} + +int ext2_fuse_link(const char* oldname, const char* newname) +{ + Inode* inode = ext2_fuse_resolve_path(oldname); + if ( !inode ) + return -errno; + Inode* newdir = ext2_fuse_parent_dir(&newname); + if ( !newdir ) + return inode->Unref(), -errno; + bool success = inode->Link(newname, inode, false); + newdir->Unref(); + inode->Unref(); + return success ? 0 : -errno; +} + +int ext2_fuse_chmod(const char* path, mode_t mode) +{ + Inode* inode = ext2_fuse_resolve_path(path); + if ( !inode ) + return -errno; + uint32_t req_mode = ExtModeFromHostMode(mode); + uint32_t old_mode = inode->Mode(); + uint32_t new_mode = (old_mode & ~S_SETABLE) | (req_mode & S_SETABLE); + inode->SetMode(new_mode); + inode->Unref(); + return 0; +} + +int ext2_fuse_chown(const char* path, uid_t owner, gid_t group) +{ + Inode* inode = ext2_fuse_resolve_path(path); + if ( !inode ) + return -errno; + inode->SetUserId((uint32_t) owner); + inode->SetGroupId((uint32_t) group); + inode->Unref(); + return 0; +} + +int ext2_fuse_truncate(const char* path, off_t size) +{ + Inode* inode = ext2_fuse_resolve_path(path); + if ( !inode ) + return -errno; + inode->Truncate((uint64_t) size); + inode->Unref(); + return 0; +} + +int ext2_fuse_ftruncate(const char* /*path*/, off_t size, + struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + inode->Truncate((uint64_t) size); + inode->Unref(); + return 0; +} + +int ext2_fuse_open(const char* path, struct fuse_file_info* fi) +{ + int flags = fi->flags; + Inode* dir = ext2_fuse_parent_dir(&path); + if ( !dir ) + return -errno; + Inode* result = dir->Open(path, flags, 0); + dir->Unref(); + if ( !result ) + return -errno; + fi->fh = (uint64_t) result->inode_id; + fi->keep_cache = 1; + result->RemoteRefer(); + result->Unref(); + return 0; +} + +int ext2_fuse_access(const char* path, int mode) +{ + Inode* dir = ext2_fuse_parent_dir(&path); + if ( !dir ) + return -errno; + Inode* result = dir->Open(path, O_RDONLY, 0); + dir->Unref(); + if ( !result ) + return -errno; + (void) mode; + result->Unref(); + return 0; +} + +int ext2_fuse_create(const char* path, mode_t mode, struct fuse_file_info* fi) +{ + int flags = fi->flags | O_CREAT; + Inode* inode = ext2_fuse_parent_dir(&path); + if ( !inode ) + return -errno; + Inode* result = inode->Open(path, flags, ExtModeFromHostMode(mode)); + inode->Unref(); + if ( !result ) + return -errno; + fi->fh = (uint64_t) result->inode_id; + fi->keep_cache = 1; + result->Unref(); + return 0; +} + +int ext2_fuse_opendir(const char* path, struct fuse_file_info* fi) +{ + return ext2_fuse_open(path, fi); +} + +int ext2_fuse_read(const char* /*path*/, char* buf, size_t count, off_t offset, + struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + if ( INT_MAX < count ) + count = INT_MAX; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + ssize_t result = inode->ReadAt((uint8_t*) buf, count, offset); + inode->Unref(); + return 0 <= result ? (int) result : -errno; +} + +int ext2_fuse_write(const char* /*path*/, const char* buf, size_t count, + off_t offset, struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + if ( INT_MAX < count ) + count = INT_MAX; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + ssize_t result = inode->WriteAt((const uint8_t*) buf, count, offset); + inode->Unref(); + return 0 <= result ? (int) result : -errno; +} + +int ext2_fuse_statfs(const char* /*path*/, struct statvfs* stvfs) +{ + (void) stvfs; + return errno = -ENOSYS, -1; +} + +int ext2_fuse_flush(const char* /*path*/, struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + inode->Sync(); + inode->Unref(); + return 0; +} + +int ext2_fuse_release(const char* /*path*/, struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + inode->RemoteUnref(); + inode->Unref(); + return 0; +} + +int ext2_fuse_releasedir(const char* path, struct fuse_file_info* fi) +{ + return ext2_fuse_release(path, fi); +} + +int ext2_fuse_fsync(const char* /*path*/, int data, struct fuse_file_info* fi) +{ + (void) data; + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + inode->Sync(); + inode->Unref(); + return 0; +} + +/*int ext2_fuse_syncdir(const char* path, int data, struct fuse_file_info* fi) +{ + return ext2_fuse_sync(path, data, fi); +}*/ + +/*int ext2_fuse_setxattr(const char *, const char *, const char *, size_t, int) +{ + return -(errno = ENOSYS); +}*/ + +/*int ext2_fuse_getxattr(const char *, const char *, char *, size_t) +{ + return -(errno = ENOSYS); +}*/ + +/*int ext2_fuse_listxattr(const char *, char *, size_t) +{ + return -(errno = ENOSYS); +}*/ + +/*int ext2_fuse_removexattr(const char *, const char *) +{ + return -(errno = ENOSYS); +}*/ + +int ext2_fuse_readdir(const char* /*path*/, void* buf, fuse_fill_dir_t filler, + off_t rec_num, struct fuse_file_info* fi) +{ + Filesystem* fs = FUSE_FS; + Inode* inode = fs->GetInode((uint32_t) fi->fh); + if ( !inode ) + return -errno; + if ( !S_ISDIR(inode->Mode()) ) + return inode->Unref(), -(errno = ENOTDIR); + + uint64_t file_size = inode->Size(); + uint64_t offset = 0; + Block* block = NULL; + uint64_t block_id = 0; + while ( offset < file_size ) + { + uint64_t entry_block_id = offset / fs->block_size; + uint64_t entry_block_offset = offset % fs->block_size; + if ( block && block_id != entry_block_id ) + block->Unref(), + block = NULL; + if ( !block && !(block = inode->GetBlock(block_id = entry_block_id)) ) + return inode->Unref(), -errno; + const uint8_t* block_data = block->block_data + entry_block_offset; + const struct ext_dirent* entry = (const struct ext_dirent*) block_data; + if ( entry->inode && entry->name_len && (!rec_num || !rec_num--) ) + { + char* entry_name = new char[entry->name_len+1]; + memcpy(entry_name, entry->name, entry->name_len); + entry_name[entry->name_len] = '\0'; + bool full = filler(buf, entry_name, NULL, 0); + delete[] entry_name; + if ( full ) + { + block->Unref(); + inode->Unref(); + return 0; + } + } + offset += entry->reclen; + } + if ( block ) + block->Unref(); + + inode->Sync(); + inode->Unref(); + return 0; +} + +/*int ext2_fuse_lock(const char*, struct fuse_file_info*, int, struct flock*) +{ + return -(errno = ENOSYS); +}*/ + +int ext2_fuse_utimens(const char* path, const struct timespec tv[2]) +{ + Inode* inode = ext2_fuse_resolve_path(path); + if ( !inode ) + return -errno; + inode->data->i_atime = tv[0].tv_sec; + inode->data->i_mtime = tv[1].tv_sec; + inode->Dirty(); + inode->Unref(); + return 0; +} + +/*int ext2_fuse_bmap(const char*, size_t blocksize, uint64_t* idx) +{ + return -(errno = ENOSYS); +}*/ + +#endif + +void CompactArguments(int* argc, char*** argv) +{ + for ( int i = 0; i < *argc; i++ ) + while ( i < *argc && !(*argv)[i] ) + { + for ( int n = i; n < *argc; n++ ) + (*argv)[n] = (*argv)[n+1]; + (*argc)--; + } +} + +void Usage(FILE* fp, const char* argv0) +{ + fprintf(fp, "Usage: %s [--probe] [--test-uuid UUID] DEVICE [MOUNT-POINT]\n", argv0); +} + +void Help(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +void Version(FILE* fp, const char* argv0) +{ + Usage(fp, argv0); +} + +int main(int argc, char* argv[]) +{ + const char* argv0 = argv[0]; + const char* test_uuid = NULL; + bool foreground = false; + bool probe = false; + bool read = false; + bool write = false; + for ( int i = 1; i < argc; i++ ) + { + const char* arg = argv[i]; + if ( arg[0] != '-' ) + continue; + argv[i] = NULL; + if ( !strcmp(arg, "--") ) + break; + if ( arg[1] != '-' ) + { + while ( char c = *++arg ) switch ( c ) + { + case 'r': read = true; break; + case 'w': write = true; break; + default: + fprintf(stderr, "%s: unknown option -- '%c'\n", argv0, c); + Usage(stderr, argv0); + exit(1); + } + } + else if ( !strcmp(arg, "--help") ) { Help(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--usage") ) { Usage(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--version") ) { Version(stdout, argv0); exit(0); } + else if ( !strcmp(arg, "--foreground") ) + foreground = true; + else if ( !strcmp(arg, "--probe") ) + probe = true; + else if ( !strcmp(arg, "--read") ) + read = true; + else if ( !strcmp(arg, "--write") ) + write = true; + else if ( !strcmp(arg, "--test-uuid") ) + { + if ( i+1 == argc ) + { + fprintf(stderr, "%s: --test-uuid: Missing operand\n", argv0); + exit(1); + } + test_uuid = argv[++i], argv[i] = NULL; + } + else + { + fprintf(stderr, "%s: unknown option: %s\n", argv0, arg); + Usage(stderr, argv0); + exit(1); + } + } + + // It doesn't make sense to have a write-only filesystem. + read = read || write; + + // Default to read and write filesystem access. + bool default_access = !read && !write ? read = write = true : false; + + if ( argc == 1 ) + { + Usage(stderr, argv0); + exit(0); + } + + CompactArguments(&argc, &argv); + + const char* device_path = 2 <= argc ? argv[1] : NULL; + const char* mount_path = 2 <= argc ? argv[2] : NULL; + + if ( !device_path ) + { + Usage(stderr, argv0); + exit(1); + } + + int fd = open(device_path, write ? O_RDWR : O_RDONLY); + if ( fd < 0 ) + error(1, errno, "`%s'", device_path); + + // Read the super block from the filesystem so we can verify it. + struct ext_superblock sb; + if ( preadall(fd, &sb, sizeof(sb), 1024) != sizeof(sb) ) + { + if ( probe ) + exit(1); + else + error(1, errno, "read: `%s'", device_path); + } + + // Verify the magic value to detect a compatible filesystem. + if ( !probe && sb.s_magic != EXT2_SUPER_MAGIC ) + error(1, 0, "`%s' isn't a valid extended filesysten", device_path); + + if ( probe && sb.s_magic != EXT2_SUPER_MAGIC ) + exit(1); + + // Test whether this was the filesystem the user was looking for. + if ( test_uuid ) + { + // TODO: Test uuid! + } + + // Test whether this revision of the extened filesystem is supported. + if ( probe && sb.s_rev_level == EXT2_GOOD_OLD_REV ) + exit(1); + + if ( !probe && sb.s_rev_level == EXT2_GOOD_OLD_REV ) + error(1, 0, "`%s' is formatted with an obsolete filesystem revision", + device_path); + + // Verify that no incompatible features are in use. + if ( probe && sb.s_feature_compat & ~EXT2_FEATURE_INCOMPAT_SUPPORTED ) + exit(1); + + if ( !probe && sb.s_feature_incompat & ~EXT2_FEATURE_INCOMPAT_SUPPORTED ) + error(1, 0, "`%s' uses unsupported and incompatible features", + device_path); + + // Verify that no incompatible features are in use if opening for write. + if ( probe && default_access && write && + sb.s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED ) + exit(1); + + if ( !probe && default_access && write && + sb.s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED ) + error(1, 0, "`%s uses unsupported and incompatible features, " + "read-only access is possible, but write-access was " + "requested", device_path); + + if ( write && sb.s_feature_ro_compat & ~EXT2_FEATURE_RO_COMPAT_SUPPORTED ) + { + if ( !probe ) + fprintf(stderr, "Warning: `%s' uses unsupported and incompatible " + "features, falling back to read-only access\n", + device_path); + // TODO: Modify the file descriptor such that writing fails! + read = false; + } + + // Check whether any features are in use that we can safely disregard. + if ( !probe && sb.s_feature_compat & ~EXT2_FEATURE_COMPAT_SUPPORTED ) + fprintf(stderr, "Note: filesystem uses unsupported but compatible " + "features\n"); + + // We have found no critical problems, so let the caller know that this + // filesystem satisfies the probe request. + if ( probe ) + exit(0); + + // Check whether the filesystem was unmounted cleanly. + if ( !probe && sb.s_state != EXT2_VALID_FS ) + fprintf(stderr, "Warning: `%s' wasn't unmounted cleanly\n", + device_path); + + uint32_t block_size = 1024U << sb.s_log_block_size; + + Device* dev = new Device(fd, block_size, write); + Filesystem* fs = new Filesystem(dev); + + fs->block_groups = new BlockGroup*[fs->num_groups]; + for ( size_t i = 0; i < fs->num_groups; i++ ) + fs->block_groups[i] = NULL; + +#if defined(__sortix__) + if ( !mount_path ) + return 0; + + // Open the mount point. + int mountfd = open(mount_path, O_RDWR | O_DIRECTORY); + if ( mountfd < 0 ) + error(1, errno, "%s", mount_path); + + // Create a filesystem server connected to the kernel that we'll listen on. + int serverfd = fsm_mkserver(); + if ( serverfd < 0 ) + error(1, errno, "fsm_mkserver"); + + // Create a file descriptor for our root directory. The kernel won't send + // a message to this file descriptor so we can mount it before starting to + // listen for messages. + int rootfd = fsm_bootstraprootfd(serverfd, EXT2_ROOT_INO, O_RDWR | O_CREAT, + S_IFDIR); + if ( rootfd < 0 ) + error(1, errno, "fsm_bootstraprootfd"); + + if ( fsm_fsbind(rootfd, mountfd, 0) < 0 ) + error(1, errno, "fsm_fsbind"); + + close(mountfd); + + // Make sure the server isn't unexpectedly killed and data is lost. + signal(SIGINT, TerminationHandler); + signal(SIGTERM, TerminationHandler); + signal(SIGQUIT, TerminationHandler); + + // Become a background process in its own process group by default. + if ( !foreground ) + { + if ( fork() ) + exit(0); + setpgid(0, 0); + } + + // Listen for filesystem messages and sync the filesystem every few seconds. + struct timespec last_sync_at; + clock_gettime(CLOCK_MONOTONIC, &last_sync_at); + int channel; + while ( 0 <= (channel = fsm_listen(serverfd)) ) + { + if ( should_terminate ) + break; + struct fsm_msg_header hdr; + ssize_t amount; + if ( (amount = fsm_recv(serverfd, channel, &hdr, sizeof(hdr))) != sizeof(hdr) ) + { + error(0, errno, "got %zi bytes, expected %zu", amount, sizeof(hdr)); + break; + } + HandleIncomingMessage(serverfd, channel, &hdr, fs); + fsm_closechannel(serverfd, channel); + + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + + if ( write && 5 <= timespec_sub(now, last_sync_at).tv_sec) + { + fs->Sync(); + last_sync_at = now; + } + } + + // Sync the filesystem before shutting down. + if ( write ) + { + fprintf(stderr, "%s: filesystem server shutting down, syncing...", argv0); + fflush(stderr); + fs->Sync(); + fprintf(stderr, " done.\n"); + } + + fsm_closeserver(serverfd); + +#elif defined(__linux__) + + (void) foreground; + + struct fuse_operations operations; + memset(&operations, 0, sizeof(operations)); + + operations.access = ext2_fuse_access; + operations.chmod = ext2_fuse_chmod; + operations.chown = ext2_fuse_chown; + operations.create = ext2_fuse_create; + operations.destroy = ext2_fuse_destroy; + operations.fgetattr = ext2_fuse_fgetattr; + operations.flush = ext2_fuse_flush; + operations.fsync = ext2_fuse_fsync; + operations.ftruncate = ext2_fuse_ftruncate; + operations.getattr = ext2_fuse_getattr; + operations.init = ext2_fuse_init; + operations.link = ext2_fuse_link; + operations.mkdir = ext2_fuse_mkdir; + operations.mknod = ext2_fuse_mknod; + operations.opendir = ext2_fuse_opendir; + operations.open = ext2_fuse_open; + operations.readdir = ext2_fuse_readdir; + operations.read = ext2_fuse_read; + operations.readlink = ext2_fuse_readlink; + operations.releasedir = ext2_fuse_releasedir; + operations.release = ext2_fuse_release; + operations.rename = ext2_fuse_rename; + operations.rmdir = ext2_fuse_rmdir; + operations.statfs = ext2_fuse_statfs; + operations.symlink = ext2_fuse_symlink; + operations.truncate = ext2_fuse_truncate; + operations.unlink = ext2_fuse_unlink; + operations.utimens = ext2_fuse_utimens; + operations.write = ext2_fuse_write; + + operations.flag_nullpath_ok = 1; + operations.flag_nopath = 1; + + char* argv_fuse[] = + { + (char*) argv[0], + (char*) "-s", + (char*) mount_path, + (char*) NULL, + }; + + int argc_fuse = sizeof(argv_fuse) / sizeof(argv_fuse[0]) - 1; + + struct ext2_fuse_ctx ext2_fuse_ctx; + ext2_fuse_ctx.fs = fs; + ext2_fuse_ctx.dev = dev; + + return fuse_main(argc_fuse, argv_fuse, &operations, &ext2_fuse_ctx); + +#else + + (void) foreground; + (void) mount_path; + +#endif + + delete fs; + delete dev; + + close(fd); + + return 0; +} diff --git a/ext/filesystem.cpp b/ext/filesystem.cpp new file mode 100644 index 00000000..b53f28e5 --- /dev/null +++ b/ext/filesystem.cpp @@ -0,0 +1,214 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + filesystem.cpp + Filesystem. + +*******************************************************************************/ + +#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) +{ + uint64_t sb_offset = 1024; + uint32_t sb_block_id = sb_offset / device->block_size; + sb_block = device->GetBlock(sb_block_id); + sb = (struct ext_superblock*) + (sb_block->block_data + sb_offset % device->block_size); + this->device = device; + block_groups = NULL; + block_size = device->block_size; + mru_inode = NULL; + lru_inode = NULL; + inode_size = this->sb->s_inode_size; + num_blocks = sb->s_blocks_count; + num_groups = divup(this->sb->s_blocks_count, this->sb->s_blocks_per_group); + num_inodes = this->sb->s_inodes_count; + dirty = false; + + struct timespec now_realtime, now_monotonic; + clock_gettime(CLOCK_REALTIME, &now_realtime); + clock_gettime(CLOCK_MONOTONIC, &now_monotonic); + mtime_realtime = now_realtime.tv_sec; + mtime_monotonic = now_monotonic.tv_sec; + sb->s_mtime = mtime_realtime; + sb->s_mnt_count++; + sb->s_state = EXT2_ERROR_FS; + Dirty(); + 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; + sb->s_state = EXT2_VALID_FS; + Dirty(); + Sync(); + sb_block->Unref(); +} + +void Filesystem::Dirty() +{ + dirty = true; + sb_block->Dirty(); +} + +void Filesystem::Sync() +{ + for ( Inode* iter = mru_inode; iter; iter = iter->next_inode ) + iter->Sync(); + 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]; + BlockGroup* group = new BlockGroup(this, 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); + 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) +{ + assert(inode_id); + assert(inode_id < num_inodes); + + for ( Inode* iter = mru_inode; iter; iter = iter->next_inode ) + if ( iter->inode_id == inode_id ) + return iter->Refer(), iter; + + Inode* inode = new Inode(this, inode_id); + + 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); + 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); + 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; + for ( uint32_t group_id = 0; group_id < num_groups; group_id++ ) + if ( uint32_t block_id = GetBlockGroup(group_id)->AllocateBlock() ) + return block_id; + sb->s_free_blocks_count--; + Dirty(); + 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; + for ( uint32_t group_id = 0; group_id < num_groups; group_id++ ) + if ( uint32_t inode_id = GetBlockGroup(group_id)->AllocateInode() ) + return inode_id; + sb->s_free_inodes_count--; + Dirty(); + return errno = ENOSPC, 0; +} + +void Filesystem::FreeBlock(uint32_t 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); + group->FreeBlock(block_id); + group->Unref(); +} + +void Filesystem::FreeInode(uint32_t 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); + group->FreeInode(inode_id); + group->Unref(); +} diff --git a/ext/filesystem.h b/ext/filesystem.h new file mode 100644 index 00000000..13e3abd0 --- /dev/null +++ b/ext/filesystem.h @@ -0,0 +1,64 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + filesystem.h + Filesystem. + +*******************************************************************************/ + +#ifndef FILESYSTEM_H +#define FILESYSTEM_H + +class BlockGroup; +class Device; +class Inode; + +class Filesystem +{ +public: + Filesystem(Device* device); + ~Filesystem(); + +public: + struct ext_superblock* sb; + Block* sb_block; + Device* device; + BlockGroup** block_groups; + uint32_t block_size; + uint32_t inode_size; + uint32_t num_blocks; + uint32_t num_groups; + uint32_t num_inodes; + Inode* mru_inode; + Inode* lru_inode; + time_t mtime_realtime; + time_t mtime_monotonic; + bool dirty; + +public: + BlockGroup* GetBlockGroup(uint32_t group_id); + Inode* GetInode(uint32_t inode_id); + uint32_t AllocateBlock(BlockGroup* preferred = NULL); + uint32_t AllocateInode(BlockGroup* preferred = NULL); + void FreeBlock(uint32_t block_id); + void FreeInode(uint32_t inode_id); + void Dirty(); + void Sync(); + +}; + +#endif diff --git a/ext/inode.cpp b/ext/inode.cpp new file mode 100644 index 00000000..e5634536 --- /dev/null +++ b/ext/inode.cpp @@ -0,0 +1,946 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + inode.cpp + Filesystem inode. + +*******************************************************************************/ + +#define __STDC_CONSTANT_MACROS +#define __STDC_LIMIT_MACROS + +#include + +#include +#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" + +#ifndef S_SETABLE +#define S_SETABLE 02777 +#endif + +Inode::Inode(Filesystem* filesystem, uint32_t inode_id) +{ + this->prev_inode = NULL; + this->next_inode = NULL; + this->filesystem = filesystem; + this->reference_count = 1; + this->inode_id = inode_id; + this->dirty = false; +} + +Inode::~Inode() +{ + Sync(); + if ( data_block ) + data_block->Unref(); + Unlink(); +} + +uint32_t Inode::Mode() +{ + // TODO: Use i_mode_high. + return data->i_mode; +} + +void Inode::SetMode(uint32_t mode) +{ + // TODO: Use i_mode_high. + data->i_mode = (uint16_t) mode; + Dirty(); +} + +uint32_t Inode::UserId() +{ + // TODO: Use i_uid_high. + return data->i_uid; +} + +void Inode::SetUserId(uint32_t user) +{ + // TODO: Use i_uid_high. + data->i_uid = (uint16_t) user; + Dirty(); +} + +uint32_t Inode::GroupId() +{ + // TODO: Use i_gid_high. + return data->i_gid; +} + +void Inode::SetGroupId(uint32_t group) +{ + // TODO: Use i_gid_high. + data->i_gid = (uint16_t) group; + Dirty(); +} + +uint64_t Inode::Size() +{ + bool largefile = filesystem->sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_LARGE_FILE; + if ( !EXT2_S_ISREG(data->i_mode) || !largefile ) + return data->i_size; + uint64_t lower = data->i_size; + uint64_t upper = data->i_dir_acl; + uint64_t size = lower | (upper << 32ULL); + return size; +} + +void Inode::SetSize(uint64_t new_size) +{ + bool largefile = filesystem->sb->s_feature_ro_compat & + EXT2_FEATURE_RO_COMPAT_LARGE_FILE; + uint32_t lower = new_size & 0xFFFFFFFF; + uint32_t upper = new_size >> 32; + // TODO: Enforce filesize limit with no largefile support or non-files. + data->i_size = lower; + + // TODO: Figure out these i_blocks semantics and how stuff is reserved. + const uint64_t ENTRIES = filesystem->block_size / sizeof(uint32_t); + uint64_t block_direct = sizeof(data->i_block) / sizeof(uint32_t) - 3; + uint64_t block_singly = ENTRIES; + uint64_t block_doubly = block_singly * block_singly; + uint64_t max_direct = block_direct; + uint64_t max_singly = max_direct + block_singly; + uint64_t max_doubly = max_singly + block_doubly; + uint64_t logical_blocks = divup(new_size, (uint64_t) filesystem->block_size); + uint64_t actual_blocks = logical_blocks; + if ( max_direct <= logical_blocks ) + actual_blocks += divup(logical_blocks - max_direct, ENTRIES); + if ( max_singly <= logical_blocks ) + actual_blocks += divup(logical_blocks - max_singly, ENTRIES * ENTRIES); + if ( max_doubly <= logical_blocks ) + actual_blocks += divup(logical_blocks - max_doubly, ENTRIES * ENTRIES * ENTRIES); + data->i_blocks = (actual_blocks * filesystem->block_size) / 512; + + if ( EXT2_S_ISREG(data->i_mode) && largefile ) + data->i_dir_acl = upper; + Dirty(); +} + +void Inode::Linked() +{ + data->i_links_count++; + Dirty(); +} + +void Inode::Unlinked() +{ + data->i_links_count--; + Dirty(); +} + +Block* Inode::GetBlockFromTable(Block* table, uint32_t index) +{ + if ( uint32_t block_id = ((uint32_t*) table->block_data)[index] ) + return filesystem->device->GetBlock(block_id); + uint32_t group_id = (inode_id - 1) / filesystem->sb->s_inodes_per_group; + assert(group_id < filesystem->num_groups); + BlockGroup* block_group = filesystem->GetBlockGroup(group_id); + uint32_t block_id = filesystem->AllocateBlock(block_group); + block_group->Unref(); + if ( block_id ) + { + Block* block = filesystem->device->GetBlockZeroed(block_id); + ((uint32_t*) table->block_data)[index] = block_id; + table->Dirty(); + return block; + } + return NULL; +} + +Block* Inode::GetBlock(uint64_t offset) +{ + const uint64_t ENTRIES = filesystem->block_size / sizeof(uint32_t); + uint64_t block_direct = sizeof(data->i_block) / sizeof(uint32_t) - 3; + uint64_t block_singly = ENTRIES; + uint64_t block_doubly = block_singly * block_singly; + uint64_t block_triply = block_doubly * block_singly; + uint64_t max_direct = block_direct; + uint64_t max_singly = max_direct + block_singly; + uint64_t max_doubly = max_singly + block_doubly; + uint64_t max_triply = max_doubly + block_triply; + uint32_t index; + + Block* table = data_block; table->Refer(); + Block* block; + + uint32_t inode_offset = (uintptr_t) data - (uintptr_t) data_block->block_data; + uint32_t table_offset = offsetof(struct ext_inode, i_block); + uint32_t inode_block_table_offset = (inode_offset + table_offset) / sizeof(uint32_t); + + // TODO: It would seem that somebody needs a lesson in induction. :-) + if ( offset < max_direct ) + { + offset -= 0; + offset += inode_block_table_offset * 1; + read_direct: + index = offset; + offset %= 1; + block = GetBlockFromTable(table, index); + table->Unref(); + if ( !block ) + return NULL; + return block; + } + else if ( offset < max_singly ) + { + offset -= max_direct; + offset += (inode_block_table_offset + 12) * ENTRIES; + read_singly: + index = offset / ENTRIES; + offset = offset % ENTRIES; + block = GetBlockFromTable(table, index); + table->Unref(); + if ( !block ) + return NULL; + table = block; + goto read_direct; + } + else if ( offset < max_doubly ) + { + offset -= max_singly; + offset += (inode_block_table_offset + 13) * ENTRIES * ENTRIES; + read_doubly: + index = offset / (ENTRIES * ENTRIES); + offset = offset % (ENTRIES * ENTRIES); + block = GetBlockFromTable(table, index); + table->Unref(); + if ( !block ) + return NULL; + table = block; + goto read_singly; + } + else if ( offset < max_triply ) + { + offset -= max_doubly; + offset += (inode_block_table_offset + 14) * ENTRIES * ENTRIES * ENTRIES; + /*read_triply:*/ + index = offset / (ENTRIES * ENTRIES * ENTRIES); + offset = offset % (ENTRIES * ENTRIES * ENTRIES); + block = GetBlockFromTable(table, index); + table->Unref(); + if ( !block ) + return NULL; + table = block; + goto read_doubly; + } + + return NULL; +} + +bool Inode::FreeIndirect(uint64_t from, uint64_t offset, uint32_t block_id, + int indirection, uint64_t entry_span) +{ + const uint64_t ENTRIES = filesystem->block_size / sizeof(uint32_t); + Block* block = filesystem->device->GetBlock(block_id); + uint32_t* table = (uint32_t*) block->block_data; + bool any_children = false; + for ( uint64_t i = 0; i < ENTRIES; i++ ) + { + if ( !table[i] ) + continue; + uint64_t entry_offset = offset + entry_span * i; + if ( entry_offset < from || + (indirection && + FreeIndirect(from, entry_offset, table[i], indirection-1, + entry_span / ENTRIES)) ) + { + any_children = true; + continue; + } + filesystem->FreeBlock(table[i]); + table[i] = 0; + block->Dirty(); + } + return any_children; +} + +void Inode::Truncate(uint64_t new_size) +{ + // TODO: Enforce a filesize limit! + uint64_t old_size = Size(); + SetSize(new_size); + if ( old_size <= new_size ) + return; + + uint64_t old_num_blocks = divup(old_size, (uint64_t) filesystem->block_size); + uint64_t new_num_blocks = divup(new_size, (uint64_t) filesystem->block_size); + + // Zero out the rest of the last partial block, if any. + uint32_t partial = new_size % filesystem->block_size; + if ( partial ) + { + Block* partial_block = GetBlock(new_num_blocks-1); + uint8_t* data = partial_block->block_data; + memset(data + partial, 0, filesystem->block_size - partial); + partial_block->Dirty(); + } + + const uint64_t ENTRIES = filesystem->block_size / sizeof(uint32_t); + uint64_t block_direct = sizeof(data->i_block) / sizeof(uint32_t) - 3; + uint64_t block_singly = ENTRIES; + uint64_t block_doubly = block_singly * block_singly; + uint64_t block_triply = block_doubly * block_singly; + uint64_t max_direct = block_direct; + uint64_t max_singly = max_direct + block_singly; + uint64_t max_doubly = max_singly + block_doubly; + uint64_t max_triply = max_doubly + block_triply; + + for ( uint64_t i = new_num_blocks; i < old_num_blocks && i < 12; i++ ) + { + filesystem->FreeBlock(data->i_block[i]); + data->i_block[i] = 0; + } + + if ( data->i_block[12] && !FreeIndirect(new_num_blocks, max_direct, data->i_block[12], 0, 1) ) + { + filesystem->FreeBlock(data->i_block[12]); + data->i_block[12] = 0; + } + + if ( data->i_block[13] && !FreeIndirect(new_num_blocks, max_singly, data->i_block[13], 1, ENTRIES) ) + { + filesystem->FreeBlock(data->i_block[13]); + data->i_block[13] = 0; + } + + if ( data->i_block[14] && !FreeIndirect(new_num_blocks, max_doubly, data->i_block[14], 2, ENTRIES * ENTRIES) ) + { + filesystem->FreeBlock(data->i_block[14]); + data->i_block[14] = 0; + } + + (void) max_triply; + + Dirty(); +} + +Inode* Inode::Open(const char* elem, int flags, mode_t mode) +{ + if ( !EXT2_S_ISDIR(Mode()) ) + return errno = ENOTDIR, (Inode*) NULL; + size_t elem_length = strlen(elem); + uint64_t filesize = Size(); + uint64_t offset = 0; + Block* block = NULL; + uint64_t block_id = 0; + while ( offset < filesize ) + { + uint64_t entry_block_id = offset / filesystem->block_size; + uint64_t entry_block_offset = offset % filesystem->block_size; + if ( block && block_id != entry_block_id ) + block->Unref(), + block = NULL; + if ( !block && !(block = GetBlock(block_id = entry_block_id)) ) + return NULL; + const uint8_t* block_data = block->block_data + entry_block_offset; + const struct ext_dirent* entry = (const struct ext_dirent*) block_data; + if ( entry->name_len == elem_length && + memcmp(elem, entry->name, elem_length) == 0 && + entry->inode ) + { + uint8_t file_type = entry->file_type; + uint32_t inode_id = entry->inode; + assert(inode_id); + block->Unref(); + if ( flags & O_EXCL ) + return errno = EEXIST, (Inode*) NULL; + if ( flags & O_DIRECTORY && file_type && file_type != EXT2_FT_DIR ) + return errno = EEXIST, (Inode*) NULL; + Inode* inode = filesystem->GetInode(inode_id); + if ( flags & O_DIRECTORY && !EXT2_S_ISDIR(inode->Mode()) ) + { + inode->Unref(); + return errno = EEXIST, (Inode*) NULL; + } + if ( S_ISREG(inode->Mode()) && flags & O_TRUNC ) + inode->Truncate(0); + return inode; + } + offset += entry->reclen; + } + if ( block ) + block->Unref(); + if ( flags & O_CREAT ) + { + // TODO: Preferred block group! + uint32_t result_inode_id = filesystem->AllocateInode(); + if ( !result_inode_id ) + return NULL; + Inode* result = filesystem->GetInode(result_inode_id); + memset(result->data, 0, sizeof(*result->data)); + result->SetMode((mode & S_SETABLE) | S_IFREG); + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + result->data->i_atime = now.tv_sec; + result->data->i_ctime = now.tv_sec; + result->data->i_mtime = now.tv_sec; + // TODO: Set all the other inode properties! + if ( !Link(elem, result, false) ) + { + memset(result->data, 0, sizeof(*result->data)); + // TODO: dtime + result->Unref(); + filesystem->FreeInode(result_inode_id); + return NULL; + } + return result; + } + return errno = ENOENT, (Inode*) NULL; +} + +bool Inode::Link(const char* elem, Inode* dest, bool directories) +{ + // TODO: Check if dest has checked the link limit! + + if ( !EXT2_S_ISDIR(Mode()) ) + return errno = ENOTDIR, false; + if ( directories && !EXT2_S_ISDIR(dest->Mode()) ) + return errno = ENOTDIR, false; + if ( !directories && EXT2_S_ISDIR(dest->Mode()) ) + return errno = EISDIR, false; + + // Search for a hole in which we can store the new directory entry and stop + // if we meet an existing link with the requested name. + size_t elem_length = strlen(elem); + size_t new_entry_size = roundup(sizeof(struct ext_dirent) + elem_length, (size_t) 4); + uint64_t filesize = Size(); + uint64_t offset = 0; + Block* block = NULL; + uint64_t block_id = 0; + bool found_hole = false; + bool splitting = false; + uint64_t hole_block_id = 0; + uint64_t hole_block_offset = 0; + while ( offset < filesize ) + { + uint64_t entry_block_id = offset / filesystem->block_size; + uint64_t entry_block_offset = offset % filesystem->block_size; + if ( block && block_id != entry_block_id ) + block->Unref(), + block = NULL; + if ( !block && !(block = GetBlock(block_id = entry_block_id)) ) + return NULL; + const uint8_t* block_data = block->block_data + entry_block_offset; + const struct ext_dirent* entry = (const struct ext_dirent*) block_data; + if ( entry->name_len == elem_length && + memcmp(elem, entry->name, elem_length) == 0 && + entry->inode ) + { + block->Unref(); + return errno = EEXIST, false; + } + if ( !found_hole ) + { + size_t entry_size = roundup(sizeof(struct ext_dirent) + entry->name_len, (size_t) 4); + if ( (!entry->name[0] || !entry->inode) && new_entry_size <= entry->reclen ) + { + hole_block_id = entry_block_id; + hole_block_offset = entry_block_offset; + new_entry_size = entry->reclen; + found_hole = true; + } + else if ( new_entry_size <= entry->reclen - entry_size ) + { + hole_block_id = entry_block_id; + hole_block_offset = entry_block_offset; + new_entry_size = entry->reclen - entry_size; + splitting = true; + found_hole = true; + } + } + offset += entry->reclen; + } + + // We'll append another block if we failed to find a suitable hole. + if ( !found_hole ) + { + hole_block_id = filesize / filesystem->block_size; + hole_block_offset = filesize % filesystem->block_size; + new_entry_size = filesystem->block_size; + } + + if ( block && block_id != hole_block_id ) + block->Unref(), + block = NULL; + if ( !block && !(block = GetBlock(block_id = hole_block_id)) ) + return NULL; + + uint8_t* block_data = block->block_data + hole_block_offset; + struct ext_dirent* entry = (struct ext_dirent*) block_data; + + // If we found a directory entry with room at the end, split it! + if ( splitting ) + { + entry->reclen = roundup(sizeof(struct ext_dirent) + entry->name_len, (size_t) 4); + assert(entry->reclen); + // Block marked dirty below. + block_data += entry->reclen; + entry = (struct ext_dirent*) block_data; + } + + // Write the new directory entry. + entry->inode = dest->inode_id; + entry->reclen = new_entry_size; + + entry->name_len = elem_length; + // TODO: Only do this if the filetype feature is on! + entry->file_type = EXT2_FT_OF_MODE(dest->Mode()); + strncpy(entry->name, elem, new_entry_size - sizeof(struct ext_dirent)); + + assert(entry->reclen); + + block->Dirty(); + + dest->Linked(); + + if ( !found_hole ) + SetSize(Size() + filesystem->block_size); + + block->Unref(); + + return true; +} + +Inode* Inode::Unlink(const char* elem, bool directories, bool force) +{ + if ( !EXT2_S_ISDIR(Mode()) ) + return errno = ENOTDIR, (Inode*) NULL; + size_t elem_length = strlen(elem); + uint32_t block_size = filesystem->block_size; + uint64_t filesize = Size(); + uint64_t num_blocks = divup(filesize, (uint64_t) block_size); + uint64_t offset = 0; + Block* block = NULL; + uint64_t block_id = 0; + struct ext_dirent* last_entry = NULL; + while ( offset < filesize ) + { + uint64_t entry_block_id = offset / block_size; + uint64_t entry_block_offset = offset % block_size; + if ( block && block_id != entry_block_id ) + last_entry = NULL, + block->Unref(), + block = NULL; + if ( !block && !(block = GetBlock(block_id = entry_block_id)) ) + return NULL; + uint8_t* block_data = block->block_data + entry_block_offset; + struct ext_dirent* entry = (struct ext_dirent*) block_data; + assert(entry->reclen); + if ( entry->name_len == elem_length && + memcmp(elem, entry->name, elem_length) == 0 && + entry->inode ) + { + Inode* inode = filesystem->GetInode(entry->inode); + + if ( !force && directories && !EXT2_S_ISDIR(inode->Mode()) ) + { + inode->Unref(); + block->Unref(); + return errno = ENOTDIR, (Inode*) NULL; + } + + if ( !force && directories && !inode->IsEmptyDirectory() ) + { + inode->Unref(); + block->Unref(); + return errno = ENOTEMPTY, (Inode*) NULL; + } + + if ( !force && !directories && EXT2_S_ISDIR(inode->Mode()) ) + { + inode->Unref(); + block->Unref(); + return errno = EISDIR, (Inode*) NULL; + } + + inode->Unlinked(); + entry->inode = 0; + entry->name_len = 0; + entry->file_type = 0; + + // Merge the current entry with the previous if any. + if ( last_entry ) + { + assert(entry->reclen); + last_entry->reclen += entry->reclen; + memset(entry, 0, entry->reclen); + entry = last_entry; + assert(last_entry->reclen); + } + + assert(entry->reclen); + strncpy(entry->name + entry->name_len, "", + entry->reclen - sizeof(struct ext_dirent) - entry->name_len); + assert(entry->reclen); + block->Dirty(); + + // If the entire block is empty, we'll need to remove it. + if ( !entry->name[0] && entry->reclen == block_size ) + { + // If this is not the last block, we'll make it. This is faster + // than shifting the entire directory a single block. We don't + // actually copy this block to the end, since we'll truncate it + // regardless. + if ( entry_block_id + 1 != num_blocks ) + { + Block* last_block = GetBlock(num_blocks-1); + memcpy(block->block_data, last_block->block_data, block_size); + block->Dirty(); + last_block->Unref(); + } + Truncate(filesize - block_size); + } + + block->Unref(); + + return inode; + } + offset += entry->reclen; + last_entry = entry; + } + if ( block ) + block->Unref(); + return errno = ENOENT, (Inode*) NULL; +} + +ssize_t Inode::ReadAt(uint8_t* buf, size_t s_count, off_t o_offset) +{ + if ( !EXT2_S_ISREG(Mode()) ) + return errno = EISDIR, -1; + if ( o_offset < 0 ) + return errno = EINVAL, -1; + if ( SSIZE_MAX < s_count ) + s_count = SSIZE_MAX; + uint64_t sofar = 0; + uint64_t count = (uint64_t) s_count; + uint64_t offset = (uint64_t) o_offset; + uint64_t file_size = Size(); + if ( file_size <= offset ) + return 0; + if ( file_size - offset < count ) + count = file_size - offset; + while ( sofar < count ) + { + uint64_t block_id = offset / filesystem->block_size; + uint32_t block_offset = offset % filesystem->block_size; + uint32_t block_left = filesystem->block_size - block_offset; + Block* block = GetBlock(block_id); + if ( !block ) + return sofar ? sofar : -1; + size_t amount = count - sofar < block_left ? count - sofar : block_left; + memcpy(buf + sofar, block->block_data + block_offset, amount); + sofar += amount; + offset += amount; + block->Unref(); + } + return (ssize_t) sofar; +} + +ssize_t Inode::WriteAt(const uint8_t* buf, size_t s_count, off_t o_offset) +{ + if ( !EXT2_S_ISREG(Mode()) ) + return errno = EISDIR, -1; + if ( o_offset < 0 ) + return errno = EINVAL, -1; + if ( SSIZE_MAX < s_count ) + s_count = SSIZE_MAX; + uint64_t sofar = 0; + uint64_t count = (uint64_t) s_count; + uint64_t offset = (uint64_t) o_offset; + uint64_t file_size = Size(); + uint64_t end_at = offset + count; + if ( offset < end_at ) + /* TODO: Overflow! off_t overflow? */{}; + if ( file_size < end_at ) + Truncate(end_at); + while ( sofar < count ) + { + uint64_t block_id = offset / filesystem->block_size; + uint32_t block_offset = offset % filesystem->block_size; + uint32_t block_left = filesystem->block_size - block_offset; + Block* block = GetBlock(block_id); + if ( !block ) + return sofar ? sofar : -1; + size_t amount = count - sofar < block_left ? count - sofar : block_left; + memcpy(block->block_data + block_offset, buf + sofar, amount); + block->Dirty(); + sofar += amount; + offset += amount; + block->Unref(); + } + return (ssize_t) sofar; +} + +bool Inode::Rename(Inode* olddir, const char* oldname, const char* newname) +{ + if ( !strcmp(oldname, ".") || !strcmp(oldname, "..") || + !strcmp(newname, ".") || !strcmp(newname, "..") ) + return errno = EPERM, false; + Inode* src_inode = olddir->Open(oldname, O_RDONLY, 0); + if ( !src_inode ) + return false; + if ( Inode* dst_inode = Open(newname, O_RDONLY, 0) ) + { + if ( src_inode->inode_id == dst_inode->inode_id ) + return dst_inode->Unref(), src_inode->Unref(), 0; + dst_inode->Unref(); + } + // TODO: Prove that this cannot fail and handle such a situation. + if ( EXT2_S_ISDIR(src_inode->Mode()) ) + { + if ( !Unlink(newname, true) && errno != ENOENT ) + return src_inode->Unref(), false; + Link(newname, src_inode, true); + olddir->Unlink(oldname, true, true); + if ( olddir != this ) + { + src_inode->Unlink("..", true, true); + src_inode->Link("..", this, true); + } + } + else + { + if ( !Unlink(newname, false) && errno != ENOENT ) + return src_inode->Unref(), false; + Link(newname, src_inode, false); + olddir->Unlink(oldname, false); + } + + src_inode->Unref(); + return true; +} + +Inode* Inode::CreateDirectory(const char* path, mode_t mode) +{ + // TODO: Preferred block group! + uint32_t result_inode_id = filesystem->AllocateInode(); + if ( !result_inode_id ) + return NULL; + + Inode* result = filesystem->GetInode(result_inode_id); + memset(result->data, 0, sizeof(*result->data)); + result->SetMode((mode & S_SETABLE) | S_IFDIR); + + // Increase the directory count statistics. + uint32_t group_id = (result->inode_id - 1) / filesystem->sb->s_inodes_per_group; + assert(group_id < filesystem->num_groups); + BlockGroup* block_group = filesystem->GetBlockGroup(group_id); + block_group->data->bg_used_dirs_count++; + block_group->Dirty(); + block_group->Unref(); + + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + result->data->i_atime = now.tv_sec; + result->data->i_ctime = now.tv_sec; + result->data->i_mtime = now.tv_sec; + // TODO: Set all the other inode properties! + + if ( !Link(path, result, true) ) + { + error: + result->Truncate(0); + memset(result->data, 0, sizeof(*result->data)); + // TODO: dtime + result->Unref(); + filesystem->FreeInode(result_inode_id); + return NULL; + } + + if ( !result->Link(".", result, true) ) + { + Unlink(path, true); + goto error; + } + + if ( !result->Link("..", this, true) ) + { + result->Unlink(".", true); + Unlink(path, true); + goto error; + } + + return result; +} + +bool Inode::RemoveDirectory(const char* path) +{ + Inode* result = Unlink(path, true); + if ( !result ) + return false; + result->Unlink("..", true); + result->Unlink(".", true); + result->Truncate(0); + + // Decrease the directory count statistics. + uint32_t group_id = (result->inode_id - 1) / filesystem->sb->s_inodes_per_group; + assert(group_id < filesystem->num_groups); + BlockGroup* block_group = filesystem->GetBlockGroup(group_id); + block_group->data->bg_used_dirs_count--; + block_group->Dirty(); + block_group->Unref(); + + result->Unref(); + + return true; +} + +bool Inode::IsEmptyDirectory() +{ + if ( !EXT2_S_ISDIR(Mode()) ) + return errno = ENOTDIR, false; + uint32_t block_size = filesystem->block_size; + uint64_t filesize = Size(); + uint64_t offset = 0; + Block* block = NULL; + uint64_t block_id = 0; + while ( offset < filesize ) + { + uint64_t entry_block_id = offset / block_size; + uint64_t entry_block_offset = offset % block_size; + if ( block && block_id != entry_block_id ) + block->Unref(), + block = NULL; + if ( !block && !(block = GetBlock(block_id = entry_block_id)) ) + return false; + uint8_t* block_data = block->block_data + entry_block_offset; + struct ext_dirent* entry = (struct ext_dirent*) block_data; + if ( entry->inode && + !((entry->name_len == 1 && entry->name[0] == '.') || + (entry->name_len == 2 && entry->name[0] == '.' && + entry->name[1] == '.' )) ) + { + block->Unref(); + return false; + } + offset += entry->reclen; + } + if ( block ) + block->Unref(); + return true; +} + +void Inode::Delete() +{ + assert(!data->i_links_count); + assert(!reference_count); + assert(!remote_reference_count); + + Truncate(0); + + uint32_t deleted_inode_id = inode_id; + memset(data, 0, sizeof(*data)); + struct timespec now; + clock_gettime(CLOCK_REALTIME, &now); + data->i_dtime = now.tv_sec; + Dirty(); + + delete this; + + filesystem->FreeInode(deleted_inode_id); +} + +void Inode::Refer() +{ + reference_count++; +} + +void Inode::Unref() +{ + reference_count--; + if ( !reference_count && !remote_reference_count ) + { + if ( !data->i_links_count ) + Delete(); + else + delete this; + } +} + +void Inode::RemoteRefer() +{ + remote_reference_count++; +} + +void Inode::RemoteUnref() +{ + remote_reference_count--; + if ( !reference_count && !remote_reference_count ) + { + if ( !data->i_links_count ) + Delete(); + else + delete this; + } +} + +void Inode::Dirty() +{ + dirty = true; + data_block->Dirty(); + Use(); +} + +void Inode::Sync() +{ + if ( dirty ) + data_block->Sync(); + dirty = false; +} + +void Inode::Use() +{ + data_block->Use(); + Unlink(); + Prelink(); +} + +void Inode::Unlink() +{ + (prev_inode ? prev_inode->next_inode : filesystem->mru_inode) = next_inode; + (next_inode ? next_inode->prev_inode : filesystem->lru_inode) = prev_inode; +} + +void Inode::Prelink() +{ + prev_inode = NULL; + next_inode = filesystem->mru_inode; + if ( filesystem->mru_inode ) + filesystem->mru_inode->prev_inode = this; + filesystem->mru_inode = this; + if ( !filesystem->lru_inode ) + filesystem->lru_inode = this; +} diff --git a/ext/inode.h b/ext/inode.h new file mode 100644 index 00000000..283570b7 --- /dev/null +++ b/ext/inode.h @@ -0,0 +1,84 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + inode.h + Filesystem inode. + +*******************************************************************************/ + +#ifndef INODE_H +#define INODE_H + +class Block; +class Filesystem; + +class Inode +{ +public: + Inode(Filesystem* filesystem, uint32_t inode_id); + ~Inode(); + +public: + Inode* prev_inode; + Inode* next_inode; + Block* data_block; + struct ext_inode* data; + Filesystem* filesystem; + size_t reference_count; + size_t remote_reference_count; + uint32_t inode_id; + bool dirty; + +public: + uint32_t Mode(); + uint32_t UserId(); + uint32_t GroupId(); + uint64_t Size(); + void SetMode(uint32_t mode); + void SetUserId(uint32_t user); + void SetGroupId(uint32_t group); + void SetSize(uint64_t new_size); + void Truncate(uint64_t new_size); + bool FreeIndirect(uint64_t from, uint64_t offset, uint32_t block_id, + int indirection, uint64_t entry_span); + Block* GetBlock(uint64_t offset); + Block* GetBlockFromTable(Block* table, uint32_t index); + Inode* Open(const char* elem, int flags, mode_t mode); + bool Link(const char* elem, Inode* dest, bool directories); + Inode* Unlink(const char* elem, bool directories, bool force=false); + ssize_t ReadAt(uint8_t* buffer, size_t count, off_t offset); + ssize_t WriteAt(const uint8_t* buffer, size_t count, off_t offset); + bool Rename(Inode* olddir, const char* oldname, const char* newname); + Inode* CreateDirectory(const char* path, mode_t mode); + bool RemoveDirectory(const char* path); + bool IsEmptyDirectory(); + void Refer(); + void Unref(); + void RemoteRefer(); + void RemoteUnref(); + void Sync(); + void Dirty(); + void Use(); + void Unlink(); + void Prelink(); + void Linked(); + void Unlinked(); + void Delete(); + +}; + +#endif diff --git a/ext/ioleast.cpp b/ext/ioleast.cpp new file mode 100644 index 00000000..7fd7317a --- /dev/null +++ b/ext/ioleast.cpp @@ -0,0 +1,73 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + ioleast.cpp + Sortix functions for reliable reads and writes. + +*******************************************************************************/ + +#include +#include +#include + +#include "ioleast.h" + +#if !defined(__sortix__) + +size_t preadleast(int fd, void* buf, size_t least, size_t max, off_t off) +{ + ssize_t amount = pread(fd, buf, max, off); + if ( amount < 0 ) { return 0; } + if ( least && !amount ) { return 0; } + if ( (size_t) amount < least ) + { + void* nextbuf = (uint8_t*) buf + amount; + size_t nextleast = least - amount; + size_t nextmax = max - amount; + off_t nextoff = off + amount; + amount += preadleast(fd, nextbuf, nextleast, nextmax, nextoff); + } + return amount; +} + +size_t preadall(int fd, void* buf, size_t count, off_t off) +{ + return preadleast(fd, buf, count, count, off); +} + +size_t pwriteleast(int fd, const void* buf, size_t least, size_t max, off_t off) +{ + ssize_t amount = pwrite(fd, buf, max, off); + if ( amount < 0 ) { return 0; } + if ( least && !amount ) { return 0; } + if ( (size_t) amount < least ) + { + const void* nextbuf = (const uint8_t*) buf + amount; + size_t nextleast = least - amount; + size_t nextmax = max - amount; + off_t nextoff = off + amount; + amount += pwriteleast(fd, nextbuf, nextleast, nextmax, nextoff); + } + return amount; +} + +size_t pwriteall(int fd, const void* buf, size_t count, off_t off) +{ + return pwriteleast(fd, buf, count, count, off); +} + +#endif diff --git a/ext/ioleast.h b/ext/ioleast.h new file mode 100644 index 00000000..55ce27c7 --- /dev/null +++ b/ext/ioleast.h @@ -0,0 +1,148 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2012, 2013. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library 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 Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + ioleast.h + Versions of {,p}{read,write} that don't return until it has returned as much + data as requested, end of file, or an error occurs. This is sometimes needed + as read(2) and write(2) is not always guaranteed to fill up the entire + buffer or write it all. + +*******************************************************************************/ + +#ifndef SORTIX_COMPATIBILITY_INCLUDE_IOLEAST_H +#define SORTIX_COMPATIBILITY_INCLUDE_IOLEAST_H + +#if defined(__sortix__) || defined(__sortix_libc__) + +#include_next + +#else + +#include + +#include +#include +#include +#include + +#if !defined(EEOF) && defined(EIO) +#define EEOF EIO +#endif + +__attribute__((unused)) static inline +size_t readleast(int fd, void* buf, size_t least, size_t max) +{ + ssize_t amount = read(fd, buf, max); + if ( amount < 0 ) + return 0; + if ( least && !amount ) + return errno = EEOF, 0; + if ( (size_t) amount < least ) + { + void* nextbuf = (uint8_t*) buf + amount; + size_t nextleast = least - amount; + size_t nextmax = max - amount; + amount += readleast(fd, nextbuf, nextleast, nextmax); + } + return amount; +} + +__attribute__((unused)) static inline +size_t writeleast(int fd, const void* buf, size_t least, size_t max) +{ + ssize_t amount = write(fd, buf, max); + if ( amount < 0 ) + return 0; + if ( least && !amount ) + return errno = EEOF, 0; + if ( (size_t) amount < least ) + { + const void* nextbuf = (const uint8_t*) buf + amount; + size_t nextleast = least - amount; + size_t nextmax = max - amount; + amount += writeleast(fd, nextbuf, nextleast, nextmax); + } + return amount; +} + +__attribute__((unused)) static inline +size_t preadleast(int fd, void* buf, size_t least, size_t max, off_t off) +{ + ssize_t amount = pread(fd, buf, max, off); + if ( amount < 0 ) + return 0; + if ( least && !amount ) + return errno = EEOF, 0; + if ( (size_t) amount < least ) + { + void* nextbuf = (uint8_t*) buf + amount; + size_t nextleast = least - amount; + size_t nextmax = max - amount; + off_t nextoff = off + amount; + amount += preadleast(fd, nextbuf, nextleast, nextmax, nextoff); + } + return amount; +} + +__attribute__((unused)) static inline +size_t pwriteleast(int fd, const void* buf, size_t least, size_t max, off_t off) +{ + ssize_t amount = pwrite(fd, buf, max, off); + if ( amount < 0 ) + return 0; + if ( least && !amount ) + return errno = EEOF, 0; + if ( (size_t) amount < least ) + { + const void* nextbuf = (const uint8_t*) buf + amount; + size_t nextleast = least - amount; + size_t nextmax = max - amount; + off_t nextoff = off + amount; + amount += pwriteleast(fd, nextbuf, nextleast, nextmax, nextoff); + } + return amount; +} + +__attribute__((unused)) static inline +size_t readall(int fd, void* buf, size_t count) +{ + return readleast(fd, buf, count, count); +} + +__attribute__((unused)) static inline +size_t writeall(int fd, const void* buf, size_t count) +{ + return writeleast(fd, buf, count, count); +} + +__attribute__((unused)) static inline +size_t preadall(int fd, void* buf, size_t count, off_t off) +{ + return preadleast(fd, buf, count, count, off); +} + +__attribute__((unused)) static inline +size_t pwriteall(int fd, const void* buf, size_t count, off_t off) +{ + return pwriteleast(fd, buf, count, count, off); +} + +#endif + +#endif diff --git a/ext/util.h b/ext/util.h new file mode 100644 index 00000000..2e5de440 --- /dev/null +++ b/ext/util.h @@ -0,0 +1,52 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + 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 . + + util.h + Utility functions for the filesystem implementation. + +*******************************************************************************/ + +#ifndef UTIL_H +#define UTIL_H + +template T divup(T a, T b) +{ + return a/b + (a % b ? 1 : 0); +} + +template T roundup(T a, T b) +{ + return a % b ? a + b - a % b : a; +} + +inline bool checkbit(const uint8_t* bitmap, size_t bit) +{ + uint8_t bits = bitmap[bit / 8UL]; + return bits & (1U << (bit % 8UL)); +} + +inline void setbit(uint8_t* bitmap, size_t bit) +{ + bitmap[bit / 8UL] |= 1U << (bit % 8UL); +} + +inline void clearbit(uint8_t* bitmap, size_t bit) +{ + bitmap[bit / 8UL] &= ~(1U << (bit % 8UL)); +} + +#endif