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