diff --git a/src/block_device.cpp b/src/block_device.cpp index 3bf9a82..dbf9376 100644 --- a/src/block_device.cpp +++ b/src/block_device.cpp @@ -1,4 +1,5 @@ #include "block_device.hpp" +#include #include #include #include @@ -20,6 +21,7 @@ auto FileDisk::block_count() -> uint16_t { m_file.seekg(0, std::ios_base::end); std::streamsize location = m_file.tellg(); + assert(location != -1); return location / block_size & 0xffff; } @@ -27,10 +29,12 @@ void FileDisk::read(uint8_t* data, uint16_t block) { m_file.seekg(block * block_size); m_file.read(reinterpret_cast(data), block_size); + assert(m_file.good()); } void FileDisk::write(const uint8_t* data, uint16_t block) { m_file.seekg(block * block_size); m_file.write(reinterpret_cast(data), block_size); + assert(m_file.good()); } diff --git a/src/fs.cpp b/src/fs.cpp index 8911d28..f99d5e6 100644 --- a/src/fs.cpp +++ b/src/fs.cpp @@ -1,75 +1,360 @@ #include "fs.hpp" +#include #include +#include +#include +#include +#include namespace vc5::filesys { -constexpr auto Ok = Status::Ok; +using BlockData = std::array; -using BlockData = std::array; +namespace { -void Fs::init() -{ - m_super = SuperBlockData { - .block_bitmap = {}, - .inode_bitmap = {}, - .inode_blocks = {}, - }; - write_superblock(); -} - -Status Fs::make_directory() -{ - uint16_t id; - if (auto result = next_inode_id(id); result != Ok) { - return result; + auto inode_mode(INodeType type) -> u16 + { + u16 mode = 0; + mode |= std::to_underlying(type) << inode_type_bitoffset; + return mode; } - auto node = INodeData { - .id = id, - .flags = static_cast(INodeType::Directory), + auto inode_mode_type(u16 mode) -> INodeType + { + return static_cast( + mode >> inode_type_bitoffset & inode_type_bitmask); + } + + auto unwrap_inode_id(u16 inode_id) -> std::tuple + { + u16 inode_block_idx = inode_id / inodes_per_block; + u16 block_offset = inode_id % inodes_per_block * inode_size; + + assert(inode_block_idx * inodes_per_block + block_offset / inode_size + == inode_id + && "calculation wrong"); + + return { inode_block_idx, block_offset }; + } + +} + +auto Fs::init_new(u16 volume_id, u16 block_count) -> Result +{ + m_superblock = Superblock { + .volume_id = volume_id, + .fs_version = 1, + .blocks_count = block_count, + .blocks_free = block_count, + .root_inode = 0, + .inode_blocks_count = 0, + .iblocks = {}, + .inode_bitmap = {}, + }; + + auto root_inode_id = make_directory_inode(); + if (not root_inode_id) + return std::unexpected(root_inode_id.error()); + + m_superblock.root_inode = *root_inode_id; + + if (auto result = sync_to_disk(); not result) + return result; + + return {}; +} + +auto Fs::make_file(u16 dir_inode_id, std::string_view name) -> Result +{ + if (name.size() > name_max_len) + return std::unexpected("invalid filename"); + + auto dir_inode_result = read_inode(dir_inode_id); + if (not dir_inode_result) + return std::unexpected(dir_inode_result.error()); + auto& dir_inode = *dir_inode_result; + + if (inode_mode_type(dir_inode.mode) != INodeType::Dir) + return std::unexpected("incorrect inode type"); + + auto inode_id = allocate_inode(); + if (not inode_id) + return inode_id; + + auto inode = INode { + .mode = inode_mode(INodeType::File), .blocks_count = 0, + .links_count = 0, + .last_block_size = 0, + .size = 0, .blocks = {}, }; + + auto entry = DirEntry { + .inode = *inode_id, + .next = 0, + .name_len = static_cast(name.size()), + .name = {}, + }; + + std::memcpy(entry.name.data(), name.data(), name.size()); + + if (auto result = append_to_directory(dir_inode, entry); not result) + return std::unexpected(result.error()); } -Status Fs::write_new_inode(INodeData& data) +auto Fs::init_from_existing() -> Result { - uint16_t block_id = data.id / inodes_per_block; - uint16_t offset = data.id % inodes_per_block * inode_size; - - BlockData block; - m_disk->read(&block[0], block_id); + return sync_from_disk(); } -Status Fs::next_inode_id(uint16_t& id) +auto Fs::append_to_directory(INode& dir_inode, const DirEntry& entry) + -> Result { - for (uint16_t map_idx = 0; map_idx < sb_inode_bitmap_count; ++map_idx) { - auto& map = m_super.inode_bitmap[map_idx]; - for (uint16_t bit_idx = 0; bit_idx < 16; ++bit_idx) { + for (u16 i = 0; i < inode_ndir_blocks; ++i) { + auto dir_entry_block = dir_inode.blocks[i]; - if ((map >> bit_idx & 1) == 0) { - id = map_idx * sb_inode_bitmap_count + bit_idx; - return Ok; - } + auto block = read_block(dir_entry_block); + if (not block) + return std::unexpected(block.error()); + } + + return {}; +} + +auto Fs::make_directory_inode() -> Result +{ + auto inode_id = allocate_inode(); + if (not inode_id) + return inode_id; + + auto inode = INode { + .mode = inode_mode(INodeType::Dir), + .blocks_count = 0, + .links_count = 0, + .last_block_size = 0, + .size = 0, + .blocks = {}, + }; + + if (auto result = write_inode(*inode_id, inode); not result) + return std::unexpected(result.error()); + + return inode_id; +} + +auto Fs::inode_last_block_id(const INode& inode) -> Result +{ + if (inode.blocks_count < inode_ndir_blocks) { + return inode.blocks_count - 1; + + } else if (inode.blocks_count >= inode_ind_block + && inode.blocks_count <= inode_ind_block + block_size) { + + auto block_id = inode.blocks[inode_ind_block]; + assert(block_id != 0); + + auto ind_block = read_block(block_id); + if (not ind_block) + return std::unexpected(ind_block.error()); + + return ind_block.value()[inode.blocks_count - inode_ind_block]; + + } else if (inode.blocks_count == inode_dind_block) { + + u16 first_id = inode_dind_block; + u16 second_id = inode.blocks_count - inode_dind_block; + + auto first_block_id = inode.blocks[inode_dind_block]; + assert(first_block_id != 0); + + auto first_ind_block = read_block(first_block_id); + if (not first_ind_block) + return std::unexpected(first_ind_block.error()); + + auto second_block_id + = first_ind_block.value()[inode.blocks_count - inode_dind_block]; + + auto second_ind_block = read_block(second_block_id); + if (not second_ind_block) + return std::unexpected(second_ind_block.error()); + + return second_ind_block.value()[inode.blocks_count - inode_dind_block]; + + } else { + return std::unexpected("inode at capacity"); + } +} + +auto Fs::read_inode(u16 inode_id) -> Result +{ + auto [inode_block_idx, block_offset] = unwrap_inode_id(inode_id); + + if (inode_block_idx >= m_superblock.inode_blocks_count) + return std::unexpected("inode lookup failed"); + + auto block_id = m_superblock.iblocks[inode_block_idx]; + + struct INodeData { + u16 mode; + u16 blocks_count; + u16 links_count; + u16 last_block_size; + u32 size; + std::array blocks; + }; + static_assert(sizeof(INodeData) == sizeof(INode)); + + return read_block_part(block_id, block_offset); +} + +auto Fs::write_inode(u16 inode_id, const INode& inode) -> Result +{ + auto [inode_block_idx, block_offset] = unwrap_inode_id(inode_id); + + if (inode_block_idx >= m_superblock.inode_blocks_count) + return std::unexpected("inode lookup failed"); + + auto block_id = m_superblock.iblocks[inode_block_idx]; + + if (auto result = write_block_part(block_id, inode, block_offset); + not result) + return result; + + return {}; +} + +auto Fs::allocate_inode() -> Result +{ + auto inode_id = std::optional(); + auto& sb = m_superblock; + for (u16 i = 0; i < sb.inode_blocks_count * 16; ++i) { + if (sb.inode_bitmap[i]) { + inode_id = i; + break; } } - return Status::INodeAllocFail; + + if (not inode_id) { + if (sb.inode_blocks_count >= iblocks_max_count) { + return std::unexpected("inode allocate failed"); + } + auto new_block_id = allocate_block(); + if (not new_block_id) + return new_block_id; + + sb.iblocks[sb.inode_blocks_count] = *new_block_id; + inode_id = sb.inode_blocks_count * 16; + sb.inode_blocks_count += 1; + } + + sb.inode_bitmap[*inode_id] = true; + return *inode_id; } -Status Fs::read_superblock() +auto Fs::allocate_block() -> Result { - BlockData block; - m_disk->read(&block[0], super_block_location); - m_super = *reinterpret_cast(&block[0]); - return Ok; + u16 block_id = 0; + + // data/inode blocks start at 4 + for (u16 i = 4; i < blocks_max_count; ++i) { + if (not m_blocks_bitmap.bitmap[i]) { + block_id = i; + break; + } + } + if (block_id == 0) { + return std::unexpected("allocate block failed"); + } + m_blocks_bitmap.bitmap[block_id] = true; + return block_id; } -Status Fs::write_superblock() +auto Fs::sync_from_disk() -> Result { - m_disk->write( - reinterpret_cast(&m_super), super_block_location); - return Ok; + auto superblock = read_block(sb_loc); + if (not superblock) + return std::unexpected(superblock.error()); + + auto blocks_bitmap = read_block(blocks_bitmap_loc); + if (not blocks_bitmap) + return std::unexpected(superblock.error()); + + m_superblock = *superblock; + m_blocks_bitmap = *blocks_bitmap; + + return {}; +} + +auto Fs::sync_to_disk() -> Result +{ + if (auto result = write_block(m_superblock, sb_loc); not result) + return result; + + if (auto result = write_block(m_blocks_bitmap, blocks_bitmap_loc); + not result) + return result; + + return {}; +} + +template + requires(sizeof(Data) <= block_size) +auto Fs::read_block_part(u16 block_id, u16 offset) -> Result +{ + assert(sizeof(Data) + offset <= block_size); + + BlockData block = {}; + m_disk->read(block.data(), block_id); + + Data data; + std::memcpy( + reinterpret_cast(&data) + offset, block.data(), sizeof(Data)); + + return data; +} + +template + requires(sizeof(Data) <= block_size) +auto Fs::write_block_part(u16 block_id, const Data& data, u16 offset) + -> Result +{ + assert(sizeof(Data) + offset <= block_size); + + BlockData block = {}; + m_disk->read(block.data(), block_id); + + std::memcpy(block.data(), + reinterpret_cast(&data) + offset, + sizeof(Data)); + m_disk->write(block.data(), block_id); + + return {}; +} + +template + requires(sizeof(Data) <= block_size) +auto Fs::read_block(u16 block_id) -> Result +{ + BlockData block = {}; + m_disk->read(block.data(), block_id); + + Data data; + std::memcpy(reinterpret_cast(&data), block.data(), sizeof(Data)); + + return data; +} + +template + requires(sizeof(Data) <= block_size) +auto Fs::write_block(const Data& data, u16 block_id) -> Result +{ + BlockData block = {}; + std::memcpy( + block.data(), reinterpret_cast(&data), sizeof(Data)); + m_disk->write(block.data(), block_id); + return {}; } } diff --git a/src/fs.hpp b/src/fs.hpp index 58d1df7..6ec3967 100644 --- a/src/fs.hpp +++ b/src/fs.hpp @@ -1,53 +1,80 @@ #pragma once #include "block_device.hpp" +#include "types.hpp" +#include #include -#include +#include +#include +#include +#include namespace vc5::filesys { -enum class Status : uint16_t { - Ok, - INodeAllocFail, +constexpr u16 block_size = 0x200; + +constexpr u16 name_max_len = 256; + +struct DirEntry { + u16 inode; + u16 next; + u16 name_len; + std::array name; }; -constexpr uint16_t block_size = 0x200; -constexpr uint16_t name_max_len = 255; +constexpr u16 inode_size = 32; +constexpr u16 inodes_per_block = block_size / inode_size; -struct DirEntryData { - uint16_t inode; - uint16_t name_len; - std::array name; -}; - -enum class INodeType : uint16_t { +enum class INodeType : u16 { File = 0, - Directory = 1, - Symlink = 2, + Dir = 1, + Link = 2, }; -constexpr uint16_t inode_size = 32; -constexpr uint16_t inodes_per_block = block_size / inode_size; -constexpr uint16_t inode_blocks_count = 8; +constexpr u16 inode_type_bitoffset = 0; +constexpr u16 inode_type_bitmask = 0b11; -struct INodeData { - uint16_t id; - uint16_t flags; - uint16_t blocks_count; - std::array blocks; +constexpr u16 inode_ndir_blocks = 8; +constexpr u16 inode_ind_block = inode_ndir_blocks; +constexpr u16 inode_dind_block = inode_ind_block + 1; +constexpr u16 inode_blocks_count = inode_dind_block + 1; + +struct INode { + u16 mode; + u16 blocks_count; + u16 links_count; + u16 last_block_size; + u32 size; + std::array blocks; }; -constexpr uint16_t super_block_location = 0x400; -constexpr uint16_t sb_block_bitmap_count = 8; -constexpr uint16_t sb_inode_bitmap_count = 8; -constexpr uint16_t sb_inode_blocks_count = 16; +static_assert(sizeof(INode) <= inode_size); -struct SuperBlockData { - std::array block_bitmap; - std::array inode_bitmap; - std::array inode_blocks; +constexpr u16 sb_loc = 3; +constexpr u16 blocks_bitmap_loc = 4; +constexpr u16 blocks_max_count = block_size * 8; +constexpr u16 iblocks_max_count = 32; +constexpr u16 inodes_max_count = block_size / inode_size * iblocks_max_count; + +struct Superblock { + u16 volume_id; + u16 fs_version; + u16 blocks_count; + u16 blocks_free; + u16 root_inode; + u16 inode_blocks_count; + std::array iblocks; + std::bitset inode_bitmap; }; +static_assert(sizeof(Superblock) <= block_size); + +struct BlocksBitmap { + std::bitset bitmap; +}; + +static_assert(sizeof(BlocksBitmap) <= block_size); + class Fs { public: explicit Fs(BlockDevice& disk) @@ -55,19 +82,115 @@ public: { } - void init(); + template using Result = std::expected; + + static auto init_new(BlockDevice& disk, u16 volume_id, u16 block_count) + -> Result + { + auto fs = Fs(disk); + if (auto result = fs.init_new(volume_id, block_count); not result) + return std::unexpected(result.error()); + return fs; + } + + static auto load(BlockDevice& disk) -> Result + { + auto fs = Fs(disk); + if (auto result = fs.init_from_existing(); not result) + return std::unexpected(result.error()); + return fs; + } + + auto root_inode_id() const -> Result + { + return m_superblock.root_inode; + } + + auto make_file(u16 dir_inode_id, std::string_view name) -> Result; private: - Status make_directory(); + auto init_new(u16 volume_id, u16 block_count) -> Result; + auto init_from_existing() -> Result; - Status write_new_inode(INodeData& data); - Status next_inode_id(uint16_t& id); + auto append_to_directory(INode& dir_inode, const DirEntry& entry) + -> Result; + auto make_directory_inode() -> Result; - Status read_superblock(); - Status write_superblock(); + auto inode_last_block_id(const INode& inode) -> Result; + + auto write_inode(u16 inode_id, const INode& inode) -> Result; + auto read_inode(u16 inode_id) -> Result; + auto allocate_inode() -> Result; + + auto allocate_block() -> Result; + + auto sync_from_disk() -> Result; + auto sync_to_disk() -> Result; + + // templates declared here to get private access + + template + requires(sizeof(Data) <= block_size) + auto read_block_part(u16 block_id, u16 offset) -> Result; + template + requires(sizeof(Data) <= block_size) + auto write_block_part(u16 block_id, const Data& data, u16 offset) + -> Result; + + template + requires(sizeof(Data) <= block_size) + auto read_block(u16 block_id) -> Result; + template + requires(sizeof(Data) <= block_size) + auto write_block(const Data& data, u16 block_id) -> Result; BlockDevice* m_disk; - SuperBlockData m_super; + Superblock m_superblock = {}; + BlocksBitmap m_blocks_bitmap = {}; +}; + +class ByteReader { +public: + template + ByteReader(const Data* data) + : m_data(reinterpret_cast(data)) {}; + + template + requires std::is_integral_v + auto operator>>(Data& v) -> ByteReader& + { + v = 0; + for (std::size_t i = 0; i < sizeof(Data); ++i) { + v |= *m_data << (sizeof(Data) - i - 1) * 8; + m_data++; + } + return *this; + } + +private: + const u8* m_data; +}; + +class ByteWriter { +public: + template + ByteWriter(Data* data) + : m_data(reinterpret_cast(data)) {}; + + template + requires std::is_integral_v + auto operator>>(Data& v) -> ByteWriter& + { + v = 0; + for (std::size_t i = 0; i < sizeof(Data); ++i) { + *m_data = v >> (sizeof(Data) - i - 1) * 8 & 0xff; + m_data++; + } + return *this; + } + +private: + u8* m_data; }; } diff --git a/src/types.hpp b/src/types.hpp new file mode 100644 index 0000000..4d2cde8 --- /dev/null +++ b/src/types.hpp @@ -0,0 +1,17 @@ +#pragma once + +#include + +namespace vc5 { + +using u8 = std::uint8_t; +using u16 = std::uint16_t; +using u32 = std::uint32_t; +using u64 = std::uint64_t; + +using i8 = std::int8_t; +using i16 = std::int16_t; +using i32 = std::int32_t; +using i64 = std::int64_t; + +} diff --git a/src/vm_main.cpp b/src/vm_main.cpp index a2e4b5d..da05d4f 100644 --- a/src/vm_main.cpp +++ b/src/vm_main.cpp @@ -1,12 +1,16 @@ #include "block_device.hpp" #include "builder.hpp" +#include "fs.hpp" #include "io_device.hpp" +#include "types.hpp" #include "vm.hpp" #include +#include #include #include #include #include +#include using namespace std::chrono_literals; @@ -22,7 +26,25 @@ int main(int argc, char** argv) return EXIT_FAILURE; } - auto disk = FileDisk(argv[1]); + auto file_disk = FileDisk(argv[1]); + auto disk = MemoryDisk(128); + + auto block = std::array {}; + + for (u16 i = 0; i < 2; ++i) { + file_disk.read(block.data(), i); + disk.write(block.data(), i); + } + + try { + auto fs = filesys::Fs::init_new(disk, 0, 128).value(); + + auto root = fs.root_inode_id().value(); + + } catch (std::bad_expected_access& ex) { + std::println(stderr, "error: {}", ex.error()); + return EXIT_FAILURE; + } auto vm = VM(*device, disk); vm.run();