From f9bf47ab30a95e77519522318c3547ccf9c69b7d Mon Sep 17 00:00:00 2001 From: Bananymous Date: Sat, 4 Nov 2023 18:13:52 +0200 Subject: [PATCH] Kernel: Start work on proper TmpFS in Heap instead of kmalloc memory --- kernel/CMakeLists.txt | 2 + kernel/include/kernel/FS/TmpFS/Definitions.h | 53 ++++ kernel/include/kernel/FS/TmpFS/FileSystem.h | 119 ++++++++ kernel/include/kernel/FS/TmpFS/Inode.h | 103 +++++++ kernel/kernel/FS/TmpFS/FileSystem.cpp | 268 ++++++++++++++++ kernel/kernel/FS/TmpFS/Inode.cpp | 303 +++++++++++++++++++ 6 files changed, 848 insertions(+) create mode 100644 kernel/include/kernel/FS/TmpFS/Definitions.h create mode 100644 kernel/include/kernel/FS/TmpFS/FileSystem.h create mode 100644 kernel/include/kernel/FS/TmpFS/Inode.h create mode 100644 kernel/kernel/FS/TmpFS/FileSystem.cpp create mode 100644 kernel/kernel/FS/TmpFS/Inode.cpp diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 770c57b09..f1d65417f 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -28,6 +28,8 @@ set(KERNEL_SOURCES kernel/FS/ProcFS/Inode.cpp kernel/FS/RamFS/FileSystem.cpp kernel/FS/RamFS/Inode.cpp + kernel/FS/TmpFS/FileSystem.cpp + kernel/FS/TmpFS/Inode.cpp kernel/FS/VirtualFileSystem.cpp kernel/Input/PS2Controller.cpp kernel/Input/PS2Keyboard.cpp diff --git a/kernel/include/kernel/FS/TmpFS/Definitions.h b/kernel/include/kernel/FS/TmpFS/Definitions.h new file mode 100644 index 000000000..92543775d --- /dev/null +++ b/kernel/include/kernel/FS/TmpFS/Definitions.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +#include +#include + +namespace Kernel +{ + + struct TmpInodeInfo + { + mode_t mode { 0 }; + uid_t uid { 0 }; + gid_t gid { 0 }; + timespec atime { 0 }; + timespec ctime { 0 }; + timespec mtime { 0 }; + nlink_t nlink { 0 }; + size_t size { 0 }; + blkcnt_t blocks { 0 }; + + // 2x direct blocks + // 1x singly indirect + // 1x doubly indirect + // 1x triply indirect + BAN::Array block; + static constexpr size_t direct_block_count = 2; + static constexpr size_t max_size = + direct_block_count * PAGE_SIZE + + (PAGE_SIZE / sizeof(paddr_t)) * PAGE_SIZE + + (PAGE_SIZE / sizeof(paddr_t)) * (PAGE_SIZE / sizeof(paddr_t)) * PAGE_SIZE + + (PAGE_SIZE / sizeof(paddr_t)) * (PAGE_SIZE / sizeof(paddr_t)) * (PAGE_SIZE / sizeof(paddr_t)) * PAGE_SIZE; + }; + static_assert(sizeof(TmpInodeInfo) == 128); + + struct TmpDirectoryEntry + { + ino_t ino; + uint8_t type; + size_t name_len; + size_t rec_len; + char name[]; + + BAN::StringView name_sv() const + { + ASSERT(type != DT_UNKNOWN); + return BAN::StringView(name, name_len); + } + }; + +} diff --git a/kernel/include/kernel/FS/TmpFS/FileSystem.h b/kernel/include/kernel/FS/TmpFS/FileSystem.h new file mode 100644 index 000000000..3e87e3696 --- /dev/null +++ b/kernel/include/kernel/FS/TmpFS/FileSystem.h @@ -0,0 +1,119 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace Kernel +{ + + template + concept for_each_indirect_paddr_allocating_callback = requires(F func, paddr_t paddr, bool was_allocated) + { + requires BAN::is_same_v; + }; + + class TmpFileSystem : public FileSystem + { + public: + static constexpr size_t no_page_limit = SIZE_MAX; + + public: + static BAN::ErrorOr create(size_t max_pages, mode_t, uid_t, gid_t); + ~TmpFileSystem(); + + virtual BAN::RefPtr root_inode() override { return m_root_inode; } + + BAN::ErrorOr> open_inode(ino_t ino); + + void read_inode(ino_t ino, TmpInodeInfo& out); + void write_inode(ino_t ino, const TmpInodeInfo&); + void delete_inode(ino_t ino); + BAN::ErrorOr allocate_inode(const TmpInodeInfo&); + + void read_block(size_t index, BAN::ByteSpan buffer); + void write_block(size_t index, BAN::ConstByteSpan buffer); + void free_block(size_t index); + BAN::ErrorOr allocate_block(); + + private: + struct PageInfo + { + enum Flags : paddr_t + { + Present = 1 << 0, + }; + + // 12 bottom bits of paddr can be used as flags, since + // paddr will always be page aligned. + static constexpr size_t flag_bits = 12; + static constexpr paddr_t flags_mask = (1 << flag_bits) - 1; + static constexpr paddr_t paddr_mask = ~flags_mask; + static_assert((1 << flag_bits) <= PAGE_SIZE); + + paddr_t paddr() const { return raw & paddr_mask; } + paddr_t flags() const { return raw & flags_mask; } + + void set_paddr(paddr_t paddr) { raw = (raw & flags_mask) | (paddr & paddr_mask); } + void set_flags(paddr_t flags) { raw = (raw & paddr_mask) | (flags & flags_mask); } + + paddr_t raw { 0 }; + }; + + struct InodeLocation + { + paddr_t paddr; + size_t index; + }; + + private: + TmpFileSystem(size_t max_pages); + BAN::ErrorOr initialize(mode_t, uid_t, gid_t); + + InodeLocation find_inode(ino_t ino); + + paddr_t find_block(size_t index); + + template + BAN::ErrorOr for_each_indirect_paddr_allocating(PageInfo page_info, F callback, size_t depth); + template + BAN::ErrorOr for_each_indirect_paddr_allocating_internal(PageInfo page_info, F callback, size_t depth); + + paddr_t find_indirect(PageInfo root, size_t index, size_t depth); + + private: + RecursiveSpinLock m_lock; + + BAN::HashMap> m_inode_cache; + BAN::RefPtr m_root_inode; + + // We store pages with triple indirection. + // With 64-bit pointers we can store 512^3 pages of data (512 GiB) + // which should be enough for now. + // In future this should be dynamically calculated based on maximum + // number of pages for this file system. + PageInfo m_data_pages {}; + static constexpr size_t first_data_page = 1; + static constexpr size_t max_data_pages = + (PAGE_SIZE / sizeof(PageInfo)) * + (PAGE_SIZE / sizeof(PageInfo)) * + (PAGE_SIZE / sizeof(PageInfo)); + + // We store inodes in pages with double indirection. + // With 64-bit pointers we can store 512^2 pages of inodes + // which should be enough for now. + // In future this should be dynamically calculated based on maximum + // number of pages for this file system. + PageInfo m_inode_pages; + static constexpr size_t first_inode = 1; + static constexpr size_t max_inodes = + (PAGE_SIZE / sizeof(PageInfo)) * + (PAGE_SIZE / sizeof(PageInfo)) * + (PAGE_SIZE / sizeof(TmpInodeInfo)); + + const size_t m_max_pages; + }; + +} \ No newline at end of file diff --git a/kernel/include/kernel/FS/TmpFS/Inode.h b/kernel/include/kernel/FS/TmpFS/Inode.h new file mode 100644 index 000000000..52c5774e1 --- /dev/null +++ b/kernel/include/kernel/FS/TmpFS/Inode.h @@ -0,0 +1,103 @@ +#pragma once + +#include +#include +#include + +namespace Kernel +{ + + class TmpFileSystem; + + class TmpInode : public Inode + { + public: + virtual ino_t ino() const override final { return m_ino; } + virtual Mode mode() const override final { return Mode(m_inode_info.mode); } + virtual nlink_t nlink() const override final { return m_inode_info.nlink; } + virtual uid_t uid() const override final { return m_inode_info.uid; } + virtual gid_t gid() const override final { return m_inode_info.gid; } + virtual off_t size() const override final { return m_inode_info.size; } + virtual timespec atime() const override final { return m_inode_info.atime; } + virtual timespec mtime() const override final { return m_inode_info.mtime; } + virtual timespec ctime() const override final { return m_inode_info.ctime; } + virtual blksize_t blksize() const override final { return PAGE_SIZE; } + virtual blkcnt_t blocks() const override final { return m_inode_info.blocks; } + virtual dev_t dev() const override final { return 0; } // TODO + virtual dev_t rdev() const override final { return 0; } // TODO + + public: + static BAN::ErrorOr> create_from_existing(TmpFileSystem&, ino_t, const TmpInodeInfo&); + + protected: + TmpInode(TmpFileSystem&, ino_t, const TmpInodeInfo&); + + void sync(); + void free_all_blocks(); + + size_t block_index(size_t data_block_index); + BAN::ErrorOr block_index_with_allocation(size_t data_block_index); + + protected: + TmpFileSystem& m_fs; + TmpInodeInfo m_inode_info; + const ino_t m_ino; + + // has to be able to increase link count + friend class TmpDirectoryInode; + }; + + class TmpFileInode : public TmpInode + { + public: + static BAN::ErrorOr> create(TmpFileSystem&, mode_t, uid_t, gid_t); + ~TmpFileInode(); + + private: + TmpFileInode(TmpFileSystem&, ino_t, const TmpInodeInfo&); + + friend class TmpInode; + }; + + class TmpSymlinkInode : public TmpInode + { + public: + ~TmpSymlinkInode(); + + private: + TmpSymlinkInode(TmpFileSystem&, ino_t, const TmpInodeInfo&, BAN::StringView target); + }; + + template + concept for_each_entry_callback = requires(F func, const TmpDirectoryEntry& entry) + { + requires BAN::is_same_v; + }; + + class TmpDirectoryInode : public TmpInode + { + public: + static BAN::ErrorOr> create_root(TmpFileSystem&, mode_t, uid_t, gid_t); + static BAN::ErrorOr> create_new(TmpFileSystem&, mode_t, uid_t, gid_t, TmpInode& parent); + + ~TmpDirectoryInode(); + + protected: + virtual BAN::ErrorOr> find_inode_impl(BAN::StringView) override final; + virtual BAN::ErrorOr list_next_inodes_impl(off_t, DirectoryEntryList*, size_t) override final; + virtual BAN::ErrorOr create_file_impl(BAN::StringView, mode_t, uid_t, gid_t) override final; + virtual BAN::ErrorOr create_directory_impl(BAN::StringView, mode_t, uid_t, gid_t) override final; + virtual BAN::ErrorOr unlink_impl(BAN::StringView) override final; + + private: + TmpDirectoryInode(TmpFileSystem&, ino_t, const TmpInodeInfo&); + + BAN::ErrorOr link_inode(TmpInode&, BAN::StringView); + + template + void for_each_entry(F callback); + + friend class TmpInode; + }; + +} diff --git a/kernel/kernel/FS/TmpFS/FileSystem.cpp b/kernel/kernel/FS/TmpFS/FileSystem.cpp new file mode 100644 index 000000000..d5f90029f --- /dev/null +++ b/kernel/kernel/FS/TmpFS/FileSystem.cpp @@ -0,0 +1,268 @@ +#include +#include +#include + +namespace Kernel +{ + + BAN::ErrorOr TmpFileSystem::create(size_t max_pages, mode_t mode, uid_t uid, gid_t gid) + { + auto* result = new TmpFileSystem(max_pages); + if (result == nullptr) + return BAN::Error::from_errno(ENOMEM); + TRY(result->initialize(mode, uid, gid)); + return result; + } + + TmpFileSystem::TmpFileSystem(size_t max_pages) + : m_max_pages(max_pages) + { } + + BAN::ErrorOr TmpFileSystem::initialize(mode_t mode, uid_t uid, gid_t gid) + { + paddr_t data_paddr = Heap::get().take_free_page(); + if (data_paddr == 0) + return BAN::Error::from_errno(ENOMEM); + m_data_pages.set_paddr(data_paddr); + m_data_pages.set_flags(PageInfo::Flags::Present); + PageTable::with_fast_page(data_paddr, [&] { + memset(PageTable::fast_page_as_ptr(), 0x00, PAGE_SIZE); + }); + + paddr_t inodes_paddr = Heap::get().take_free_page(); + if (inodes_paddr == 0) + return BAN::Error::from_errno(ENOMEM); + m_inode_pages.set_paddr(inodes_paddr); + m_inode_pages.set_flags(PageInfo::Flags::Present); + PageTable::with_fast_page(inodes_paddr, [&] { + memset(PageTable::fast_page_as_ptr(), 0x00, PAGE_SIZE); + }); + + m_root_inode = TRY(TmpDirectoryInode::create_root(*this, mode, uid, gid)); + TRY(m_inode_cache.insert(m_root_inode->ino(), m_root_inode)); + + return {}; + } + + TmpFileSystem::~TmpFileSystem() + { + ASSERT_NOT_REACHED(); + } + + BAN::ErrorOr> TmpFileSystem::open_inode(ino_t ino) + { + if (m_inode_cache.contains(ino)) + return m_inode_cache[ino]; + + TmpInodeInfo inode_info; + + auto inode_location = find_inode(ino); + PageTable::with_fast_page(inode_location.paddr, [&] { + inode_info = PageTable::fast_page_as_sized(inode_location.index); + }); + + auto inode = TRY(TmpInode::create_from_existing(*this, ino, inode_info)); + TRY(m_inode_cache.insert(ino, inode)); + return inode; + } + + void TmpFileSystem::read_inode(ino_t ino, TmpInodeInfo& out) + { + auto inode_location = find_inode(ino); + PageTable::with_fast_page(inode_location.paddr, [&] { + out = PageTable::fast_page_as_sized(inode_location.index); + }); + } + + void TmpFileSystem::write_inode(ino_t ino, const TmpInodeInfo& info) + { + auto inode_location = find_inode(ino); + PageTable::with_fast_page(inode_location.paddr, [&] { + auto& inode_info = PageTable::fast_page_as_sized(inode_location.index); + inode_info = info; + }); + } + + void TmpFileSystem::delete_inode(ino_t ino) + { + auto inode_location = find_inode(ino); + PageTable::with_fast_page(inode_location.paddr, [&] { + auto& inode_info = PageTable::fast_page_as_sized(inode_location.index); + ASSERT_EQ(inode_info.nlink, 0); + for (auto paddr : inode_info.block) + ASSERT_EQ(paddr, 0); + inode_info = {}; + }); + } + + BAN::ErrorOr TmpFileSystem::allocate_inode(const TmpInodeInfo& info) + { + constexpr size_t inodes_per_page = PAGE_SIZE / sizeof(TmpInodeInfo); + + ino_t ino = first_inode; + TRY(for_each_indirect_paddr_allocating(m_inode_pages, [&](paddr_t paddr, bool) { + BAN::Iteration result = BAN::Iteration::Continue; + PageTable::with_fast_page(paddr, [&] { + for (size_t i = 0; i < inodes_per_page; i++, ino++) + { + auto& inode_info = PageTable::fast_page_as_sized(i); + if (inode_info.mode != 0) + continue; + inode_info = info; + result = BAN::Iteration::Break; + return; + } + }); + return result; + }, 2)); + + return ino; + } + + TmpFileSystem::InodeLocation TmpFileSystem::find_inode(ino_t ino) + { + ASSERT_GTE(ino, first_inode); + ASSERT_LT(ino, max_inodes); + + constexpr size_t inodes_per_page = PAGE_SIZE / sizeof(TmpInodeInfo); + + size_t index_of_page = (ino - first_inode) / inodes_per_page; + size_t index_in_page = (ino - first_inode) % inodes_per_page; + + return { + .paddr = find_indirect(m_inode_pages, index_of_page, 2), + .index = index_in_page + }; + } + + void TmpFileSystem::read_block(size_t index, BAN::ByteSpan buffer) + { + ASSERT(buffer.size() >= PAGE_SIZE); + paddr_t block_paddr = find_block(index); + PageTable::with_fast_page(block_paddr, [&] { + memcpy(buffer.data(), PageTable::fast_page_as_ptr(), PAGE_SIZE); + }); + } + + void TmpFileSystem::write_block(size_t index, BAN::ConstByteSpan buffer) + { + ASSERT(buffer.size() >= PAGE_SIZE); + paddr_t block_paddr = find_block(index); + PageTable::with_fast_page(block_paddr, [&] { + memcpy(PageTable::fast_page_as_ptr(), buffer.data(), PAGE_SIZE); + }); + } + + void TmpFileSystem::free_block(size_t index) + { + ASSERT_NOT_REACHED(); + } + + BAN::ErrorOr TmpFileSystem::allocate_block() + { + size_t result = first_data_page; + TRY(for_each_indirect_paddr_allocating(m_data_pages, [&] (paddr_t paddr, bool allocated) { + if (allocated) + return BAN::Iteration::Break; + result++; + return BAN::Iteration::Continue; + }, 3)); + return result; + } + + paddr_t TmpFileSystem::find_block(size_t index) + { + ASSERT_GT(index, 0); + return find_indirect(m_data_pages, index - first_data_page, 3); + } + + paddr_t TmpFileSystem::find_indirect(PageInfo root, size_t index, size_t depth) + { + ASSERT(root.flags() & PageInfo::Flags::Present); + if (depth == 0) + return root.paddr(); + + constexpr size_t addresses_per_page = PAGE_SIZE / sizeof(PageInfo); + + size_t divisor = 1; + for (size_t i = 0; i < depth; i++) + divisor *= addresses_per_page; + + size_t index_of_page = index / divisor; + size_t index_in_page = index % divisor; + + ASSERT(index_of_page < addresses_per_page); + + PageInfo next; + PageTable::with_fast_page(root.paddr(), [&] { + next = PageTable::fast_page_as_sized(index_of_page); + }); + + return find_indirect(next, index_in_page, depth - 1); + } + + template + BAN::ErrorOr TmpFileSystem::for_each_indirect_paddr_allocating_internal(PageInfo page_info, F callback, size_t depth) + { + ASSERT_GT(depth, 0); + ASSERT(page_info.flags() & PageInfo::Flags::Present); + + for (size_t i = 0; i < PAGE_SIZE / sizeof(PageInfo); i++) + { + PageInfo next_info; + PageTable::with_fast_page(page_info.paddr(), [&] { + next_info = PageTable::fast_page_as_sized(i); + }); + + bool allocated = false; + + if (!(next_info.flags() & PageInfo::Flags::Present)) + { + paddr_t new_paddr = Heap::get().take_free_page(); + if (new_paddr == 0) + return BAN::Error::from_errno(ENOMEM); + + PageTable::with_fast_page(new_paddr, [&] { + memset(PageTable::fast_page_as_ptr(), 0x00, PAGE_SIZE); + }); + + next_info.set_paddr(new_paddr); + next_info.set_flags(PageInfo::Flags::Present); + + PageTable::with_fast_page(page_info.paddr(), [&] { + auto& to_update_info = PageTable::fast_page_as_sized(i); + to_update_info = next_info; + }); + + allocated = true; + } + + BAN::Iteration result; + if (depth == 1) + result = callback(next_info.paddr(), allocated); + else + result = TRY(for_each_indirect_paddr_allocating_internal(next_info, callback, depth - 1)); + + switch (result) + { + case BAN::Iteration::Continue: + break; + case BAN::Iteration::Break: + return BAN::Iteration::Break; + default: + ASSERT_NOT_REACHED(); + } + } + + return BAN::Iteration::Continue; + } + + template + BAN::ErrorOr TmpFileSystem::for_each_indirect_paddr_allocating(PageInfo page_info, F callback, size_t depth) + { + BAN::Iteration result = TRY(for_each_indirect_paddr_allocating_internal(page_info, callback, depth)); + ASSERT(result == BAN::Iteration::Break); + return {}; + } + +} diff --git a/kernel/kernel/FS/TmpFS/Inode.cpp b/kernel/kernel/FS/TmpFS/Inode.cpp new file mode 100644 index 000000000..c7f293d93 --- /dev/null +++ b/kernel/kernel/FS/TmpFS/Inode.cpp @@ -0,0 +1,303 @@ +#include +#include +#include + +namespace Kernel +{ + + static TmpInodeInfo create_inode_info(mode_t mode, uid_t uid, gid_t gid) + { + auto current_time = SystemTimer::get().real_time(); + + TmpInodeInfo info; + info.uid = uid; + info.gid = gid; + info.mode = mode; + info.atime = current_time; + info.mtime = current_time; + info.ctime = current_time; + + return info; + } + + static uint8_t inode_mode_to_dt_type(Inode::Mode mode) + { + if (mode.ifreg()) + return DT_REG; + if (mode.ifdir()) + return DT_DIR; + if (mode.ifchr()) + return DT_CHR; + if (mode.ifblk()) + return DT_BLK; + if (mode.ififo()) + return DT_FIFO; + if (mode.ifsock()) + return DT_SOCK; + if (mode.iflnk()) + return DT_LNK; + ASSERT_NOT_REACHED(); + } + + /* GENERAL INODE */ + + BAN::ErrorOr> TmpInode::create_from_existing(TmpFileSystem& fs, ino_t ino, const TmpInodeInfo& info) + { + TmpInode* inode_ptr = nullptr; + switch (info.mode & Mode::TYPE_MASK) + { + case Mode::IFDIR: + inode_ptr = new TmpDirectoryInode(fs, ino, info); + break; + case Mode::IFREG: + inode_ptr = new TmpFileInode(fs, ino, info); + break; + default: + ASSERT_NOT_REACHED(); + } + if (inode_ptr == nullptr) + return BAN::Error::from_errno(ENOMEM); + return BAN::RefPtr::adopt(inode_ptr); + } + + TmpInode::TmpInode(TmpFileSystem& fs, ino_t ino, const TmpInodeInfo& info) + : m_fs(fs) + , m_inode_info(info) + , m_ino(ino) + {} + + void TmpInode::sync() + { + m_fs.write_inode(m_ino, m_inode_info); + } + + void TmpInode::free_all_blocks() + { + for (auto block : m_inode_info.block) + ASSERT(block == 0); + } + + size_t TmpInode::block_index(size_t data_block_index) + { + ASSERT(data_block_index < TmpInodeInfo::direct_block_count); + ASSERT(m_inode_info.block[data_block_index]); + return m_inode_info.block[data_block_index]; + } + + BAN::ErrorOr TmpInode::block_index_with_allocation(size_t data_block_index) + { + if (data_block_index >= TmpInodeInfo::direct_block_count) + { + dprintln("only {} blocks supported :D", TmpInodeInfo::direct_block_count); + return BAN::Error::from_errno(ENOSPC); + } + if (m_inode_info.block[data_block_index] == 0) + { + m_inode_info.block[data_block_index] = TRY(m_fs.allocate_block()); + m_inode_info.blocks++; + } + return m_inode_info.block[data_block_index]; + } + + /* FILE INODE */ + + BAN::ErrorOr> TmpFileInode::create(TmpFileSystem& fs, mode_t mode, uid_t uid, gid_t gid) + { + auto info = create_inode_info(Mode::IFREG | mode, uid, gid); + ino_t ino = TRY(fs.allocate_inode(info)); + + auto* inode_ptr = new TmpFileInode(fs, ino, info); + if (inode_ptr == nullptr) + return BAN::Error::from_errno(ENOMEM); + + return BAN::RefPtr::adopt(inode_ptr); + } + + TmpFileInode::TmpFileInode(TmpFileSystem& fs, ino_t ino, const TmpInodeInfo& info) + : TmpInode(fs, ino, info) + { + ASSERT(mode().ifreg()); + } + + TmpFileInode::~TmpFileInode() + { + if (nlink() > 0) + { + sync(); + return; + } + free_all_blocks(); + m_fs.delete_inode(ino()); + } + + /* DIRECTORY INODE */ + + BAN::ErrorOr> TmpDirectoryInode::create_root(TmpFileSystem& fs, mode_t mode, uid_t uid, gid_t gid) + { + auto info = create_inode_info(Mode::IFDIR | mode, uid, gid); + ino_t ino = TRY(fs.allocate_inode(info)); + + auto* inode_ptr = new TmpDirectoryInode(fs, ino, info); + if (inode_ptr == nullptr) + return BAN::Error::from_errno(ENOMEM); + + auto inode = BAN::RefPtr::adopt(inode_ptr); + TRY(inode->link_inode(*inode, "."sv)); + TRY(inode->link_inode(*inode, ".."sv)); + + return inode; + } + + BAN::ErrorOr> TmpDirectoryInode::create_new(TmpFileSystem& fs, mode_t mode, uid_t uid, gid_t gid, TmpInode& parent) + { + auto info = create_inode_info(Mode::IFDIR | mode, uid, gid); + ino_t ino = TRY(fs.allocate_inode(info)); + + auto* inode_ptr = new TmpDirectoryInode(fs, ino, info); + if (inode_ptr == nullptr) + return BAN::Error::from_errno(ENOMEM); + + auto inode = BAN::RefPtr::adopt(inode_ptr); + TRY(inode->link_inode(*inode, "."sv)); + TRY(inode->link_inode(parent, "."sv)); + + return inode; + } + + TmpDirectoryInode::TmpDirectoryInode(TmpFileSystem& fs, ino_t ino, const TmpInodeInfo& info) + : TmpInode(fs, ino, info) + { + ASSERT(mode().ifdir()); + } + + TmpDirectoryInode::~TmpDirectoryInode() + { + if (nlink() >= 2) + { + sync(); + return; + } + free_all_blocks(); + m_fs.delete_inode(ino()); + } + + BAN::ErrorOr> TmpDirectoryInode::find_inode_impl(BAN::StringView name) + { + ino_t result = 0; + + for_each_entry([&](const TmpDirectoryEntry& entry) { + if (entry.type == DT_UNKNOWN) + return BAN::Iteration::Continue; + if (entry.name_sv() != name) + return BAN::Iteration::Continue; + result = entry.ino; + return BAN::Iteration::Break; + }); + + if (result == 0) + return BAN::Error::from_errno(ENOENT); + + auto inode = TRY(m_fs.open_inode(result)); + return BAN::RefPtr(inode); + } + + BAN::ErrorOr TmpDirectoryInode::list_next_inodes_impl(off_t, DirectoryEntryList*, size_t) + { + return BAN::Error::from_errno(ENOTSUP); + } + + BAN::ErrorOr TmpDirectoryInode::create_file_impl(BAN::StringView name, mode_t mode, uid_t uid, gid_t gid) + { + auto new_inode = TRY(TmpFileInode::create(m_fs, mode, uid, gid)); + TRY(link_inode(*new_inode, name)); + return {}; + } + + BAN::ErrorOr TmpDirectoryInode::create_directory_impl(BAN::StringView name, mode_t mode, uid_t uid, gid_t gid) + { + auto new_inode = TRY(TmpDirectoryInode::create_new(m_fs, mode, uid, gid, *this)); + TRY(link_inode(*new_inode, name)); + return {}; + } + + BAN::ErrorOr TmpDirectoryInode::unlink_impl(BAN::StringView) + { + return BAN::Error::from_errno(ENOTSUP); + } + + BAN::ErrorOr TmpDirectoryInode::link_inode(TmpInode& inode, BAN::StringView name) + { + static constexpr size_t directory_entry_alignment = 16; + + size_t current_size = size(); + + size_t new_entry_size = sizeof(TmpDirectoryEntry) + name.size(); + if (auto rem = new_entry_size % directory_entry_alignment) + new_entry_size += directory_entry_alignment - rem; + ASSERT(new_entry_size < (size_t)blksize()); + + size_t new_entry_offset = current_size % blksize(); + + // Target is the last block, or if it doesn't fit the new entry, the next one. + size_t target_data_block = current_size / blksize(); + if (blksize() - new_entry_offset < new_entry_size) + target_data_block++; + + size_t block_index = TRY(block_index_with_allocation(target_data_block)); + + BAN::Vector buffer; + TRY(buffer.resize(blksize())); + + BAN::ByteSpan bytespan = buffer.span(); + + m_fs.read_block(block_index, bytespan); + + auto& new_entry = bytespan.slice(new_entry_offset).as(); + new_entry.type = inode_mode_to_dt_type(inode.mode()); + new_entry.ino = inode.ino(); + new_entry.name_len = name.size(); + new_entry.rec_len = new_entry_size; + memcpy(new_entry.name, name.data(), name.size()); + + m_fs.write_block(block_index, bytespan); + + // increase current size + m_inode_info.size += new_entry_size; + + // add link to linked inode + inode.m_inode_info.nlink++; + + return {}; + } + + template + void TmpDirectoryInode::for_each_entry(F callback) + { + size_t full_offset = 0; + while (full_offset < (size_t)size()) + { + size_t data_block_index = full_offset / blksize(); + size_t block_index = this->block_index(data_block_index); + + // FIXME: implement fast heap pages? + BAN::Vector buffer; + MUST(buffer.resize(blksize())); + + BAN::ByteSpan bytespan = buffer.span(); + m_fs.read_block(block_index, bytespan); + + size_t byte_count = BAN::Math::min(blksize(), size() - full_offset); + + bytespan = bytespan.slice(0, byte_count); + while (bytespan.size() > 0) + { + auto& entry = bytespan.as(); + callback(entry); + bytespan = bytespan.slice(entry.rec_len); + } + + full_offset += blksize(); + } + } + +}