diff options
| author | Marcel Braun <marcel.braun@ost.ch> | 2026-04-12 19:15:38 +0200 |
|---|---|---|
| committer | Marcel Braun <marcel.braun@ost.ch> | 2026-04-12 19:15:38 +0200 |
| commit | 4d2a1d028f8ba28b655026b93124e71a12562619 (patch) | |
| tree | f49deef4dd3e8728fd1000b04c0908966f37663f | |
| parent | 21fd1281cf19572e202d583689b99c33ec68da50 (diff) | |
| parent | cb7edbe6d4454ee5b217b522f62f4a7b92475a32 (diff) | |
| download | kernel-4d2a1d028f8ba28b655026b93124e71a12562619.tar.xz kernel-4d2a1d028f8ba28b655026b93124e71a12562619.zip | |
Merge branch 'ext2' into 'develop-BA-FS26'
ext2 and tests
See merge request teachos/kernel!22
85 files changed, 3533 insertions, 182 deletions
diff --git a/.devcontainer/x86-64/devcontainer.json b/.devcontainer/x86-64/devcontainer.json index 00e33ad..6bf1616 100644 --- a/.devcontainer/x86-64/devcontainer.json +++ b/.devcontainer/x86-64/devcontainer.json @@ -5,7 +5,7 @@ "ghcr.io/devcontainers/features/git:1": {}, "ghcr.io/devcontainers/features/git-lfs:1": {}, "ghcr.io/devcontainers-extra/features/apt-packages:1": { - "packages": "build-essential,clang-tidy,clangd,cmake,grub2-common,grub-pc,mtools,ninja-build,qemu-system-x86,ssh,xorriso" + "packages": "build-essential,clang-tidy,clangd,cmake,grub2-common,grub-pc,mtools,ninja-build,qemu-system-x86,ssh,xorriso,gdb" } }, "customizations": { @@ -17,10 +17,11 @@ "matepek.vscode-catch2-test-adapter", "ms-vscode.cmake-tools", "KylinIdeTeam.cppdebug", - "zixuanwang.linkerscript" + "zixuanwang.linkerscript", + "ms-vscode.hexeditor" ] } }, "remoteUser": "ubuntu", "updateRemoteUserUID": true -} +}
\ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..5713ac7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +*.img filter=lfs diff=lfs merge=lfs -text diff --git a/arch/x86_64/support/grub.cfg.in b/arch/x86_64/support/grub.cfg.in index 834345a..45c3356 100644 --- a/arch/x86_64/support/grub.cfg.in +++ b/arch/x86_64/support/grub.cfg.in @@ -3,7 +3,8 @@ default=0 menuentry "TeachOS" { multiboot2 /$<TARGET_FILE_NAME:kernel> - module2 /modules/test.img bbbbbbb - module2 /modules/test.img aaaa + module2 /modules/ext2_4KB_fs.img + module2 /modules/ext2_1KB_fs.img + module2 /modules/ext2_2KB_fs.img boot }
\ No newline at end of file diff --git a/arch/x86_64/support/modules/README.md b/arch/x86_64/support/modules/README.md new file mode 100644 index 0000000..f3955fa --- /dev/null +++ b/arch/x86_64/support/modules/README.md @@ -0,0 +1,82 @@ +# Default images +The default images contain predefined data and structures that are specifically designed for testing purposes. +The ext2_4KB_fs image is intentionally fragmented, as some files were created and deleted before additional files were added, resulting in a non-contiguous layout. + +## ext2_1KB_fs +. +./lost+found +./information +./information/info_1.txt +./information/info_2.txt +./closed.txt + +## ext2_2KB_fs +. +./lost+found +./monkey_house +./monkey_house/infrastructure +./monkey_house/infrastructure/info.txt +./monkey_house/infrastructure/water.txt +./monkey_house/monkey_1.txt +./monkey_house/monkey_2.txt +./monkey_house/monkey_3.txt +./monkey_house/caretaker +./monkey_house/caretaker/isabelle.txt +./monkey_house/caretaker/peter.txt + +## ext2_4KB_fs +. +./lost+found +./entrance +./entrance/tickets.txt +./entrance/map.txt +./enclosures +./enclosures/lion_house +./enclosures/lion_house/cage_a +./enclosures/lion_house/cage_a/history.txt +./enclosures/lion_house/cage_a/animals.txt +./enclosures/lion_house/cage_b +./enclosures/lion_house/cage_b/animals.txt +./enclosures/lion_house/cage_b/history.txt +./enclosures/elephant_house +./enclosures/elephant_house/elephant_1.txt +./enclosures/aquarium +./enclosures/aquarium/tank_1 +./enclosures/aquarium/tank_1/fish_1.txt +./enclosures/aquarium/tank_1/fish_2.txt +./enclosures/aquarium/tank_1/fish_3.txt +./enclosures/aquarium/tank_1/fish_4.txt +./enclosures/aquarium/tank_2 +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_1.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_2.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_3.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_4.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_5.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_6.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_7.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_8.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_9.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_10.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_11.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_12.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_13.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_14.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_15.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_16.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_17.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_18.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_19.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_20.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_21.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_22.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_23.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_24.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_25.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_26.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_27.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_28.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_29.txt +./enclosures/aquarium/tank_2/this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_30.txt +./enclosures/aquarium/spawn_fish.sh +./enclosures/info.txt +./info.txt diff --git a/arch/x86_64/support/modules/ext2_1KB_fs.img b/arch/x86_64/support/modules/ext2_1KB_fs.img new file mode 100644 index 0000000..9f1ee4a --- /dev/null +++ b/arch/x86_64/support/modules/ext2_1KB_fs.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94d3988bc309eb9e81f06042c72bf4c4fb5991cd7fdd597eb00c518a96c792d8 +size 10485760 diff --git a/arch/x86_64/support/modules/ext2_2KB_fs.img b/arch/x86_64/support/modules/ext2_2KB_fs.img new file mode 100644 index 0000000..1880911 --- /dev/null +++ b/arch/x86_64/support/modules/ext2_2KB_fs.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a13da5abb9c65c737105b1da0d4344c7cd7604c7952c762c4f4e3d3f96fd42d +size 5242880 diff --git a/arch/x86_64/support/modules/ext2_4KB_fs.img b/arch/x86_64/support/modules/ext2_4KB_fs.img new file mode 100644 index 0000000..3aaceb8 --- /dev/null +++ b/arch/x86_64/support/modules/ext2_4KB_fs.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ce6a1aea277906e1af6de223c017ff900b96569f076b4d99fc04eaa1ee986f4 +size 10485760 diff --git a/arch/x86_64/support/modules/test.img b/arch/x86_64/support/modules/test.img Binary files differdeleted file mode 100644 index 914fa7f..0000000 --- a/arch/x86_64/support/modules/test.img +++ /dev/null diff --git a/cmake/Modules/GenerateBootableIso.cmake b/cmake/Modules/GenerateBootableIso.cmake index b798787..39a0ebd 100644 --- a/cmake/Modules/GenerateBootableIso.cmake +++ b/cmake/Modules/GenerateBootableIso.cmake @@ -3,6 +3,14 @@ include_guard(GLOBAL) function(target_generate_bootable_iso TARGET) find_package("grub-mkrescue") + set(MODULES_DIR "${PROJECT_SOURCE_DIR}/arch/${CMAKE_SYSTEM_PROCESSOR}/support/modules") + set(COPY_MODULES_COMMAND) + if(EXISTS "${MODULES_DIR}") + set(COPY_MODULES_COMMAND + COMMAND "${CMAKE_COMMAND}" -E copy_directory "${MODULES_DIR}" "$<TARGET_FILE_DIR:${TARGET}>/isofs/modules" + ) + endif() + file(GENERATE OUTPUT "$<TARGET_FILE_DIR:${TARGET}>/isofs/boot/grub/grub.cfg" INPUT "${PROJECT_SOURCE_DIR}/arch/${CMAKE_SYSTEM_PROCESSOR}/support/grub.cfg.in" @@ -11,7 +19,7 @@ function(target_generate_bootable_iso TARGET) add_custom_command(TARGET "${TARGET}" POST_BUILD COMMAND "${CMAKE_COMMAND}" -E make_directory "$<TARGET_FILE_DIR:${TARGET}>/isofs" - COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/arch/${CMAKE_SYSTEM_PROCESSOR}/support/modules" "$<TARGET_FILE_DIR:${TARGET}>/isofs/modules" + ${COPY_MODULES_COMMAND} COMMAND "${GRUB_MKRESCUE_EXE}" "-o" "$<TARGET_FILE_DIR:${TARGET}>/${TARGET}.iso" diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index 74233cb..2f6113a 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -101,7 +101,13 @@ else() "src/test_support/kapi/cio.cpp" "src/test_support/kapi/interrupts.cpp" "src/test_support/kapi/memory.cpp" - "src/test_support/log_buffer.cpp" + "src/test_support/devices/block_device.cpp" + "src/test_support/devices/character_device.cpp" + "src/test_support/filesystem/inode.cpp" + "src/test_support/filesystem/filesystem.cpp" + "src/test_support/filesystem/ext2.cpp" + "src/test_support/filesystem/storage_boot_module_fixture.cpp" + "src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp" "src/test_support/log_buffer.cpp" "src/test_support/output_device.cpp" "src/test_support/page_mapper.cpp" "src/test_support/simulated_memory.cpp" @@ -126,7 +132,24 @@ else() "src/memory/bitmap_allocator.tests.cpp" "src/memory/block_list_allocator.tests.cpp" + # Filesystem Subsystem Tests + "src/filesystem/devfs/filesystem.tests.cpp" + "src/filesystem/devfs/inode.tests.cpp" + "src/filesystem/ext2/filesystem.tests.cpp" + "src/filesystem/ext2/inode.tests.cpp" + "src/filesystem/rootfs/filesystem.tests.cpp" + "src/filesystem/rootfs/inode.tests.cpp" + "src/filesystem/dentry.tests.cpp" + "src/filesystem/device_inode.tests.cpp" + "src/filesystem/file_descriptor_table.tests.cpp" + "src/filesystem/mount_table.tests.cpp" + "src/filesystem/mount.tests.cpp" + "src/filesystem/open_file_description.tests.cpp" + "src/filesystem/vfs.tests.cpp" + # Storage Subsystem Tests + "src/devices/block_device_utils.tests.cpp" + "src/devices/block_device.tests.cpp" "src/devices/storage/ram_disk/device.tests.cpp" ) add_executable("os::kernel_tests" ALIAS "kernel_tests") @@ -136,6 +159,10 @@ else() "os::kernel_test_support" ) + target_compile_definitions("kernel_tests" PRIVATE + KERNEL_TEST_ASSETS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/src/test_support/filesystem/test_assets" + ) + set_target_properties("kernel_tests" PROPERTIES C_CLANG_TIDY "" CXX_CLANG_TIDY "" diff --git a/kernel/include/kernel/devices/block_device_utils.hpp b/kernel/include/kernel/devices/block_device_utils.hpp index 5e862ba..7b1daec 100644 --- a/kernel/include/kernel/devices/block_device_utils.hpp +++ b/kernel/include/kernel/devices/block_device_utils.hpp @@ -9,7 +9,34 @@ namespace kernel::devices::block_device_utils { - auto read(kstd::shared_ptr<kapi::devices::device> const & device, void * buffer, size_t offset, size_t size) -> size_t; + /** + @brief Utility functions for block devices, such as reading/writing data at specific offsets. These functions handle + the necessary logic to interact with block devices, such as calculating block boundaries and ensuring proper access + patterns. They abstract away the details of block device interactions, providing a simple interface for reading and + writing data to block devices. + */ + + /** + @brief Reads data from a @p device into a @p buffer, starting at a specific @p offset and for a given @p size. + @warning Panics if @p buffer or @p device is null. + @param device The block device to read from. + @param buffer The buffer to read data into. + @param offset The offset on the block device to start reading from. + @param size The number of bytes to read. + @return The number of bytes actually read, which may be less than the requested size. + */ + auto read(kstd::shared_ptr<kapi::devices::device> const & device, void * buffer, size_t offset, size_t size) + -> size_t; + + /** + @brief Writes data from a @p buffer to a @p device, starting at a specific @p offset and for a given @p size. + @warning Panics if @p buffer or @p device is null. + @param device The block device to write to. + @param buffer The buffer to write data from. + @param offset The offset on the block device to start writing to. + @param size The number of bytes to write. + @return The number of bytes actually written, which may be less than the requested size. + */ auto write(kstd::shared_ptr<kapi::devices::device> const & device, void const * buffer, size_t offset, size_t size) -> size_t; } // namespace kernel::devices::block_device_utils diff --git a/kernel/include/kernel/filesystem/dentry.hpp b/kernel/include/kernel/filesystem/dentry.hpp index fc85a7d..58a918f 100644 --- a/kernel/include/kernel/filesystem/dentry.hpp +++ b/kernel/include/kernel/filesystem/dentry.hpp @@ -12,23 +12,78 @@ namespace kernel::filesystem { + /** + @brief Represents a directory entry (dentry) in the filesystem. A dentry is a node in the directory tree that + represents a file or directory. It contains a reference to its parent dentry, a reference to the associated real + filesystem inode, and a list of child dentries. + */ struct dentry { + /** + @brief Flags for the dentry. + */ enum class dentry_flags : uint32_t { dcache_mounted = 1 << 15 }; - dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & node, std::string_view name = {}); + /** + @brief Create a dentry with the given @p parent, associated @p inode, and optional @p name. The dentry is + initialized with the provided parent and inode, and the name is stored for lookup purposes. + @param parent The parent dentry. + @param inode The associated inode for this dentry. + @param name The name of the dentry (optional). + */ + dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & inode, std::string_view name = {}); + /** + @brief Get the associated inode. + @return A reference to the associated inode. + */ [[nodiscard]] auto get_inode() const -> kstd::shared_ptr<inode> const &; + + /** + @brief Get the parent dentry. + @return A reference to the parent dentry. + */ [[nodiscard]] auto get_parent() const -> kstd::shared_ptr<dentry> const &; + /** + @brief Get the name of the dentry. + @return The name of the dentry. + */ + [[nodiscard]] auto get_name() const -> std::string_view; + + /** + @brief Add a @p child dentry. + @param child The child dentry to add. + */ auto add_child(kstd::shared_ptr<dentry> const & child) -> void; + + /** + @brief Find a child dentry by @p name. + @param name The name of the child dentry to find. + @return A pointer to the found child dentry, or a null pointer if not found. + */ [[nodiscard]] auto find_child(std::string_view name) const -> kstd::shared_ptr<dentry>; + /** + @brief Set a @p flag for the dentry. + @param flag The flag to set. + */ auto set_flag(dentry_flags flag) -> void; + + /** + @brief Unset a @p flag for the dentry. + @param flag The flag to unset. + */ auto unset_flag(dentry_flags flag) -> void; + + /** + @brief Check if the dentry has a specific @p flag. + @param flag The flag to check. + @return True if the dentry has the flag, false otherwise. + */ [[nodiscard]] auto has_flag(dentry_flags flag) const -> bool; private: diff --git a/kernel/include/kernel/filesystem/devfs/filesystem.hpp b/kernel/include/kernel/filesystem/devfs/filesystem.hpp index 29ae388..137eca3 100644 --- a/kernel/include/kernel/filesystem/devfs/filesystem.hpp +++ b/kernel/include/kernel/filesystem/devfs/filesystem.hpp @@ -2,6 +2,7 @@ #define TEACH_OS_KERNEL_FILESYSTEM_DEVFS_FILESYSTEM_HPP #include "kapi/devices/device.hpp" + #include "kernel/filesystem/filesystem.hpp" #include "kernel/filesystem/inode.hpp" @@ -12,9 +13,27 @@ namespace kernel::filesystem::devfs { + /** + @brief A filesystem for managing device nodes in the virtual filesystem. This filesystem provides a way to represent + devices as files in the /dev directory, allowing user-space applications to interact with devices using standard file + operations. The devfs filesystem dynamically creates inodes for devices registered in the system, enabling seamless + access to device functionality through the filesystem interface. + */ struct filesystem : kernel::filesystem::filesystem { - auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int override; + /** + @brief Initializes the devfs instance and builds the device inode table. + @param device Backing device passed by the generic filesystem interface (not required by devfs). + @return The result of the mount operation. + */ + auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result override; + + /** + @brief Looks up an inode by @p name within a @p parent directory. + @param parent The parent directory inode. + @param name The name of the inode to look up. + @return A pointer to the found inode, or a null pointer if not found. + */ auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) -> kstd::shared_ptr<kernel::filesystem::inode> override; diff --git a/kernel/include/kernel/filesystem/devfs/inode.hpp b/kernel/include/kernel/filesystem/devfs/inode.hpp index 9c11edf..c117bd2 100644 --- a/kernel/include/kernel/filesystem/devfs/inode.hpp +++ b/kernel/include/kernel/filesystem/devfs/inode.hpp @@ -7,11 +7,33 @@ namespace kernel::filesystem::devfs { + /** + @brief Inode implementation for the devfs filesystem. + This inode represents root device node in the /dev directory. + */ struct inode : kernel::filesystem::inode { + /** + @brief Create a devfs inode. The inode is initialized with the appropriate kind (directory). + */ inode(); + /** + @brief Reads from the devfs directory inode. + @param buffer Destination buffer. + @param offset Read offset in bytes. + @param size Number of bytes requested. + @return Number of bytes read (always 0 because this inode does not expose file data). + */ auto read(void * buffer, size_t offset, size_t size) const -> size_t override; + + /** + @brief Writes to the devfs directory inode. + @param buffer Source buffer. + @param offset Write offset in bytes. + @param size Number of bytes requested. + @return Number of bytes written (always 0 because writes are not supported for this inode). + */ auto write(void const * buffer, size_t offset, size_t size) -> size_t override; }; } // namespace kernel::filesystem::devfs diff --git a/kernel/include/kernel/filesystem/device_inode.hpp b/kernel/include/kernel/filesystem/device_inode.hpp index 18a98f5..c33be2a 100644 --- a/kernel/include/kernel/filesystem/device_inode.hpp +++ b/kernel/include/kernel/filesystem/device_inode.hpp @@ -2,6 +2,7 @@ #define TEACH_OS_KERNEL_FILESYSTEM_DEVICE_INODE_HPP #include "kapi/devices/device.hpp" + #include "kernel/filesystem/inode.hpp" #include <kstd/memory> @@ -10,13 +11,43 @@ namespace kernel::filesystem { + /** + @brief Inode implementation for device inodes in the filesystem. This inode represents a device file that provides + access to a device registered in the system. The device inode allows reading from and writing to the associated + device. + */ struct device_inode : inode { + /** + @brief Create a device inode with the given @p device. + @param device The device to associate with the inode. + */ explicit device_inode(kstd::shared_ptr<kapi::devices::device> const & device); + /** + @brief Read data from the device inode (and in the background from the associated device) into a @p buffer, starting + at @p offset and reading @p size bytes. + @param buffer The buffer to read data into. + @param offset The offset to read from. + @param size The number of bytes to read. + @return The number of bytes read. + */ auto read(void * buffer, size_t offset, size_t size) const -> size_t override; + + /** + @brief Write data to the device inode (and in the background from the associated device) from a @p buffer, starting + at @p offset and writing @p size bytes. + @param buffer The buffer containing data to write. + @param offset The offset to write to. + @param size The number of bytes to write. + @return The number of bytes written. + */ auto write(void const * buffer, size_t offset, size_t size) -> size_t override; + /** + @brief Get the associated device. + @return A reference to the associated device. + */ [[nodiscard]] auto device() const -> kstd::shared_ptr<kapi::devices::device> const &; private: diff --git a/kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp b/kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp index a23c045..7fbba3f 100644 --- a/kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp +++ b/kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp @@ -6,7 +6,10 @@ namespace kernel::filesystem::ext2 { - struct block_group_descriptor + /** + @brief Represents a block group descriptor in the ext2 filesystem. + */ + struct [[gnu::packed]] block_group_descriptor { uint32_t block_bitmap; uint32_t inode_bitmap; diff --git a/kernel/include/kernel/filesystem/ext2/filesystem.hpp b/kernel/include/kernel/filesystem/ext2/filesystem.hpp index 078da31..a71385f 100644 --- a/kernel/include/kernel/filesystem/ext2/filesystem.hpp +++ b/kernel/include/kernel/filesystem/ext2/filesystem.hpp @@ -2,20 +2,88 @@ #define TEACH_OS_KERNEL_FILESYSTEM_EXT2_FILESYSTEM_HPP #include "kapi/devices/device.hpp" + +#include "kernel/filesystem/ext2/block_group_descriptor.hpp" +#include "kernel/filesystem/ext2/inode.hpp" +#include "kernel/filesystem/ext2/superblock.hpp" #include "kernel/filesystem/filesystem.hpp" #include "kernel/filesystem/inode.hpp" #include <kstd/memory> +#include <kstd/vector> +#include <cstddef> +#include <cstdint> #include <string_view> namespace kernel::filesystem::ext2 { + /** + @brief Constants related to the ext2 filesystem. + */ + namespace constants + { + constexpr size_t inline base_block_size = 1024; + constexpr size_t inline superblock_offset = base_block_size; + constexpr uint16_t inline magic_number = 0xEF53; + + constexpr uint32_t inline root_inode_number = 2; + + constexpr size_t inline direct_block_count = 12; + constexpr size_t inline singly_indirect_block_index = direct_block_count; + constexpr size_t inline doubly_indirect_block_index = singly_indirect_block_index + 1; + constexpr size_t inline triply_indirect_block_index = doubly_indirect_block_index + 1; + + constexpr uint16_t inline mode_mask = 0xF000; + constexpr uint16_t inline mode_regular = 0x8000; + constexpr uint16_t inline mode_directory = 0x4000; + } // namespace constants + + /** + @brief A filesystem implementation for the ext2 filesystem format. This class provides methods for mounting an ext2 + filesystem, and looking up inodes. + */ struct filesystem : kernel::filesystem::filesystem { - auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int override; + /** + @brief Initializes the ext2 filesystem with the given @p device. + @param device The device to mount. + @return The result of the mount operation. + */ + auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result override; + + /** + @brief Looks up an inode by @p name within a @p parent directory. + @param parent The parent directory inode. + @param name The name of the inode to look up. + @return A pointer to the found inode, or a null pointer if not found. + */ auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) -> kstd::shared_ptr<kernel::filesystem::inode> override; + + /** + @brief Gets the size of a block in the filesystem. + @return The size of a block in bytes. + */ + auto get_block_size() -> size_t; + + /** + @brief Maps an inode block index to a global block number. + @param inode_block_index The index of the block within the inode. + @param data The inode data. + @return The global block number. + */ + auto map_inode_block_index_to_global_block_number(uint32_t inode_block_index, inode_data data) -> uint32_t; + + private: + auto read_inode(uint32_t inode_number) -> kstd::shared_ptr<kernel::filesystem::ext2::inode>; + auto read_block_number_at_index(uint32_t block_number, uint32_t index) -> uint32_t; + + auto get_inode_size() -> size_t; + auto get_inode_block_count(inode_data const & data) -> uint32_t; + + superblock m_superblock{}; + kstd::vector<block_group_descriptor> m_block_group_descriptors; }; } // namespace kernel::filesystem::ext2 diff --git a/kernel/include/kernel/filesystem/ext2/inode.hpp b/kernel/include/kernel/filesystem/ext2/inode.hpp index 2c27c17..a1645cd 100644 --- a/kernel/include/kernel/filesystem/ext2/inode.hpp +++ b/kernel/include/kernel/filesystem/ext2/inode.hpp @@ -11,13 +11,13 @@ namespace kernel::filesystem::ext2 { - struct inode : kernel::filesystem::inode - { - inode(); - - auto read(void * buffer, size_t offset, size_t size) const -> size_t override; - auto write(void const * buffer, size_t offset, size_t size) -> size_t override; + struct filesystem; + /** + @brief Represents the data associated with an ext2 inode. + */ + struct [[gnu::packed]] inode_data + { uint16_t mode; uint16_t uid; uint32_t size; @@ -30,15 +30,48 @@ namespace kernel::filesystem::ext2 uint32_t blocks; uint32_t flags; uint32_t osd1; - // uint32_t block[15]; // TODO BA-FS26 really correct? std::array<uint32_t, 15> block; // NOLINT(readability-magic-numbers) uint32_t generation; uint32_t file_acl; uint32_t dir_acl; uint32_t faddr; - // uint8_t osd2[12]; // TODO BA-FS26 really correct? std::array<uint8_t, 12> osd2; // NOLINT(readability-magic-numbers) }; + + struct inode : kernel::filesystem::inode + { + /** + @brief Create an ext2 inode associated with the given filesystem. + */ + explicit inode(filesystem * fs); + + /** + @brief Reads from the ext2 inode into a @p buffer, starting at the specified @p offset and for a given @p size. + @param buffer Destination buffer. + @param offset Read offset in bytes. + @param size Number of bytes requested. + @return Number of bytes read. + */ + auto read(void * buffer, size_t offset, size_t size) const -> size_t override; + + /** + @brief Writes to the ext2 inode into a @p buffer, starting at the specified @p offset and for a given @p size. + @warning This method is not implemented yet and will panic if called. + @param buffer Source buffer. + @param offset Write offset in bytes. + @param size Number of bytes requested. + @return Number of bytes written. + */ + auto write(void const * buffer, size_t offset, size_t size) -> size_t override; + + /** + @brief The raw inode data as read from the disk. + */ + inode_data m_data{}; + + private: + filesystem * m_filesystem; + }; } // namespace kernel::filesystem::ext2 #endif
\ No newline at end of file diff --git a/kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp b/kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp index 8dd42a1..4097cbb 100644 --- a/kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp +++ b/kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp @@ -6,14 +6,16 @@ namespace kernel::filesystem::ext2 { - struct linked_directory_entry + /** + @brief Represents a linked directory entry in the ext2 filesystem. + */ + struct [[gnu::packed]] linked_directory_entry { uint32_t inode; uint16_t rec_len; uint8_t name_len; uint8_t file_type; - uint8_t pad; - std::array<uint8_t, 255> name; // NOLINT(readability-magic-numbers) + std::array<char, 255> name; // NOLINT(readability-magic-numbers) }; } // namespace kernel::filesystem::ext2 diff --git a/kernel/include/kernel/filesystem/ext2/superblock.hpp b/kernel/include/kernel/filesystem/ext2/superblock.hpp index 8600b4c..41ad935 100644 --- a/kernel/include/kernel/filesystem/ext2/superblock.hpp +++ b/kernel/include/kernel/filesystem/ext2/superblock.hpp @@ -6,7 +6,10 @@ namespace kernel::filesystem::ext2 { - struct superblock + /** + @brief Represents the superblock in the ext2 filesystem. + */ + struct [[gnu::packed]] superblock { uint32_t inodes_count; uint32_t blocks_count; @@ -41,11 +44,8 @@ namespace kernel::filesystem::ext2 uint32_t feature_compat; uint32_t feature_incompat; uint32_t feature_ro_compat; - // uint8_t uuid[16]; // TODO BA-FS26 really correct? std::array<uint8_t, 16> uuid; - // uint8_t volume_name[16]; // TODO BA-FS26 really correct? std::array<uint8_t, 16> volume_name; - // uint8_t last_mounted[64]; // TODO BA-FS26 really correct? std::array<uint8_t, 64> last_mounted; uint32_t algorithm_usage_bitmap; @@ -55,14 +55,12 @@ namespace kernel::filesystem::ext2 uint16_t padding1; // Journaling Support - // uint8_t journal_uuid[16]; // TODO BA-FS26 really correct? std::array<uint8_t, 16> journal_uuid; uint32_t journal_inum; uint32_t journal_dev; uint32_t last_orphan; // Directory Indexing Support - // uint32_t hash_seed[4]; // TODO BA-FS26 really correct? std::array<uint32_t, 4> hash_seed; uint8_t def_hash_version; std::array<uint8_t, 3> padding2; diff --git a/kernel/include/kernel/filesystem/file_descriptor_table.hpp b/kernel/include/kernel/filesystem/file_descriptor_table.hpp index 91e2960..5d52c19 100644 --- a/kernel/include/kernel/filesystem/file_descriptor_table.hpp +++ b/kernel/include/kernel/filesystem/file_descriptor_table.hpp @@ -8,15 +8,49 @@ namespace kernel::filesystem { + /** + @brief A table for managing file descriptors in the filesystem. This class provides methods for adding, retrieving, + and removing open file descriptions. + */ struct file_descriptor_table { + /** + @brief Initialize the global file descriptor table. This method creates the singleton instance of the file + descriptor table. + @warning Panics if called more than once. + */ auto static init() -> void; + + /** + @brief Get the global file descriptor table instance. + @return A reference to the global file descriptor table. + @warning Panics if the file descriptor table has not been initialized. + */ auto static get() -> file_descriptor_table &; + /** + @brief Destructor for the file descriptor table. + */ ~file_descriptor_table() = default; + /** + @brief Add a file to the descriptor table. + @param f The file description to add. + @return The file descriptor index assigned to the file, or -1 on failure. + */ auto add_file(kstd::shared_ptr<open_file_description> const & f) -> int; + + /** + @brief Get a file from the descriptor table. + @param fd The file descriptor index to retrieve. + @return A pointer to the requested file description, or a null pointer if not found. + */ [[nodiscard]] auto get_file(int fd) const -> kstd::shared_ptr<open_file_description>; + + /** + @brief Remove a file from the descriptor table. + @param fd The file descriptor index to remove. + */ auto remove_file(int fd) -> void; private: diff --git a/kernel/include/kernel/filesystem/filesystem.hpp b/kernel/include/kernel/filesystem/filesystem.hpp index 1d86178..ef6929a 100644 --- a/kernel/include/kernel/filesystem/filesystem.hpp +++ b/kernel/include/kernel/filesystem/filesystem.hpp @@ -2,6 +2,7 @@ #define TEACH_OS_KERNEL_FILESYSTEM_FILESYSTEM_HPP #include "kapi/devices/device.hpp" + #include "kernel/filesystem/inode.hpp" #include <kstd/memory> @@ -11,15 +12,63 @@ namespace kernel::filesystem { + /** + @brief A base class for implementing filesystems in the kernel. This class provides a common interface for managing + files and directories within the virtual filesystem. + */ struct filesystem { + enum class operation_result : int + { + success = 0, + invalid_magic_number = -1, + invalid_root_inode = -2, + unmount_failed = -3 + }; + + /** + @brief Virtual destructor for the filesystem. + */ virtual ~filesystem() = default; - virtual auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int; + /** + @brief Probes the given @p device to determine if it contains a recognizable filesystem, and if so, mounts it and + returns a pointer to the mounted filesystem instance. This method iterates through known filesystem types and + attempts to initialize it with the device until the mount was successful or all types have been tried. + @param device The device to probe and mount. + @return A pointer to the mounted filesystem instance if successful, or a null pointer if no recognizable filesystem + is found on the device. + @warning Panics if @p device is null. + */ + auto static probe_and_mount(kstd::shared_ptr<kapi::devices::device> const & device) -> kstd::shared_ptr<filesystem>; + + /** + @brief Initializes the filesystem with the given @p device. + @param device The device to mount. + @return The result of the mount operation. + */ + virtual auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result; + + /** + @brief Looks up a child inode within the given @p parent inode with the specified @p name. This method must be + implemented by concrete filesystem subclasses to provide the logic for traversing the filesystem structure and + finding the requested inode. + @param parent The parent inode. + @param name The name of the child inode to look up. + @return A pointer to the requested child inode, or a null pointer if not found. + */ virtual auto lookup(kstd::shared_ptr<inode> const & parent, std::string_view name) -> kstd::shared_ptr<inode> = 0; + /** + @brief Returns a reference to the root inode of the filesystem. + */ [[nodiscard]] auto root_inode() const -> kstd::shared_ptr<inode> const &; + /** + @brief Returns a reference to the device associated with the filesystem. + */ + [[nodiscard]] auto device() const -> kstd::shared_ptr<kapi::devices::device> const &; + protected: kstd::shared_ptr<inode> m_root_inode{}; kstd::shared_ptr<kapi::devices::device> m_device{}; diff --git a/kernel/include/kernel/filesystem/inode.hpp b/kernel/include/kernel/filesystem/inode.hpp index d97b5ab..5208be2 100644 --- a/kernel/include/kernel/filesystem/inode.hpp +++ b/kernel/include/kernel/filesystem/inode.hpp @@ -5,8 +5,14 @@ namespace kernel::filesystem { + /** + @brief Represents an inode in the filesystem. + */ struct inode { + /** + @brief Represents the kind of an inode. + */ enum class inode_kind { regular, @@ -14,18 +20,57 @@ namespace kernel::filesystem device }; + /** + @brief Create an inode with the given @p kind. + @param kind The kind of the inode. + */ explicit inode(inode_kind kind); + /** + @brief Virtual destructor for the inode. + */ virtual ~inode() = default; + /** + @brief Reads from the inode into a @p buffer, starting at the specified @p offset and for a given @p size. This + method must be implemented by concrete inode subclasses. + @param buffer Destination buffer. + @param offset Read offset in bytes. + @param size Number of bytes requested. + @return Number of bytes read. + */ virtual auto read(void * buffer, size_t offset, size_t size) const -> size_t = 0; + + /** + @brief Writes to the inode into a @p buffer, starting at the specified @p offset and for a given @p size. This + method must be implemented by concrete inode subclasses. + @param buffer Source buffer. + @param offset Write offset in bytes. + @param size Number of bytes to write. + @return Number of bytes written. + */ virtual auto write(void const * buffer, size_t offset, size_t size) -> size_t = 0; + /** + @brief Returns whether the inode is a directory. + @return true if the inode is a directory, false otherwise. + */ [[nodiscard]] auto is_directory() const -> bool; + + /** + @brief Returns whether the inode is a regular file. + @return true if the inode is a regular file, false otherwise. + */ [[nodiscard]] auto is_regular() const -> bool; + + /** + @brief Returns whether the inode is a device. + @return true if the inode is a device, false otherwise. + */ [[nodiscard]] auto is_device() const -> bool; - private: + // TODO BA-FS26 avoid public member + public: inode_kind m_kind{inode_kind::regular}; }; } // namespace kernel::filesystem diff --git a/kernel/include/kernel/filesystem/mount.hpp b/kernel/include/kernel/filesystem/mount.hpp index a054750..0ac6b2f 100644 --- a/kernel/include/kernel/filesystem/mount.hpp +++ b/kernel/include/kernel/filesystem/mount.hpp @@ -11,22 +11,56 @@ namespace kernel::filesystem { + /** + @brief Represents a mounted filesystem in the kernel. + */ struct mount { + /** + @brief Creates a mount for the given @p filesystem at the specified @p mount_path. The @p mount_dentry represents + the dentry where the filesystem is mounted, and the @p root_dentry represents the root dentry of the mounted + filesystem. + @param mount_dentry The dentry where the filesystem is mounted. + @param root_dentry The root dentry of the mounted filesystem. + @param fs The filesystem instance being mounted. + @param mount_path The path at which the filesystem is mounted. + @param parent_mount The parent mount that this mount is attached beneath. + */ mount(kstd::shared_ptr<dentry> const & mount_dentry, kstd::shared_ptr<dentry> const & root_dentry, - kstd::shared_ptr<filesystem> const & fs, std::string_view mount_path); + kstd::shared_ptr<filesystem> const & fs, std::string_view mount_path, + kstd::shared_ptr<mount> const & parent_mount); - [[nodiscard]] auto get_mount_dentry() const -> kstd::shared_ptr<dentry>; + /** + @brief Get the dentry where the filesystem is mounted. + */ + [[nodiscard]] auto get_mount_dentry() const -> kstd::shared_ptr<dentry> const &; + + /** + @brief Get the root dentry of the mounted filesystem. + */ [[nodiscard]] auto root_dentry() const -> kstd::shared_ptr<dentry> const &; + /** + @brief Get the filesystem instance being mounted. + */ [[nodiscard]] auto get_filesystem() const -> kstd::shared_ptr<filesystem> const &; + + /** + @brief Get the path at which the filesystem is mounted. + */ [[nodiscard]] auto get_mount_path() const -> std::string_view; + /** + @brief Get the parent mount that this mount was attached beneath. + */ + [[nodiscard]] auto get_parent_mount() const -> kstd::shared_ptr<mount> const &; + private: kstd::string m_mount_path; kstd::shared_ptr<dentry> m_mount_dentry; kstd::shared_ptr<dentry> m_root_dentry; kstd::shared_ptr<filesystem> m_filesystem{}; + kstd::shared_ptr<mount> m_parent_mount{}; }; } // namespace kernel::filesystem diff --git a/kernel/include/kernel/filesystem/mount_table.hpp b/kernel/include/kernel/filesystem/mount_table.hpp index 6dc2218..a5cdde6 100644 --- a/kernel/include/kernel/filesystem/mount_table.hpp +++ b/kernel/include/kernel/filesystem/mount_table.hpp @@ -10,14 +10,45 @@ namespace kernel::filesystem { + /** + @brief A table for managing mounted filesystems in the kernel. + */ struct mount_table { - public: - void add_mount(kstd::shared_ptr<mount>); + /** + @brief Results for mount table operations. + */ + enum class operation_result : int + { + removed = 0, + has_child_mounts = -1, + mount_not_found = -2 + }; + /** + @brief Adds a mount to the table. + @param mount The mount to add. + */ + auto add_mount(kstd::shared_ptr<mount> const & mount) -> void; + + /** + @brief Removes the topmost mount at the given @p path. + @param path The mount path to remove. + @return The result of the removal operation. + */ + [[nodiscard]] auto remove_mount(std::string_view path) -> operation_result; + + /** + @brief Finds the mount with the longest prefix matching the given @p path. This method is used to determine which + mounted filesystem should handle a given path lookup. + @param path The path to match against the mount paths in the table. + @return A pointer to the mount with the longest matching prefix, or a null pointer if no mount matches the path. + */ [[nodiscard]] auto find_longest_prefix_mount(std::string_view path) const -> kstd::shared_ptr<mount>; private: + [[nodiscard]] auto has_child_mounts(kstd::shared_ptr<mount> const & parent_mount) const -> bool; + kstd::vector<kstd::shared_ptr<mount>> m_mounts; }; } // namespace kernel::filesystem diff --git a/kernel/include/kernel/filesystem/open_file_description.hpp b/kernel/include/kernel/filesystem/open_file_description.hpp index 45719cf..738afd4 100644 --- a/kernel/include/kernel/filesystem/open_file_description.hpp +++ b/kernel/include/kernel/filesystem/open_file_description.hpp @@ -9,15 +9,49 @@ namespace kernel::filesystem { + /** + @brief Represents an open file description in the filesystem. This class encapsulates the state of an open file, + including a reference to the associated inode and the current file offset. + */ struct open_file_description { + /** + @brief Constructs an open file description for the given @p inode. + @param inode The inode to associate with the open file description. + */ explicit open_file_description(kstd::shared_ptr<inode> const & inode); + /** + @brief Destructor for the open file description. + */ ~open_file_description() = default; + /** + @brief Reads data from the open file description into a @p buffer, starting at the current file offset and for a + given + @p size. The file offset is advanced by the number of bytes read. + @param buffer The buffer to read data into. + @param size The number of bytes to read. + @return The number of bytes read. + */ auto read(void * buffer, size_t size) -> size_t; + + /** + @brief Writes data to the open file description from a @p buffer, starting at the current file offset and for a + given + @p size. The file offset is advanced by the number of bytes written. + @param buffer The buffer to write data from. + @param size The number of bytes to write. + @return The number of bytes written. + */ auto write(void const * buffer, size_t size) -> size_t; + /** + @brief Returns the current file offset for this open file description. + @return The current file offset in bytes. + */ + [[nodiscard]] auto offset() const -> size_t; + private: kstd::shared_ptr<inode> m_inode; size_t m_offset; diff --git a/kernel/include/kernel/filesystem/rootfs/filesystem.hpp b/kernel/include/kernel/filesystem/rootfs/filesystem.hpp index 5632d86..0155c41 100644 --- a/kernel/include/kernel/filesystem/rootfs/filesystem.hpp +++ b/kernel/include/kernel/filesystem/rootfs/filesystem.hpp @@ -2,6 +2,7 @@ #define TEACH_OS_KERNEL_FILESYSTEM_ROOTFS_FILESYSTEM_HPP #include "kapi/devices/device.hpp" + #include "kernel/filesystem/filesystem.hpp" #include "kernel/filesystem/inode.hpp" @@ -13,9 +14,27 @@ namespace kernel::filesystem::rootfs { + /** + @brief A filesystem for the root filesystem. This filesystem provides access to the root directory and its contents, + which are typically populated by the init process during system startup. The rootfs filesystem serves as the top-level + directory in the filesystem hierarchy. It is responsible for providing a stable and consistent interface to the root + directory. + */ struct filesystem : kernel::filesystem::filesystem { - auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int override; + /** + @brief Initializes the rootfs filesystem with the given @p device. + @param device The device to mount (not required by rootfs). + @return The result of the mount operation. + */ + auto mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result override; + + /** + @brief Looks up an inode by @p name within a @p parent directory. + @param parent The parent directory inode. + @param name The name of the inode to look up. + @return A pointer to the found inode, or a null pointer if not found. + */ auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) -> kstd::shared_ptr<kernel::filesystem::inode> override; }; diff --git a/kernel/include/kernel/filesystem/rootfs/inode.hpp b/kernel/include/kernel/filesystem/rootfs/inode.hpp index 24d3e6b..469e47a 100644 --- a/kernel/include/kernel/filesystem/rootfs/inode.hpp +++ b/kernel/include/kernel/filesystem/rootfs/inode.hpp @@ -13,14 +13,48 @@ namespace kernel::filesystem::rootfs { + /** + @brief Represents an inode in the rootfs filesystem. This inode represents a directory in the root filesystem and + maintains a list of child inodes corresponding to files and subdirectories within the root directory. The rootfs inode + provides methods for reading and writing data (which are no-ops for the root directory), as well as adding and looking + up child inodes by name. + */ struct inode : kernel::filesystem::inode { + /** + @brief Create a rootfs inode. The inode is initialized with the appropriate kind (directory). + */ inode(); + /** + @brief Reads from the rootfs directory inode. + @param buffer Destination buffer. + @param offset Read offset in bytes. + @param size Number of bytes requested. + @return Number of bytes read (always 0 because this inode does not expose file data). + */ auto read(void * buffer, size_t offset, size_t size) const -> size_t override; + + /** + @brief Writes to the rootfs directory inode. + @param buffer Source buffer. + @param offset Write offset in bytes. + @param size Number of bytes requested. + @return Number of bytes written (always 0 because writes are not supported for this inode). + */ auto write(void const * buffer, size_t offset, size_t size) -> size_t override; + /** + @brief Adds a child inode to the rootfs directory inode with the specified @p name. + @param name The name of the child inode. + */ auto add_child(std::string_view name) -> void; + + /** + @brief Looks up a child inode by @p name. + @param name The name of the child inode to look up. + @return A pointer to the found child inode, or a null pointer if not found. + */ auto lookup_child(std::string_view name) -> kstd::shared_ptr<inode>; private: diff --git a/kernel/include/kernel/filesystem/vfs.hpp b/kernel/include/kernel/filesystem/vfs.hpp index 5823a83..4dd2a83 100644 --- a/kernel/include/kernel/filesystem/vfs.hpp +++ b/kernel/include/kernel/filesystem/vfs.hpp @@ -12,15 +12,65 @@ namespace kernel::filesystem { + /** + @brief The virtual filesystem (VFS) is responsible for managing mounted filesystems and providing a unified interface + for file operations across different filesystem types. The VFS maintains a mount table to keep track of mounted + filesystems and their associated mount points. It provides methods for opening files by path, which involvesresolving + the path to the appropriate mounted filesystem and delegating the file operation to that filesystem's implementation. + */ struct vfs { + /** + @brief Results for VFS operations. + */ + enum class operation_result : int + { + success = 0, + invalid_path = -1, + mount_point_not_found = -2, + filesystem_null = -3, + unmount_failed = -4 + }; + + /** + @brief Initialize the virtual filesystem. + @warning Panics if the VFS has already been initialized. + */ auto static init() -> void; + + /** + @brief Get the singleton instance of the virtual filesystem. + @return A reference to the VFS instance. + @warning Panics if the VFS has not been initialized yet. + */ auto static get() -> vfs &; + /** + @brief Destructor for the VFS. + */ ~vfs() = default; + /** + @brief Open a file by its @p path. This method resolves the path and creates an open file description. + @param path The path to the file to open. + @return A shared pointer to the open file description or a null pointer if the file could not be opened. + */ auto open(std::string_view path) -> kstd::shared_ptr<open_file_description>; - auto do_mount(std::string_view path, kstd::shared_ptr<filesystem> const & filesystem) -> int; + + /** + @brief Mount a @p filesystem at a specific @p path. + @param path The path where the filesystem should be mounted. + @param filesystem The filesystem to mount. + @return The result of the mount operation. + */ + auto do_mount(std::string_view path, kstd::shared_ptr<filesystem> const & filesystem) -> operation_result; + + /** + @brief Unmount the filesystem mounted at the specified @p path. + @param path The path where the filesystem is mounted. + @return The result of the unmount operation. + */ + auto unmount(std::string_view path) -> operation_result; private: vfs() = default; diff --git a/kernel/include/kernel/test_support/boot_modules.hpp b/kernel/include/kernel/test_support/boot_modules.hpp new file mode 100644 index 0000000..2343a10 --- /dev/null +++ b/kernel/include/kernel/test_support/boot_modules.hpp @@ -0,0 +1,10 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_BOOT_MODULES_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_BOOT_MODULES_HPP + +namespace kernel::tests::boot_modules +{ + //! Deinitialize the boot module registry. + auto deinit() -> void; +} // namespace kernel::tests::boot_modules + +#endif
\ No newline at end of file diff --git a/kernel/include/kernel/test_support/devices/block_device.hpp b/kernel/include/kernel/test_support/devices/block_device.hpp new file mode 100644 index 0000000..110872f --- /dev/null +++ b/kernel/include/kernel/test_support/devices/block_device.hpp @@ -0,0 +1,31 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_BLOCK_DEVICE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_BLOCK_DEVICE_HPP + +#include "kernel/devices/block_device.hpp" + +#include <kstd/string> +#include <kstd/vector> + +#include <cstddef> +#include <cstdint> + +namespace kernel::tests::devices +{ + + struct block_device : kernel::devices::block_device + { + block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size, size_t initial_size = 0); + + auto init() -> bool override; + + auto read_block(size_t block_index, void * buffer) const -> void override; + auto write_block(size_t block_index, void const * buffer) -> void override; + + [[nodiscard]] auto size() const -> size_t override; + + kstd::vector<uint8_t> data{}; + }; + +} // namespace kernel::tests::devices + +#endif
\ No newline at end of file diff --git a/kernel/include/kernel/test_support/devices/character_device.hpp b/kernel/include/kernel/test_support/devices/character_device.hpp new file mode 100644 index 0000000..a106cfb --- /dev/null +++ b/kernel/include/kernel/test_support/devices/character_device.hpp @@ -0,0 +1,23 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_CHARACTER_DEVICE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_CHARACTER_DEVICE_HPP + +#include "kapi/devices/device.hpp" + +#include <kstd/string> +#include <kstd/vector> + +#include <cstddef> + +namespace kernel::tests::devices +{ + // TODO fix inheritance when character devices are implemented + struct character_device : kapi::devices::device + { + character_device(size_t major, size_t minor, kstd::string const & name); + + auto init() -> bool override; + }; + +} // namespace kernel::tests::devices + +#endif
\ No newline at end of file diff --git a/kernel/include/kernel/test_support/devices/storage/management.hpp b/kernel/include/kernel/test_support/devices/storage/management.hpp new file mode 100644 index 0000000..581aa91 --- /dev/null +++ b/kernel/include/kernel/test_support/devices/storage/management.hpp @@ -0,0 +1,10 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_STORAGE_MANAGEMENT_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_STORAGE_MANAGEMENT_HPP + +namespace kernel::tests::devices::storage::management +{ + //! Deinitialize the storage management singleton. + auto deinit() -> void; +} // namespace kernel::tests::devices::storage::management + +#endif
\ No newline at end of file diff --git a/kernel/include/kernel/test_support/filesystem/ext2.hpp b/kernel/include/kernel/test_support/filesystem/ext2.hpp new file mode 100644 index 0000000..edbda29 --- /dev/null +++ b/kernel/include/kernel/test_support/filesystem/ext2.hpp @@ -0,0 +1,17 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP + +#include "kernel/test_support/devices/block_device.hpp" + +#include <cstddef> +#include <cstdint> + +namespace kernel::tests::filesystem::ext2 +{ + auto write_bytes(kernel::tests::devices::block_device & device, size_t offset, void const * source, size_t size) + -> void; + auto write_u32(kernel::tests::devices::block_device & device, size_t offset, uint32_t value) -> void; + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void; +} // namespace kernel::tests::filesystem::ext2 + +#endif
\ No newline at end of file diff --git a/kernel/include/kernel/test_support/filesystem/file_descriptor_table.hpp b/kernel/include/kernel/test_support/filesystem/file_descriptor_table.hpp new file mode 100644 index 0000000..bbc0f4a --- /dev/null +++ b/kernel/include/kernel/test_support/filesystem/file_descriptor_table.hpp @@ -0,0 +1,10 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILE_DESCRIPTOR_TABLE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILE_DESCRIPTOR_TABLE_HPP + +namespace kernel::tests::filesystem::file_descriptor_table +{ + //! Deinitialize the file descriptor table singleton. + auto deinit() -> void; +} // namespace kernel::tests::filesystem::file_descriptor_table + +#endif
\ No newline at end of file diff --git a/kernel/include/kernel/test_support/filesystem/filesystem.hpp b/kernel/include/kernel/test_support/filesystem/filesystem.hpp new file mode 100644 index 0000000..13aade4 --- /dev/null +++ b/kernel/include/kernel/test_support/filesystem/filesystem.hpp @@ -0,0 +1,22 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILESYSTEM_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILESYSTEM_HPP + +#include "kernel/filesystem/filesystem.hpp" +#include "kernel/filesystem/inode.hpp" + +#include <kstd/memory> + +#include <string_view> + +namespace kernel::tests::filesystem +{ + struct filesystem : kernel::filesystem::filesystem + { + filesystem() = default; + + auto lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) + -> kstd::shared_ptr<kernel::filesystem::inode> override; + }; +} // namespace kernel::tests::filesystem + +#endif diff --git a/kernel/include/kernel/test_support/filesystem/inode.hpp b/kernel/include/kernel/test_support/filesystem/inode.hpp new file mode 100644 index 0000000..6568f24 --- /dev/null +++ b/kernel/include/kernel/test_support/filesystem/inode.hpp @@ -0,0 +1,19 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_INODE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_INODE_HPP + +#include "kernel/filesystem/inode.hpp" + +#include <cstddef> + +namespace kernel::tests::filesystem +{ + struct inode : kernel::filesystem::inode + { + inode(); + + auto read(void * buffer, size_t offset, size_t size) const -> size_t override; + auto write(void const * buffer, size_t offset, size_t size) -> size_t override; + }; +} // namespace kernel::tests::filesystem + +#endif diff --git a/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp b/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp new file mode 100644 index 0000000..ee658e2 --- /dev/null +++ b/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp @@ -0,0 +1,32 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_STORAGE_BOOT_MODULE_FIXTURE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_STORAGE_BOOT_MODULE_FIXTURE_HPP + +#include "kapi/boot_module/boot_module_registry.hpp" + +#include <kstd/string> +#include <kstd/vector> + +#include <cstddef> +#include <filesystem> + +namespace kernel::tests::filesystem +{ + struct storage_boot_module_fixture + { + ~storage_boot_module_fixture(); + + auto setup_modules(std::size_t module_count, std::size_t module_size = 4096) -> void; + auto setup_modules_from_img(kstd::vector<kstd::string> const & module_names, + kstd::vector<std::filesystem::path> const & img_paths) -> void; + + protected: + kapi::boot_modules::boot_module_registry m_registry{}; + kstd::vector<kstd::string> m_module_names{}; + kstd::vector<kstd::vector<std::byte>> m_module_data{}; + + private: + auto setup_module_from_img(kstd::string const & module_name, std::filesystem::path const & img_path) -> void; + }; +} // namespace kernel::tests::filesystem + +#endif
\ No newline at end of file diff --git a/kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp b/kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp new file mode 100644 index 0000000..98012b0 --- /dev/null +++ b/kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp @@ -0,0 +1,24 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_STORAGE_BOOT_MODULE_VFS_FIXTURE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_STORAGE_BOOT_MODULE_VFS_FIXTURE_HPP + +#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp" + +#include <kstd/string> +#include <kstd/vector> + +#include <cstddef> +#include <filesystem> + +namespace kernel::tests::filesystem +{ + struct storage_boot_module_vfs_fixture : storage_boot_module_fixture + { + ~storage_boot_module_vfs_fixture(); + + auto setup_modules_and_init_vfs(std::size_t module_count, std::size_t module_size = 4096) -> void; + auto setup_modules_from_img_and_init_vfs(kstd::vector<kstd::string> const & module_names, + kstd::vector<std::filesystem::path> const & img_paths) -> void; + }; +} // namespace kernel::tests::filesystem + +#endif
\ No newline at end of file diff --git a/kernel/include/kernel/test_support/filesystem/vfs.hpp b/kernel/include/kernel/test_support/filesystem/vfs.hpp new file mode 100644 index 0000000..739e353 --- /dev/null +++ b/kernel/include/kernel/test_support/filesystem/vfs.hpp @@ -0,0 +1,10 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_VFS_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_VFS_HPP + +namespace kernel::tests::filesystem::vfs +{ + //! Deinitialize the VFS singleton. + auto deinit() -> void; +} // namespace kernel::tests::filesystem::vfs + +#endif
\ No newline at end of file diff --git a/kernel/kapi/boot_modules.cpp b/kernel/kapi/boot_modules.cpp index 5a0ef7f..0549368 100644 --- a/kernel/kapi/boot_modules.cpp +++ b/kernel/kapi/boot_modules.cpp @@ -4,14 +4,13 @@ #include <optional> -namespace kapi::boot_modules +namespace { + constinit auto static registry = std::optional<kapi::boot_modules::boot_module_registry>{}; +} // namespace - namespace - { - constinit auto static registry = std::optional<kapi::boot_modules::boot_module_registry>{}; - } // namespace - +namespace kapi::boot_modules +{ auto set_boot_module_registry(boot_module_registry & new_registry) -> void { if (registry) @@ -32,3 +31,11 @@ namespace kapi::boot_modules return *registry; } } // namespace kapi::boot_modules + +namespace kernel::tests::boot_modules +{ + auto deinit() -> void + { + registry.reset(); + } +} // namespace kernel::tests::boot_modules diff --git a/kernel/src/devices/block_device.cpp b/kernel/src/devices/block_device.cpp index c006198..b7cb26e 100644 --- a/kernel/src/devices/block_device.cpp +++ b/kernel/src/devices/block_device.cpp @@ -1,8 +1,7 @@ #include "kernel/devices/block_device.hpp" -#include "kapi/system.hpp" - #include "kapi/devices/device.hpp" +#include "kapi/system.hpp" #include <kstd/string> diff --git a/kernel/src/devices/block_device.tests.cpp b/kernel/src/devices/block_device.tests.cpp new file mode 100644 index 0000000..378437e --- /dev/null +++ b/kernel/src/devices/block_device.tests.cpp @@ -0,0 +1,46 @@ +#include "kernel/test_support/devices/block_device.hpp" + +#include "kernel/test_support/cpu.hpp" + +#include <kstd/memory> +#include <kstd/print> +#include <kstd/string> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +#include <cstddef> + +SCENARIO("Block device construction", "[devices][block_device]") +{ + GIVEN("parameters for a block device") + { + size_t major = 1; + size_t minor = 0; + kstd::string name = "test_block_device"; + size_t block_size = 512; + + WHEN("constructing a block device") + { + auto device = + kstd::make_shared<kernel::tests::devices::block_device>(major, minor, name, block_size, 3 * block_size); + + THEN("the block device has the correct properties") + { + REQUIRE(device->major() == major); + REQUIRE(device->minor() == minor); + REQUIRE(device->name() == name); + REQUIRE(device->block_size() == block_size); + REQUIRE(device->capacity() == 3 * 512); + } + } + + WHEN("constructing a block device with zero block size") + { + THEN("the constructor panics") + { + REQUIRE_THROWS_AS((kernel::tests::devices::block_device(major, minor, name, 0)), kernel::tests::cpu::halt); + } + } + } +} diff --git a/kernel/src/devices/block_device_utils.cpp b/kernel/src/devices/block_device_utils.cpp index 6fe89fe..59e9b97 100644 --- a/kernel/src/devices/block_device_utils.cpp +++ b/kernel/src/devices/block_device_utils.cpp @@ -1,9 +1,9 @@ #include "kernel/devices/block_device_utils.hpp" +#include "kapi/devices/device.hpp" #include "kapi/system.hpp" #include "kernel/devices/block_device.hpp" -#include "kapi/devices/device.hpp" #include <kstd/cstring> #include <kstd/memory> @@ -31,12 +31,13 @@ namespace kernel::devices::block_device_utils return 0; } - auto * block_dev = static_cast<devices::block_device *>(device.get()); - if (block_dev == nullptr) + if (!device->is_block_device()) { kapi::system::panic("[FILESYSTEM] device_file: expected block_device."); } + auto * block_dev = static_cast<devices::block_device *>(device.get()); + size_t const block_size = block_dev->block_size(); size_t const capacity = block_dev->capacity(); diff --git a/kernel/src/devices/block_device_utils.tests.cpp b/kernel/src/devices/block_device_utils.tests.cpp new file mode 100644 index 0000000..f78e477 --- /dev/null +++ b/kernel/src/devices/block_device_utils.tests.cpp @@ -0,0 +1,219 @@ +#include "kernel/devices/block_device_utils.hpp" + +#include "kernel/test_support/cpu.hpp" +#include "kernel/test_support/devices/block_device.hpp" +#include "kernel/test_support/devices/character_device.hpp" + +#include <kstd/memory> +#include <kstd/print> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +#include <cstddef> +#include <cstdint> + +SCENARIO("reading from a block device with block_device_utils", "[devices][block_device_utils]") +{ + GIVEN("a block device with known data") + { + auto const block_size = 512; + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "test_block_device", block_size); + kstd::vector<uint8_t> block_data(block_size); + for (size_t i = 0; i < block_data.size(); ++i) + { + block_data[i] = static_cast<uint8_t>(i % 256); + } + device->write_block(0, block_data.data()); + device->write_block(1, block_data.data()); + + WHEN("reading from the block device using block_device_utils") + { + kstd::vector<uint8_t> read_buffer(block_size); + auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, read_buffer.size()); + + THEN("the correct number of bytes is read") + { + REQUIRE(bytes_read == read_buffer.size()); + } + + THEN("the data read matches the data written to the block device") + { + REQUIRE(read_buffer == block_data); + } + } + + WHEN("reading over block boundaries") + { + kstd::vector<uint8_t> read_buffer(1024); + auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 256, read_buffer.size()); + + THEN("the correct number of bytes is read") + { + REQUIRE(bytes_read == 1.5 * block_size); + } + + THEN("the data read matches the expected data across block boundaries") + { + for (size_t i = 0; i < bytes_read; ++i) + { + uint8_t expected_value = static_cast<uint8_t>((256 + i) % 256); + REQUIRE(read_buffer[i] == expected_value); + } + } + } + + WHEN("reading beyond the device capacity") + { + kstd::vector<uint8_t> read_buffer(block_size); + auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 1024, read_buffer.size()); + + THEN("no bytes are read") + { + REQUIRE(bytes_read == 0); + } + } + + WHEN("reading nothing") + { + kstd::vector<uint8_t> read_buffer(block_size); + auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, 0); + + THEN("no bytes are read") + { + REQUIRE(bytes_read == 0); + } + } + + WHEN("reading with a null buffer") + { + THEN("the system panics") + { + REQUIRE_THROWS_AS(kernel::devices::block_device_utils::read(device, nullptr, 0, 512), kernel::tests::cpu::halt); + } + } + } +} + +SCENARIO("writing to a block device using block_device_utils", "[devices][block_device_utils]") +{ + GIVEN("a block device") + { + auto const block_size = 512; + auto device = + kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "test_block_device", block_size, 2 * block_size); + + WHEN("writing to the block device using block_device_utils") + { + kstd::vector<uint8_t> write_buffer(block_size); + for (size_t i = 0; i < write_buffer.size(); ++i) + { + write_buffer[i] = static_cast<uint8_t>(i % 256); + } + + auto bytes_written = + kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, write_buffer.size()); + + THEN("the correct number of bytes is written") + { + REQUIRE(bytes_written == write_buffer.size()); + } + + THEN("the data written matches the data read back from the block device") + { + kstd::vector<uint8_t> read_buffer(block_size); + device->read_block(0, read_buffer.data()); + REQUIRE(read_buffer == write_buffer); + } + } + + WHEN("writing over block boundaries") + { + kstd::vector<uint8_t> write_buffer(2 * block_size); + for (size_t i = 0; i < write_buffer.size(); ++i) + { + write_buffer[i] = static_cast<uint8_t>(i % 256); + } + + auto bytes_written = + kernel::devices::block_device_utils::write(device, write_buffer.data(), 256, write_buffer.size()); + + THEN("the correct number of bytes is written") + { + REQUIRE(bytes_written == 1.5 * block_size); + } + + THEN("the data written matches the data read back from the block device across block boundaries") + { + kstd::vector<uint8_t> read_buffer(2 * block_size); + auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 256, 2 * block_size); + + for (size_t i = 0; i < bytes_read; ++i) + { + REQUIRE(read_buffer[i] == write_buffer[i]); + } + } + } + + WHEN("writing beyond the device capacity") + { + kstd::vector<uint8_t> write_buffer(block_size); + auto bytes_written = + kernel::devices::block_device_utils::write(device, write_buffer.data(), 1024, write_buffer.size()); + + THEN("no bytes are written") + { + REQUIRE(bytes_written == 0); + } + } + + WHEN("writing nothing") + { + kstd::vector<uint8_t> write_buffer(block_size); + auto bytes_written = kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, 0); + + THEN("no bytes are written") + { + REQUIRE(bytes_written == 0); + } + } + + WHEN("writing with a null buffer") + { + THEN("the system panics") + { + REQUIRE_THROWS_AS(kernel::devices::block_device_utils::write(device, nullptr, 0, block_size), + kernel::tests::cpu::halt); + } + } + } +} + +SCENARIO("block_device_utils with a non-block device", "[devices][block_device_utils]") +{ + GIVEN("a non-block device") + { + auto device = kstd::make_shared<kernel::tests::devices::character_device>(0, 0, "test_character_device"); + + WHEN("attempting to read from the non-block device using block_device_utils") + { + kstd::vector<uint8_t> read_buffer(512); + THEN("the system panics") + { + REQUIRE_THROWS_AS(kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, read_buffer.size()), + kernel::tests::cpu::halt); + } + } + + WHEN("attempting to write to the non-block device using block_device_utils") + { + kstd::vector<uint8_t> write_buffer(512); + THEN("the system panics") + { + REQUIRE_THROWS_AS( + kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, write_buffer.size()), + kernel::tests::cpu::halt); + } + } + } +} diff --git a/kernel/src/devices/storage/management.cpp b/kernel/src/devices/storage/management.cpp index d440bf0..c9fa0a8 100644 --- a/kernel/src/devices/storage/management.cpp +++ b/kernel/src/devices/storage/management.cpp @@ -1,9 +1,9 @@ #include "kernel/devices/storage/management.hpp" #include "kapi/boot_modules.hpp" +#include "kapi/devices/device.hpp" #include "kapi/system.hpp" -#include "kapi/devices/device.hpp" #include "kernel/devices/storage/controller.hpp" #include "kernel/devices/storage/ram_disk/controller.hpp" @@ -14,17 +14,17 @@ #include <cstddef> #include <optional> -namespace kernel::devices::storage +namespace { - namespace - { - constexpr size_t static MINORS_PER_DEVICE = 16; - constexpr size_t static START_MAJOR = 1; - constinit size_t static next_free_major = START_MAJOR; + constexpr size_t static MINORS_PER_DEVICE = 16; + constexpr size_t static START_MAJOR = 1; + constinit size_t static next_free_major = START_MAJOR; - constinit auto static active_storage_management = std::optional<management>{}; - } // namespace + constinit auto static active_storage_management = std::optional<kernel::devices::storage::management>{}; +} // namespace +namespace kernel::devices::storage +{ auto management::init() -> void { if (active_storage_management) @@ -81,5 +81,13 @@ namespace kernel::devices::storage { return device_by_major_minor(START_MAJOR, 0); } +} // namespace kernel::devices::storage -} // namespace kernel::devices::storage
\ No newline at end of file +namespace kernel::tests::devices::storage::management +{ + auto deinit() -> void + { + active_storage_management.reset(); + next_free_major = START_MAJOR; + } +} // namespace kernel::tests::devices::storage::management diff --git a/kernel/src/filesystem/dentry.cpp b/kernel/src/filesystem/dentry.cpp index 2f99e91..6591011 100644 --- a/kernel/src/filesystem/dentry.cpp +++ b/kernel/src/filesystem/dentry.cpp @@ -12,10 +12,10 @@ namespace kernel::filesystem { - dentry::dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & node, std::string_view name) + dentry::dentry(kstd::shared_ptr<dentry> const & parent, kstd::shared_ptr<inode> const & inode, std::string_view name) : m_name(name) , m_parent(parent) - , m_inode(node) + , m_inode(inode) { if (!m_inode) { @@ -33,6 +33,11 @@ namespace kernel::filesystem return m_parent; } + auto dentry::get_name() const -> std::string_view + { + return m_name.view(); + } + auto dentry::add_child(kstd::shared_ptr<dentry> const & child) -> void { m_children.push_back(child); diff --git a/kernel/src/filesystem/dentry.tests.cpp b/kernel/src/filesystem/dentry.tests.cpp new file mode 100644 index 0000000..a6620d3 --- /dev/null +++ b/kernel/src/filesystem/dentry.tests.cpp @@ -0,0 +1,135 @@ +#include "kernel/filesystem/dentry.hpp" + +#include "kernel/test_support/cpu.hpp" +#include "kernel/test_support/filesystem/inode.hpp" + +#include <kstd/memory> +#include <kstd/print> + +#include <catch2/catch_test_macros.hpp> + +SCENARIO("Dentry construction", "[filesystem][dentry]") +{ + GIVEN("A parent dentry and inode") + { + auto inode = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto parent_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, inode); + + WHEN("constructing a dentry") + { + auto child_dentry = kernel::filesystem::dentry{parent_dentry, inode, "child"}; + + THEN("the dentry has the correct parent, inode, and name") + { + REQUIRE(child_dentry.get_parent() == parent_dentry); + REQUIRE(child_dentry.get_inode() == inode); + REQUIRE(child_dentry.get_name() == "child"); + } + + THEN("no flag is set") + { + REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + } + } + + WHEN("constructing a dentry with an empty name") + { + auto child_dentry = kernel::filesystem::dentry{parent_dentry, inode}; + + THEN("the dentry has the correct parent and inode, and an empty name") + { + REQUIRE(child_dentry.get_parent() == parent_dentry); + REQUIRE(child_dentry.get_inode() == inode); + REQUIRE(child_dentry.get_name().empty()); + } + + THEN("no flag is set") + { + REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + } + } + + WHEN("constructing a dentry with a null parent") + { + auto child_dentry = kernel::filesystem::dentry{nullptr, inode, "child"}; + + THEN("the dentry has a null parent, the correct inode, and the correct name") + { + REQUIRE(child_dentry.get_parent() == nullptr); + REQUIRE(child_dentry.get_inode() == inode); + REQUIRE(child_dentry.get_name() == "child"); + } + + THEN("no flag is set") + { + REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + } + } + + WHEN("constructing a dentry with a null inode") + { + THEN("the system panics") + { + REQUIRE_THROWS_AS((kernel::filesystem::dentry{parent_dentry, nullptr, "child"}), kernel::tests::cpu::halt); + } + } + } +} + +SCENARIO("Dentry child logic", "[filesystem][dentry]") +{ + GIVEN("A parent dentry and inode") + { + auto inode = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto parent_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, inode); + + WHEN("adding child dentries") + { + auto child1 = kstd::make_shared<kernel::filesystem::dentry>(parent_dentry, inode, "child1"); + auto child2 = kstd::make_shared<kernel::filesystem::dentry>(parent_dentry, inode, "child2"); + parent_dentry->add_child(child1); + parent_dentry->add_child(child2); + + THEN("the children can be found by name") + { + REQUIRE(parent_dentry->find_child("child1") == child1); + REQUIRE(parent_dentry->find_child("child2") == child2); + } + + THEN("finding a non-existent child returns null") + { + REQUIRE(parent_dentry->find_child("nonexistent") == nullptr); + } + } + } +} + +SCENARIO("Dentry Flag logic", "[filesystem][dentry]") +{ + GIVEN("A dentry") + { + auto inode = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto dentry = kernel::filesystem::dentry{nullptr, inode, "test"}; + + WHEN("setting a flag") + { + dentry.set_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted); + + THEN("the flag is set") + { + REQUIRE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + } + } + + WHEN("unsetting a flag") + { + dentry.set_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted); + dentry.unset_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted); + + THEN("the flag is unset") + { + REQUIRE_FALSE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + } + } + } +} diff --git a/kernel/src/filesystem/devfs/filesystem.cpp b/kernel/src/filesystem/devfs/filesystem.cpp index 9043ac5..03b4218 100644 --- a/kernel/src/filesystem/devfs/filesystem.cpp +++ b/kernel/src/filesystem/devfs/filesystem.cpp @@ -1,6 +1,7 @@ #include "kernel/filesystem/devfs/filesystem.hpp" #include "kapi/devices/device.hpp" + #include "kernel/devices/storage/management.hpp" #include "kernel/filesystem/devfs/inode.hpp" #include "kernel/filesystem/device_inode.hpp" @@ -13,12 +14,12 @@ namespace kernel::filesystem::devfs { - auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const &) -> int + auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const &) -> operation_result { m_root_inode = kstd::make_shared<inode>(); build_device_inode_table(); - return 0; + return operation_result::success; } auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) diff --git a/kernel/src/filesystem/devfs/filesystem.tests.cpp b/kernel/src/filesystem/devfs/filesystem.tests.cpp new file mode 100644 index 0000000..f8c4764 --- /dev/null +++ b/kernel/src/filesystem/devfs/filesystem.tests.cpp @@ -0,0 +1,72 @@ +#include "kernel/filesystem/devfs/filesystem.hpp" + +#include "kernel/filesystem/filesystem.hpp" +#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp" + +#include <catch2/catch_test_macros.hpp> + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, + "Devfs filesystem lookup uses storage management devices", "[filesystem][devfs][filesystem]") +{ + GIVEN("a boot module registry with one module") + { + setup_modules(1); + + auto fs = kernel::filesystem::devfs::filesystem{}; + auto result = fs.mount(nullptr); + + THEN("mount succeeds") + { + REQUIRE(result == kernel::filesystem::filesystem::operation_result::success); + REQUIRE(fs.root_inode() != nullptr); + } + + THEN("lookup on root finds ram0 device inode") + { + auto inode = fs.lookup(fs.root_inode(), "ram0"); + REQUIRE(inode != nullptr); + REQUIRE(inode->is_device()); + } + + THEN("lookup of an unknown device returns null") + { + auto inode = fs.lookup(fs.root_inode(), "ram99"); + REQUIRE(inode == nullptr); + } + + THEN("lookup with wrong parent returns null") + { + auto other_fs = kernel::filesystem::devfs::filesystem{}; + other_fs.mount(nullptr); + + auto inode = fs.lookup(other_fs.root_inode(), "ram0"); + REQUIRE(inode == nullptr); + } + + THEN("lookup with a non-directory parent returns null") + { + auto non_directory_inode = fs.lookup(fs.root_inode(), "ram0"); + REQUIRE(non_directory_inode != nullptr); + REQUIRE(!non_directory_inode->is_directory()); + + auto result = fs.lookup(non_directory_inode, "anything"); + REQUIRE(result == nullptr); + } + } + + GIVEN("a boot module registry with three modules") + { + setup_modules(3, 2048); + + auto fs = kernel::filesystem::devfs::filesystem{}; + auto result = fs.mount(nullptr); + REQUIRE(result == kernel::filesystem::filesystem::operation_result::success); + + THEN("lookup finds all generated RAM devices") + { + REQUIRE(fs.lookup(fs.root_inode(), "ram0") != nullptr); + REQUIRE(fs.lookup(fs.root_inode(), "ram16") != nullptr); + REQUIRE(fs.lookup(fs.root_inode(), "ram32") != nullptr); + } + } +} diff --git a/kernel/src/filesystem/devfs/inode.tests.cpp b/kernel/src/filesystem/devfs/inode.tests.cpp new file mode 100644 index 0000000..50e34a7 --- /dev/null +++ b/kernel/src/filesystem/devfs/inode.tests.cpp @@ -0,0 +1,54 @@ +#include "kernel/filesystem/devfs/inode.hpp" + +#include <kstd/memory> +#include <kstd/print> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +#include <cstdint> + +SCENARIO("Devfs inode creation", "[filesystem][devfs][inode]") +{ + GIVEN("a devfs inode") + { + auto inode = kernel::filesystem::devfs::inode{}; + + THEN("the inode has the correct kind") + { + REQUIRE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_regular()); + } + } +} + +SCENARIO("Devfs inode read/write", "[filesystem][devfs][inode]") +{ + GIVEN("a devfs inode") + { + auto inode = kernel::filesystem::devfs::inode{}; + + WHEN("attempting to read from the devfs inode") + { + kstd::vector<uint8_t> buffer(512); + auto bytes_read = inode.read(buffer.data(), 0, buffer.size()); + + THEN("no bytes are read") + { + REQUIRE(bytes_read == 0); + } + } + + WHEN("attempting to write to the devfs inode") + { + kstd::vector<uint8_t> buffer(512); + auto bytes_written = inode.write(buffer.data(), 0, buffer.size()); + + THEN("no bytes are written") + { + REQUIRE(bytes_written == 0); + } + } + } +} diff --git a/kernel/src/filesystem/device_inode.cpp b/kernel/src/filesystem/device_inode.cpp index af8cecc..397a0fd 100644 --- a/kernel/src/filesystem/device_inode.cpp +++ b/kernel/src/filesystem/device_inode.cpp @@ -1,14 +1,12 @@ #include "kernel/filesystem/device_inode.hpp" +#include "kapi/devices/device.hpp" #include "kapi/system.hpp" #include "kernel/devices/block_device_utils.hpp" -#include "kapi/devices/device.hpp" #include "kernel/filesystem/inode.hpp" -#include <kstd/cstring> #include <kstd/memory> -#include <kstd/vector> #include <cstddef> diff --git a/kernel/src/filesystem/device_inode.tests.cpp b/kernel/src/filesystem/device_inode.tests.cpp new file mode 100644 index 0000000..4e31812 --- /dev/null +++ b/kernel/src/filesystem/device_inode.tests.cpp @@ -0,0 +1,108 @@ +#include "kernel/filesystem/device_inode.hpp" + +#include "kernel/test_support/cpu.hpp" +#include "kernel/test_support/devices/block_device.hpp" +#include "kernel/test_support/devices/character_device.hpp" + +#include <kstd/memory> +#include <kstd/print> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +#include <cstddef> +#include <cstdint> + +SCENARIO("Device inode construction", "[filesystem][device_inode]") +{ + GIVEN("a block device") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "test_block_device", 512, 3 * 512); + + WHEN("constructing a device inode with the block device") + { + auto inode = kernel::filesystem::device_inode{device}; + + THEN("the device inode has the correct device") + { + REQUIRE(inode.device() == device); + } + + THEN("the device inode has the correct kind") + { + REQUIRE(inode.is_device()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_regular()); + } + } + + WHEN("constructing a device inode with a null device") + { + THEN("the constructor panics") + { + REQUIRE_THROWS_AS((kernel::filesystem::device_inode{nullptr}), kernel::tests::cpu::halt); + } + } + } +} + +SCENARIO("Device inode read/write", "[filesystem][device_inode]") +{ + GIVEN("a block device and a device inode for that device") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "test_block_device", 512, 3 * 512); + auto inode = kernel::filesystem::device_inode{device}; + + WHEN("writing to the device inode") + { + kstd::vector<uint8_t> write_buffer(1024); + for (size_t i = 0; i < write_buffer.size(); ++i) + { + write_buffer[i] = static_cast<uint8_t>(i % 256); + } + + auto bytes_written = inode.write(write_buffer.data(), 256, write_buffer.size()); + + THEN("the correct number of bytes is written") + { + REQUIRE(bytes_written == 1024); + } + + THEN("the data written matches the data read back from the device inode") + { + kstd::vector<uint8_t> read_buffer(1024); + auto bytes_read = inode.read(read_buffer.data(), 256, read_buffer.size()); + + REQUIRE(bytes_read == write_buffer.size()); + REQUIRE(read_buffer == write_buffer); + } + } + } +} + +SCENARIO("Device inode read/write with a non-block device", "[filesystem][device_inode]") +{ + GIVEN("a non-block device and a device inode for that device") + { + auto device = kstd::make_shared<kernel::tests::devices::character_device>(0, 0, "test_character_device"); + auto inode = kernel::filesystem::device_inode{device}; + + WHEN("reading from the device inode") + { + kstd::vector<uint8_t> read_buffer(512); + THEN("the system panics") + { + REQUIRE_THROWS_AS(inode.read(read_buffer.data(), 0, read_buffer.size()), kernel::tests::cpu::halt); + } + } + + WHEN("writing to the device inode") + { + kstd::vector<uint8_t> write_buffer(512); + THEN("the system panics") + { + REQUIRE_THROWS_AS(inode.write(write_buffer.data(), 0, write_buffer.size()), kernel::tests::cpu::halt); + } + } + } +} diff --git a/kernel/src/filesystem/ext2/filesystem.cpp b/kernel/src/filesystem/ext2/filesystem.cpp index eb9edc4..0ad5c97 100644 --- a/kernel/src/filesystem/ext2/filesystem.cpp +++ b/kernel/src/filesystem/ext2/filesystem.cpp @@ -1,13 +1,17 @@ #include "kernel/filesystem/ext2/filesystem.hpp" -#include "kernel/devices/block_device_utils.hpp" #include "kapi/devices/device.hpp" + +#include "kernel/devices/block_device_utils.hpp" +#include "kernel/filesystem/ext2/block_group_descriptor.hpp" #include "kernel/filesystem/ext2/inode.hpp" +#include "kernel/filesystem/ext2/linked_directory_entry.hpp" #include "kernel/filesystem/ext2/superblock.hpp" #include "kernel/filesystem/filesystem.hpp" #include "kernel/filesystem/inode.hpp" #include <kstd/memory> +#include <kstd/vector> #include <cstddef> #include <cstdint> @@ -17,56 +21,211 @@ namespace kernel::filesystem::ext2 { namespace { - // constexpr size_t SUPERBLOCK_OFFSET = 1024; - // constexpr uint16_t EXT2_MAGIC = 0xEF53; - - // // Mode bits - // constexpr uint16_t S_IFMT = 0xF000; - // constexpr uint16_t S_IFREG = 0x8000; - // constexpr uint16_t S_IFDIR = 0x4000; - - // auto S_ISREG(uint16_t mode) -> bool - // { - // return (mode & S_IFMT) == S_IFREG; - // } - // auto S_ISDIR(uint16_t mode) -> bool - // { - // return (mode & S_IFMT) == S_IFDIR; - // } - - // auto get_block_size(superblock const & superblock) -> size_t - // { - // return 1024U << superblock.log_block_size; - // } - - // auto get_inode_size(superblock const & superblock) -> size_t - // { - // return superblock.rev_level == 0 ? 128 : superblock.inode_size; - // } + auto S_ISREG(uint16_t mode) -> bool + { + return (mode & constants::mode_mask) == constants::mode_regular; + } + + auto S_ISDIR(uint16_t mode) -> bool + { + return (mode & constants::mode_mask) == constants::mode_directory; + } } // namespace - auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int + auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result { - kernel::filesystem::filesystem::mount(device); // TODO BA-FS26 error handling? - // TODO BA-FS26 load proper root inode from ext2 metadata - // m_root_inode = inode{inode_kind::directory}; - - // TODO BA-FS26 implement - m_root_inode = kstd::make_shared<inode>(); - // devices::block_device_utils::read(device, nullptr, 0, 0); // TODO BA-FS26 just for testing - return 0; + kernel::filesystem::filesystem::mount(device); + + kernel::devices::block_device_utils::read(m_device, &m_superblock, constants::superblock_offset, + sizeof(m_superblock)); + + if (m_superblock.magic != constants::magic_number) + { + return operation_result::invalid_magic_number; + } + + auto const block_size = get_block_size(); + auto const blocks_per_group = m_superblock.blocks_per_group; + auto const num_block_groups = (m_superblock.blocks_count + blocks_per_group - 1) / blocks_per_group; + + m_block_group_descriptors = kstd::vector<block_group_descriptor>(num_block_groups); + + auto const block_group_descriptor_table_offset = block_size == 1024 ? 2 * block_size : block_size; + kernel::devices::block_device_utils::read(m_device, m_block_group_descriptors.data(), + block_group_descriptor_table_offset, + num_block_groups * sizeof(block_group_descriptor)); + + m_root_inode = read_inode(constants::root_inode_number); + + if (!m_root_inode || !m_root_inode->is_directory()) + { + return operation_result::invalid_root_inode; + } + return operation_result::success; } - auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & /*parent*/, std::string_view name) + auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) -> kstd::shared_ptr<kernel::filesystem::inode> { - // TODO BA-FS26 implement ext2 directory traversal and inode loading - if (name == "dev") + if (!parent || !parent->is_directory()) + { + return nullptr; + } + + auto * ext2_parent = static_cast<inode *>(parent.get()); + if (!ext2_parent) + { + return nullptr; + } + + auto const block_size = get_block_size(); + auto const & inode_data = ext2_parent->m_data; + kstd::vector<uint8_t> buffer(block_size); + + for (uint32_t i = 0; i < get_inode_block_count(inode_data); ++i) + { + auto const global_block_number = map_inode_block_index_to_global_block_number(i, inode_data); + auto const block_offset = global_block_number * block_size; + kernel::devices::block_device_utils::read(m_device, buffer.data(), block_offset, block_size); + + auto const * entry = reinterpret_cast<linked_directory_entry const *>(buffer.data()); + auto bytes_read = 0uz; + + while (bytes_read < block_size && entry->inode != 0) + { + auto const entry_name = std::string_view{entry->name.data(), entry->name_len}; + if (entry_name == name) + { + return read_inode(entry->inode); + } + + bytes_read += entry->rec_len; + entry = reinterpret_cast<linked_directory_entry const *>(buffer.data() + bytes_read); + } + } + + return nullptr; + } + + auto filesystem::read_inode(uint32_t inode_number) -> kstd::shared_ptr<inode> + { + auto const block_size = get_block_size(); + auto const inodes_per_group = m_superblock.inodes_per_group; + auto const block_group_index = (inode_number - 1) / inodes_per_group; + auto const inode_index_within_group = (inode_number - 1) % inodes_per_group; + + if (block_group_index >= m_block_group_descriptors.size()) { - // TODO BA-FS26 just for testing return nullptr; } - return kstd::make_shared<inode>(); + auto const & block_group_descriptor = m_block_group_descriptors.at(block_group_index); + auto const inode_table_start_block = block_group_descriptor.inode_table; + auto const inode_table_offset = static_cast<size_t>(inode_table_start_block) * block_size; + auto const inode_offset = inode_table_offset + inode_index_within_group * get_inode_size(); + + auto new_inode = kstd::make_shared<inode>(this); + kernel::devices::block_device_utils::read(m_device, &new_inode->m_data, inode_offset, sizeof(inode_data)); + + // TODO BA-FS26 improve inode_kind really needed? or just map it to the mode bits? + if (S_ISREG(new_inode->m_data.mode)) + { + new_inode->m_kind = inode::inode_kind::regular; + } + else if (S_ISDIR(new_inode->m_data.mode)) + { + new_inode->m_kind = inode::inode_kind::directory; + } + else + { + // TODO BA-FS26 really correct?? + return nullptr; + } + + return new_inode; + } + + auto filesystem::map_inode_block_index_to_global_block_number(uint32_t inode_block_index, inode_data data) -> uint32_t + { + if (inode_block_index < constants::direct_block_count) + { + return data.block.at(inode_block_index); + } + inode_block_index -= constants::direct_block_count; + + auto const block_size = get_block_size(); + auto const numbers_per_block = block_size / sizeof(uint32_t); + + auto const block_numbers_per_singly_indirect_block = numbers_per_block; + auto const block_numbers_per_doubly_indirect_block = numbers_per_block * block_numbers_per_singly_indirect_block; + auto const block_numbers_per_triply_indirect_block = numbers_per_block * block_numbers_per_doubly_indirect_block; + + if (inode_block_index < block_numbers_per_singly_indirect_block) + { + auto const singly_indirect_block_number = data.block.at(constants::singly_indirect_block_index); + return read_block_number_at_index(singly_indirect_block_number, inode_block_index); + } + inode_block_index -= block_numbers_per_singly_indirect_block; + + if (inode_block_index < block_numbers_per_doubly_indirect_block) + { + auto const doubly_indirect_block_number = data.block.at(constants::doubly_indirect_block_index); + auto const singly_indirect_block_index_in_doubly_indirect_block = + inode_block_index / block_numbers_per_singly_indirect_block; + auto const singly_indirect_block_number = read_block_number_at_index( + doubly_indirect_block_number, singly_indirect_block_index_in_doubly_indirect_block); + + auto const block_index_in_singly_indirect_block = inode_block_index % block_numbers_per_singly_indirect_block; + return read_block_number_at_index(singly_indirect_block_number, block_index_in_singly_indirect_block); + } + inode_block_index -= block_numbers_per_doubly_indirect_block; + + if (inode_block_index < block_numbers_per_triply_indirect_block) + { + auto const triply_indirect_block_number = data.block.at(constants::triply_indirect_block_index); + auto const doubly_indirect_block_index_in_triply_indirect_block = + inode_block_index / block_numbers_per_doubly_indirect_block; + auto const doubly_indirect_block_number = read_block_number_at_index( + triply_indirect_block_number, doubly_indirect_block_index_in_triply_indirect_block); + + auto const remaining_block_numbers = inode_block_index % block_numbers_per_doubly_indirect_block; + + auto const singly_indirect_block_index_in_doubly_indirect_block = + remaining_block_numbers / block_numbers_per_singly_indirect_block; + auto const singly_indirect_block_number = read_block_number_at_index( + doubly_indirect_block_number, singly_indirect_block_index_in_doubly_indirect_block); + + auto const block_index_in_singly_indirect_block = + remaining_block_numbers % block_numbers_per_singly_indirect_block; + return read_block_number_at_index(singly_indirect_block_number, block_index_in_singly_indirect_block); + } + + return 0; // TODO BA-FS26 really correct?? + } + + auto filesystem::read_block_number_at_index(uint32_t block_number, uint32_t index) -> uint32_t + { + uint32_t block_number_buffer = 0; + + auto const block_start_offset = block_number * get_block_size(); + auto const number_start_address = block_start_offset + index * sizeof(uint32_t); + kernel::devices::block_device_utils::read(m_device, &block_number_buffer, number_start_address, sizeof(uint32_t)); + + return block_number_buffer; + } + + auto filesystem::get_block_size() -> size_t + { + return constants::base_block_size << m_superblock.log_block_size; + } + + auto filesystem::get_inode_size() -> size_t + { + return m_superblock.rev_level == 0 ? 128 : m_superblock.inode_size; + } + + auto filesystem::get_inode_block_count(inode_data const & data) -> uint32_t + { + return data.blocks / (2 << m_superblock.log_block_size); } } // namespace kernel::filesystem::ext2 diff --git a/kernel/src/filesystem/ext2/filesystem.tests.cpp b/kernel/src/filesystem/ext2/filesystem.tests.cpp new file mode 100644 index 0000000..b13ebf3 --- /dev/null +++ b/kernel/src/filesystem/ext2/filesystem.tests.cpp @@ -0,0 +1,132 @@ +#include "kernel/filesystem/ext2/filesystem.hpp" + +#include "kernel/devices/storage/management.hpp" +#include "kernel/filesystem/ext2/inode.hpp" +#include "kernel/filesystem/filesystem.hpp" +#include "kernel/test_support/devices/block_device.hpp" +#include "kernel/test_support/filesystem/ext2.hpp" +#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp" + +#include <kstd/memory> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +#include <array> +#include <cstdint> +#include <filesystem> + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, + "Ext2 filesystem mount and lookup with real image", "[filesystem][ext2][filesystem][img]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("a mounted ext2 filesystem from a real image") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); + + auto boot_device = kernel::devices::storage::management::get().determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(boot_device) == kernel::filesystem::filesystem::operation_result::success); + + THEN("the root inode is available and is a directory") + { + REQUIRE(fs.root_inode() != nullptr); + REQUIRE(fs.root_inode()->is_directory()); + } + + THEN("lookup resolves known entries from the image") + { + auto information = fs.lookup(fs.root_inode(), "information"); + REQUIRE(information != nullptr); + REQUIRE(information->is_directory()); + + auto info_1 = fs.lookup(information, "info_1.txt"); + REQUIRE(info_1 != nullptr); + REQUIRE(info_1->is_regular()); + } + + THEN("lookup returns null for invalid inputs") + { + REQUIRE(fs.lookup(nullptr, "information") == nullptr); + + auto information = fs.lookup(fs.root_inode(), "information"); + REQUIRE(information != nullptr); + auto info_1 = fs.lookup(information, "info_1.txt"); + REQUIRE(info_1 != nullptr); + + REQUIRE(fs.lookup(info_1, "anything") == nullptr); + REQUIRE(fs.lookup(fs.root_inode(), "does_not_exist") == nullptr); + } + } +} + +SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem]") +{ + auto const block_size = 1024; + GIVEN("a block device that does not contain an ext2 superblock") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 2 * block_size); + REQUIRE(device != nullptr); + + auto fs = kernel::filesystem::ext2::filesystem{}; + + THEN("mount fails with invalid_magic_number") + { + REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::invalid_magic_number); + } + } +} + +SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesystem][ext2][filesystem]") +{ + auto const block_size = 1024; + + GIVEN("a minimally valid ext2 layout with configured indirect block tables") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 128 * block_size); + REQUIRE(device != nullptr); + + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::success); + + auto inode_data = kernel::filesystem::ext2::inode_data{}; + inode_data.block[0] = 7; + + inode_data.block[12] = 30; + kernel::tests::filesystem::ext2::write_u32(*device, 30 * block_size, 31); + + inode_data.block[13] = 40; + kernel::tests::filesystem::ext2::write_u32(*device, 40 * block_size, 41); + kernel::tests::filesystem::ext2::write_u32(*device, 41 * block_size, 42); + + inode_data.block[14] = 50; + kernel::tests::filesystem::ext2::write_u32(*device, 50 * block_size, 51); + kernel::tests::filesystem::ext2::write_u32(*device, 51 * block_size, 52); + kernel::tests::filesystem::ext2::write_u32(*device, 52 * block_size, 53); + + auto const numbers_per_block = static_cast<uint32_t>(block_size / sizeof(uint32_t)); + auto const singly_start = static_cast<uint32_t>(kernel::filesystem::ext2::constants::direct_block_count); + auto const doubly_start = singly_start + numbers_per_block; + auto const triply_start = doubly_start + numbers_per_block * numbers_per_block; + + THEN("mapping resolves direct, singly, doubly and triply indirect indexes") + { + REQUIRE(fs.map_inode_block_index_to_global_block_number(0, inode_data) == 7); + REQUIRE(fs.map_inode_block_index_to_global_block_number(singly_start, inode_data) == 31); + REQUIRE(fs.map_inode_block_index_to_global_block_number(doubly_start, inode_data) == 42); + REQUIRE(fs.map_inode_block_index_to_global_block_number(triply_start, inode_data) == 53); + } + + THEN("mapping returns zero for out-of-range indexes") + { + auto const beyond_triply = triply_start + numbers_per_block * numbers_per_block * numbers_per_block; + REQUIRE(fs.map_inode_block_index_to_global_block_number(beyond_triply, inode_data) == 0); + } + } +} diff --git a/kernel/src/filesystem/ext2/inode.cpp b/kernel/src/filesystem/ext2/inode.cpp index b75969a..a29bb3b 100644 --- a/kernel/src/filesystem/ext2/inode.cpp +++ b/kernel/src/filesystem/ext2/inode.cpp @@ -1,24 +1,59 @@ #include "kernel/filesystem/ext2/inode.hpp" +#include "kapi/system.hpp" + +#include "kernel/devices/block_device_utils.hpp" +#include "kernel/filesystem/ext2/filesystem.hpp" #include "kernel/filesystem/inode.hpp" +#include <algorithm> #include <cstddef> +#include <cstdint> namespace kernel::filesystem::ext2 { - inode::inode() + inode::inode(filesystem * fs) : kernel::filesystem::inode(inode_kind::regular) - {} + , m_filesystem(fs) + { + if (!m_filesystem) + { + kapi::system::panic("[EXT2] ext2::inode constructed with filesystem null pointer"); + } + } - auto inode::read(void * /*buffer*/, size_t /*offset*/, size_t /*size*/) const -> size_t + auto inode::read(void * buffer, size_t offset, size_t size) const -> size_t { - // TODO BA-FS26 implement - return 0; + auto block_index = offset / m_filesystem->get_block_size(); + auto in_block_offset = offset % m_filesystem->get_block_size(); + + auto bytes_read = 0uz; + + while (bytes_read < size) + { + auto const block_number = m_filesystem->map_inode_block_index_to_global_block_number(block_index, m_data); + if (block_number == 0) // TODO BA-FS26 really correct? + { + break; + } + + auto const block_start_offset = block_number * m_filesystem->get_block_size(); + auto const read_offset = block_start_offset + in_block_offset; + auto const bytes_to_read = std::min(size - bytes_read, m_filesystem->get_block_size() - in_block_offset); + + bytes_read += kernel::devices::block_device_utils::read( + m_filesystem->device(), static_cast<uint8_t *>(buffer) + bytes_read, read_offset, bytes_to_read); + + block_index++; + in_block_offset = 0; // After the first block, we always start at the beginning of the block + } + + return bytes_read; } auto inode::write(void const * /*buffer*/, size_t /*offset*/, size_t /*size*/) -> size_t { - // TODO BA-FS26 implement + kapi::system::panic("[EXT2] inode::write is not implemented yet"); return 0; } } // namespace kernel::filesystem::ext2
\ No newline at end of file diff --git a/kernel/src/filesystem/ext2/inode.tests.cpp b/kernel/src/filesystem/ext2/inode.tests.cpp new file mode 100644 index 0000000..795ff10 --- /dev/null +++ b/kernel/src/filesystem/ext2/inode.tests.cpp @@ -0,0 +1,159 @@ +#include "kernel/filesystem/ext2/inode.hpp" + +#include "kernel/devices/storage/management.hpp" +#include "kernel/filesystem/ext2/filesystem.hpp" +#include "kernel/filesystem/filesystem.hpp" +#include "kernel/test_support/cpu.hpp" +#include "kernel/test_support/devices/block_device.hpp" +#include "kernel/test_support/filesystem/ext2.hpp" +#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp" + +#include <kstd/memory> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +#include <cstddef> +#include <filesystem> +#include <string_view> + +SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]") +{ + GIVEN("an ext2 filesystem") + { + auto fs = kernel::filesystem::ext2::filesystem{}; + + THEN("the inode is initialized and has the kind regular") + { + auto inode = kernel::filesystem::ext2::inode{&fs}; + REQUIRE(inode.is_regular()); + REQUIRE(!inode.is_directory()); + REQUIRE(!inode.is_device()); + } + } + + GIVEN("no filesystem (null pointer)") + { + THEN("constructing an inode with a null filesystem pointer panics") + { + REQUIRE_THROWS_AS(kernel::filesystem::ext2::inode{nullptr}, kernel::tests::cpu::halt); + } + } +} + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 inode reads from real image", + "[filesystem][ext2][inode][img]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("a mounted ext2 filesystem and a regular file inode") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); + + auto boot_device = kernel::devices::storage::management::get().determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(boot_device) == kernel::filesystem::filesystem::operation_result::success); + + auto information = fs.lookup(fs.root_inode(), "information"); + REQUIRE(information != nullptr); + auto file = fs.lookup(information, "info_1.txt"); + REQUIRE(file != nullptr); + REQUIRE(file->is_regular()); + + THEN("reading from offset zero returns expected file prefix") + { + auto buffer = kstd::vector<std::byte>(6); + auto const bytes_read = file->read(buffer.data(), 0, buffer.size()); + + REQUIRE(bytes_read == 6); + + auto const text = std::string_view{reinterpret_cast<char const *>(buffer.data()), bytes_read}; + REQUIRE(text == "info_1"); + } + + THEN("reading with an offset returns the expected byte") + { + auto buffer = kstd::vector<std::byte>(1); + auto const bytes_read = file->read(buffer.data(), 5, buffer.size()); + + REQUIRE(bytes_read == 1); + REQUIRE(static_cast<char>(buffer[0]) == '1'); + } + } +} + +SCENARIO("Ext2 inode read stops when block mapping resolves to zero", "[filesystem][ext2][inode]") +{ + auto const block_size = 1024; + GIVEN("an ext2 inode without mapped data blocks") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::success); + + auto inode = kernel::filesystem::ext2::inode{&fs}; + inode.m_data.blocks = 2; + inode.m_data.block[0] = 0; + + auto buffer = kstd::vector<std::byte>(32, std::byte{0xAB}); + + THEN("no bytes are read") + { + auto const bytes_read = inode.read(buffer.data(), 0, buffer.size()); + REQUIRE(bytes_read == 0); + } + } +} + +SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]") +{ + auto const block_size = 1024; + GIVEN("an ext2 inode with two direct blocks and a block size of 1024 bytes") + { + auto device = kstd::make_shared<kernel::tests::devices::block_device>(0, 0, "mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(device) == kernel::filesystem::filesystem::operation_result::success); + + auto inode = kernel::filesystem::ext2::inode{&fs}; + inode.m_data.blocks = 2; + inode.m_data.block[0] = 20; + kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size - 6, "Hello ", 6); + inode.m_data.block[1] = 21; + kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size, "World!", 6); + + auto buffer = kstd::vector<std::byte>(12, std::byte{0x00}); + + THEN("reading across the block boundary returns the combined content") + { + auto const bytes_read = inode.read(buffer.data(), block_size - 6, buffer.size()); + REQUIRE(bytes_read == 12); + + auto const text = std::string_view{reinterpret_cast<char const *>(buffer.data()), bytes_read}; + REQUIRE(text == "Hello World!"); + } + } +} + +SCENARIO("Ext2 inode write is not implemented", "[filesystem][ext2][inode]") +{ + GIVEN("an ext2 inode") + { + auto fs = kernel::filesystem::ext2::filesystem{}; + auto inode = kernel::filesystem::ext2::inode{&fs}; + + THEN("writing to the inode panics") + { + auto buffer = kstd::vector<std::byte>(32, std::byte{0x00}); + REQUIRE_THROWS_AS(inode.write(buffer.data(), 0, buffer.size()), kernel::tests::cpu::halt); + } + } +} diff --git a/kernel/src/filesystem/file_descriptor_table.cpp b/kernel/src/filesystem/file_descriptor_table.cpp index 287aea2..1c062a1 100644 --- a/kernel/src/filesystem/file_descriptor_table.cpp +++ b/kernel/src/filesystem/file_descriptor_table.cpp @@ -10,13 +10,13 @@ #include <cstddef> #include <optional> -namespace kernel::filesystem +namespace { - namespace - { - constinit auto static global_file_descriptor_table = std::optional<file_descriptor_table>{}; - } // namespace + constinit auto static global_file_descriptor_table = std::optional<kernel::filesystem::file_descriptor_table>{}; +} // namespace +namespace kernel::filesystem +{ auto file_descriptor_table::init() -> void { if (global_file_descriptor_table) @@ -87,4 +87,12 @@ namespace kernel::filesystem m_open_files.at(index) = nullptr; } -} // namespace kernel::filesystem
\ No newline at end of file +} // namespace kernel::filesystem + +namespace kernel::tests::filesystem::file_descriptor_table +{ + auto deinit() -> void + { + global_file_descriptor_table.reset(); + } +} // namespace kernel::tests::filesystem::file_descriptor_table diff --git a/kernel/src/filesystem/file_descriptor_table.tests.cpp b/kernel/src/filesystem/file_descriptor_table.tests.cpp new file mode 100644 index 0000000..5aeadb2 --- /dev/null +++ b/kernel/src/filesystem/file_descriptor_table.tests.cpp @@ -0,0 +1,113 @@ +#include "kernel/filesystem/file_descriptor_table.hpp" + +#include "kernel/filesystem/open_file_description.hpp" +#include "kernel/test_support/filesystem/inode.hpp" + +#include <kstd/memory> +#include <kstd/print> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +SCENARIO("File descriptor table add/get file", "[filesystem][file_descriptor_table]") +{ + GIVEN("a file descriptor table and an open file description") + { + auto & table = kernel::filesystem::file_descriptor_table::get(); + auto inode = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto file_description_1 = kstd::make_shared<kernel::filesystem::open_file_description>(inode); + auto file_description_2 = kstd::make_shared<kernel::filesystem::open_file_description>(inode); + + WHEN("adding the open file description to the file descriptor table") + { + auto fd_1 = table.add_file(file_description_1); + auto fd_2 = table.add_file(file_description_2); + auto fd_3 = table.add_file(file_description_2); + + THEN("a valid file descriptor is returned") + { + REQUIRE(fd_1 == 0); + REQUIRE(fd_2 == 1); + REQUIRE(fd_3 == 2); + } + + THEN("the file description can be retrieved using the returned file descriptor") + { + auto retrieved_description = table.get_file(fd_1); + REQUIRE(retrieved_description == file_description_1); + } + } + } + + GIVEN("a invalid open file description") + { + auto & table = kernel::filesystem::file_descriptor_table::get(); + + THEN("adding a null file description returns an error code") + { + auto fd = table.add_file(nullptr); + REQUIRE(fd == -1); + } + + THEN("retrieving a file description with a negative file descriptor returns a null pointer") + { + auto retrieved_description = table.get_file(-1); + REQUIRE(retrieved_description == nullptr); + } + + THEN("retrieving a file description with an out-of-bounds file descriptor returns a null pointer") + { + auto retrieved_description = table.get_file(1000); + REQUIRE(retrieved_description == nullptr); + } + } +} + +SCENARIO("File descriptor table remove file", "[filesystem][file_descriptor_table]") +{ + GIVEN("a file descriptor table with an open file description") + { + auto & table = kernel::filesystem::file_descriptor_table::get(); + auto inode = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto file_description = kstd::make_shared<kernel::filesystem::open_file_description>(inode); + auto fd = table.add_file(file_description); + + WHEN("removing the file description using the file descriptor") + { + table.remove_file(fd); + + THEN("the file description can no longer be retrieved using the file descriptor") + { + auto retrieved_description = table.get_file(fd); + REQUIRE(retrieved_description == nullptr); + } + } + + WHEN("removing a file description the other file descriptor keep the same index") + { + auto fd2 = table.add_file(file_description); + table.remove_file(fd); + + THEN("the second file description can still be retrieved using its file descriptor") + { + auto retrieved_description = table.get_file(fd2); + REQUIRE(retrieved_description == file_description); + } + } + } + + GIVEN("an invalid file descriptor") + { + auto & table = kernel::filesystem::file_descriptor_table::get(); + + THEN("removing a file with a negative file descriptor does nothing") + { + REQUIRE_NOTHROW(table.remove_file(-1)); + } + + THEN("removing a file with an out-of-bounds file descriptor does nothing") + { + REQUIRE_NOTHROW(table.remove_file(1000)); + } + } +}
\ No newline at end of file diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp index 0ac9cf8..d8b04eb 100644 --- a/kernel/src/filesystem/filesystem.cpp +++ b/kernel/src/filesystem/filesystem.cpp @@ -1,24 +1,62 @@ #include "kernel/filesystem/filesystem.hpp" #include "kapi/devices/device.hpp" +#include "kapi/system.hpp" + +#include "kernel/filesystem/ext2/filesystem.hpp" #include "kernel/filesystem/inode.hpp" #include <kstd/memory> +#include <array> + namespace kernel::filesystem { - auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const & device) -> int + namespace + { + constexpr auto static filesystem_factories = std::array{ + []() { return kstd::make_shared<ext2::filesystem>(); }, + }; + } // namespace + + auto filesystem::probe_and_mount(kstd::shared_ptr<kapi::devices::device> const & device) + -> kstd::shared_ptr<filesystem> + { + if (!device) + { + kapi::system::panic("[FILESYSTEM] cannot mount filesystem: device is null."); + } + + for (auto & factory : filesystem_factories) + { + auto fs = factory(); + if (fs->mount(device) == operation_result::success) + { + return fs; + } + } + + return nullptr; + } + + auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const & device) -> operation_result { if (!device) { - return -1; // TODO BA-FS26 panic or errorcode? + kapi::system::panic("[FILESYSTEM] cannot mount filesystem: device is null."); } + m_device = device; - return 0; + return operation_result::success; } auto filesystem::root_inode() const -> kstd::shared_ptr<inode> const & { return m_root_inode; } + + auto filesystem::device() const -> kstd::shared_ptr<kapi::devices::device> const & + { + return m_device; + } } // namespace kernel::filesystem
\ No newline at end of file diff --git a/kernel/src/filesystem/mount.cpp b/kernel/src/filesystem/mount.cpp index f9e709c..d165385 100644 --- a/kernel/src/filesystem/mount.cpp +++ b/kernel/src/filesystem/mount.cpp @@ -13,11 +13,13 @@ namespace kernel::filesystem { mount::mount(kstd::shared_ptr<dentry> const & mount_dentry, kstd::shared_ptr<dentry> const & root_dentry, - kstd::shared_ptr<filesystem> const & fs, std::string_view mount_path) + kstd::shared_ptr<filesystem> const & fs, std::string_view mount_path, + kstd::shared_ptr<mount> const & parent_mount) : m_mount_path(mount_path) , m_mount_dentry(mount_dentry) , m_root_dentry(root_dentry) , m_filesystem(fs) + , m_parent_mount(parent_mount) { if (!m_filesystem) { @@ -25,7 +27,7 @@ namespace kernel::filesystem } } - auto mount::get_mount_dentry() const -> kstd::shared_ptr<dentry> + auto mount::get_mount_dentry() const -> kstd::shared_ptr<dentry> const & { return m_mount_dentry; } @@ -44,4 +46,9 @@ namespace kernel::filesystem { return m_mount_path.view(); } + + auto mount::get_parent_mount() const -> kstd::shared_ptr<mount> const & + { + return m_parent_mount; + } } // namespace kernel::filesystem
\ No newline at end of file diff --git a/kernel/src/filesystem/mount.tests.cpp b/kernel/src/filesystem/mount.tests.cpp new file mode 100644 index 0000000..4c4393a --- /dev/null +++ b/kernel/src/filesystem/mount.tests.cpp @@ -0,0 +1,49 @@ +#include "kernel/filesystem/mount.hpp" + +#include "kernel/filesystem/dentry.hpp" +#include "kernel/test_support/cpu.hpp" +#include "kernel/test_support/filesystem/filesystem.hpp" +#include "kernel/test_support/filesystem/inode.hpp" + +#include <kstd/memory> +#include <kstd/print> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +SCENARIO("Mount construction", "[filesystem][mount]") +{ + GIVEN("a filesystem and a root dentry") + { + auto fs = kstd::make_shared<kernel::tests::filesystem::filesystem>(); + auto root_inode = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto root_dentry = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode, "/"); + + WHEN("constructing a mount with the filesystem and root dentry") + { + auto mount = kernel::filesystem::mount{root_dentry, root_dentry, fs, "/", nullptr}; + + THEN("the mount has the correct filesystem, root dentry, mount dentry, and mount path") + { + REQUIRE(mount.get_filesystem() == fs); + REQUIRE(mount.root_dentry() == root_dentry); + REQUIRE(mount.get_mount_dentry() == root_dentry); + REQUIRE(mount.get_mount_path() == "/"); + } + + THEN("the mount has no parent mount") + { + REQUIRE(mount.get_parent_mount() == nullptr); + } + } + + WHEN("constructing a mount with a null filesystem") + { + THEN("the constructor panics") + { + REQUIRE_THROWS_AS((kernel::filesystem::mount{root_dentry, root_dentry, nullptr, "/", nullptr}), + kernel::tests::cpu::halt); + } + } + } +} diff --git a/kernel/src/filesystem/mount_table.cpp b/kernel/src/filesystem/mount_table.cpp index 737434e..3b1dee3 100644 --- a/kernel/src/filesystem/mount_table.cpp +++ b/kernel/src/filesystem/mount_table.cpp @@ -1,17 +1,83 @@ #include "kernel/filesystem/mount_table.hpp" +#include "kernel/filesystem/dentry.hpp" #include "kernel/filesystem/mount.hpp" #include <kstd/memory> +#include <kstd/vector> +#include <algorithm> #include <cstddef> +#include <ranges> #include <string_view> namespace kernel::filesystem { - void mount_table::add_mount(kstd::shared_ptr<mount> mount) + namespace + { + auto is_descendant_of(kstd::shared_ptr<mount> const & candidate, kstd::shared_ptr<mount> const & ancestor) -> bool + { + for (auto current = candidate; current; current = current->get_parent_mount()) + { + if (current == ancestor) + { + return true; + } + } + + return false; + } + + auto is_strict_prefix(std::string_view prefix, std::string_view path) -> bool + { + return prefix != "/" && path.starts_with(prefix) && path.size() > prefix.size() && path[prefix.size()] == '/'; + } + + auto is_visible_mount(kstd::shared_ptr<mount> const & candidate, + kstd::vector<kstd::shared_ptr<mount>> const & mounts) -> bool + { + return std::ranges::none_of(mounts, [&](auto const & other) { + return other != candidate && is_strict_prefix(other->get_mount_path(), candidate->get_mount_path()) && + !is_descendant_of(candidate, other); + }); + } + } // namespace + + auto mount_table::has_child_mounts(kstd::shared_ptr<mount> const & parent_mount) const -> bool + { + return std::ranges::any_of( + m_mounts, [&parent_mount](auto const & mount) { return mount->get_parent_mount() == parent_mount; }); + } + + void mount_table::add_mount(kstd::shared_ptr<mount> const & mount) { m_mounts.push_back(mount); + if (auto mount_dentry = mount->get_mount_dentry()) + { + mount_dentry->set_flag(dentry::dentry_flags::dcache_mounted); + } + } + + auto mount_table::remove_mount(std::string_view path) -> operation_result + { + auto mount_it = std::ranges::find_if(std::ranges::reverse_view(m_mounts), [&](auto const & mount) { + return mount->get_mount_path() == path && is_visible_mount(mount, m_mounts); + }); + + if (mount_it == std::ranges::reverse_view(m_mounts).end()) + { + return operation_result::mount_not_found; + } + + auto const & mount = *mount_it; + if (has_child_mounts(mount)) + { + return operation_result::has_child_mounts; + } + + mount->get_mount_dentry()->unset_flag(dentry::dentry_flags::dcache_mounted); + m_mounts.erase(std::ranges::find(m_mounts, mount)); + return operation_result::removed; } auto mount_table::find_longest_prefix_mount(std::string_view path) const -> kstd::shared_ptr<mount> @@ -25,8 +91,9 @@ namespace kernel::filesystem // /a/b/c should match /a/b but not /a/bb or /a/b/c/d, / should match everything bool is_prefix = path.starts_with(mp) && (mp == "/" || path.size() == mp.size() || path[mp.size()] == '/'); + bool visible = is_visible_mount(mount, m_mounts); - if (is_prefix && mp.size() >= best_len) + if (is_prefix && visible && mp.size() >= best_len) { mount_with_longest_prefix = mount; best_len = mp.size(); diff --git a/kernel/src/filesystem/mount_table.tests.cpp b/kernel/src/filesystem/mount_table.tests.cpp new file mode 100644 index 0000000..439fe97 --- /dev/null +++ b/kernel/src/filesystem/mount_table.tests.cpp @@ -0,0 +1,163 @@ +#include "kernel/filesystem/mount_table.hpp" + +#include "kernel/filesystem/dentry.hpp" +#include "kernel/filesystem/mount.hpp" +#include "kernel/test_support/filesystem/filesystem.hpp" +#include "kernel/test_support/filesystem/inode.hpp" + +#include <kstd/memory> +#include <kstd/print> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +#include <string_view> + +SCENARIO("Mount table construction", "[filesystem][mount_table]") +{ + GIVEN("an empty mount table") + { + kernel::filesystem::mount_table table; + + THEN("finding any mount returns null") + { + REQUIRE(table.find_longest_prefix_mount("/") == nullptr); + REQUIRE(table.find_longest_prefix_mount("/any/path") == nullptr); + } + + THEN("removing any mount returns mount_not_found") + { + REQUIRE(table.remove_mount("/") == kernel::filesystem::mount_table::operation_result::mount_not_found); + REQUIRE(table.remove_mount("/any/path") == kernel::filesystem::mount_table::operation_result::mount_not_found); + } + } +} + +SCENARIO("Adding, finding and removing mounts in the mount table", "[filesystem][mount_table]") +{ + GIVEN("a mount table and some mounts") + { + kernel::filesystem::mount_table table; + + auto fs1 = kstd::make_shared<kernel::tests::filesystem::filesystem>(); + auto root_inode1 = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto root_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode1, "/"); + auto mount1 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry1, fs1, "/", nullptr); + + auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>(); + auto root_inode2 = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode2, "/"); + auto mount2 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry2, fs2, "/mnt", nullptr); + + table.add_mount(mount1); + table.add_mount(mount2); + + THEN("dentry flags are set correctly for mounted dentries") + { + REQUIRE(root_dentry1->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + REQUIRE(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + } + + THEN("finding mounts by path returns the correct mount") + { + REQUIRE(table.find_longest_prefix_mount("/") == mount1); + REQUIRE(table.find_longest_prefix_mount("/file") == mount1); + REQUIRE(table.find_longest_prefix_mount("/mnt") == mount2); + REQUIRE(table.find_longest_prefix_mount("/mnt/file") == mount2); + REQUIRE(table.find_longest_prefix_mount("/other") == mount1); + } + + THEN("removing a mount that has no child mounts succeeds") + { + REQUIRE(table.remove_mount("/mnt") == kernel::filesystem::mount_table::operation_result::removed); + REQUIRE(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + REQUIRE(table.find_longest_prefix_mount("/mnt") == mount1); + } + + THEN("removing a mount that does not exist returns mount_not_found") + { + REQUIRE(table.remove_mount("/nonexistent") == kernel::filesystem::mount_table::operation_result::mount_not_found); + } + } + + GIVEN("multiple mounts with the same path") + { + kernel::filesystem::mount_table table; + + auto fs1 = kstd::make_shared<kernel::tests::filesystem::filesystem>(); + auto root_inode1 = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto root_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode1, "/"); + auto mount1 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry1, fs1, "/", nullptr); + + auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>(); + auto root_inode2 = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode2, "/"); + auto mount2 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry2, fs2, "/", mount1); + + table.add_mount(mount1); + table.add_mount(mount2); + + THEN("finding mounts by path returns the correct mount based on longest prefix") + { + REQUIRE(table.find_longest_prefix_mount("/") == mount2); + REQUIRE(table.find_longest_prefix_mount("/file") == mount2); + REQUIRE(table.find_longest_prefix_mount("/mnt") == mount2); + REQUIRE(table.find_longest_prefix_mount("/mnt/file") == mount2); + REQUIRE(table.find_longest_prefix_mount("/other") == mount2); + } + + THEN("removing the topmost mount with the same path succeeds") + { + REQUIRE(table.remove_mount("/") == kernel::filesystem::mount_table::operation_result::removed); + REQUIRE(!root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + REQUIRE(table.find_longest_prefix_mount("/") == mount1); + } + } + + GIVEN("a mount with child mounts") + { + kernel::filesystem::mount_table table; + + auto fs1 = kstd::make_shared<kernel::tests::filesystem::filesystem>(); + auto root_inode1 = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto root_dentry1 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode1, "/"); + auto mount1 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry1, fs1, "/", nullptr); + + auto fs2 = kstd::make_shared<kernel::tests::filesystem::filesystem>(); + auto root_inode2 = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto root_dentry2 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode2, "/"); + auto mount2 = kstd::make_shared<kernel::filesystem::mount>(root_dentry1, root_dentry2, fs2, "/mnt", mount1); + + auto fs3 = kstd::make_shared<kernel::tests::filesystem::filesystem>(); + auto root_inode3 = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto root_dentry3 = kstd::make_shared<kernel::filesystem::dentry>(nullptr, root_inode3, "/"); + auto mount3 = kstd::make_shared<kernel::filesystem::mount>(root_dentry2, root_dentry3, fs3, "/mnt/submnt", mount2); + + table.add_mount(mount1); + table.add_mount(mount2); + table.add_mount(mount3); + + THEN("finding mounts by path returns the correct mount based on longest prefix") + { + REQUIRE(table.find_longest_prefix_mount("/") == mount1); + REQUIRE(table.find_longest_prefix_mount("/file") == mount1); + REQUIRE(table.find_longest_prefix_mount("/mnt") == mount2); + REQUIRE(table.find_longest_prefix_mount("/mnt/file") == mount2); + REQUIRE(table.find_longest_prefix_mount("/mnt/submnt") == mount3); + REQUIRE(table.find_longest_prefix_mount("/other") == mount1); + } + + THEN("removing a mount with child mounts returns has_child_mounts") + { + REQUIRE(table.remove_mount("/") == kernel::filesystem::mount_table::operation_result::has_child_mounts); + REQUIRE(table.remove_mount("/mnt") == kernel::filesystem::mount_table::operation_result::has_child_mounts); + } + + THEN("removing a leaf mount succeeds") + { + REQUIRE(table.remove_mount("/mnt/submnt") == kernel::filesystem::mount_table::operation_result::removed); + REQUIRE(!root_dentry3->has_flag(kernel::filesystem::dentry::dentry_flags::dcache_mounted)); + REQUIRE(table.find_longest_prefix_mount("/mnt/submnt") == mount2); + } + } +} diff --git a/kernel/src/filesystem/open_file_description.cpp b/kernel/src/filesystem/open_file_description.cpp index 8c04225..f049a34 100644 --- a/kernel/src/filesystem/open_file_description.cpp +++ b/kernel/src/filesystem/open_file_description.cpp @@ -32,4 +32,10 @@ namespace kernel::filesystem m_offset += written_bytes; return written_bytes; } + + auto open_file_description::offset() const -> size_t + { + return m_offset; + } + } // namespace kernel::filesystem
\ No newline at end of file diff --git a/kernel/src/filesystem/open_file_description.tests.cpp b/kernel/src/filesystem/open_file_description.tests.cpp new file mode 100644 index 0000000..db8eb49 --- /dev/null +++ b/kernel/src/filesystem/open_file_description.tests.cpp @@ -0,0 +1,114 @@ +#include "kernel/filesystem/open_file_description.hpp" + +#include "kernel/filesystem/vfs.hpp" +#include "kernel/test_support/filesystem/inode.hpp" +#include "kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp" + +#include <kstd/memory> +#include <kstd/print> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +#include <cstddef> +#include <filesystem> +#include <string_view> + +SCENARIO("Open file description construction", "[filesystem][open_file_description]") +{ + GIVEN("an inode and an open file description for that inode") + { + auto inode = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto file_description = kernel::filesystem::open_file_description{inode}; + + THEN("the initial offset is zero") + { + REQUIRE(file_description.offset() == 0); + } + } +} + +SCENARIO("Open file description read/write offset management", "[filesystem][open_file_description]") +{ + GIVEN("an inode that tracks read/write calls and an open file description for that inode") + { + auto inode = kstd::make_shared<kernel::tests::filesystem::inode>(); + auto file_description = kernel::filesystem::open_file_description{inode}; + + THEN("the offset is updated correctly after reads") + { + REQUIRE(file_description.read(nullptr, 100) == 100); + REQUIRE(file_description.offset() == 100); + REQUIRE(file_description.read(nullptr, 50) == 50); + REQUIRE(file_description.offset() == 150); + } + + THEN("the offset is updated correctly after writes") + { + REQUIRE(file_description.write(nullptr, 200) == 200); + REQUIRE(file_description.offset() == 200); + REQUIRE(file_description.write(nullptr, 25) == 25); + REQUIRE(file_description.offset() == 225); + } + + THEN("reads and writes both update the same offset") + { + REQUIRE(file_description.read(nullptr, 10) == 10); + REQUIRE(file_description.offset() == 10); + REQUIRE(file_description.write(nullptr, 20) == 20); + REQUIRE(file_description.offset() == 30); + REQUIRE(file_description.read(nullptr, 5) == 5); + REQUIRE(file_description.offset() == 35); + REQUIRE(file_description.write(nullptr, 15) == 15); + REQUIRE(file_description.offset() == 50); + } + } +} + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, + "Open file description read with real image", "[filesystem][open_file_description][img]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("an open file description for a file in a real image") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module"}, {image_path})); + + auto & vfs = kernel::filesystem::vfs::get(); + auto ofd = vfs.open("/information/info_1.txt"); + REQUIRE(ofd != nullptr); + + THEN("the file can be read and the offset is updated") + { + kstd::vector<std::byte> buffer(32); + auto bytes_read = ofd->read(buffer.data(), buffer.size()); + REQUIRE(bytes_read == 32); + REQUIRE(ofd->offset() == 32); + + std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), static_cast<size_t>(bytes_read)}; + auto const content_end = buffer_as_str.find('\0'); + REQUIRE(buffer_as_str.substr(0, content_end) == "info_1\n"); + + for (auto i = content_end; i < buffer_as_str.size(); ++i) + { + REQUIRE(buffer_as_str[i] == '\0'); + } + } + + THEN("the file can be read multiple times") + { + kstd::vector<std::byte> buffer(4); + auto bytes_read_1 = ofd->read(buffer.data(), buffer.size() / 2); + REQUIRE(bytes_read_1 == buffer.size() / 2); + REQUIRE(ofd->offset() == buffer.size() / 2); + + auto bytes_read_2 = ofd->read(buffer.data() + buffer.size() / 2, buffer.size() / 2); + REQUIRE(bytes_read_2 == buffer.size() / 2); + REQUIRE(ofd->offset() == buffer.size()); + + std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), bytes_read_1 + bytes_read_2}; + REQUIRE(buffer_as_str == "info"); + } + } +} diff --git a/kernel/src/filesystem/rootfs/filesystem.cpp b/kernel/src/filesystem/rootfs/filesystem.cpp index 37bf588..dffef99 100644 --- a/kernel/src/filesystem/rootfs/filesystem.cpp +++ b/kernel/src/filesystem/rootfs/filesystem.cpp @@ -1,6 +1,7 @@ #include "kernel/filesystem/rootfs/filesystem.hpp" #include "kapi/devices/device.hpp" + #include "kernel/filesystem/inode.hpp" #include "kernel/filesystem/rootfs/inode.hpp" @@ -10,13 +11,13 @@ namespace kernel::filesystem::rootfs { - auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const &) -> int + auto filesystem::mount(kstd::shared_ptr<kapi::devices::device> const &) -> operation_result { auto rfs_inode = kstd::make_shared<inode>(); rfs_inode->add_child("dev"); m_root_inode = rfs_inode; - return 0; + return operation_result::success; } auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const & parent, std::string_view name) diff --git a/kernel/src/filesystem/rootfs/filesystem.tests.cpp b/kernel/src/filesystem/rootfs/filesystem.tests.cpp new file mode 100644 index 0000000..b013f78 --- /dev/null +++ b/kernel/src/filesystem/rootfs/filesystem.tests.cpp @@ -0,0 +1,45 @@ +#include "kernel/filesystem/rootfs/filesystem.hpp" + +#include "kernel/filesystem/filesystem.hpp" + +#include <kstd/memory> +#include <kstd/print> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +SCENARIO("Rootfs filesystem mount and lookup", "[filesystem][rootfs][filesystem]") +{ + GIVEN("a mounted rootfs filesystem") + { + auto fs = kernel::filesystem::rootfs::filesystem{}; + auto result = fs.mount(nullptr); + + THEN("the filesystem can be mounted successfully") + { + REQUIRE(result == kernel::filesystem::filesystem::operation_result::success); + REQUIRE(fs.root_inode() != nullptr); + } + + THEN("looking up the 'dev' directory returns a valid inode") + { + auto dev_inode = fs.lookup(fs.root_inode(), "dev"); + REQUIRE(dev_inode != nullptr); + REQUIRE(dev_inode->is_directory()); + } + + THEN("looking up a non-existent directory returns null") + { + auto non_existent_inode_1 = fs.lookup(fs.root_inode(), ""); + REQUIRE(non_existent_inode_1 == nullptr); + auto non_existent_inode_2 = fs.lookup(fs.root_inode(), "nonexistent"); + REQUIRE(non_existent_inode_2 == nullptr); + } + + THEN("looking up with a null parent inode returns null") + { + auto result = fs.lookup(nullptr, "dev"); + REQUIRE(result == nullptr); + } + } +} diff --git a/kernel/src/filesystem/rootfs/inode.tests.cpp b/kernel/src/filesystem/rootfs/inode.tests.cpp new file mode 100644 index 0000000..a0c5938 --- /dev/null +++ b/kernel/src/filesystem/rootfs/inode.tests.cpp @@ -0,0 +1,83 @@ +#include "kernel/filesystem/rootfs/inode.hpp" + +#include <kstd/memory> +#include <kstd/print> +#include <kstd/vector> + +#include <catch2/catch_test_macros.hpp> + +SCENARIO("Rootfs inode creation", "[filesystem][rootfs][inode]") +{ + GIVEN("a rootfs inode") + { + auto inode = kernel::filesystem::rootfs::inode{}; + + THEN("the inode has the correct kind") + { + REQUIRE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_regular()); + } + + THEN("the inode has no children") + { + REQUIRE(inode.lookup_child("child") == nullptr); + } + } +} + +SCENARIO("Rootfs inode child management", "[filesystem][rootfs][inode]") +{ + GIVEN("a rootfs inode") + { + auto inode = kernel::filesystem::rootfs::inode{}; + + WHEN("adding a child inode") + { + inode.add_child("child"); + inode.add_child("another child"); + + THEN("the child can be looked up by name") + { + auto child_inode = inode.lookup_child("child"); + REQUIRE(child_inode != nullptr); + REQUIRE(child_inode->is_directory()); + } + + THEN("looking up a non-existent child returns null") + { + REQUIRE(inode.lookup_child("nonexistent") == nullptr); + } + } + } +} + +SCENARIO("Rootfs inode read/write", "[filesystem][rootfs][inode]") +{ + GIVEN("a rootfs inode") + { + auto inode = kernel::filesystem::rootfs::inode{}; + + WHEN("reading from the inode") + { + kstd::vector<char> buffer(10); + auto bytes_read = inode.read(buffer.data(), 0, buffer.size()); + + THEN("no bytes are read") + { + REQUIRE(bytes_read == 0); + } + } + + WHEN("writing to the inode") + { + kstd::vector<char> buffer(10, 'x'); + auto bytes_written = inode.write(buffer.data(), 0, buffer.size()); + + THEN("no bytes are written") + { + REQUIRE(bytes_written == 0); + } + } + } +} diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp index 06214d2..67d1af2 100644 --- a/kernel/src/filesystem/vfs.cpp +++ b/kernel/src/filesystem/vfs.cpp @@ -5,9 +5,9 @@ #include "kernel/devices/storage/management.hpp" #include "kernel/filesystem/dentry.hpp" #include "kernel/filesystem/devfs/filesystem.hpp" -#include "kernel/filesystem/ext2/filesystem.hpp" #include "kernel/filesystem/filesystem.hpp" #include "kernel/filesystem/mount.hpp" +#include "kernel/filesystem/mount_table.hpp" #include "kernel/filesystem/open_file_description.hpp" #include "kernel/filesystem/rootfs/filesystem.hpp" @@ -17,13 +17,13 @@ #include <ranges> #include <string_view> -namespace kernel::filesystem +namespace { - namespace - { - constinit auto static active_vfs = std::optional<vfs>{}; - } // namespace + constinit auto static active_vfs = std::optional<kernel::filesystem::vfs>{}; +} // namespace +namespace kernel::filesystem +{ auto vfs::init() -> void { if (active_vfs) @@ -41,15 +41,16 @@ namespace kernel::filesystem root_fs->mount(nullptr); auto root_fs_root_dentry = kstd::make_shared<dentry>(nullptr, root_fs->root_inode()); - m_mount_table.add_mount(kstd::make_shared<mount>(nullptr, root_fs_root_dentry, root_fs, "")); + m_mount_table.add_mount(kstd::make_shared<mount>(nullptr, root_fs_root_dentry, root_fs, "", nullptr)); auto storage_mgmt = devices::storage::management::get(); if (auto boot_device = storage_mgmt.determine_boot_device()) { - // TODO BA-FS26 detect fs type from boot device and load corresponding fs, for now just assume ext2 - auto boot_root_fs = kstd::make_shared<ext2::filesystem>(); - boot_root_fs->mount(boot_device); - do_mount_internal("/", root_fs_root_dentry, boot_root_fs); + auto boot_root_fs = kernel::filesystem::filesystem::probe_and_mount(boot_device); + if (boot_root_fs) + { + do_mount_internal("/", root_fs_root_dentry, boot_root_fs); + } } auto device_fs = kstd::make_shared<devfs::filesystem>(); @@ -77,41 +78,55 @@ namespace kernel::filesystem return nullptr; } - auto vfs::do_mount(std::string_view path, kstd::shared_ptr<filesystem> const & filesystem) -> int + auto vfs::do_mount(std::string_view path, kstd::shared_ptr<filesystem> const & filesystem) -> operation_result { if (!filesystem) { - return -1; // TODO BA-FS26 panic or errorcode? + return operation_result::filesystem_null; } - if (path.empty() || path.front() != '/') + if (path.empty() || path.front() != '/' || (path.size() > 1 && path.back() == '/')) { - return -1; // TODO BA-FS26 panic or errorcode? + return operation_result::invalid_path; } - // TODO BA-FS26 better path validation - if ((path.size() > 1 && path.back() == '/')) + if (auto mount_point_dentry = resolve_path(path)) { - return -1; // TODO BA-FS26 panic or errorcode? + do_mount_internal(path, mount_point_dentry, filesystem); + return operation_result::success; } - if (auto mount_point_dentry = resolve_path(path)) + return operation_result::mount_point_not_found; + } + + auto vfs::unmount(std::string_view path) -> operation_result + { + if (path.empty() || path.front() != '/' || (path.size() > 1 && path.back() == '/')) { - do_mount_internal(path, mount_point_dentry, filesystem); - return 0; + return operation_result::invalid_path; + } + + auto remove_result = m_mount_table.remove_mount(path); + if (remove_result == mount_table::operation_result::removed) + { + return operation_result::success; } - return -1; + if (remove_result == mount_table::operation_result::has_child_mounts) + { + return operation_result::unmount_failed; + } + + return operation_result::mount_point_not_found; } auto vfs::do_mount_internal(std::string_view path, kstd::shared_ptr<dentry> const & mount_point_dentry, kstd::shared_ptr<filesystem> const & fs) -> void { - // TODO BA-FS26 check if mount point is already mounted and handle it (unmount old fs, fail, etc.) + auto parent_mount = m_mount_table.find_longest_prefix_mount(path); auto new_fs_root = kstd::make_shared<dentry>(mount_point_dentry, fs->root_inode()); - auto new_mount = kstd::make_shared<mount>(mount_point_dentry, new_fs_root, fs, path); + auto new_mount = kstd::make_shared<mount>(mount_point_dentry, new_fs_root, fs, path, parent_mount); m_mount_table.add_mount(new_mount); - mount_point_dentry->set_flag(dentry::dentry_flags::dcache_mounted); } auto vfs::resolve_path(std::string_view path) -> kstd::shared_ptr<dentry> @@ -160,5 +175,12 @@ namespace kernel::filesystem return current_dentry; } +} // namespace kernel::filesystem -} // namespace kernel::filesystem
\ No newline at end of file +namespace kernel::tests::filesystem::vfs +{ + auto deinit() -> void + { + active_vfs.reset(); + } +} // namespace kernel::tests::filesystem::vfs diff --git a/kernel/src/filesystem/vfs.tests.cpp b/kernel/src/filesystem/vfs.tests.cpp new file mode 100644 index 0000000..f363041 --- /dev/null +++ b/kernel/src/filesystem/vfs.tests.cpp @@ -0,0 +1,166 @@ +#include "kernel/filesystem/vfs.hpp" + +#include "kernel/devices/storage/management.hpp" +#include "kernel/filesystem/filesystem.hpp" +#include "kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp" + +#include <catch2/catch_test_macros.hpp> + +#include <filesystem> + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS with dummy modules", + "[filesystem][vfs]") +{ + GIVEN("an initialized boot module registry with multiple modules") + { + REQUIRE_NOTHROW(setup_modules_and_init_vfs(5)); + + THEN("vfs initializes and provides /dev mount") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto dev = vfs.open("/dev"); + + REQUIRE(dev != nullptr); + } + + THEN("vfs initializes root filesystem with boot device if boot module is present") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto root_file = vfs.open("/"); + + REQUIRE(root_file != nullptr); + } + } +} + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS with file backed image", + "[filesystem][vfs][img]") +{ + auto const image_path_1 = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + auto const image_path_2 = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_2KB_fs.img"; + auto const image_path_3 = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_4KB_fs.img"; + + GIVEN("a real image file") + { + REQUIRE(std::filesystem::exists(image_path_1)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module"}, {image_path_1})); + + THEN("vfs initializes and provides expected mount points") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto root = vfs.open("/"); + auto dev = vfs.open("/dev"); + auto information = vfs.open("/information/info_1.txt"); + + REQUIRE(root != nullptr); + REQUIRE(dev != nullptr); + REQUIRE(information != nullptr); + } + } + + GIVEN("three real image files") + { + REQUIRE(std::filesystem::exists(image_path_1)); + REQUIRE(std::filesystem::exists(image_path_2)); + REQUIRE(std::filesystem::exists(image_path_3)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1", "test_img_module_2", "test_img_module_3"}, + {image_path_1, image_path_2, image_path_3})); + + auto & vfs = kernel::filesystem::vfs::get(); + auto storage_mgmt = kernel::devices::storage::management::get(); + auto device_1 = storage_mgmt.device_by_major_minor(1, 16); + auto fs_1 = kernel::filesystem::filesystem::probe_and_mount(device_1); + auto device_2 = storage_mgmt.device_by_major_minor(1, 32); + auto fs_2 = kernel::filesystem::filesystem::probe_and_mount(device_2); + + THEN("vfs initializes first module as root") + { + auto & vfs = kernel::filesystem::vfs::get(); + auto info1 = vfs.open("/information/info_1.txt"); + auto info2 = vfs.open("/information/info_2.txt"); + + REQUIRE(info1 != nullptr); + REQUIRE(info2 != nullptr); + } + + THEN("second image can be mounted, data retrieved and unmounted again") + { + REQUIRE(vfs.do_mount("/information", fs_1) == kernel::filesystem::vfs::operation_result::success); + + auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); + REQUIRE(mounted_monkey_1 != nullptr); + + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); + auto unmounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); + REQUIRE(unmounted_monkey_1 == nullptr); + + auto info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 != nullptr); + } + + THEN("third image can be mounted in a mounted file system, unmount only if no child mount exists") + { + REQUIRE(vfs.do_mount("/information", fs_1) == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.do_mount("/information/monkey_house/infrastructure", fs_2) == + kernel::filesystem::vfs::operation_result::success); + + auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); + auto mounted_fish1 = vfs.open("/information/monkey_house/infrastructure/enclosures/aquarium/tank_1/fish_1.txt"); + + REQUIRE(mounted_monkey_1 != nullptr); + REQUIRE(mounted_fish1 != nullptr); + + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::unmount_failed); + REQUIRE(vfs.unmount("/information/monkey_house/infrastructure") == + kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); + } + + THEN("images can be stacked mounted and correct file system is unmounted again") + { + REQUIRE(vfs.do_mount("/information", fs_1) == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.do_mount("/information", fs_2) == kernel::filesystem::vfs::operation_result::success); + + auto mounted_tickets = vfs.open("/information/entrance/tickets.txt"); + REQUIRE(mounted_tickets != nullptr); + + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); + mounted_tickets = vfs.open("/information/entrance/tickets.txt"); + REQUIRE(mounted_tickets == nullptr); + + auto mounted_monkey = vfs.open("/information/monkey_house/monkey_1.txt"); + REQUIRE(mounted_monkey != nullptr); + } + + THEN("mount with null file system fails") + { + REQUIRE(vfs.do_mount("/information", nullptr) == kernel::filesystem::vfs::operation_result::filesystem_null); + } + + THEN("mount with invalid path fails") + { + REQUIRE(vfs.do_mount("", fs_1) == kernel::filesystem::vfs::operation_result::invalid_path); + REQUIRE(vfs.do_mount("information", fs_1) == kernel::filesystem::vfs::operation_result::invalid_path); + REQUIRE(vfs.do_mount("/information/", fs_1) == kernel::filesystem::vfs::operation_result::invalid_path); + } + + THEN("mount with non-existent mount point fails") + { + REQUIRE(vfs.do_mount("/information/nonexistent", fs_1) == + kernel::filesystem::vfs::operation_result::mount_point_not_found); + } + + THEN("unmount with invalid path fails") + { + REQUIRE(vfs.unmount("") == kernel::filesystem::vfs::operation_result::invalid_path); + REQUIRE(vfs.unmount("information") == kernel::filesystem::vfs::operation_result::invalid_path); + REQUIRE(vfs.unmount("/information/") == kernel::filesystem::vfs::operation_result::invalid_path); + } + + THEN("unmounting non-existent mount point returns expected error code") + { + REQUIRE(vfs.unmount("/information/nonexistent") == + kernel::filesystem::vfs::operation_result::mount_point_not_found); + } + } +} diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp index b920674..79ed703 100644 --- a/kernel/src/main.cpp +++ b/kernel/src/main.cpp @@ -8,8 +8,8 @@ #include "kernel/devices/storage/management.hpp" #include "kernel/filesystem/device_inode.hpp" -#include "kernel/filesystem/ext2/filesystem.hpp" #include "kernel/filesystem/file_descriptor_table.hpp" +#include "kernel/filesystem/filesystem.hpp" #include "kernel/filesystem/open_file_description.hpp" #include "kernel/filesystem/vfs.hpp" #include "kernel/memory.hpp" @@ -23,6 +23,7 @@ #include <algorithm> #include <cstddef> +#include <string_view> using namespace kstd::units_literals; @@ -87,8 +88,6 @@ auto test_file_description_manually() -> void auto test_device_with_vfs() -> void { - // TODO BA-FS26 - auto vfs = kernel::filesystem::vfs::get(); auto ofd = vfs.open("/dev/ram0"); if (!ofd) @@ -112,40 +111,64 @@ auto test_device_with_vfs() -> void auto test_file_lookup() -> void { - // TODO BA-FS26 implement a more complete test with multiple files and directories and mounts etc. - auto vfs = kernel::filesystem::vfs::get(); + auto read_and_write_file = [&vfs](std::string_view path) { + kstd::println("[TEST] Reading and writing file at path: {}", path); + auto ofd = vfs.open(path); + if (!ofd) + { + kstd::os::panic("test code failed"); + } + + kstd::vector<std::byte> buffer{32}; + auto number_of_read_bytes = ofd->read(buffer.data(), buffer.size()); + kstd::println("read bytes: {}", number_of_read_bytes); + kstd::println("buffer: {::#04x}", buffer); + + std::string_view buffer_as_str{reinterpret_cast<char *>(buffer.data()), number_of_read_bytes}; + kstd::println("buffer_as_str: {}", buffer_as_str); + }; + + read_and_write_file("/info.txt"); + read_and_write_file("/enclosures/info.txt"); + read_and_write_file("/enclosures/aquarium/tank_1/fish_4.txt"); + read_and_write_file("/enclosures/elephant_house/elephant_1.txt"); + read_and_write_file( + "/enclosures/aquarium/tank_2/" + "this_is_a_very_very_long_fish_filename_that_keeps_going_and_going_until_it_almost_breaks_linux_filesystem_" + "limits_for_testing_purposes_and_we_add_more_characters_to_make_it_even_longer_30.txt"); + auto storage_mgmt = kernel::devices::storage::management::get(); + auto device_1 = storage_mgmt.device_by_major_minor(1, 16); + auto fs_1 = kernel::filesystem::filesystem::probe_and_mount(device_1); - auto ofd1 = vfs.open("/a/b/c"); - auto ofd2 = vfs.open("/dev/ram0"); - auto ofd3 = vfs.open("/a/d/e"); - if (!ofd1 || !ofd2 || !ofd3) - { - kstd::os::panic("test code failed"); - } + vfs.do_mount("/enclosures/aquarium", fs_1); + read_and_write_file("/enclosures/aquarium/closed.txt"); + read_and_write_file("/enclosures/aquarium/information/info_2.txt"); - if (auto ofd4 = vfs.open("/dev/xxx")) - { - kstd::os::panic("test code failed"); - } + vfs.unmount("/enclosures/aquarium"); + read_and_write_file("/enclosures/aquarium/tank_1/fish_4.txt"); - auto new_filesystem = kstd::make_shared<kernel::filesystem::ext2::filesystem>(); - auto device = storage_mgmt.device_by_major_minor(1, 16); - new_filesystem->mount(device); - if (vfs.do_mount("/a/b", new_filesystem) != 0) - { - kstd::os::panic("test code failed"); - } - auto ofd5 = vfs.open("/a/b/c"); - if (!ofd5) + auto device_2 = storage_mgmt.device_by_major_minor(1, 32); + auto fs_2 = kernel::filesystem::filesystem::probe_and_mount(device_2); + + vfs.do_mount("/enclosures/elephant_house", fs_2); + read_and_write_file("/enclosures/elephant_house/monkey_house/infrastructure/info.txt"); + + vfs.do_mount("/enclosures/elephant_house/monkey_house", fs_1); + read_and_write_file("/enclosures/elephant_house/monkey_house/information/info_2.txt"); + + auto result = vfs.unmount("/enclosures/elephant_house"); + if (result == kernel::filesystem::vfs::operation_result::unmount_failed) { - kstd::os::panic("test code failed"); + kstd::println("[TEST] Unmount failed as expected due to active child mount."); } - if (auto ofd6 = vfs.open("x/y/z")) + vfs.unmount("/enclosures/elephant_house/monkey_house"); + result = vfs.unmount("/enclosures/elephant_house"); + if (result == kernel::filesystem::vfs::operation_result::success) { - kstd::os::panic("test code failed"); + kstd::println("[TEST] Unmount succeeded after unmounting child mount."); } } diff --git a/kernel/src/test_support/devices/block_device.cpp b/kernel/src/test_support/devices/block_device.cpp new file mode 100644 index 0000000..d1d4101 --- /dev/null +++ b/kernel/src/test_support/devices/block_device.cpp @@ -0,0 +1,61 @@ +#include "kernel/test_support/devices/block_device.hpp" + +#include "kernel/devices/block_device.hpp" + +#include <kstd/string> +#include <kstd/vector> + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <string.h> + +namespace kernel::tests::devices +{ + block_device::block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size, + size_t initial_size) + : kernel::devices::block_device(major, minor, name, block_size) + { + data.resize(initial_size, 0); + } + + auto block_device::init() -> bool + { + return true; + } + + auto block_device::read_block(size_t block_index, void * buffer) const -> void + { + auto const offset = block_index * block_size(); + if (offset >= data.size()) + { + kstd::libc::memset(buffer, 0, block_size()); + return; + } + + auto const bytes_to_read = std::min(block_size(), data.size() - offset); + kstd::libc::memcpy(buffer, data.data() + offset, bytes_to_read); + if (bytes_to_read < block_size()) + { + kstd::libc::memset(static_cast<uint8_t *>(buffer) + bytes_to_read, 0, block_size() - bytes_to_read); + } + } + + auto block_device::write_block(size_t block_index, void const * buffer) -> void + { + auto const offset = block_index * block_size(); + auto const write_end = offset + block_size(); + if (write_end > data.size()) + { + data.resize(write_end, 0); + } + + kstd::libc::memcpy(data.data() + offset, static_cast<uint8_t const *>(buffer), block_size()); + } + + auto block_device::size() const -> size_t + { + return data.size(); + } +} // namespace kernel::tests::devices
\ No newline at end of file diff --git a/kernel/src/test_support/devices/character_device.cpp b/kernel/src/test_support/devices/character_device.cpp new file mode 100644 index 0000000..9e9227d --- /dev/null +++ b/kernel/src/test_support/devices/character_device.cpp @@ -0,0 +1,20 @@ + +#include "kernel/test_support/devices/character_device.hpp" + +#include "kapi/devices.hpp" + +#include <kstd/string> + +#include <cstddef> + +namespace kernel::tests::devices +{ + character_device::character_device(size_t major, size_t minor, kstd::string const & name) + : kapi::devices::device(major, minor, name) + {} + + auto character_device::init() -> bool + { + return true; + } +} // namespace kernel::tests::devices
\ No newline at end of file diff --git a/kernel/src/test_support/filesystem/ext2.cpp b/kernel/src/test_support/filesystem/ext2.cpp new file mode 100644 index 0000000..3627373 --- /dev/null +++ b/kernel/src/test_support/filesystem/ext2.cpp @@ -0,0 +1,62 @@ +#include "kernel/test_support/filesystem/ext2.hpp" + +#include "kernel/filesystem/ext2/block_group_descriptor.hpp" +#include "kernel/filesystem/ext2/filesystem.hpp" +#include "kernel/filesystem/ext2/inode.hpp" +#include "kernel/filesystem/ext2/superblock.hpp" +#include "kernel/test_support/devices/block_device.hpp" + +#include <cstdint> +#include <cstring> + +namespace kernel::tests::filesystem::ext2 +{ + namespace + { + constexpr uint32_t root_directory_data_block = 20; + } // namespace + + auto write_bytes(kernel::tests::devices::block_device & device, size_t offset, void const * source, size_t size) + -> void + { + auto const required_size = offset + size; + if (device.data.size() < required_size) + { + device.data.resize(required_size, 0); + } + + std::memcpy(device.data.data() + offset, source, size); + } + + auto write_u32(kernel::tests::devices::block_device & device, size_t offset, uint32_t value) -> void + { + write_bytes(device, offset, &value, sizeof(value)); + } + + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void + { + auto superblock = kernel::filesystem::ext2::superblock{}; + superblock.magic = kernel::filesystem::ext2::constants::magic_number; + superblock.log_block_size = 0; + superblock.blocks_count = 64; + superblock.blocks_per_group = 64; + superblock.inodes_per_group = 32; + superblock.rev_level = 1; + superblock.inode_size = 128; + write_bytes(device, kernel::filesystem::ext2::constants::superblock_offset, &superblock, sizeof(superblock)); + + auto group_descriptor = kernel::filesystem::ext2::block_group_descriptor{}; + group_descriptor.inode_table = 5; + write_bytes(device, 2048, &group_descriptor, sizeof(group_descriptor)); + + auto root_inode_data = kernel::filesystem::ext2::inode_data{}; + root_inode_data.mode = kernel::filesystem::ext2::constants::mode_directory; + root_inode_data.blocks = 2; + root_inode_data.block[0] = root_directory_data_block; + + auto const root_inode_offset = + static_cast<size_t>(group_descriptor.inode_table) * kernel::filesystem::ext2::constants::base_block_size + + (kernel::filesystem::ext2::constants::root_inode_number - 1) * superblock.inode_size; + write_bytes(device, root_inode_offset, &root_inode_data, sizeof(root_inode_data)); + } +} // namespace kernel::tests::filesystem::ext2
\ No newline at end of file diff --git a/kernel/src/test_support/filesystem/filesystem.cpp b/kernel/src/test_support/filesystem/filesystem.cpp new file mode 100644 index 0000000..225d096 --- /dev/null +++ b/kernel/src/test_support/filesystem/filesystem.cpp @@ -0,0 +1,17 @@ +#include "kernel/test_support/filesystem/filesystem.hpp" + +#include "kernel/filesystem/inode.hpp" +#include "kernel/test_support/filesystem/inode.hpp" + +#include <kstd/memory> + +#include <string_view> + +namespace kernel::tests::filesystem +{ + auto filesystem::lookup(kstd::shared_ptr<kernel::filesystem::inode> const &, std::string_view) + -> kstd::shared_ptr<kernel::filesystem::inode> + { + return kstd::make_shared<inode>(); + } +} // namespace kernel::tests::filesystem
\ No newline at end of file diff --git a/kernel/src/test_support/filesystem/inode.cpp b/kernel/src/test_support/filesystem/inode.cpp new file mode 100644 index 0000000..5df7bcd --- /dev/null +++ b/kernel/src/test_support/filesystem/inode.cpp @@ -0,0 +1,22 @@ +#include "kernel/test_support/filesystem/inode.hpp" + +#include "kernel/filesystem/inode.hpp" + +#include <cstddef> + +namespace kernel::tests::filesystem +{ + inode::inode() + : kernel::filesystem::inode(inode_kind::regular) + {} + + auto inode::read(void *, size_t, size_t size) const -> size_t + { + return size; + } + + auto inode::write(void const *, size_t, size_t size) -> size_t + { + return size; + } +} // namespace kernel::tests::filesystem
\ No newline at end of file diff --git a/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp b/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp new file mode 100644 index 0000000..a139f63 --- /dev/null +++ b/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp @@ -0,0 +1,109 @@ +#include "kernel/test_support/filesystem/storage_boot_module_fixture.hpp" + +#include "kapi/boot_module/boot_module.hpp" +#include "kapi/boot_modules.hpp" +#include "kapi/memory.hpp" + +#include "kernel/devices/storage/management.hpp" +#include "kernel/test_support/boot_modules.hpp" +#include "kernel/test_support/devices/storage/management.hpp" + +#include <kstd/string> +#include <kstd/vector> + +#include <cstddef> +#include <filesystem> +#include <fstream> +#include <ios> +#include <stdexcept> + +namespace kernel::tests::filesystem +{ + storage_boot_module_fixture::~storage_boot_module_fixture() + { + kernel::tests::devices::storage::management::deinit(); + kernel::tests::boot_modules::deinit(); + } + + auto storage_boot_module_fixture::setup_modules(std::size_t module_count, std::size_t module_size) -> void + { + m_module_names.clear(); + m_module_data.clear(); + m_registry = {}; + + m_module_names.reserve(module_count); + m_module_data.reserve(module_count); + + for (std::size_t i = 0; i < module_count; ++i) + { + m_module_names.push_back("test_mod" + kstd::to_string(i)); + m_module_data.emplace_back(module_size, std::byte{static_cast<unsigned char>(0x40 + (i % 16))}); + } + + for (std::size_t i = 0; i < module_count; ++i) + { + m_registry.add_boot_module(kapi::boot_modules::boot_module{ + m_module_names[i].view(), kapi::memory::linear_address{m_module_data[i].data()}, m_module_data[i].size()}); + } + + kapi::boot_modules::set_boot_module_registry(m_registry); + kernel::devices::storage::management::init(); + } + + auto storage_boot_module_fixture::setup_modules_from_img(kstd::vector<kstd::string> const & module_names, + kstd::vector<std::filesystem::path> const & img_paths) + -> void + { + m_module_names.clear(); + m_module_data.clear(); + m_registry = {}; + + if (module_names.size() != img_paths.size()) + { + throw std::invalid_argument{"Module names and image paths vectors must have the same size."}; + } + + for (size_t i = 0; i < module_names.size(); ++i) + { + setup_module_from_img(module_names[i], img_paths[i]); + } + + kapi::boot_modules::set_boot_module_registry(m_registry); + kernel::devices::storage::management::init(); + } + + auto storage_boot_module_fixture::setup_module_from_img(kstd::string const & module_name, + std::filesystem::path const & img_path) -> void + { + auto file = std::ifstream{img_path, std::ios::binary | std::ios::ate}; + if (!file) + { + throw std::runtime_error{"Failed to open image file for test boot module: " + img_path.string()}; + } + + auto const end_pos = file.tellg(); + if (end_pos < 0) + { + throw std::runtime_error{"Failed to determine image file size for test boot module: " + img_path.string()}; + } + + auto const size = static_cast<std::size_t>(end_pos); + file.seekg(0, std::ios::beg); + + m_module_names.push_back(module_name); + m_module_data.emplace_back(size); + + if (size > 0) + { + file.read(reinterpret_cast<char *>(m_module_data.back().data()), static_cast<std::streamsize>(size)); + if (!file) + { + throw std::runtime_error{"Failed to read image file content for test boot module: " + img_path.string()}; + } + } + + m_registry.add_boot_module(kapi::boot_modules::boot_module{ + m_module_names.back().view(), kapi::memory::linear_address{m_module_data.back().data()}, + m_module_data.back().size()}); + } +} // namespace kernel::tests::filesystem
\ No newline at end of file diff --git a/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp b/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp new file mode 100644 index 0000000..cba7278 --- /dev/null +++ b/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp @@ -0,0 +1,32 @@ +#include "kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp" + +#include "kernel/filesystem/vfs.hpp" +#include "kernel/test_support/filesystem/vfs.hpp" + +#include <kstd/string> +#include <kstd/vector> + +#include <cstddef> +#include <filesystem> + +namespace kernel::tests::filesystem +{ + storage_boot_module_vfs_fixture::~storage_boot_module_vfs_fixture() + { + kernel::tests::filesystem::vfs::deinit(); + } + + auto storage_boot_module_vfs_fixture::setup_modules_and_init_vfs(std::size_t module_count, std::size_t module_size) + -> void + { + setup_modules(module_count, module_size); + kernel::filesystem::vfs::init(); + } + + auto storage_boot_module_vfs_fixture::setup_modules_from_img_and_init_vfs( + kstd::vector<kstd::string> const & module_names, kstd::vector<std::filesystem::path> const & img_paths) -> void + { + setup_modules_from_img(module_names, img_paths); + kernel::filesystem::vfs::init(); + } +} // namespace kernel::tests::filesystem
\ No newline at end of file diff --git a/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img new file mode 100644 index 0000000..9f1ee4a --- /dev/null +++ b/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:94d3988bc309eb9e81f06042c72bf4c4fb5991cd7fdd597eb00c518a96c792d8 +size 10485760 diff --git a/kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img new file mode 100644 index 0000000..1880911 --- /dev/null +++ b/kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9a13da5abb9c65c737105b1da0d4344c7cd7604c7952c762c4f4e3d3f96fd42d +size 5242880 diff --git a/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img new file mode 100644 index 0000000..3aaceb8 --- /dev/null +++ b/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4ce6a1aea277906e1af6de223c017ff900b96569f076b4d99fc04eaa1ee986f4 +size 10485760 diff --git a/kernel/src/test_support/state_reset_listener.cpp b/kernel/src/test_support/state_reset_listener.cpp index 8c95cc8..9120c89 100644 --- a/kernel/src/test_support/state_reset_listener.cpp +++ b/kernel/src/test_support/state_reset_listener.cpp @@ -2,8 +2,13 @@ #include "kapi/cpu.hpp" #include "kapi/memory.hpp" +#include "kernel/filesystem/file_descriptor_table.hpp" +#include "kernel/test_support/boot_modules.hpp" #include "kernel/test_support/cio.hpp" #include "kernel/test_support/cpu.hpp" +#include "kernel/test_support/devices/storage/management.hpp" +#include "kernel/test_support/filesystem/file_descriptor_table.hpp" +#include "kernel/test_support/filesystem/vfs.hpp" #include "kernel/test_support/memory.hpp" #include <catch2/catch_test_case_info.hpp> @@ -17,6 +22,8 @@ struct state_reset_listener : Catch::EventListenerBase void testCaseStarting(Catch::TestCaseInfo const &) override { + kernel::filesystem::file_descriptor_table::init(); + kapi::cio::init(); kapi::cpu::init(); kapi::memory::init(); @@ -24,6 +31,11 @@ struct state_reset_listener : Catch::EventListenerBase void testCaseEnded(Catch::TestCaseStats const &) override { + kernel::tests::filesystem::file_descriptor_table::deinit(); + kernel::tests::filesystem::vfs::deinit(); + kernel::tests::boot_modules::deinit(); + kernel::tests::devices::storage::management::deinit(); + kernel::tests::memory::deinit(); kernel::tests::cpu::deinit(); kernel::tests::cio::deinit(); diff --git a/libs/kstd/include/kstd/vector b/libs/kstd/include/kstd/vector index 9e41cb6..e51cbac 100644 --- a/libs/kstd/include/kstd/vector +++ b/libs/kstd/include/kstd/vector @@ -559,6 +559,45 @@ namespace kstd m_size = old_size; } + //! Resize this vector to contain @p new_size elements. + constexpr auto resize(size_type new_size) -> void + { + resize(new_size, value_type{}); + } + + //! Resize this vector to contain @p new_size elements, filling new elements with @p value. + constexpr auto resize(size_type new_size, const_reference value) -> void + { + if (new_size < size()) + { + destroy_n(begin() + new_size, size() - new_size); + m_size = new_size; + return; + } + + if (new_size == size()) + { + return; + } + + if (new_size > max_size()) + { + kstd::os::panic("[kstd:vector] Tried to resize more space than theoretically possible."); + } + + if (new_size > capacity()) + { + reserve(new_size); + } + + for (auto i = size(); i < new_size; ++i) + { + std::allocator_traits<allocator_type>::construct(m_allocator, m_data + i, value); + } + + m_size = new_size; + } + //! Get the number of element this vector has currently space for, including elements currently in this vector. [[nodiscard]] constexpr auto capacity() const noexcept -> size_type { diff --git a/scripts/qemu-wrapper.sh b/scripts/qemu-wrapper.sh index 49c01ec..dd0200d 100755 --- a/scripts/qemu-wrapper.sh +++ b/scripts/qemu-wrapper.sh @@ -12,7 +12,7 @@ if [ -z "$ISO_PATH" ]; then fi ARGS=( - "-m" "32M" + "-m" "64M" "-machine" "q35" "-smp" "4,sockets=1,cores=4,threads=1" "-display" "curses" |
