From cd7e75423e2bcdace6ff5fc9fe7d5e8c5d2a0f1f Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Wed, 26 Aug 2026 20:33:56 +0200 Subject: kernel: split vfs and filesystem drivers --- kernel/CMakeLists.txt | 54 +- kernel/kapi/filesystem.cpp | 41 +- kernel/kapi/filesystem.tests.cpp | 6 +- kernel/kernel/drivers/pseudo/null.cpp | 6 +- kernel/kernel/drivers/pseudo/zero.cpp | 6 +- kernel/kernel/drivers/storage/ram_disk.cpp | 4 +- .../filesystem/byte_offset_file_descriptor.cpp | 83 -- .../filesystem/byte_offset_file_descriptor.hpp | 41 - .../byte_offset_file_descriptor.tests.cpp | 219 ---- kernel/kernel/filesystem/constants.hpp | 18 - kernel/kernel/filesystem/dentry.cpp | 96 -- kernel/kernel/filesystem/dentry.hpp | 98 -- kernel/kernel/filesystem/dentry.tests.cpp | 150 --- kernel/kernel/filesystem/devfs/filesystem.cpp | 82 -- kernel/kernel/filesystem/devfs/filesystem.hpp | 48 - .../kernel/filesystem/devfs/filesystem.tests.cpp | 134 --- kernel/kernel/filesystem/devfs/inode.cpp | 99 -- kernel/kernel/filesystem/devfs/inode.hpp | 35 - kernel/kernel/filesystem/devfs/inode.tests.cpp | 235 ----- kernel/kernel/filesystem/devfs/module.cpp | 28 - kernel/kernel/filesystem/device_inode.cpp | 120 --- kernel/kernel/filesystem/device_inode.hpp | 103 -- kernel/kernel/filesystem/device_inode.tests.cpp | 244 ----- .../kernel/filesystem/device_number_registry.cpp | 283 ----- .../kernel/filesystem/device_number_registry.hpp | 117 --- .../filesystem/device_number_registry.tests.cpp | 154 --- .../filesystem/directory_file_descriptor.cpp | 34 - .../filesystem/directory_file_descriptor.hpp | 35 - .../filesystem/directory_file_descriptor.tests.cpp | 81 -- .../kernel/filesystem/directory_listing_cursor.hpp | 23 - .../kernel/filesystem/directory_listing_entry.hpp | 23 - kernel/kernel/filesystem/driver_registry.cpp | 130 --- kernel/kernel/filesystem/driver_registry.hpp | 73 -- kernel/kernel/filesystem/error.cpp | 113 -- kernel/kernel/filesystem/error.hpp | 56 - .../filesystem/ext2/block_group_descriptor.hpp | 22 - .../kernel/filesystem/ext2/directory_iterator.cpp | 109 -- .../kernel/filesystem/ext2/directory_iterator.hpp | 56 - .../filesystem/ext2/directory_iterator.tests.cpp | 123 --- kernel/kernel/filesystem/ext2/error.cpp | 103 -- kernel/kernel/filesystem/ext2/error.hpp | 45 - kernel/kernel/filesystem/ext2/filesystem.cpp | 1103 -------------------- kernel/kernel/filesystem/ext2/filesystem.hpp | 164 --- kernel/kernel/filesystem/ext2/filesystem.tests.cpp | 360 ------- kernel/kernel/filesystem/ext2/inode.cpp | 361 ------- kernel/kernel/filesystem/ext2/inode.hpp | 161 --- kernel/kernel/filesystem/ext2/inode.tests.cpp | 874 ---------------- .../filesystem/ext2/linked_directory_entry.hpp | 25 - kernel/kernel/filesystem/ext2/module.cpp | 26 - kernel/kernel/filesystem/ext2/mount_state.hpp | 26 - kernel/kernel/filesystem/ext2/superblock.hpp | 122 --- kernel/kernel/filesystem/ext2/write_batch.cpp | 66 -- kernel/kernel/filesystem/ext2/write_batch.hpp | 51 - kernel/kernel/filesystem/file_type_bindings.hpp | 30 - kernel/kernel/filesystem/filesystem.hpp | 77 -- kernel/kernel/filesystem/inode.cpp | 103 -- kernel/kernel/filesystem/inode.hpp | 142 --- kernel/kernel/filesystem/mount.cpp | 135 --- kernel/kernel/filesystem/mount.hpp | 109 -- kernel/kernel/filesystem/mount.tests.cpp | 95 -- kernel/kernel/filesystem/mount_table.cpp | 79 -- kernel/kernel/filesystem/mount_table.hpp | 52 - kernel/kernel/filesystem/mount_table.tests.cpp | 184 ---- kernel/kernel/filesystem/open_file_descriptor.cpp | 53 - kernel/kernel/filesystem/open_file_descriptor.hpp | 68 -- .../filesystem/open_file_descriptor.tests.cpp | 110 -- kernel/kernel/filesystem/open_file_table.cpp | 90 -- kernel/kernel/filesystem/open_file_table.hpp | 57 - kernel/kernel/filesystem/open_file_table.tests.cpp | 107 -- kernel/kernel/filesystem/path.hpp | 97 -- kernel/kernel/filesystem/path.tests.cpp | 128 --- kernel/kernel/filesystem/reserved_numbers.hpp | 36 - kernel/kernel/filesystem/rootfs/filesystem.cpp | 47 - kernel/kernel/filesystem/rootfs/filesystem.hpp | 47 - .../kernel/filesystem/rootfs/filesystem.tests.cpp | 48 - kernel/kernel/filesystem/rootfs/inode.cpp | 36 - kernel/kernel/filesystem/rootfs/inode.hpp | 36 - kernel/kernel/filesystem/rootfs/inode.tests.cpp | 42 - kernel/kernel/filesystem/rootfs/module.cpp | 28 - kernel/kernel/filesystem/vfs.cpp | 481 --------- kernel/kernel/filesystem/vfs.hpp | 125 --- kernel/kernel/filesystem/vfs.tests.cpp | 703 ------------- kernel/kernel/filesystems/devfs/filesystem.cpp | 82 ++ kernel/kernel/filesystems/devfs/filesystem.hpp | 46 + .../kernel/filesystems/devfs/filesystem.tests.cpp | 134 +++ kernel/kernel/filesystems/devfs/inode.cpp | 102 ++ kernel/kernel/filesystems/devfs/inode.hpp | 35 + kernel/kernel/filesystems/devfs/inode.tests.cpp | 235 +++++ kernel/kernel/filesystems/devfs/module.cpp | 28 + .../filesystems/ext2/block_group_descriptor.hpp | 22 + .../kernel/filesystems/ext2/directory_iterator.cpp | 109 ++ .../kernel/filesystems/ext2/directory_iterator.hpp | 56 + .../filesystems/ext2/directory_iterator.tests.cpp | 123 +++ kernel/kernel/filesystems/ext2/error.cpp | 103 ++ kernel/kernel/filesystems/ext2/error.hpp | 45 + kernel/kernel/filesystems/ext2/filesystem.cpp | 1103 ++++++++++++++++++++ kernel/kernel/filesystems/ext2/filesystem.hpp | 164 +++ .../kernel/filesystems/ext2/filesystem.tests.cpp | 360 +++++++ kernel/kernel/filesystems/ext2/inode.cpp | 362 +++++++ kernel/kernel/filesystems/ext2/inode.hpp | 161 +++ kernel/kernel/filesystems/ext2/inode.tests.cpp | 874 ++++++++++++++++ .../filesystems/ext2/linked_directory_entry.hpp | 25 + kernel/kernel/filesystems/ext2/module.cpp | 26 + kernel/kernel/filesystems/ext2/mount_state.hpp | 26 + kernel/kernel/filesystems/ext2/superblock.hpp | 122 +++ kernel/kernel/filesystems/ext2/write_batch.cpp | 66 ++ kernel/kernel/filesystems/ext2/write_batch.hpp | 50 + kernel/kernel/filesystems/rootfs/filesystem.cpp | 47 + kernel/kernel/filesystems/rootfs/filesystem.hpp | 42 + .../kernel/filesystems/rootfs/filesystem.tests.cpp | 48 + kernel/kernel/filesystems/rootfs/inode.cpp | 36 + kernel/kernel/filesystems/rootfs/inode.hpp | 36 + kernel/kernel/filesystems/rootfs/inode.tests.cpp | 42 + kernel/kernel/filesystems/rootfs/module.cpp | 28 + kernel/kernel/main.cpp | 10 +- .../filesystem/device_number_registry.hpp | 9 - .../test_support/filesystem/driver_registry.hpp | 10 - kernel/kernel/test_support/filesystem/ext2.cpp | 79 -- kernel/kernel/test_support/filesystem/ext2.hpp | 26 - .../kernel/test_support/filesystem/filesystem.cpp | 39 - .../kernel/test_support/filesystem/filesystem.hpp | 39 - kernel/kernel/test_support/filesystem/inode.cpp | 27 - kernel/kernel/test_support/filesystem/inode.hpp | 23 - .../test_support/filesystem/open_file_table.hpp | 10 - .../filesystem/storage_boot_module_fixture.cpp | 151 --- .../filesystem/storage_boot_module_fixture.hpp | 52 - .../filesystem/storage_boot_module_vfs_fixture.cpp | 31 - .../filesystem/storage_boot_module_vfs_fixture.hpp | 23 - .../test_support/filesystem/test_assets/README.md | 1 - .../filesystem/test_assets/ext2_1KB_devnode_fs.img | 3 - .../filesystem/test_assets/ext2_1KB_fs.img | 3 - .../filesystem/test_assets/ext2_2KB_fs.img | 3 - .../filesystem/test_assets/ext2_4KB_fs.img | 3 - kernel/kernel/test_support/filesystem/vfs.hpp | 10 - kernel/kernel/test_support/filesystems/ext2.cpp | 80 ++ kernel/kernel/test_support/filesystems/ext2.hpp | 26 + .../kernel/test_support/filesystems/filesystem.cpp | 39 + .../kernel/test_support/filesystems/filesystem.hpp | 38 + kernel/kernel/test_support/filesystems/inode.cpp | 27 + kernel/kernel/test_support/filesystems/inode.hpp | 23 + .../kernel/test_support/state_reset_listener.cpp | 30 +- .../test_support/vfs/device_number_registry.hpp | 9 + kernel/kernel/test_support/vfs/driver_registry.hpp | 10 + kernel/kernel/test_support/vfs/open_file_table.hpp | 10 + .../vfs/storage_boot_module_fixture.cpp | 151 +++ .../vfs/storage_boot_module_fixture.hpp | 52 + .../vfs/storage_boot_module_vfs_fixture.cpp | 31 + .../vfs/storage_boot_module_vfs_fixture.hpp | 23 + .../kernel/test_support/vfs/test_assets/README.md | 1 + .../vfs/test_assets/ext2_1KB_devnode_fs.img | 3 + .../test_support/vfs/test_assets/ext2_1KB_fs.img | 3 + .../test_support/vfs/test_assets/ext2_2KB_fs.img | 3 + .../test_support/vfs/test_assets/ext2_4KB_fs.img | 3 + kernel/kernel/test_support/vfs/vfs.hpp | 10 + kernel/kernel/vfs.cpp | 481 +++++++++ kernel/kernel/vfs.hpp | 139 +++ kernel/kernel/vfs.tests.cpp | 702 +++++++++++++ kernel/kernel/vfs/byte_offset_file_descriptor.cpp | 83 ++ kernel/kernel/vfs/byte_offset_file_descriptor.hpp | 41 + .../vfs/byte_offset_file_descriptor.tests.cpp | 216 ++++ kernel/kernel/vfs/constants.hpp | 18 + kernel/kernel/vfs/dentry.cpp | 96 ++ kernel/kernel/vfs/dentry.hpp | 98 ++ kernel/kernel/vfs/dentry.tests.cpp | 150 +++ kernel/kernel/vfs/device_inode.cpp | 120 +++ kernel/kernel/vfs/device_inode.hpp | 103 ++ kernel/kernel/vfs/device_inode.tests.cpp | 244 +++++ kernel/kernel/vfs/device_number_registry.cpp | 283 +++++ kernel/kernel/vfs/device_number_registry.hpp | 117 +++ kernel/kernel/vfs/device_number_registry.tests.cpp | 154 +++ kernel/kernel/vfs/directory_file_descriptor.cpp | 34 + kernel/kernel/vfs/directory_file_descriptor.hpp | 35 + .../kernel/vfs/directory_file_descriptor.tests.cpp | 81 ++ kernel/kernel/vfs/directory_listing_cursor.hpp | 23 + kernel/kernel/vfs/directory_listing_entry.hpp | 23 + kernel/kernel/vfs/driver_registry.cpp | 130 +++ kernel/kernel/vfs/driver_registry.hpp | 73 ++ kernel/kernel/vfs/error.cpp | 113 ++ kernel/kernel/vfs/error.hpp | 56 + kernel/kernel/vfs/file_type_bindings.hpp | 30 + kernel/kernel/vfs/filesystem.hpp | 77 ++ kernel/kernel/vfs/inode.cpp | 103 ++ kernel/kernel/vfs/inode.hpp | 142 +++ kernel/kernel/vfs/mount.cpp | 135 +++ kernel/kernel/vfs/mount.hpp | 109 ++ kernel/kernel/vfs/mount.tests.cpp | 95 ++ kernel/kernel/vfs/mount_table.cpp | 79 ++ kernel/kernel/vfs/mount_table.hpp | 52 + kernel/kernel/vfs/mount_table.tests.cpp | 184 ++++ kernel/kernel/vfs/open_file_descriptor.cpp | 53 + kernel/kernel/vfs/open_file_descriptor.hpp | 68 ++ kernel/kernel/vfs/open_file_descriptor.tests.cpp | 110 ++ kernel/kernel/vfs/open_file_table.cpp | 90 ++ kernel/kernel/vfs/open_file_table.hpp | 57 + kernel/kernel/vfs/open_file_table.tests.cpp | 107 ++ kernel/kernel/vfs/path.hpp | 97 ++ kernel/kernel/vfs/path.tests.cpp | 128 +++ kernel/kernel/vfs/reserved_numbers.hpp | 36 + 198 files changed, 10720 insertions(+), 10721 deletions(-) delete mode 100644 kernel/kernel/filesystem/byte_offset_file_descriptor.cpp delete mode 100644 kernel/kernel/filesystem/byte_offset_file_descriptor.hpp delete mode 100644 kernel/kernel/filesystem/byte_offset_file_descriptor.tests.cpp delete mode 100644 kernel/kernel/filesystem/constants.hpp delete mode 100644 kernel/kernel/filesystem/dentry.cpp delete mode 100644 kernel/kernel/filesystem/dentry.hpp delete mode 100644 kernel/kernel/filesystem/dentry.tests.cpp delete mode 100644 kernel/kernel/filesystem/devfs/filesystem.cpp delete mode 100644 kernel/kernel/filesystem/devfs/filesystem.hpp delete mode 100644 kernel/kernel/filesystem/devfs/filesystem.tests.cpp delete mode 100644 kernel/kernel/filesystem/devfs/inode.cpp delete mode 100644 kernel/kernel/filesystem/devfs/inode.hpp delete mode 100644 kernel/kernel/filesystem/devfs/inode.tests.cpp delete mode 100644 kernel/kernel/filesystem/devfs/module.cpp delete mode 100644 kernel/kernel/filesystem/device_inode.cpp delete mode 100644 kernel/kernel/filesystem/device_inode.hpp delete mode 100644 kernel/kernel/filesystem/device_inode.tests.cpp delete mode 100644 kernel/kernel/filesystem/device_number_registry.cpp delete mode 100644 kernel/kernel/filesystem/device_number_registry.hpp delete mode 100644 kernel/kernel/filesystem/device_number_registry.tests.cpp delete mode 100644 kernel/kernel/filesystem/directory_file_descriptor.cpp delete mode 100644 kernel/kernel/filesystem/directory_file_descriptor.hpp delete mode 100644 kernel/kernel/filesystem/directory_file_descriptor.tests.cpp delete mode 100644 kernel/kernel/filesystem/directory_listing_cursor.hpp delete mode 100644 kernel/kernel/filesystem/directory_listing_entry.hpp delete mode 100644 kernel/kernel/filesystem/driver_registry.cpp delete mode 100644 kernel/kernel/filesystem/driver_registry.hpp delete mode 100644 kernel/kernel/filesystem/error.cpp delete mode 100644 kernel/kernel/filesystem/error.hpp delete mode 100644 kernel/kernel/filesystem/ext2/block_group_descriptor.hpp delete mode 100644 kernel/kernel/filesystem/ext2/directory_iterator.cpp delete mode 100644 kernel/kernel/filesystem/ext2/directory_iterator.hpp delete mode 100644 kernel/kernel/filesystem/ext2/directory_iterator.tests.cpp delete mode 100644 kernel/kernel/filesystem/ext2/error.cpp delete mode 100644 kernel/kernel/filesystem/ext2/error.hpp delete mode 100644 kernel/kernel/filesystem/ext2/filesystem.cpp delete mode 100644 kernel/kernel/filesystem/ext2/filesystem.hpp delete mode 100644 kernel/kernel/filesystem/ext2/filesystem.tests.cpp delete mode 100644 kernel/kernel/filesystem/ext2/inode.cpp delete mode 100644 kernel/kernel/filesystem/ext2/inode.hpp delete mode 100644 kernel/kernel/filesystem/ext2/inode.tests.cpp delete mode 100644 kernel/kernel/filesystem/ext2/linked_directory_entry.hpp delete mode 100644 kernel/kernel/filesystem/ext2/module.cpp delete mode 100644 kernel/kernel/filesystem/ext2/mount_state.hpp delete mode 100644 kernel/kernel/filesystem/ext2/superblock.hpp delete mode 100644 kernel/kernel/filesystem/ext2/write_batch.cpp delete mode 100644 kernel/kernel/filesystem/ext2/write_batch.hpp delete mode 100644 kernel/kernel/filesystem/file_type_bindings.hpp delete mode 100644 kernel/kernel/filesystem/filesystem.hpp delete mode 100644 kernel/kernel/filesystem/inode.cpp delete mode 100644 kernel/kernel/filesystem/inode.hpp delete mode 100644 kernel/kernel/filesystem/mount.cpp delete mode 100644 kernel/kernel/filesystem/mount.hpp delete mode 100644 kernel/kernel/filesystem/mount.tests.cpp delete mode 100644 kernel/kernel/filesystem/mount_table.cpp delete mode 100644 kernel/kernel/filesystem/mount_table.hpp delete mode 100644 kernel/kernel/filesystem/mount_table.tests.cpp delete mode 100644 kernel/kernel/filesystem/open_file_descriptor.cpp delete mode 100644 kernel/kernel/filesystem/open_file_descriptor.hpp delete mode 100644 kernel/kernel/filesystem/open_file_descriptor.tests.cpp delete mode 100644 kernel/kernel/filesystem/open_file_table.cpp delete mode 100644 kernel/kernel/filesystem/open_file_table.hpp delete mode 100644 kernel/kernel/filesystem/open_file_table.tests.cpp delete mode 100644 kernel/kernel/filesystem/path.hpp delete mode 100644 kernel/kernel/filesystem/path.tests.cpp delete mode 100644 kernel/kernel/filesystem/reserved_numbers.hpp delete mode 100644 kernel/kernel/filesystem/rootfs/filesystem.cpp delete mode 100644 kernel/kernel/filesystem/rootfs/filesystem.hpp delete mode 100644 kernel/kernel/filesystem/rootfs/filesystem.tests.cpp delete mode 100644 kernel/kernel/filesystem/rootfs/inode.cpp delete mode 100644 kernel/kernel/filesystem/rootfs/inode.hpp delete mode 100644 kernel/kernel/filesystem/rootfs/inode.tests.cpp delete mode 100644 kernel/kernel/filesystem/rootfs/module.cpp delete mode 100644 kernel/kernel/filesystem/vfs.cpp delete mode 100644 kernel/kernel/filesystem/vfs.hpp delete mode 100644 kernel/kernel/filesystem/vfs.tests.cpp create mode 100644 kernel/kernel/filesystems/devfs/filesystem.cpp create mode 100644 kernel/kernel/filesystems/devfs/filesystem.hpp create mode 100644 kernel/kernel/filesystems/devfs/filesystem.tests.cpp create mode 100644 kernel/kernel/filesystems/devfs/inode.cpp create mode 100644 kernel/kernel/filesystems/devfs/inode.hpp create mode 100644 kernel/kernel/filesystems/devfs/inode.tests.cpp create mode 100644 kernel/kernel/filesystems/devfs/module.cpp create mode 100644 kernel/kernel/filesystems/ext2/block_group_descriptor.hpp create mode 100644 kernel/kernel/filesystems/ext2/directory_iterator.cpp create mode 100644 kernel/kernel/filesystems/ext2/directory_iterator.hpp create mode 100644 kernel/kernel/filesystems/ext2/directory_iterator.tests.cpp create mode 100644 kernel/kernel/filesystems/ext2/error.cpp create mode 100644 kernel/kernel/filesystems/ext2/error.hpp create mode 100644 kernel/kernel/filesystems/ext2/filesystem.cpp create mode 100644 kernel/kernel/filesystems/ext2/filesystem.hpp create mode 100644 kernel/kernel/filesystems/ext2/filesystem.tests.cpp create mode 100644 kernel/kernel/filesystems/ext2/inode.cpp create mode 100644 kernel/kernel/filesystems/ext2/inode.hpp create mode 100644 kernel/kernel/filesystems/ext2/inode.tests.cpp create mode 100644 kernel/kernel/filesystems/ext2/linked_directory_entry.hpp create mode 100644 kernel/kernel/filesystems/ext2/module.cpp create mode 100644 kernel/kernel/filesystems/ext2/mount_state.hpp create mode 100644 kernel/kernel/filesystems/ext2/superblock.hpp create mode 100644 kernel/kernel/filesystems/ext2/write_batch.cpp create mode 100644 kernel/kernel/filesystems/ext2/write_batch.hpp create mode 100644 kernel/kernel/filesystems/rootfs/filesystem.cpp create mode 100644 kernel/kernel/filesystems/rootfs/filesystem.hpp create mode 100644 kernel/kernel/filesystems/rootfs/filesystem.tests.cpp create mode 100644 kernel/kernel/filesystems/rootfs/inode.cpp create mode 100644 kernel/kernel/filesystems/rootfs/inode.hpp create mode 100644 kernel/kernel/filesystems/rootfs/inode.tests.cpp create mode 100644 kernel/kernel/filesystems/rootfs/module.cpp delete mode 100644 kernel/kernel/test_support/filesystem/device_number_registry.hpp delete mode 100644 kernel/kernel/test_support/filesystem/driver_registry.hpp delete mode 100644 kernel/kernel/test_support/filesystem/ext2.cpp delete mode 100644 kernel/kernel/test_support/filesystem/ext2.hpp delete mode 100644 kernel/kernel/test_support/filesystem/filesystem.cpp delete mode 100644 kernel/kernel/test_support/filesystem/filesystem.hpp delete mode 100644 kernel/kernel/test_support/filesystem/inode.cpp delete mode 100644 kernel/kernel/test_support/filesystem/inode.hpp delete mode 100644 kernel/kernel/test_support/filesystem/open_file_table.hpp delete mode 100644 kernel/kernel/test_support/filesystem/storage_boot_module_fixture.cpp delete mode 100644 kernel/kernel/test_support/filesystem/storage_boot_module_fixture.hpp delete mode 100644 kernel/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.cpp delete mode 100644 kernel/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp delete mode 120000 kernel/kernel/test_support/filesystem/test_assets/README.md delete mode 100644 kernel/kernel/test_support/filesystem/test_assets/ext2_1KB_devnode_fs.img delete mode 100644 kernel/kernel/test_support/filesystem/test_assets/ext2_1KB_fs.img delete mode 100644 kernel/kernel/test_support/filesystem/test_assets/ext2_2KB_fs.img delete mode 100644 kernel/kernel/test_support/filesystem/test_assets/ext2_4KB_fs.img delete mode 100644 kernel/kernel/test_support/filesystem/vfs.hpp create mode 100644 kernel/kernel/test_support/filesystems/ext2.cpp create mode 100644 kernel/kernel/test_support/filesystems/ext2.hpp create mode 100644 kernel/kernel/test_support/filesystems/filesystem.cpp create mode 100644 kernel/kernel/test_support/filesystems/filesystem.hpp create mode 100644 kernel/kernel/test_support/filesystems/inode.cpp create mode 100644 kernel/kernel/test_support/filesystems/inode.hpp create mode 100644 kernel/kernel/test_support/vfs/device_number_registry.hpp create mode 100644 kernel/kernel/test_support/vfs/driver_registry.hpp create mode 100644 kernel/kernel/test_support/vfs/open_file_table.hpp create mode 100644 kernel/kernel/test_support/vfs/storage_boot_module_fixture.cpp create mode 100644 kernel/kernel/test_support/vfs/storage_boot_module_fixture.hpp create mode 100644 kernel/kernel/test_support/vfs/storage_boot_module_vfs_fixture.cpp create mode 100644 kernel/kernel/test_support/vfs/storage_boot_module_vfs_fixture.hpp create mode 120000 kernel/kernel/test_support/vfs/test_assets/README.md create mode 100644 kernel/kernel/test_support/vfs/test_assets/ext2_1KB_devnode_fs.img create mode 100644 kernel/kernel/test_support/vfs/test_assets/ext2_1KB_fs.img create mode 100644 kernel/kernel/test_support/vfs/test_assets/ext2_2KB_fs.img create mode 100644 kernel/kernel/test_support/vfs/test_assets/ext2_4KB_fs.img create mode 100644 kernel/kernel/test_support/vfs/vfs.hpp create mode 100644 kernel/kernel/vfs.cpp create mode 100644 kernel/kernel/vfs.hpp create mode 100644 kernel/kernel/vfs.tests.cpp create mode 100644 kernel/kernel/vfs/byte_offset_file_descriptor.cpp create mode 100644 kernel/kernel/vfs/byte_offset_file_descriptor.hpp create mode 100644 kernel/kernel/vfs/byte_offset_file_descriptor.tests.cpp create mode 100644 kernel/kernel/vfs/constants.hpp create mode 100644 kernel/kernel/vfs/dentry.cpp create mode 100644 kernel/kernel/vfs/dentry.hpp create mode 100644 kernel/kernel/vfs/dentry.tests.cpp create mode 100644 kernel/kernel/vfs/device_inode.cpp create mode 100644 kernel/kernel/vfs/device_inode.hpp create mode 100644 kernel/kernel/vfs/device_inode.tests.cpp create mode 100644 kernel/kernel/vfs/device_number_registry.cpp create mode 100644 kernel/kernel/vfs/device_number_registry.hpp create mode 100644 kernel/kernel/vfs/device_number_registry.tests.cpp create mode 100644 kernel/kernel/vfs/directory_file_descriptor.cpp create mode 100644 kernel/kernel/vfs/directory_file_descriptor.hpp create mode 100644 kernel/kernel/vfs/directory_file_descriptor.tests.cpp create mode 100644 kernel/kernel/vfs/directory_listing_cursor.hpp create mode 100644 kernel/kernel/vfs/directory_listing_entry.hpp create mode 100644 kernel/kernel/vfs/driver_registry.cpp create mode 100644 kernel/kernel/vfs/driver_registry.hpp create mode 100644 kernel/kernel/vfs/error.cpp create mode 100644 kernel/kernel/vfs/error.hpp create mode 100644 kernel/kernel/vfs/file_type_bindings.hpp create mode 100644 kernel/kernel/vfs/filesystem.hpp create mode 100644 kernel/kernel/vfs/inode.cpp create mode 100644 kernel/kernel/vfs/inode.hpp create mode 100644 kernel/kernel/vfs/mount.cpp create mode 100644 kernel/kernel/vfs/mount.hpp create mode 100644 kernel/kernel/vfs/mount.tests.cpp create mode 100644 kernel/kernel/vfs/mount_table.cpp create mode 100644 kernel/kernel/vfs/mount_table.hpp create mode 100644 kernel/kernel/vfs/mount_table.tests.cpp create mode 100644 kernel/kernel/vfs/open_file_descriptor.cpp create mode 100644 kernel/kernel/vfs/open_file_descriptor.hpp create mode 100644 kernel/kernel/vfs/open_file_descriptor.tests.cpp create mode 100644 kernel/kernel/vfs/open_file_table.cpp create mode 100644 kernel/kernel/vfs/open_file_table.hpp create mode 100644 kernel/kernel/vfs/open_file_table.tests.cpp create mode 100644 kernel/kernel/vfs/path.hpp create mode 100644 kernel/kernel/vfs/path.tests.cpp create mode 100644 kernel/kernel/vfs/reserved_numbers.hpp (limited to 'kernel') diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index c44383b8..359ad1f6 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -56,38 +56,38 @@ target_sources("kernel_lib" PRIVATE "kernel/drivers/pseudo/zero.cpp" "kernel/drivers/storage/ram_disk.cpp" - # Filesystem Subsystem - "kernel/filesystem/byte_offset_file_descriptor.cpp" - "kernel/filesystem/dentry.cpp" - "kernel/filesystem/device_inode.cpp" - "kernel/filesystem/device_number_registry.cpp" - "kernel/filesystem/directory_file_descriptor.cpp" - "kernel/filesystem/driver_registry.cpp" - "kernel/filesystem/error.cpp" - "kernel/filesystem/inode.cpp" - "kernel/filesystem/mount_table.cpp" - "kernel/filesystem/mount.cpp" - "kernel/filesystem/open_file_descriptor.cpp" - "kernel/filesystem/open_file_table.cpp" - "kernel/filesystem/vfs.cpp" + # Virtual Filesystem Subsystem + "kernel/vfs/byte_offset_file_descriptor.cpp" + "kernel/vfs/dentry.cpp" + "kernel/vfs/device_inode.cpp" + "kernel/vfs/device_number_registry.cpp" + "kernel/vfs/directory_file_descriptor.cpp" + "kernel/vfs/driver_registry.cpp" + "kernel/vfs/error.cpp" + "kernel/vfs/inode.cpp" + "kernel/vfs/mount_table.cpp" + "kernel/vfs/mount.cpp" + "kernel/vfs/open_file_descriptor.cpp" + "kernel/vfs/open_file_table.cpp" + "kernel/vfs.cpp" # DevFS Filesystem - "kernel/filesystem/devfs/filesystem.cpp" - "kernel/filesystem/devfs/inode.cpp" - "kernel/filesystem/devfs/module.cpp" + "kernel/filesystems/devfs/filesystem.cpp" + "kernel/filesystems/devfs/inode.cpp" + "kernel/filesystems/devfs/module.cpp" # ext2 Filesystem - "kernel/filesystem/ext2/directory_iterator.cpp" - "kernel/filesystem/ext2/error.cpp" - "kernel/filesystem/ext2/filesystem.cpp" - "kernel/filesystem/ext2/inode.cpp" - "kernel/filesystem/ext2/module.cpp" - "kernel/filesystem/ext2/write_batch.cpp" + "kernel/filesystems/ext2/directory_iterator.cpp" + "kernel/filesystems/ext2/error.cpp" + "kernel/filesystems/ext2/filesystem.cpp" + "kernel/filesystems/ext2/inode.cpp" + "kernel/filesystems/ext2/module.cpp" + "kernel/filesystems/ext2/write_batch.cpp" # Rootfs Filesystem - "kernel/filesystem/rootfs/filesystem.cpp" - "kernel/filesystem/rootfs/inode.cpp" - "kernel/filesystem/rootfs/module.cpp" + "kernel/filesystems/rootfs/filesystem.cpp" + "kernel/filesystems/rootfs/inode.cpp" + "kernel/filesystems/rootfs/module.cpp" ) file(GLOB_RECURSE KERNEL_HEADERS @@ -184,7 +184,7 @@ if(BUILD_TESTING) ) target_compile_definitions("${TEST_TARGET}" PRIVATE - KERNEL_TEST_ASSETS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/kernel/test_support/filesystem/test_assets" + KERNEL_TEST_ASSETS_DIR="${CMAKE_CURRENT_SOURCE_DIR}/kernel/test_support/vfs/test_assets" ) if(COMMAND "enable_coverage" AND TEACHOS_ENABLE_TEST_COVERAGE) diff --git a/kernel/kapi/filesystem.cpp b/kernel/kapi/filesystem.cpp index e4b3443a..e50ce2c0 100644 --- a/kernel/kapi/filesystem.cpp +++ b/kernel/kapi/filesystem.cpp @@ -1,10 +1,6 @@ #include -#include -#include -#include -#include -#include +#include #include #include @@ -20,77 +16,76 @@ namespace kapi::filesystem { auto mount(std::string_view source, std::string_view target) -> kstd::result { - return kernel::filesystem::vfs::get().mount(source, target); + return kernel::vfs::vfs::get().mount(source, target); } auto umount(std::string_view target) -> kstd::result { - return kernel::filesystem::vfs::get().unmount(target); + return kernel::vfs::vfs::get().unmount(target); } auto open(std::string_view path) -> kstd::result { - return kernel::filesystem::vfs::get() + return kernel::vfs::vfs::get() .open(path) - .transform([](auto dentry) -> kstd::shared_ptr { + .transform([](auto dentry) -> kstd::shared_ptr { if (dentry->inode()->is_directory()) { - return kstd::make_shared(dentry); + return kstd::make_shared(dentry); } - return kstd::make_shared(dentry); + return kstd::make_shared(dentry); }) - .and_then( - [](auto file_descriptor) { return kernel::filesystem::open_file_table::get().add_file(file_descriptor); }); + .and_then([](auto file_descriptor) { return kernel::vfs::open_file_table::get().add_file(file_descriptor); }); } auto close(size_t file_descriptor) -> kstd::result { - return kernel::filesystem::open_file_table::get() + return kernel::vfs::open_file_table::get() .file(file_descriptor) .transform([](auto file) { return file->get_dentry()->absolute_path(); }) - .and_then([](auto path) { return kernel::filesystem::vfs::get().close(path); }) - .and_then([=]() { return kernel::filesystem::open_file_table::get().remove_file(file_descriptor); }); + .and_then([](auto path) { return kernel::vfs::vfs::get().close(path); }) + .and_then([=]() { return kernel::vfs::open_file_table::get().remove_file(file_descriptor); }); } auto read(size_t file_descriptor, std::span buffer) -> kstd::result { - return kernel::filesystem::open_file_table::get().file(file_descriptor).and_then([=](auto descriptor) { + return kernel::vfs::open_file_table::get().file(file_descriptor).and_then([=](auto descriptor) { return descriptor->read(buffer); }); } auto write(size_t file_descriptor, std::span buffer) -> kstd::result { - return kernel::filesystem::open_file_table::get().file(file_descriptor).and_then([=](auto descriptor) { + return kernel::vfs::open_file_table::get().file(file_descriptor).and_then([=](auto descriptor) { return descriptor->write(buffer); }); } auto seek(size_t file_descriptor, kstd::offset offset, seek_origin origin) -> kstd::result { - return kernel::filesystem::open_file_table::get().file(file_descriptor).and_then([=](auto file) { + return kernel::vfs::open_file_table::get().file(file_descriptor).and_then([=](auto file) { return file->seek(offset, origin); }); } auto mkdir(std::string_view path) -> kstd::result { - return kernel::filesystem::vfs::get().mkdir(path); + return kernel::vfs::vfs::get().mkdir(path); } auto create(std::string_view path) -> kstd::result { - return kernel::filesystem::vfs::get().create(path); + return kernel::vfs::vfs::get().create(path); } auto status(std::string_view path) -> kstd::result { - return kernel::filesystem::vfs::get().status(path); + return kernel::vfs::vfs::get().status(path); } auto create_device_node(std::string_view path, file_type type, device_number number) -> kstd::result { - return kernel::filesystem::vfs::get().create_device_node(path, static_cast(type), number); + return kernel::vfs::vfs::get().create_device_node(path, static_cast(type), number); } } // namespace kapi::filesystem \ No newline at end of file diff --git a/kernel/kapi/filesystem.tests.cpp b/kernel/kapi/filesystem.tests.cpp index 2144af3a..d23255c3 100644 --- a/kernel/kapi/filesystem.tests.cpp +++ b/kernel/kapi/filesystem.tests.cpp @@ -1,6 +1,6 @@ #include -#include +#include #include #include @@ -18,7 +18,7 @@ using namespace kstd::units_literals; // NOLINTBEGIN (readability-magic-numbers) -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Kapi filesystem with real images", +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_vfs_fixture, "Kapi filesystem with real images", "[kapi][filesystem]") { auto const image_path_1 = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; @@ -297,7 +297,7 @@ SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Kap // NOTE: This must be it's own scenario, because it relies on concrete minor numbers, which in turn depend on global // singleton state that only resets on a new scenario. -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_vfs_fixture, "Kapi filesystem resolves a persistent device node to the matching devfs entry", "[kapi][filesystem]") { auto image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_devnode_fs.img"; diff --git a/kernel/kernel/drivers/pseudo/null.cpp b/kernel/kernel/drivers/pseudo/null.cpp index d2ae81c6..4c8bde12 100644 --- a/kernel/kernel/drivers/pseudo/null.cpp +++ b/kernel/kernel/drivers/pseudo/null.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include @@ -100,14 +100,14 @@ namespace kernel::drivers::pseudo auto null::claimed_major() const -> std::optional { - return kernel::filesystem::character_major_numbers::memory; + return kernel::vfs::character_major_numbers::memory; } auto null::claimed_minor(kapi::devices::device & device) const -> std::optional { if (device.name() == "null") { - return kernel::filesystem::character_minor_numbers::null; + return kernel::vfs::character_minor_numbers::null; } return std::nullopt; } diff --git a/kernel/kernel/drivers/pseudo/zero.cpp b/kernel/kernel/drivers/pseudo/zero.cpp index 0d5e0f8f..67ec2519 100644 --- a/kernel/kernel/drivers/pseudo/zero.cpp +++ b/kernel/kernel/drivers/pseudo/zero.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include @@ -101,14 +101,14 @@ namespace kernel::drivers::pseudo auto zero::claimed_major() const -> std::optional { - return kernel::filesystem::character_major_numbers::memory; + return kernel::vfs::character_major_numbers::memory; } auto zero::claimed_minor(kapi::devices::device & device) const -> std::optional { if (device.name() == "zero") { - return kernel::filesystem::character_minor_numbers::zero; + return kernel::vfs::character_minor_numbers::zero; } return std::nullopt; } diff --git a/kernel/kernel/drivers/storage/ram_disk.cpp b/kernel/kernel/drivers/storage/ram_disk.cpp index 383a9417..80f7eedc 100644 --- a/kernel/kernel/drivers/storage/ram_disk.cpp +++ b/kernel/kernel/drivers/storage/ram_disk.cpp @@ -1,7 +1,7 @@ #include #include -#include +#include #include #include @@ -147,7 +147,7 @@ namespace kernel::drivers::storage auto ram_disk::claimed_major() const -> std::optional { - return kernel::filesystem::block_major_numbers::ram_disk; + return kernel::vfs::block_major_numbers::ram_disk; } auto ram_disk::name() const noexcept -> std::string_view diff --git a/kernel/kernel/filesystem/byte_offset_file_descriptor.cpp b/kernel/kernel/filesystem/byte_offset_file_descriptor.cpp deleted file mode 100644 index 6071155c..00000000 --- a/kernel/kernel/filesystem/byte_offset_file_descriptor.cpp +++ /dev/null @@ -1,83 +0,0 @@ -#include - -#include - -#include - -#include -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - - auto byte_offset_file_descriptor::read(std::span buffer) -> kstd::result - { - if (auto result = get_dentry()->inode()->read(buffer, m_offset); !result) - { - return kstd::failure(result.error()); - } - else - { - auto read_bytes = result.value(); - m_offset += read_bytes; - return read_bytes; - } - } - - auto byte_offset_file_descriptor::write(std::span buffer) -> kstd::result - { - if (auto result = get_dentry()->inode()->write(buffer, m_offset); !result) - { - return kstd::failure(result.error()); - } - else - { - auto written_bytes = result.value(); - m_offset += written_bytes; - return written_bytes; - } - } - - auto byte_offset_file_descriptor::seek(kstd::offset offset, kapi::filesystem::seek_origin origin) - -> kstd::result - { - auto const base = [&] { - switch (origin) - { - case kapi::filesystem::seek_origin::beginning: - { - return kstd::bytes{0}; - } - case kapi::filesystem::seek_origin::current_position: - { - return m_offset; - } - case kapi::filesystem::seek_origin::end: - { - return get_dentry()->inode()->status()->size; - } - default: - { - return kstd::bytes{0}; - } - } - }(); - - auto new_offset = base + offset; - if (new_offset) - { - m_offset = *new_offset; - } - return new_offset; - } - - auto byte_offset_file_descriptor::offset() const noexcept -> kstd::bytes - { - return m_offset; - } - -} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/kernel/filesystem/byte_offset_file_descriptor.hpp b/kernel/kernel/filesystem/byte_offset_file_descriptor.hpp deleted file mode 100644 index 50eedb67..00000000 --- a/kernel/kernel/filesystem/byte_offset_file_descriptor.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_BYTE_OFFSET_FILE_DESCRIPTOR_HPP -#define TEACHOS_KERNEL_FILESYSTEM_BYTE_OFFSET_FILE_DESCRIPTOR_HPP - -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - //! An open file. - //! - //! This class encapsulates the state of an open file, including a reference to the associated directory and the - //! current byte offset within the file. - struct byte_offset_file_descriptor : open_file_descriptor - { - using open_file_descriptor::open_file_descriptor; - - auto read(std::span buffer) -> kstd::result override; - - auto write(std::span buffer) -> kstd::result override; - - auto seek(kstd::offset offset, kapi::filesystem::seek_origin origin) -> kstd::result override; - - [[nodiscard]] auto offset() const noexcept -> kstd::bytes; - - private: - kstd::bytes m_offset{}; - }; - -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/byte_offset_file_descriptor.tests.cpp b/kernel/kernel/filesystem/byte_offset_file_descriptor.tests.cpp deleted file mode 100644 index a17ce686..00000000 --- a/kernel/kernel/filesystem/byte_offset_file_descriptor.tests.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -using namespace kstd::units_literals; - -// NOLINTBEGIN(readability-magic-numbers) - -SCENARIO("Open file descriptor construction", "[filesystem][byte_offset_file_descriptor]") -{ - GIVEN("a dentry and an open file descriptor for that dentry") - { - auto inode = kstd::make_shared(); - auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); - auto file_descriptor = kernel::filesystem::byte_offset_file_descriptor{dentry}; - - THEN("the initial offset is zero") - { - REQUIRE(file_descriptor.offset() == 0_B); - } - } -} - -SCENARIO("Open file descriptor read/write offset management", "[filesystem][byte_offset_file_descriptor]") -{ - GIVEN("a dentry that tracks read/write calls and an open file descriptor for that dentry") - { - auto inode = kstd::make_shared(); - auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); - auto file_descriptor = kernel::filesystem::byte_offset_file_descriptor{dentry}; - - THEN("the offset is updated correctly after reads") - { - auto buffer = std::vector{100}; - - REQUIRE(file_descriptor.read(buffer) == 100_B); - REQUIRE(file_descriptor.offset() == 100_B); - REQUIRE(file_descriptor.read(std::span{buffer}.subspan(0, 50)) == 50_B); - REQUIRE(file_descriptor.offset() == 150_B); - } - - THEN("the offset is updated correctly after writes") - { - auto buffer = std::vector{200}; - - REQUIRE(file_descriptor.write(buffer) == 200_B); - REQUIRE(file_descriptor.offset() == 200_B); - REQUIRE(file_descriptor.write(std::span{buffer}.subspan(0, 25)) == 25_B); - REQUIRE(file_descriptor.offset() == 225_B); - } - - THEN("reads and writes both update the same offset") - { - auto buffer = std::vector{20}; - - REQUIRE(file_descriptor.read(std::span{buffer}.subspan(0, 10)) == 10_B); - REQUIRE(file_descriptor.offset() == 10_B); - REQUIRE(file_descriptor.write(std::span{buffer}.subspan(0, 20)) == 20_B); - REQUIRE(file_descriptor.offset() == 30_B); - REQUIRE(file_descriptor.read(std::span{buffer}.subspan(0, 5)) == 5_B); - REQUIRE(file_descriptor.offset() == 35_B); - REQUIRE(file_descriptor.write(std::span{buffer}.subspan(0, 15)) == 15_B); - REQUIRE(file_descriptor.offset() == 50_B); - } - } -} - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Open file descriptor read with real image", - "[filesystem][byte_offset_file_descriptor][img]") -{ - auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; - - GIVEN("an open file descriptor 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 dentry = vfs.open("/information/info_1.txt"); - REQUIRE(dentry); - auto ofd = kstd::make_shared(dentry.value()); - - THEN("the file can be read and the offset is updated") - { - kstd::vector buffer(32); - auto bytes_read = ofd->read(buffer); - REQUIRE(bytes_read == 7_B); - REQUIRE(ofd->offset() == 7_B); - - std::string_view buffer_as_str{reinterpret_cast(buffer.data()), bytes_read->value}; - REQUIRE(buffer_as_str == "info_1\n"); - } - - THEN("the file can be read multiple times and the offset is updated") - { - kstd::vector buffer(4); - auto bytes_read_1 = ofd->read(std::span{buffer}.first(buffer.size() / 2)); - REQUIRE(bytes_read_1 == kstd::bytes{buffer.size() / 2}); - REQUIRE(ofd->offset() == kstd::bytes{buffer.size() / 2}); - - auto bytes_read_2 = ofd->read(std::span{buffer}.last(buffer.size() / 2)); - REQUIRE(bytes_read_2 == kstd::bytes{buffer.size() / 2}); - REQUIRE(ofd->offset() == kstd::bytes{buffer.size()}); - - std::string_view buffer_as_str{reinterpret_cast(buffer.data()), - bytes_read_1->value + bytes_read_2->value}; - REQUIRE(buffer_as_str == "info"); - } - - THEN("the file can be written to and the offset is updated") - { - auto write_buffer = kstd::vector(12, std::byte{0xAA}); - auto const bytes_written = ofd->write(write_buffer); - REQUIRE(bytes_written == 12_B); - REQUIRE(ofd->offset() == 12_B); - } - - THEN("the file can be written to multiple times and the offset is updated") - { - auto write_buffer = kstd::vector(8, std::byte{0xAA}); - auto const bytes_written_1 = ofd->write(std::span{write_buffer}.first(write_buffer.size() / 2)); - REQUIRE(bytes_written_1 == kstd::bytes{write_buffer.size() / 2}); - REQUIRE(ofd->offset() == kstd::bytes{write_buffer.size() / 2}); - - auto const bytes_written_2 = ofd->write(std::span{write_buffer}.last(write_buffer.size() / 2)); - REQUIRE(bytes_written_2 == kstd::bytes{write_buffer.size() / 2}); - REQUIRE(ofd->offset() == kstd::bytes{write_buffer.size()}); - } - } -} - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Open file descriptor handles device removal", - "[filesystem][byte_offset_file_descriptor]") -{ - GIVEN("A file descriptor open on a bound, published devices") - { - setup_modules(1); - - auto device = kernel::devices::storage::determine_boot_device(); - REQUIRE(device); - - auto weak_reference = kstd::weak_ptr{device}; - - auto device_inode = kstd::make_shared(device); - auto dentry = kstd::make_shared(nullptr, device_inode, "ram0"); - auto fd = kstd::make_shared(dentry); - - device.reset(); - REQUIRE_FALSE(weak_reference.expired()); - - WHEN("the device is removed while the descriptor is still open") - { - auto locked = weak_reference.lock(); - REQUIRE(locked); - REQUIRE(kapi::devices::remove_device(*locked)); - locked.reset(); - - THEN("the device is kept alive through the file descriptor") - { - REQUIRE_FALSE(weak_reference.expired()); - } - - THEN("reading through the file descriptor fails with 'no such device'") - { - auto buffer = kstd::vector(10); - auto bytes_read = fd->read(buffer); - - REQUIRE(bytes_read.error() == kstd::errc::no_such_device); - } - - THEN("writing through the file descriptor fails with 'no such device'") - { - auto buffer = kstd::vector(10); - auto bytes_written = fd->write(buffer); - - REQUIRE(bytes_written.error() == kstd::errc::no_such_device); - } - - THEN("resetting the chain destroys the device") - { - REQUIRE_FALSE(weak_reference.expired()); - - fd.reset(); - REQUIRE_FALSE(weak_reference.expired()); - - dentry.reset(); - REQUIRE_FALSE(weak_reference.expired()); - - device_inode.reset(); - REQUIRE(weak_reference.expired()); - } - } - } -} - -// NOLINTEND(readability-magic-numbers) \ No newline at end of file diff --git a/kernel/kernel/filesystem/constants.hpp b/kernel/kernel/filesystem/constants.hpp deleted file mode 100644 index 09996467..00000000 --- a/kernel/kernel/filesystem/constants.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_CONSTANTS_HPP -#define TEACHOS_KERNEL_FILESYSTEM_CONSTANTS_HPP - -#include - -namespace kernel::filesystem::constants -{ - //! The maximum allowed length for any path in the system. - constexpr size_t inline max_path_length = 4096; - - //! The maximum allowed path length for the target of a symlink. - constexpr size_t inline symlink_max_path_length = 4096; - - //! The maximum number of loops allowed in a path, via symlinks. - constexpr size_t inline symloop_max = 40; -} // namespace kernel::filesystem::constants - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/dentry.cpp b/kernel/kernel/filesystem/dentry.cpp deleted file mode 100644 index e82c5419..00000000 --- a/kernel/kernel/filesystem/dentry.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include - -#include - -#include - -#include -#include - -#include -#include -#include - -namespace kernel::filesystem -{ - dentry::dentry(kstd::shared_ptr const & parent, kstd::shared_ptr const & inode, - std::string_view name) - : m_name(name) - , m_parent(parent) - , m_inode(inode) - , m_flags(0) - { - if (!m_inode) - { - kapi::system::panic("[FILESYSTEM] dentry constructed with null inode."); - } - if (m_name.empty()) - { - kapi::system::panic("[FILESYSTEM] dentry constructed with empty name."); - } - } - - auto dentry::inode() const -> kstd::shared_ptr const & - { - return m_inode; - } - - auto dentry::parent() const -> kstd::shared_ptr - { - return m_parent.lock(); - } - - auto dentry::name() const -> std::string_view - { - return m_name; - } - - auto dentry::absolute_path() const -> kstd::string - { - kstd::string path = m_name; - - auto parent = this->parent(); - while (parent) - { - auto parent_name = parent->m_name; - if (parent_name == "/") - { - path = "/" + path; - } - else - { - path = parent_name + "/" + path; - } - - parent = parent->parent(); - } - - return path; - } - - auto dentry::add_child(kstd::shared_ptr const & child) -> void - { - m_children.push_back(child); - } - - auto dentry::find_child(std::string_view name) const -> kstd::shared_ptr - { - auto it = std::ranges::find_if(m_children, [&](auto const & child) { return child->m_name == name; }); - return (it != m_children.end()) ? *it : nullptr; - } - - auto dentry::set_flag(dentry_flags flag) -> void - { - m_flags |= static_cast(flag); - } - - auto dentry::unset_flag(dentry_flags flag) -> void - { - m_flags &= ~static_cast(flag); - } - - auto dentry::has_flag(dentry_flags flag) const -> bool - { - return (m_flags & static_cast(flag)) != 0; - } -} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/kernel/filesystem/dentry.hpp b/kernel/kernel/filesystem/dentry.hpp deleted file mode 100644 index f7897764..00000000 --- a/kernel/kernel/filesystem/dentry.hpp +++ /dev/null @@ -1,98 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_DENTRY_HPP -#define TEACHOS_KERNEL_FILESYSTEM_DENTRY_HPP - -#include - -#include -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - //! A directory entry in the filesystem. - struct dentry - { - using dentry_ptr = kstd::shared_ptr; - using inode_ptr = kstd::shared_ptr; - - //! Flags for the dentry. - enum class dentry_flags : uint32_t - { - is_mount_point = 1 << 0 - }; - - //! Create a directory entry with the given parent, associated inode, and name. - //! - //! @warning This function will panic if invoked with a null pointer for the inode argument. - //! - //! @param parent The parent directory entry, if any. - //! @param inode The associated inode for this dentry. - //! @param name The name of the dentry. - dentry(dentry_ptr const & parent, inode_ptr const & inode, std::string_view name); - - //! Get this entrys associated inode. - //! - //! @return The inode associated with this directory entry. - [[nodiscard]] auto inode() const -> inode_ptr const &; - - //! Get this entrys parent directory entry. - //! - //! @return The parent directory entry, or @p nullptr if this entry has no parent. - [[nodiscard]] auto parent() const -> dentry_ptr; - - //! Get this entrys name. - //! - //! @return The name of this directory entry. - [[nodiscard]] auto name() const -> std::string_view; - - //! Get the full path of this entry by traversing up to the root. - //! - //! @note This function performs a full path traversal to the root. Even if all entries on that path a cached, this - //! may imply a non insignificant time penalty, as traversal complexity is linear in the depth of the path. - //! - //! @return The full path of this directory entry. - [[nodiscard]] auto absolute_path() const -> kstd::string; - - //! Add a child to this entry. - //! - //! @param child The child directory entry to add. - auto add_child(dentry_ptr const & child) -> void; - - //! Find a child entry with the given name. - //! - //! @note This function performs no path traversal. The name supplied to this function must name a direct child of - //! this directory entry in order for it to be found. - //! - //! @param name The name of the child directory entry to find. - //! @return A pointer to the child dentry if it exists, a null pointer otherwise. - [[nodiscard]] auto find_child(std::string_view name) const -> dentry_ptr; - - //! Set a flag for this entry. - //! - //! @param flag The flag to set. - auto set_flag(dentry_flags flag) -> void; - - //! Clear a flag from this entry. - //! - //! @param flag The flag to clear. - auto unset_flag(dentry_flags flag) -> void; - - //! Check if this entry has a specific flag set. - //! - //! @param flag The flag to check. - //! @return @c true iff. the flag is set on this entry, @c false otherwise. - [[nodiscard]] auto has_flag(dentry_flags flag) const -> bool; - - private: - kstd::string m_name; - kstd::weak_ptr m_parent; - kstd::vector m_children; - inode_ptr m_inode; - uint32_t m_flags; - }; -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/dentry.tests.cpp b/kernel/kernel/filesystem/dentry.tests.cpp deleted file mode 100644 index cf3915de..00000000 --- a/kernel/kernel/filesystem/dentry.tests.cpp +++ /dev/null @@ -1,150 +0,0 @@ -#include - -#include -#include - -#include -#include - -#include - -SCENARIO("Dentry construction", "[filesystem][dentry]") -{ - GIVEN("A parent dentry and inode") - { - auto inode = kstd::make_shared(); - auto parent_dentry = kstd::make_shared(nullptr, inode, "parent"); - - 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.parent() == parent_dentry); - REQUIRE(child_dentry.inode() == inode); - REQUIRE(child_dentry.name() == "child"); - } - - THEN("no flag is set") - { - REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point)); - } - } - - WHEN("constructing a dentry with an empty name") - { - THEN("the dentry has the correct parent and inode, and an empty name") - { - REQUIRE_THROWS_AS((kernel::filesystem::dentry{parent_dentry, inode, ""}), kernel::tests::cpu::halt); - } - } - - 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.parent() == nullptr); - REQUIRE(child_dentry.inode() == inode); - REQUIRE(child_dentry.name() == "child"); - } - - THEN("no flag is set") - { - REQUIRE_FALSE(child_dentry.has_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point)); - } - } - - 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(); - auto parent_dentry = kstd::make_shared(nullptr, inode, "parent"); - - WHEN("adding child dentries") - { - auto child1 = kstd::make_shared(parent_dentry, inode, "child1"); - auto child2 = kstd::make_shared(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(); - auto dentry = kernel::filesystem::dentry{nullptr, inode, "test"}; - - WHEN("setting a flag") - { - dentry.set_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point); - - THEN("the flag is set") - { - REQUIRE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point)); - } - } - - WHEN("unsetting a flag") - { - dentry.set_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point); - dentry.unset_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point); - - THEN("the flag is unset") - { - REQUIRE_FALSE(dentry.has_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point)); - } - } - } -} - -SCENARIO("Dentry path resolution", "[filesystem][dentry]") -{ - GIVEN("A dentry with a parent hierarchy") - { - auto root_inode = kstd::make_shared(); - auto root_dentry = kstd::make_shared(nullptr, root_inode, "/"); - - auto home_inode = kstd::make_shared(); - auto home_dentry = kstd::make_shared(root_dentry, home_inode, "home"); - root_dentry->add_child(home_dentry); - - auto user_inode = kstd::make_shared(); - auto user_dentry = kstd::make_shared(home_dentry, user_inode, "user"); - home_dentry->add_child(user_dentry); - - THEN("the full path is constructed correctly") - { - REQUIRE(root_dentry->absolute_path() == "/"); - REQUIRE(home_dentry->absolute_path() == "/home"); - REQUIRE(user_dentry->absolute_path() == "/home/user"); - } - } -} \ No newline at end of file diff --git a/kernel/kernel/filesystem/devfs/filesystem.cpp b/kernel/kernel/filesystem/devfs/filesystem.cpp deleted file mode 100644 index 0de513a7..00000000 --- a/kernel/kernel/filesystem/devfs/filesystem.cpp +++ /dev/null @@ -1,82 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include - -namespace kernel::filesystem::devfs -{ - - static_assert(sizeof(kernel::filesystem::filesystem) == sizeof(devfs::filesystem)); - - auto filesystem::probe(inode_ptr const & inode) const -> kstd::result - { - if (inode) - { - return kstd::failure(vfs_errc::invalid_argument); - } - - return 0; - } - - auto filesystem::mount(inode_ptr const &) -> kstd::result - { - auto root = kstd::make_shared(); - return mount_result{root, root}; - } - - auto filesystem::lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const - -> kstd::result - { - if (!parent) - { - return kstd::failure(vfs_errc::invalid_inode); - } - - if (!parent->is_directory()) - { - return kstd::failure(vfs_errc::not_a_directory); - } - - if (parent.get() != driver_data.get()) - { - return kstd::failure(vfs_errc::invalid_inode); - } - - auto entries = device_number_registry::get().all(); - auto found = std::ranges::find_if(entries, [&](auto const & entry) { return entry.name == name; }); - - if (found == entries.end()) - { - return kstd::failure(vfs_errc::no_such_file_or_directory); - } - - if (auto device = found->device.lock()) - { - return kstd::make_shared(device); - } - - return kstd::failure(vfs_errc::no_such_file_or_directory); - } - - auto filesystem::create_inode(inode_ptr const &, std::string_view, kapi::filesystem::file_type, driver_data_ptr, - std::optional) -> kstd::result - { - return kstd::failure(vfs_errc::read_only_file_system); - } - -} // namespace kernel::filesystem::devfs \ No newline at end of file diff --git a/kernel/kernel/filesystem/devfs/filesystem.hpp b/kernel/kernel/filesystem/devfs/filesystem.hpp deleted file mode 100644 index aa33c58b..00000000 --- a/kernel/kernel/filesystem/devfs/filesystem.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_DEVFS_FILESYSTEM_HPP -#define TEACHOS_KERNEL_FILESYSTEM_DEVFS_FILESYSTEM_HPP - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include - -namespace kernel::filesystem::devfs -{ - - //! 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 final : kernel::filesystem::filesystem - { - [[nodiscard]] auto probe(inode_ptr const & inode) const -> kstd::result override; - - auto mount(inode_ptr const & backing_inode) -> kstd::result override; - - [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const - -> kstd::result override; - - //! @copydoc kernel::filesystem::filesystem::create_inode - //! @return Always returns an error, as creating new inodes is not supported in devfs. - [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, - driver_data_ptr driver_data, - std::optional raw_device = std::nullopt) - -> kstd::result override; - }; -} // namespace kernel::filesystem::devfs - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/devfs/filesystem.tests.cpp b/kernel/kernel/filesystem/devfs/filesystem.tests.cpp deleted file mode 100644 index ec0c1144..00000000 --- a/kernel/kernel/filesystem/devfs/filesystem.tests.cpp +++ /dev/null @@ -1,134 +0,0 @@ -#include - -#include -#include -#include -#include - -#include -#include - -#include -#include - -#include - -#include - -using namespace kstd::units_literals; - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, - "Devfs filesystem lookup uses storage management devices", "[filesystem][devfs]") -{ - GIVEN("a boot module registry with one module") - { - setup_modules(1); - - auto fs = kstd::make_shared(); - auto mount = fs->mount(nullptr); - REQUIRE(mount); - auto [root, driver_data] = *mount; - REQUIRE(root); - - THEN("lookup on root finds ram0 device inode") - { - auto inode = fs->lookup(root, "ram0", driver_data); - REQUIRE(inode); - REQUIRE(inode.value()->is_device()); - } - - THEN("lookup of an unknown device returns error") - { - auto inode = fs->lookup(root, "ram99", driver_data); - REQUIRE(!inode); - } - - THEN("lookup with wrong parent returns error") - { - auto other_fs = kstd::make_shared(); - auto other_root = other_fs->mount(nullptr); - CHECK(other_root); - - auto inode = fs->lookup(other_root->first, "ram0", driver_data); - REQUIRE(!inode); - } - - THEN("lookup with a non-directory parent returns error") - { - auto non_directory_inode = fs->lookup(root, "ram0", driver_data); - REQUIRE(non_directory_inode); - REQUIRE_FALSE(non_directory_inode.value()->is_directory()); - - auto result = fs->lookup(*non_directory_inode, "anything", driver_data); - REQUIRE(!result); - } - - THEN("create_inode always returns an error") - { - auto result = fs->create_inode(root, "new_device", kapi::filesystem::file_type::regular, driver_data); - REQUIRE(!result); - } - } - - GIVEN("a boot module registry with three modules") - { - setup_modules(3, 2048); - - auto fs = kstd::make_shared(); - auto mount = fs->mount(nullptr); - REQUIRE(mount); - auto [root, driver_data] = *mount; - REQUIRE(root); - - THEN("lookup finds all generated RAM devices") - { - REQUIRE(fs->lookup(root, "ram0", driver_data)); - REQUIRE(fs->lookup(root, "ram1", driver_data)); - REQUIRE(fs->lookup(root, "ram2", driver_data)); - } - } -} - -SCENARIO("Devfs filesystem updates", "[filesystem][devfs]") -{ - GIVEN("A mounted devfs, and a bus attached to the root without a device attached") - { - auto fs = kstd::make_shared(); - auto mount = fs->mount(nullptr); - REQUIRE(mount); - auto [root, driver_data] = *mount; - REQUIRE(root); - - auto bus = kstd::make_shared("devfs_live_update_bus"); - kapi::devices::get_root_bus()->add_child(bus); - - REQUIRE_FALSE(fs->lookup(root, "devfs_live_update_device_node", driver_data)); - - WHEN("a device is attached, bound, and published") - { - auto device = kstd::make_shared("devfs_live_update_device", 512_B); - bus->add_child(device); - CHECK(kernel::tests::devices::bind(*device, std::uint8_t{99})); - - REQUIRE( - kapi::devices::publish_facet(device, "devfs_live_update_device_node")); - - THEN("devfs finds it immediately") - { - REQUIRE(fs->lookup(root, "devfs_live_update_device_node", driver_data)); - } - - WHEN("the device is detached") - { - REQUIRE(kapi::devices::remove_device(*device)); - - THEN("devfs does not find it anymore") - { - REQUIRE_FALSE(fs->lookup(root, "devfs_live_update_device_node", driver_data)); - } - } - } - - REQUIRE(kapi::devices::remove_device(*bus)); - } -} \ No newline at end of file diff --git a/kernel/kernel/filesystem/devfs/inode.cpp b/kernel/kernel/filesystem/devfs/inode.cpp deleted file mode 100644 index 8813eb2a..00000000 --- a/kernel/kernel/filesystem/devfs/inode.cpp +++ /dev/null @@ -1,99 +0,0 @@ -#include - -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace kstd::units_literals; - -namespace kernel::filesystem::devfs -{ - namespace - { - constexpr auto static sentinel = directory_listing_cursor{std::numeric_limits::max()}; - } - - auto inode::read(std::span, kstd::bytes) const -> kstd::result - { - return 0_B; - } - - auto inode::write(std::span, kstd::bytes) -> kstd::result - { - return 0_B; - } - - auto inode::is_directory() const -> bool - { - return true; - } - - auto inode::read_directory(directory_listing_cursor position, std::span entries) const - -> kstd::result> - { - auto const requested = entries.size(); - if (requested == 0) - { - return kstd::failure(vfs_errc::invalid_argument); - } - - if (position == sentinel) - { - return std::pair{0, sentinel}; - } - - auto devices = kernel::filesystem::device_number_registry::get().all(); - if (devices.empty()) - { - return std::pair{0, position}; - } - - std::ranges::stable_sort(devices, std::ranges::less{}, &kernel::filesystem::device_number_registry::entry::number); - - auto const device_number = kapi::filesystem::device_number{static_cast(position.value >> 8), - static_cast(position.value & 0xff)}; - auto const begin = std::ranges::lower_bound(devices, device_number, std::ranges::less{}, - &kernel::filesystem::device_number_registry::entry::number); - auto const end = std::ranges::end(devices); - auto const to_read = std::min(requested, static_cast(std::ranges::distance(begin, end))); - - if (to_read == 0) - { - return std::pair{0, position}; - } - - std::ranges::transform(begin, begin + to_read, std::ranges::begin(entries), [](auto const & entry) { - return directory_listing_entry{.name = entry.name, - .type = entry.type, - .inode_number = std::bit_cast(entry.number)}; - }); - - auto const last_copied = begin + to_read; - if (last_copied == end) - { - return std::pair{to_read, sentinel}; - } - - auto const new_device_number = last_copied->number; - auto const new_cursor = directory_listing_cursor{static_cast(new_device_number.minor) | - (static_cast(new_device_number.major) << 8)}; - return std::pair{to_read, new_cursor}; - } -} // namespace kernel::filesystem::devfs \ No newline at end of file diff --git a/kernel/kernel/filesystem/devfs/inode.hpp b/kernel/kernel/filesystem/devfs/inode.hpp deleted file mode 100644 index b83d484c..00000000 --- a/kernel/kernel/filesystem/devfs/inode.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_DEVFS_INODE_HPP -#define TEACHOS_KERNEL_FILESYSTEM_DEVFS_INODE_HPP - -#include -#include -#include - -#include -#include - -#include -#include -#include - -namespace kernel::filesystem::devfs -{ - //! Inode implementation for the devfs filesystem. - //! - //! This inode represents root device node in the /dev directory. - struct inode final : kernel::filesystem::inode - { - [[nodiscard]] auto read(std::span buffer, kstd::bytes offset) const - -> kstd::result override; - - auto write(std::span buffer, kstd::bytes offset) -> kstd::result override; - - [[nodiscard]] auto is_directory() const -> bool override; - - [[nodiscard]] auto read_directory(directory_listing_cursor position, - std::span entries) const - -> kstd::result> override; - }; -} // namespace kernel::filesystem::devfs - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/devfs/inode.tests.cpp b/kernel/kernel/filesystem/devfs/inode.tests.cpp deleted file mode 100644 index 581bf6c5..00000000 --- a/kernel/kernel/filesystem/devfs/inode.tests.cpp +++ /dev/null @@ -1,235 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -using namespace kstd::units_literals; - -namespace -{ - auto make_and_publish(kstd::shared_ptr const & bus, kstd::string name) - -> kstd::shared_ptr - - { - auto device = kstd::make_shared(name, 512_B); - bus->add_child(device); - REQUIRE(kernel::tests::devices::bind(*device)); - REQUIRE(kapi::devices::publish_facet(device, name)); - return device; - } -} // namespace - -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()); - REQUIRE_FALSE(inode.is_symbolic_link()); - } - } -} - -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 buffer(512); - auto bytes_read = inode.read(buffer, 0_B); - - THEN("no bytes are read") - { - REQUIRE(bytes_read == 0_B); - } - } - - WHEN("attempting to write to the devfs inode") - { - kstd::vector buffer(512); - auto bytes_written = inode.write(buffer, 0_B); - - THEN("no bytes are written") - { - REQUIRE(bytes_written == 0_B); - } - } - } -} - -SCENARIO("Devfs inode directory listing", "[filesystem][devfs][inode]") -{ - GIVEN("a devfs inode and no registered devices") - { - auto root = kernel::filesystem::devfs::inode{}; - - THEN("the directory listing is empty") - { - auto entries = kstd::vector{4}; - auto result = root.read_directory(kernel::filesystem::directory_listing_cursor{}, entries); - - REQUIRE(result); - auto [count, next] = *result; - REQUIRE(count == 0); - } - } - - GIVEN("a devfs inode and three registered devices") - { - auto root = kernel::filesystem::devfs::inode{}; - - auto bus = kstd::make_shared("devfs_readdir_bus"); - kapi::devices::get_root_bus()->add_child(bus); - - auto device1 = make_and_publish(bus, "devfs_readdir_device1"); - auto device2 = make_and_publish(bus, "devfs_readdir_device2"); - auto device3 = make_and_publish(bus, "devfs_readdir_device3"); - - WHEN("the directory listing is requested with a buffer large enough for all of them") - { - auto entries = kstd::vector{4}; - auto result = root.read_directory(kernel::filesystem::directory_listing_cursor{}, entries); - - THEN("all devices are listed") - { - REQUIRE(result); - auto [count, next] = *result; - REQUIRE(count == 3); - - auto names = kstd::vector{3}; - for (auto i = 0uz; i < count; ++i) - { - REQUIRE(entries[i].type == kapi::filesystem::file_type::block_device); - names.push_back(entries[i].name); - } - - REQUIRE(std::ranges::find(names, "devfs_readdir_device1") != names.end()); - REQUIRE(std::ranges::find(names, "devfs_readdir_device2") != names.end()); - REQUIRE(std::ranges::find(names, "devfs_readdir_device3") != names.end()); - - AND_THEN("listing again with the returned cursor reports 'end-of-directory'") - { - auto result = root.read_directory(next, entries); - - REQUIRE(result); - auto [count, cursor] = *result; - REQUIRE(count == 0); - REQUIRE(cursor == next); - } - } - } - - WHEN("read_directory is called one entry at a time, chaining the returned cursor") - { - auto seen = kstd::vector{}; - auto position = kernel::filesystem::directory_listing_cursor{}; - - for (auto guard = 0; guard < 4; ++guard) - { - auto one = kstd::vector(1); - auto result = root.read_directory(position, one); - REQUIRE(result); - auto [count, next] = *result; - if (count == 0) - { - break; - } - seen.push_back(one[0].name); - position = next; - } - - THEN("nothing is skipped or duplicated across the paginated calls") - { - REQUIRE(seen.size() == 3); - auto sorted = seen; - std::ranges::sort(sorted); - std::ignore = std::ranges::unique(sorted); - REQUIRE(sorted.size() == 3); - } - } - - REQUIRE(kapi::devices::remove_device(*bus)); - } -} - -SCENARIO("Devfs inode read_directory orders by device number, not registration order", - "[filesystem][devfs][inode][readdir]") -{ - GIVEN("a device that frees the lowest minor number before a later device reuses it") - { - auto root = kernel::filesystem::devfs::inode{}; - - auto bus = kstd::make_shared("devfs_readdir_reuse_bus"); - kapi::devices::get_root_bus()->add_child(bus); - - auto first_out = make_and_publish(bus, "readdir_reuse_first_out"); - auto still_in = make_and_publish(bus, "readdir_reuse_still_in"); - REQUIRE(kapi::devices::remove_device(*first_out)); - auto later_in = make_and_publish(bus, "readdir_reuse_later_in"); - - WHEN("read_directory is called with room for both remaining entries") - { - auto entries = kstd::vector(2); - auto result = root.read_directory(kernel::filesystem::directory_listing_cursor{}, entries); - - THEN("later_in (the reused, lower device number) comes back before still_in, by number, not by " - "when each was registered") - { - REQUIRE(result); - auto [count, next] = *result; - REQUIRE(count == 2); - REQUIRE(entries[0].name == "readdir_reuse_later_in"); - REQUIRE(entries[1].name == "readdir_reuse_still_in"); - } - } - - REQUIRE(kapi::devices::remove_device(*bus)); - } -} - -SCENARIO("Devfs read_directory rejects non-directory inodes", "[filesystem][devfs][inode][readdir]") -{ - GIVEN("a device_inode, which does not represent a directory") - { - auto bus = kstd::make_shared("devfs_readdir_nondir_bus"); - kapi::devices::get_root_bus()->add_child(bus); - auto device = make_and_publish(bus, "readdir_nondir_device"); - auto node = kernel::filesystem::device_inode{device}; - - THEN("read_directory falls through to the base class default rather than enumerating anything") - { - auto entries = kstd::vector(1); - auto result = node.read_directory(kernel::filesystem::directory_listing_cursor{}, entries); - - REQUIRE_FALSE(result); - } - - REQUIRE(kapi::devices::remove_device(*bus)); - } -} \ No newline at end of file diff --git a/kernel/kernel/filesystem/devfs/module.cpp b/kernel/kernel/filesystem/devfs/module.cpp deleted file mode 100644 index a7a1aca9..00000000 --- a/kernel/kernel/filesystem/devfs/module.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include -#include - -#include - -#include - -namespace -{ - - struct descriptor final : kernel::filesystem::driver_descriptor - { - [[nodiscard]] auto name() const noexcept -> std::string_view override - { - return "devfs"; - } - - [[nodiscard]] auto make_instance() const -> kernel::filesystem::filesystem::filesystem_ptr override - { - return kstd::make_shared(); - } - }; - - [[gnu::used]] - constexpr auto registration = kernel::filesystem::driver_module{}; - -} // namespace \ No newline at end of file diff --git a/kernel/kernel/filesystem/device_inode.cpp b/kernel/kernel/filesystem/device_inode.cpp deleted file mode 100644 index f45023d3..00000000 --- a/kernel/kernel/filesystem/device_inode.cpp +++ /dev/null @@ -1,120 +0,0 @@ -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include - -namespace kernel::filesystem -{ - device_inode::device_inode(kstd::shared_ptr const & device) - : device_inode{device, nullptr} - { - if (!device) - { - kapi::system::panic("[FILESYSTEM] device_inode constructed with null device."); - } - } - - device_inode::device_inode(kstd::shared_ptr const & device, - kstd::shared_ptr const & origin) - : m_device{device} - , m_origin{origin} - { - if (!device) - { - kapi::system::panic("[FILESYSTEM] device_inode constructed with null device."); - } - } - - auto device_inode::read(std::span buffer, kstd::bytes offset) const -> kstd::result - { - if (m_device->state() == kapi::devices::state::removed) - { - return kstd::failure(vfs_errc::no_such_device); - } - - if (auto block_device = - kapi::devices::facet_registry::get().resolve(*m_device)) - { - return devices::block_device_utils::read(*block_device, buffer, offset); - } - else if (auto character_device = - kapi::devices::facet_registry::get().resolve(*m_device)) - { - return character_device->read(buffer); - } - - return kstd::failure(vfs_errc::no_such_device); - } - - auto device_inode::write(std::span buffer, kstd::bytes offset) -> kstd::result - { - if (m_device->state() == kapi::devices::state::removed) - { - return kstd::failure(vfs_errc::no_such_device); - } - - if (auto block_device = - kapi::devices::facet_registry::get().resolve(*m_device)) - { - return devices::block_device_utils::write(*block_device, buffer, offset); - } - else if (auto character_device = - kapi::devices::facet_registry::get().resolve(*m_device)) - { - return character_device->write(buffer); - } - - return kstd::failure(vfs_errc::no_such_device); - } - - auto device_inode::device() const -> kstd::shared_ptr const & - { - return m_device; - } - - auto device_inode::is_block_device() const -> bool - { - return kapi::devices::facet_registry::get().resolve(*m_device); - } - - auto device_inode::is_character_device() const -> bool - { - return kapi::devices::facet_registry::get().resolve(*m_device); - } - - auto device_inode::status() const -> kstd::result - { - if (m_origin) - { - return m_origin->status(); - } - - return inode::status(); - } - - auto device_inode::raw_device() const -> std::optional - { - if (auto number = device_number_registry::get().number_of(*m_device)) - { - return *number; - } - - return std::nullopt; - } - -} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/kernel/filesystem/device_inode.hpp b/kernel/kernel/filesystem/device_inode.hpp deleted file mode 100644 index 49b17d89..00000000 --- a/kernel/kernel/filesystem/device_inode.hpp +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_DEVICE_INODE_HPP -#define TEACHOS_KERNEL_FILESYSTEM_DEVICE_INODE_HPP - -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include - -namespace kernel::filesystem -{ - - //! Inode implementation for device inodes in the filesystem. - //! - //! An object of this type 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 final : inode - { - //! Create an inode representing a given device. - //! - //! @param device The device to associate with the inode. - explicit device_inode(kstd::shared_ptr const & device); - - //! Create a device inode wrapping a device and carrying another inodes metadata. - //! - //! This constructor enables reconstituting a persistend device node from a filesystem. POSIX status information - //! will be queried by the origin inode. - //! - //! @param device The device that the new inode should represent. - //! @param origin The original inode being wrapped by the new inode. - device_inode(kstd::shared_ptr const & device, kstd::shared_ptr const & origin); - - //! @name I/O Operations - //! @{ - - //! Read data, starting at a given offset, from the device into a buffer; - //! - //! @param buffer The buffer to read data into. - //! @param offset The offset to read from. - //! @return The number of bytes read on success, an error on otherwise. - [[nodiscard]] auto read(std::span buffer, kstd::bytes offset) const - -> kstd::result override; - - //! Write data from a buffer to the device, at a given offset. - //! - //! @param buffer The buffer containing data to write. - //! @param offset The offset to write to. - //! @return The number of bytes written on success, an error otherwise. - auto write(std::span buffer, kstd::bytes offset) -> kstd::result override; - - //! @} - - //! @name Property Queries - //! @{ - - [[nodiscard]] auto is_block_device() const -> bool override; - - [[nodiscard]] auto is_character_device() const -> bool override; - - //! @} - - //! @name POSIX Information Access - //! @{ - - [[nodiscard]] auto status() const -> kstd::result override; - - [[nodiscard]] auto raw_device() const -> std::optional override; - - //! @} - - //! @name Device Access - //! @{ - - //! @brief Get the device, if any, associated with this inode. - //! - //! @return The associated device, of nullptr if no such device exists. - [[nodiscard]] auto device() const -> kstd::shared_ptr const &; - - //! @} - - private: - //! The handle to the device represented by this inode. - //! - //! Any I/O operations will be redirected to this device, allowing for filesystem based device I/O operations. - kstd::shared_ptr m_device; - - //! The original inode this inode was reconstituted from, if any. - //! - //! If this inode was reconstituted from a filesystem inode representing a device, e.g. from an ext2 block device - //! inode, Some queries must be forwarded to that origin inode. This data member will be used to forward those call - //! when applicable. - kstd::shared_ptr m_origin; - }; -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/device_inode.tests.cpp b/kernel/kernel/filesystem/device_inode.tests.cpp deleted file mode 100644 index c4b117fb..00000000 --- a/kernel/kernel/filesystem/device_inode.tests.cpp +++ /dev/null @@ -1,244 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -using namespace kstd::units_literals; - -namespace -{ - struct registry_reset_guard - { - ~registry_reset_guard() - { - kernel::tests::filesystem::device_number_registry::deinit(); - } - }; - - struct mock_origin_inode : kernel::filesystem::inode - { - auto read(std::span, kstd::bytes) const -> kstd::result override - { - return 0_B; - } - - auto write(std::span, kstd::bytes) -> kstd::result override - { - return 0_B; - } - - [[nodiscard]] auto status() const -> kstd::result override - { - return kapi::filesystem::file_status{ - .mode = std::to_underlying(kapi::filesystem::file_type::block_device), - .size = 4096_B, - .raw_device = kapi::filesystem::device_number{.major = 1, .minor = 0}, - .inode_number = 40, - .link_count = 1, - }; - } - }; -} // namespace - -SCENARIO("Device inode construction", "[filesystem][device_inode]") -{ - GIVEN("a block device") - { - auto device = kstd::make_shared("test_block_device", 512_B, 3 * 512_B); - - 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()); - REQUIRE_FALSE(inode.is_symbolic_link()); - } - } - - 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("test_block_device", 512_B, 3 * 512_B); - auto inode = kernel::filesystem::device_inode{device}; - - WHEN("writing to the device inode") - { - kstd::vector write_buffer(1024); - for (size_t i = 0; i < write_buffer.size(); ++i) - { - write_buffer[i] = static_cast(i % 256); - } - - auto bytes_written = inode.write(write_buffer, 256_B); - - THEN("the correct number of bytes is written") - { - REQUIRE(bytes_written == 1024_B); - } - - THEN("the data written matches the data read back from the device inode") - { - kstd::vector read_buffer(1024); - auto bytes_read = inode.read(read_buffer, 256_B); - - REQUIRE(bytes_read.value().value == 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("test_character_device"); - auto inode = kernel::filesystem::device_inode{device}; - - WHEN("reading from the device inode") - { - kstd::vector read_buffer(512); - - THEN("the result is an error") - { - auto result = inode.read(read_buffer, 0_B); - REQUIRE(!result); - REQUIRE(result.error() == kstd::errc::no_such_device); - } - } - - WHEN("writing to the device inode") - { - kstd::vector write_buffer(512); - - THEN("the system panics") - { - auto result = inode.write(write_buffer, 0_B); - REQUIRE(!result); - REQUIRE(result.error() == kstd::errc::no_such_device); - } - } - } -} - -SCENARIO("Device inode raw_device()", "[filesystem][device-inode]") -{ - auto resetter = registry_reset_guard{}; - - GIVEN("A block device that has been published and numbered") - { - auto device = kstd::make_shared("ram0", 512_B); - - CHECK(kernel::tests::devices::bind(*device, std::uint8_t{1})); - - REQUIRE(kapi::devices::facet_registry::get().publish(device, "ram0")); - - auto inode = kernel::filesystem::device_inode{device}; - - THEN("raw_device() return the number assigned to the device") - { - auto expected = kernel::filesystem::device_number_registry::get().number_of(*device); - REQUIRE(expected); - - auto actual = inode.raw_device(); - REQUIRE(actual == expected); - } - } - - GIVEN("a block device that has never been published") - { - auto device = kstd::make_shared("never_published", 512_B); - auto inode = kernel::filesystem::device_inode{device}; - - THEN("raw_device() is nullopt, not a stale or default number") - { - REQUIRE_FALSE(inode.raw_device()); - } - } -} - -SCENARIO("Device inode status()", "[filesystem][device_inode]") -{ - registry_reset_guard const guard{}; - - GIVEN("a device inode with no origin") - { - auto device = kstd::make_shared("ram0", 512_B); - auto inode = kernel::filesystem::device_inode{device}; - - THEN("status() falls back to the minimal default") - { - auto const result = inode.status(); - REQUIRE(result.has_value()); - REQUIRE(result->mode == std::to_underlying(kapi::filesystem::file_type::block_device)); - REQUIRE(result->size == 0_B); - REQUIRE(result->inode_number == 0); - } - } - - GIVEN("a device inode constructed with an origin inode") - { - auto device = kstd::make_shared("ram0", 512_B, 512_B); - auto origin = kstd::make_shared(); - auto inode = kernel::filesystem::device_inode{device, origin}; - - THEN("status() forwards to the origin outright, not device_inode's own default") - { - auto const result = inode.status(); - REQUIRE(result.has_value()); - REQUIRE(result->size == 4096_B); - REQUIRE(result->inode_number == 40); - REQUIRE(result->link_count == 1); - REQUIRE(result->raw_device == kapi::filesystem::device_number{.major = 1, .minor = 0}); - } - - THEN("read/write still delegate to the device, never to the origin") - { - kstd::vector buffer(512); - auto const result = inode.read(buffer, 0_B); - REQUIRE(result == 512_B); - } - } -} \ No newline at end of file diff --git a/kernel/kernel/filesystem/device_number_registry.cpp b/kernel/kernel/filesystem/device_number_registry.cpp deleted file mode 100644 index 64cdb062..00000000 --- a/kernel/kernel/filesystem/device_number_registry.cpp +++ /dev/null @@ -1,283 +0,0 @@ -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace -{ - constinit auto static instance = std::optional{}; -} // namespace - -namespace kernel::filesystem -{ - - auto device_number_registry::get() -> device_number_registry & - { - if (!instance) - { - instance.emplace(kapi::devices::facet_registry::get()); - } - return *instance; - } - - device_number_registry::device_number_registry(kapi::devices::facet_registry & facet_registry) - : m_facet_registry{&facet_registry} - { - for (auto const & binding : file_type_bindings) - { - for (auto const & published : facet_registry.all(binding.facet)) - { - try_number(binding.facet, published); - } - } - - m_facet_registry->subscribe(*this); - } - - device_number_registry::~device_number_registry() - { - m_facet_registry->unsubscribe(*this); - } - - auto device_number_registry::resolve(kapi::filesystem::device_number number, file_type type) const - -> kstd::result> - { - auto guard = kstd::lock_guard{m_lock}; - - erase_if(m_entries, [&](auto entry) { return entry.device.expired(); }); - - auto found = - std::ranges::find_if(m_entries, [&](auto entry) { return entry.number == number && entry.type == type; }); - - if (found == std::ranges::cend(m_entries)) - { - return kstd::failure(vfs_errc::no_such_device); - } - - if (auto device = found->device.lock()) - { - return device; - } - - return kstd::failure(vfs_errc::no_such_device); - } - - auto device_number_registry::number_of(kapi::devices::device const & device) const - -> kstd::result - { - auto guard = kstd::lock_guard{m_lock}; - - erase_if(m_entries, [&](auto entry) { return entry.device.expired(); }); - - auto found = std::ranges::find_if(m_entries, [&](auto entry) { - auto locked = entry.device.lock(); - return locked.get() == &device; - }); - - if (found == std::ranges::cend(m_entries)) - { - return kstd::failure(vfs_errc::no_such_device); - } - - return found->number; - } - - auto device_number_registry::all() const -> kstd::vector - { - auto guard = kstd::lock_guard{m_lock}; - - erase_if(m_entries, [&](auto entry) { return entry.device.expired(); }); - - return m_entries; - } - - auto device_number_registry::subscribe(kstd::weak_ptr observer) -> void - { - auto guard = kstd::lock_guard{m_lock}; - - erase_if(m_observers, [&](auto existing) { return existing.expired(); }); - - m_observers.push_back(std::move(observer)); - } - - auto device_number_registry::on_facet_published(kapi::capabilities::facet_id facet, - kapi::devices::facet_registry::entry const & published) -> void - { - try_number(facet, published); - } - - auto device_number_registry::on_facet_withdrawn(kapi::capabilities::facet_id facet, kapi::devices::device & device) - -> void - { - // Check if there even is a binding defined for the given facet. - auto binding = - std::ranges::find_if(file_type_bindings, [&](auto const & binding) { return binding.facet == facet; }); - if (binding == std::ranges::cend(file_type_bindings)) - { - return; - } - - auto unnumbered = entry{}; - - { - auto guard = kstd::lock_guard{m_lock}; - - auto found = std::ranges::find_if(m_entries, [&](auto entry) { - auto locked = entry.device.lock(); - return entry.type == binding->posix_type && locked.get() == &device; - }); - - if (found == std::ranges::cend(m_entries)) - { - return; - } - - unnumbered = *found; - m_entries.erase(found); - } - - auto observers = kstd::vector>{}; - { - auto guard = kstd::lock_guard{m_lock}; - observers = m_observers; - } - - std::ranges::for_each(observers, [&](auto const & weak_observer) { - auto observer = weak_observer.lock(); - if (observer) - { - observer->on_device_unnumbered(unnumbered); - } - }); - } - - auto device_number_registry::try_number(kapi::capabilities::facet_id facet, - kapi::devices::facet_registry::entry const & published) -> void - { - // Check if there even is a binding defined for the given facet. - auto binding = - std::ranges::find_if(file_type_bindings, [&](auto const & binding) { return binding.facet == facet; }); - if (binding == std::ranges::cend(file_type_bindings)) - { - return; - } - - // Check if the device is still alive (it might have died while we are being notified). - auto device = published.device(); - if (!device) - { - return; - } - - // Check if the device has a bound driver (otherwise there is no point in numbering it). - auto driver = device->bound_driver(); - if (!driver) - { - return; - } - - // Check if the driver has claimed a major number (otherwise we can't number it). - auto driver_major = driver->claimed_major(); - if (!driver_major) - { - return; - } - - auto numbered = entry{}; - { - auto guard = kstd::lock_guard{m_lock}; - - auto minor = std::uint8_t{}; - auto driver_minor = driver->claimed_minor(*device); - if (driver_minor) - { - minor = *driver_minor; - auto filtered = std::views::filter( - m_entries, [&](auto e) { return e.type == binding->posix_type && e.number.major == *driver_major; }); - auto found = std::ranges::find_if(filtered, [&](auto e) { return e.minor == minor; }, &entry::number); - if (found != std::ranges::cend(filtered)) - { - kstd::println(kstd::print_sink::stderr, "[OS:DEV] Driver '{}' tried to claim duplicate minor {}", - driver->name(), minor); - return; - } - } - else - { - minor = allocate_minor(binding->posix_type, *driver_major); - } - - numbered = entry{ - .number = {.major = *driver_major, .minor = minor}, - .type = binding->posix_type, - .name = published.name(), - .device = device, - }; - - m_entries.push_back(numbered); - } - - auto observers = kstd::vector>{}; - { - auto guard = kstd::lock_guard{m_lock}; - observers = m_observers; - } - - std::ranges::for_each(observers, [&](auto const & weak_observer) { - auto observer = weak_observer.lock(); - if (observer) - { - observer->on_device_numbered(numbered); - } - }); - } - - auto device_number_registry::allocate_minor(file_type type, std::uint8_t major) const -> std::uint8_t - { - auto used = kstd::vector{}; - for (auto const & entry : m_entries) - { - if (entry.type == type && entry.number.major == major) - { - used.push_back(entry.number.minor); - } - } - std::ranges::sort(used); - - auto candidate = std::uint8_t{}; - for (auto minor : used) - { - if (minor != candidate) - { - break; - } - ++candidate; - } - - return candidate; - } - -} // namespace kernel::filesystem - -namespace kernel::tests::filesystem::device_number_registry -{ - auto deinit() -> void - { - instance.reset(); - } -} // namespace kernel::tests::filesystem::device_number_registry diff --git a/kernel/kernel/filesystem/device_number_registry.hpp b/kernel/kernel/filesystem/device_number_registry.hpp deleted file mode 100644 index 2f0f0226..00000000 --- a/kernel/kernel/filesystem/device_number_registry.hpp +++ /dev/null @@ -1,117 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_DEVICE_NUMBER_REGISTRY_HPP -#define TEACHOS_KERNEL_FILESYSTEM_DEVICE_NUMBER_REGISTRY_HPP - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include - -namespace kernel::filesystem -{ - - struct device_number_registry_observer; - - //! A registry to assign stable device numbers to published devices. - struct device_number_registry final : kapi::devices::facet_registry_observer - { - using file_type = kapi::filesystem::file_type; - - //! A single numbered device. - //! - //! Theses entries are used, for example, in devfs to provide access to devices. - struct entry - { - kapi::filesystem::device_number number; - file_type type; - kstd::string name; - kstd::weak_ptr device; - }; - - //! Construct a new device number registry, subscribing to the given facet registry. - //! - //! @param facet_registry The facet registry to back this device number registry. - explicit device_number_registry(kapi::devices::facet_registry & facet_registry); - - //! Destroy this registry. - ~device_number_registry() override; - - device_number_registry(device_number_registry const &) = delete; - device_number_registry(device_number_registry &&) = delete; - auto operator=(device_number_registry const &) -> device_number_registry & = delete; - auto operator=(device_number_registry &&) -> device_number_registry & = delete; - - //! Get the system global instance of the registry. - [[nodiscard]] auto static get() -> device_number_registry &; - - //! Resolve a device number and file type to a device. - //! - //! @param number The device number. - //! @param type The expected file type of the device. - //! @return A device if a fitting entry is found, an error otherwise. - [[nodiscard]] auto resolve(kapi::filesystem::device_number number, file_type type) const - -> kstd::result>; - - //! Resolve a device to it's device number. - //! - //! @param device The device to find the number for. - //! @return The device number associated with the given device, or an error otherwise. - [[nodiscard]] auto number_of(kapi::devices::device const & device) const - -> kstd::result; - - //! Get all currently registered devices. - //! - //! @return A range containing all currently registered devices numbers mappings. - [[nodiscard]] auto all() const -> kstd::vector; - - //! Subscribe to device numbering events. - //! - //! Observers subscribed through this function are held through non-owning pointers. It is safe of an observer - //! subscribed through this function to be destroyed without being unsubscribed. - //! - //! @param observer The observer to notify. - auto subscribe(kstd::weak_ptr observer) -> void; - - private: - auto on_facet_published(kapi::capabilities::facet_id facet, kapi::devices::facet_registry::entry const & entry) - -> void override; - - auto on_facet_withdrawn(kapi::capabilities::facet_id facet, kapi::devices::device & device) -> void override; - - auto try_number(kapi::capabilities::facet_id facet, kapi::devices::facet_registry::entry const & published) -> void; - - auto allocate_minor(file_type type, std::uint8_t major) const -> std::uint8_t; - - mutable kapi::tracked_mutex m_lock{}; - - kstd::observer_ptr m_facet_registry; - mutable kstd::vector m_entries{}; - kstd::vector> m_observers{}; - }; - - //! The facet for types interested in observing numbering transactions on a device number registry. - struct device_number_registry_observer - { - //! Enable correct destruction through base pointers. - virtual ~device_number_registry_observer() = default; - - //! Called after a device has been assigned a device number. - //! - //! @param numbered The newly numbered entry. - virtual auto on_device_numbered(device_number_registry::entry const & numbered) -> void = 0; - - //! Called after a device's number has been withdrawn. - //! - //! @param unnumbered The entry that is no longer numbered. - virtual auto on_device_unnumbered(device_number_registry::entry const & unnumbered) -> void = 0; - }; - -} // namespace kernel::filesystem - -#endif diff --git a/kernel/kernel/filesystem/device_number_registry.tests.cpp b/kernel/kernel/filesystem/device_number_registry.tests.cpp deleted file mode 100644 index 60e17337..00000000 --- a/kernel/kernel/filesystem/device_number_registry.tests.cpp +++ /dev/null @@ -1,154 +0,0 @@ -#include - -#include -#include - -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include - -using namespace kstd::units_literals; - -namespace -{ - auto make_bound_block_device(kapi::devices::facet_registry & facets, kstd::string const & name, - kstd::bytes block_size, std::optional major) - -> kstd::shared_ptr - { - auto device = kstd::make_shared(name, block_size); - [[maybe_unused]] auto driver = kernel::tests::devices::bind(*device, major); - if (!facets.publish(device, name)) - { - throw std::runtime_error{"failed to publish block device!"}; - } - return device; - } -} // namespace - -SCENARIO("Device number registry resolves and numbers published block devices", - "[kernel][filesystem][device_number_registry]") -{ - auto facets = kapi::devices::facet_registry{}; - auto registry = kernel::filesystem::device_number_registry{facets}; - - GIVEN("No devices have been published") - { - THEN("all() returns an empty vector") - { - REQUIRE(registry.all().empty()); - } - } - - GIVEN("A single block device has been published, bound to a driver claiming major 1") - { - auto device = make_bound_block_device(facets, "ram0", 512_B, std::uint8_t{1}); - - THEN("all() returns a vector with one element") - { - auto all = registry.all(); - REQUIRE(all.size() == 1); - - AND_THEN("that entry has the published name") - { - REQUIRE(all[0].name == "ram0"); - } - - AND_THEN("resolve() finds the device by number") - { - REQUIRE(registry.resolve(all[0].number, kapi::filesystem::file_type::block_device)); - } - - AND_THEN("number_of() finds the device number") - { - REQUIRE(registry.number_of(*device)); - } - - AND_THEN("number_of() is the inverse of resolve()") - { - auto number = registry.number_of(*device); - CHECK(number); - - auto resolved = registry.resolve(*number, kapi::filesystem::file_type::block_device); - CHECK(resolved); - - REQUIRE(resolved == device); - } - } - - THEN("number_of() fails on a device that has never been published") - { - auto not_published = kstd::make_shared("ram1", 512_B); - - REQUIRE(!registry.number_of(*not_published)); - } - } - - GIVEN("A device was published") - { - auto device = make_bound_block_device(facets, "ram0", 512_B, std::uint8_t{1}); - auto number = registry.number_of(*device); - - WHEN("destroying it with no further owner") - { - device.reset(); - - THEN("resolve() does not report it anymore") - { - REQUIRE(!registry.resolve(*number, kapi::filesystem::file_type::block_device)); - } - } - } - - GIVEN("Two block-capable drivers claiming different majors") - { - [[maybe_unused]] auto device_a = make_bound_block_device(facets, "ramA0", 512_B, std::uint8_t{1}); - [[maybe_unused]] auto device_b = make_bound_block_device(facets, "ramB0", 512_B, std::uint8_t{2}); - - THEN("their minors are numbered independently, starting at 0 for each driver") - { - auto all = registry.all(); - REQUIRE(all.size() == 2); - - auto entry_a = std::ranges::find_if(all, [](auto const & entry) { return entry.name == "ramA0"; }); - auto entry_b = std::ranges::find_if(all, [](auto const & entry) { return entry.name == "ramB0"; }); - REQUIRE(entry_a != all.end()); - REQUIRE(entry_b != all.end()); - - CHECK(entry_a->number.major == 1); - CHECK(entry_a->number.minor == 0); - CHECK(entry_b->number.major == 2); - CHECK(entry_b->number.minor == 0); - } - } - - GIVEN("A driver that never overrides claimed_major()") - { - make_bound_block_device(facets, "unnumbered0", 512_B, std::nullopt); - - THEN("it is never numbered, regardless of what it publishes") - { - REQUIRE(registry.all().empty()); - } - } - - GIVEN("A published device with no bound driver at all") - { - auto device = kstd::make_shared("unbound0", 512_B); - CHECK(facets.publish(device, "unbound0")); - - THEN("it is never numbered") - { - REQUIRE(registry.all().empty()); - } - } -} diff --git a/kernel/kernel/filesystem/directory_file_descriptor.cpp b/kernel/kernel/filesystem/directory_file_descriptor.cpp deleted file mode 100644 index 8c73ae86..00000000 --- a/kernel/kernel/filesystem/directory_file_descriptor.cpp +++ /dev/null @@ -1,34 +0,0 @@ -#include - -#include -#include -#include - -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - - auto directory_file_descriptor::read_directory(std::span entries) - -> kstd::result - { - auto result = get_dentry()->inode()->read_directory(m_position, entries); - if (!result) - { - return kstd::failure(result.error()); - } - - m_position = result->second; - return result->first; - } - - auto directory_file_descriptor::position() const noexcept -> directory_listing_cursor - { - return m_position; - } - -} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/kernel/filesystem/directory_file_descriptor.hpp b/kernel/kernel/filesystem/directory_file_descriptor.hpp deleted file mode 100644 index 075937ca..00000000 --- a/kernel/kernel/filesystem/directory_file_descriptor.hpp +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_DIRECTORY_FILE_DESCRIPTOR_HPP -#define TEACHOS_KERNEL_FILESYSTEM_DIRECTORY_FILE_DESCRIPTOR_HPP - -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - - struct directory_file_descriptor : open_file_descriptor - { - using open_file_descriptor::open_file_descriptor; - - auto read_directory(std::span entries) -> kstd::result override; - - [[nodiscard]] auto position() const noexcept -> directory_listing_cursor; - - private: - directory_listing_cursor m_position{}; - }; - -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/directory_file_descriptor.tests.cpp b/kernel/kernel/filesystem/directory_file_descriptor.tests.cpp deleted file mode 100644 index ac60aad9..00000000 --- a/kernel/kernel/filesystem/directory_file_descriptor.tests.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include - -#include -#include -#include -#include -#include - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, - "directory_file_descriptor owns its cursor across calls, not the caller", - "[filesystem][directory_file_descriptor][img]") -{ - auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; - - GIVEN("a directory_file_descriptor open on a real directory") - { - 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 dentry = vfs.open("/information"); - REQUIRE(dentry); - - auto fd = kernel::filesystem::directory_file_descriptor{dentry.value()}; - - THEN("position starts at a default-constructed cursor") - { - REQUIRE(fd.position() == kernel::filesystem::directory_listing_cursor{}); - } - - WHEN("read_directory is called one entry at a time, with no cursor passed by the caller") - { - auto seen = std::vector{}; - - for (auto guard = 0; guard < 16; ++guard) - { - auto one = kstd::vector(1); - auto result = fd.read_directory(one); - REQUIRE(result); - if (*result == 0) - { - break; - } - seen.push_back(std::string{one[0].name}); - } - - THEN("every call advanced position on its own, and the known entries all appear exactly once") - { - REQUIRE(fd.position() != kernel::filesystem::directory_listing_cursor{}); - REQUIRE(std::ranges::find(seen, ".") != seen.end()); - REQUIRE(std::ranges::find(seen, "info_1.txt") != seen.end()); - - AND_THEN("a repeated call after exhaustion returns zero without changing position again") - { - auto const position_at_exhaustion = fd.position(); - - auto one = kstd::vector(1); - auto result = fd.read_directory(one); - - REQUIRE(result); - REQUIRE(*result == 0); - REQUIRE(fd.position() == position_at_exhaustion); - } - } - } - } -} \ No newline at end of file diff --git a/kernel/kernel/filesystem/directory_listing_cursor.hpp b/kernel/kernel/filesystem/directory_listing_cursor.hpp deleted file mode 100644 index 5eb36c7a..00000000 --- a/kernel/kernel/filesystem/directory_listing_cursor.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_DIRECTORY_LISTING_CURSOR_HPP -#define TEACHOS_KERNEL_FILESYSTEM_DIRECTORY_LISTING_CURSOR_HPP - -#include -#include - -namespace kernel::filesystem -{ - - //! A cursor used to keep track of positions in directory listings. - struct directory_listing_cursor - { - constexpr auto friend operator<=>(directory_listing_cursor const &, directory_listing_cursor const &) noexcept - -> std::strong_ordering = default; - constexpr auto friend operator==(directory_listing_cursor const &, directory_listing_cursor const &) noexcept - -> bool = default; - - std::uint64_t value; - }; - -}; // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/directory_listing_entry.hpp b/kernel/kernel/filesystem/directory_listing_entry.hpp deleted file mode 100644 index f3dcf367..00000000 --- a/kernel/kernel/filesystem/directory_listing_entry.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_DIRECTORY_LISTING_ENTRY_HPP -#define TEACHOS_KERNEL_FILESYSTEM_DIRECTORY_LISTING_ENTRY_HPP - -#include - -#include - -#include - -namespace kernel::filesystem -{ - - //! An entry in a directory listing. - struct directory_listing_entry - { - kstd::string name; - kapi::filesystem::file_type type; - std::uint64_t inode_number; - }; - -}; // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/driver_registry.cpp b/kernel/kernel/filesystem/driver_registry.cpp deleted file mode 100644 index 9fa2d474..00000000 --- a/kernel/kernel/filesystem/driver_registry.cpp +++ /dev/null @@ -1,130 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace kernel::filesystem -{ - - extern "C" - { - // We need to suppress clang-tidy linting warnings here, since these symbols are generated by the linker and we - // cannot choose their names, unless we wanted to extend the linker script needlessly. - // NOLINTBEGIN(readability-identifier-naming) - extern kstd::observer_ptr const __start_filesystem_drivers; - extern kstd::observer_ptr const __stop_filesystem_drivers; - // NOLINTEND(readability-identifier-naming) - } - - namespace - { - auto constinit instance = std::optional{}; - } - - auto driver_registry::init() -> void - { - if (instance) - { - kapi::system::panic("[OS:FS] The filesystem driver registry was already initialized!"); - } - - instance.emplace(); - - auto instances = - std::span{&__start_filesystem_drivers, &__stop_filesystem_drivers} | - std::views::filter([](auto descriptor) { return descriptor != nullptr; }) | - std::views::transform([](auto descriptor) { return std::pair{descriptor, descriptor->make_instance()}; }) | - std::views::filter([](auto entry) { return entry.second != nullptr; }); - - std::ranges::for_each(instances, [](auto entry) { - auto [descriptor, driver] = entry; - kstd::println("[OS:FS] Registering filesystem driver '{}'", descriptor->name()); - instance->add(descriptor->name(), driver); - }); - } - - auto driver_registry::get() -> driver_registry & - { - if (!instance) - { - kapi::system::panic("[OS:FS] The filesystem driver registry has not been initialized!"); - } - - return *instance; - } - - auto driver_registry::find(std::string_view name) const -> std::optional> - { - if (auto it = m_drivers.find(name); it != m_drivers.end()) - { - return it->second; - } - return std::nullopt; - } - - auto driver_registry::add(std::string_view name, kstd::shared_ptr driver) -> bool - { - auto result = m_drivers.emplace(name, driver); - if (!result.second) - { - kstd::println(kstd::print_sink::stderr, "[OS:FS] Tried to register duplicate filesystem driver '{}'!", name); - } - - return result.second; - } - - auto driver_registry::match(kstd::shared_ptr const & inode) const -> kstd::result> - { - struct candidate - { - std::uint32_t priority; - kstd::shared_ptr driver; - }; - - auto candidates = kstd::vector{}; - - for (auto const & entry : m_drivers) - { - auto const & [name, driver] = entry; - auto priority = driver->probe(inode); - if (priority) - { - candidates.push_back({*priority, driver}); - } - } - - std::ranges::stable_sort(candidates, std::ranges::greater{}, &candidate::priority); - - if (candidates.empty()) - { - return kstd::failure(vfs_errc::unsupported_filesystem); - } - - return candidates.front().driver; - } -} // namespace kernel::filesystem - -namespace kernel::tests::filesystem::driver_registry -{ - auto deinit() -> void - { - kernel::filesystem::instance.reset(); - } -} // namespace kernel::tests::filesystem::driver_registry \ No newline at end of file diff --git a/kernel/kernel/filesystem/driver_registry.hpp b/kernel/kernel/filesystem/driver_registry.hpp deleted file mode 100644 index 1a6cd47e..00000000 --- a/kernel/kernel/filesystem/driver_registry.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef TEACHOS_KERNEL_DRIVER_REGISTRY_HPP -#define TEACHOS_KERNEL_DRIVER_REGISTRY_HPP - -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace kernel::filesystem -{ - - struct driver_descriptor - { - virtual ~driver_descriptor() = default; - - //! Get the name of the driver. - [[nodiscard]] virtual auto name() const noexcept -> std::string_view = 0; - - //! Create a new instance of the driver represented by this descriptor. - [[nodiscard]] virtual auto make_instance() const -> kstd::shared_ptr = 0; - }; - - template Type> - struct driver_module - { - constexpr auto static instance = Type{}; - [[using gnu: section("filesystem_drivers"), used, visibility("hidden")]] constexpr auto static pointer{ - kstd::make_observer(&instance), - }; - }; - - struct driver_registry - { - constexpr driver_registry() noexcept = default; - - auto static init() -> void; - - auto static get() -> driver_registry &; - - //! Register a new filesystem. - //! - //! @param name The name of the filesystem. - //! @param driver The filesystem driver to register. - //! @return @c true iff. the driver was successfully added, @c false if not. - auto add(std::string_view name, kstd::shared_ptr driver) -> bool; - - //! Find a registered filesystem driver - //! - //! @param name The name of the filesystem - [[nodiscard]] auto find(std::string_view name) const -> std::optional>; - - //! Match all currently registered filesystem drivers against a given inode. - //! - //! @param inode The inode to match on, if any. - //! @return a match priority if th - [[nodiscard]] auto match(kstd::shared_ptr const & inode) const -> kstd::result>; - - private: - //! A map from filesystem names to filesystem drivers. - kstd::flat_map> m_drivers{}; - }; - -} // namespace kernel::filesystem - -#endif diff --git a/kernel/kernel/filesystem/error.cpp b/kernel/kernel/filesystem/error.cpp deleted file mode 100644 index 0d6ae01e..00000000 --- a/kernel/kernel/filesystem/error.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include - -#include - -#include - -namespace kernel::filesystem -{ - - namespace - { - struct vfs_category_t final : kstd::error_category - { - [[nodiscard]] constexpr auto name() const noexcept -> std::string_view override - { - return "vfs"; - } - - [[nodiscard]] constexpr auto message(int value) const noexcept -> std::string_view override - { - switch (static_cast(value)) - { - case vfs_errc::invalid_path: - return "invalid path"; - case vfs_errc::no_such_file_or_directory: - return "no such file or directory"; - case vfs_errc::mount_point_not_found: - return "mount point not found"; - case vfs_errc::mount_busy: - return "mount point is busy"; - case vfs_errc::has_child_mounts: - return "mount point has child mounts"; - case vfs_errc::invalid_filesystem: - return "invalid filesystem"; - case vfs_errc::unmount_failed: - return "unmount failed"; - case vfs_errc::too_many_symbolic_link_levels: - return "too many symbolic link levels"; - case vfs_errc::unsupported_filesystem: - return "unsupported filesystem"; - case vfs_errc::invalid_file_descriptor: - return "invalid file descriptor"; - case vfs_errc::invalid_inode: - return "invalid inode"; - case vfs_errc::not_a_directory: - return "not a directory"; - case vfs_errc::is_a_directory: - return "is a directory"; - case vfs_errc::read_only_file_system: - return "readonly filesystem"; - case vfs_errc::file_exists: - return "file already exists"; - case vfs_errc::no_such_device: - return "no such device"; - case vfs_errc::invalid_argument: - return "invalid argument"; - case vfs_errc::not_mounted: - return "filesystem is not mounted"; - case vfs_errc::invalid_driver_data: - return "invalid driver data"; - default: - return "unknown VFS error"; - } - } - - [[nodiscard]] constexpr auto default_error_condition(int value) const noexcept -> kstd::error_condition override - { - switch (static_cast(value)) - { - case vfs_errc::invalid_path: - case vfs_errc::invalid_filesystem: - case vfs_errc::invalid_inode: - case vfs_errc::invalid_argument: - case vfs_errc::invalid_driver_data: - return make_error_condition(kstd::errc::invalid_argument); - case vfs_errc::no_such_file_or_directory: - case vfs_errc::mount_point_not_found: - return make_error_condition(kstd::errc::no_such_file_or_directory); - case kernel::filesystem::vfs_errc::mount_busy: - case kernel::filesystem::vfs_errc::has_child_mounts: - case vfs_errc::unmount_failed: - return make_error_condition(kstd::errc::device_or_resource_busy); - case vfs_errc::too_many_symbolic_link_levels: - return make_error_condition(kstd::errc::too_many_symbolic_link_levels); - case vfs_errc::unsupported_filesystem: - return make_error_condition(kstd::errc::not_supported); - case vfs_errc::invalid_file_descriptor: - return make_error_condition(kstd::errc::bad_file_descriptor); - case vfs_errc::not_a_directory: - return make_error_condition(kstd::errc::not_a_directory); - case vfs_errc::is_a_directory: - return make_error_condition(kstd::errc::is_a_directory); - case vfs_errc::read_only_file_system: - return make_error_condition(kstd::errc::read_only_file_system); - case vfs_errc::file_exists: - return make_error_condition(kstd::errc::file_exists); - case vfs_errc::no_such_device: - return make_error_condition(kstd::errc::no_such_device); - case vfs_errc::not_mounted: - return make_error_condition(kstd::errc::not_connected); - default: - return kstd::error_condition{value, *this}; - } - } - } constexpr vfs_category_instance{}; - } // namespace - - [[nodiscard]] auto vfs_category() noexcept -> kstd::error_category const & - { - return vfs_category_instance; - } - -} // namespace kernel::filesystem diff --git a/kernel/kernel/filesystem/error.hpp b/kernel/kernel/filesystem/error.hpp deleted file mode 100644 index 6b167355..00000000 --- a/kernel/kernel/filesystem/error.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_ERROR_HPP -#define TEACHOS_KERNEL_FILESYSTEM_ERROR_HPP - -#include - -#include - -namespace kernel::filesystem -{ - - enum struct vfs_errc : int - { - invalid_path = 1, - no_such_file_or_directory, - mount_point_not_found, - mount_busy, - has_child_mounts, - invalid_filesystem, - unmount_failed, - too_many_symbolic_link_levels, - unsupported_filesystem, - invalid_file_descriptor, - invalid_inode, - not_a_directory, - is_a_directory, - read_only_file_system, - file_exists, - no_such_device, - invalid_argument, - not_mounted, - invalid_driver_data, - }; - - [[nodiscard]] auto vfs_category() noexcept -> kstd::error_category const &; - - [[nodiscard]] constexpr auto inline make_error_code(vfs_errc error) noexcept -> kstd::error_code - { - return {static_cast(error), vfs_category()}; - } - - [[nodiscard]] constexpr auto inline make_error_condition(vfs_errc error) noexcept -> kstd::error_condition - { - return {static_cast(error), vfs_category()}; - } - -} // namespace kernel::filesystem - -namespace kstd -{ - template<> - struct is_error_code_enum : std::true_type - { - }; -} // namespace kstd - -#endif diff --git a/kernel/kernel/filesystem/ext2/block_group_descriptor.hpp b/kernel/kernel/filesystem/ext2/block_group_descriptor.hpp deleted file mode 100644 index 16f6d6e2..00000000 --- a/kernel/kernel/filesystem/ext2/block_group_descriptor.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_BLOCK_GROUP_DESCRIPTOR_HPP -#define TEACHOS_KERNEL_FILESYSTEM_EXT2_BLOCK_GROUP_DESCRIPTOR_HPP - -#include -#include - -namespace kernel::filesystem::ext2 -{ - //! A block group descriptor in the ext2 filesystem. - struct [[gnu::packed]] block_group_descriptor - { - uint32_t block_bitmap; - uint32_t inode_bitmap; - uint32_t inode_table; - uint16_t free_blocks_count; - uint16_t free_inodes_count; - uint16_t used_dirs_count; - std::array padding; - std::array reserved; // NOLINT(readability-magic-numbers) - }; -} // namespace kernel::filesystem::ext2 -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/directory_iterator.cpp b/kernel/kernel/filesystem/ext2/directory_iterator.cpp deleted file mode 100644 index 7e2df1e3..00000000 --- a/kernel/kernel/filesystem/ext2/directory_iterator.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include -#include - -using namespace kstd::literals; - -namespace kernel::filesystem::ext2 -{ - - directory_iterator::directory_iterator(inode const & inode, mount_state const & state) - : directory_iterator{inode, 0_B, state} - {} - - directory_iterator::directory_iterator(inode const & inode, kstd::bytes offset, mount_state const & state) - : m_inode{&inode} - , m_state{&state} - , m_file_offset{offset} - , m_buffer{sizeof(value_type)} - { - if (!inode.is_directory()) - { - kapi::system::panic("[FS:ext2] Tried perform directory iteration on non-directory inode {}", inode.number()); - } - - if (m_file_offset >= data_size(*m_inode, *m_state)) - { - m_inode = nullptr; - return; - } - - read(); - } - - auto directory_iterator::operator*() const -> reference - { - return *reinterpret_cast(m_buffer.data()); - } - - auto directory_iterator::operator->() const -> pointer - { - return reinterpret_cast(m_buffer.data()); - } - - auto directory_iterator::operator++() -> directory_iterator & - { - if (!m_inode || m_file_offset >= data_size(*m_inode, *m_state)) - { - m_inode = nullptr; - return *this; - } - read(); - return *this; - } - - auto directory_iterator::operator++(int) -> directory_iterator - { - auto copy = *this; - ++(*this); - return copy; - } - - [[nodiscard]] auto directory_iterator::offset() const noexcept -> kstd::bytes - { - return m_file_offset; - } - - auto operator==(directory_iterator const & lhs, directory_iterator const & rhs) -> bool - { - if (lhs.m_inode == rhs.m_inode) - { - return lhs.m_inode == nullptr || lhs.m_file_offset == rhs.m_file_offset; - } - return false; - } - - auto directory_iterator::read() -> void - { - if (auto result = do_read(*m_inode, m_buffer, m_file_offset, *m_state); !result) - { - kapi::system::panic("[FS:ext2] failed to read directory entry", result.error()); - } - - auto entry = reinterpret_cast(m_buffer.data()); - - auto const remainder = entry->name_len - 1; - - if (remainder > 0) - { - m_buffer.resize(m_buffer.size() + remainder); - - if (auto result = do_read(*m_inode, m_buffer, m_file_offset, *m_state); !result) - { - kapi::system::panic("[FS:ext2] failed to read directory entry", result.error()); - } - - entry = reinterpret_cast(m_buffer.data()); - } - - m_file_offset = m_file_offset + static_cast(entry->rec_len); - } - -} // namespace kernel::filesystem::ext2 \ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/directory_iterator.hpp b/kernel/kernel/filesystem/ext2/directory_iterator.hpp deleted file mode 100644 index 0ef8d134..00000000 --- a/kernel/kernel/filesystem/ext2/directory_iterator.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_DIRECTORY_ITERATOR_HPP -#define TEACHOS_KERNEL_FILESYSTEM_EXT2_DIRECTORY_ITERATOR_HPP - -#include -#include -#include - -#include -#include -#include - -#include -#include - -namespace kernel::filesystem::ext2 -{ - - struct directory_iterator - { - using iterator_category = std::forward_iterator_tag; - using iterator_concept = std::forward_iterator_tag; - using value_type = linked_directory_entry; - using pointer = linked_directory_entry const *; - using reference = linked_directory_entry const &; - using difference_type = std::ptrdiff_t; - - constexpr directory_iterator() = default; - - directory_iterator(inode const & inode, mount_state const & state); - - directory_iterator(inode const & inode, kstd::bytes offset, mount_state const & state); - - auto operator*() const -> reference; - - auto operator->() const -> pointer; - - auto operator++() -> directory_iterator &; - - auto operator++(int) -> directory_iterator; - - [[nodiscard]] auto offset() const noexcept -> kstd::bytes; - - auto friend operator==(directory_iterator const & lhs, directory_iterator const & rhs) -> bool; - - private: - auto read() -> void; - - kstd::observer_ptr m_inode{}; - kstd::observer_ptr m_state{}; - kstd::bytes m_file_offset{}; - kstd::vector m_buffer{}; - }; - -} // namespace kernel::filesystem::ext2 - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/directory_iterator.tests.cpp b/kernel/kernel/filesystem/ext2/directory_iterator.tests.cpp deleted file mode 100644 index 2ef931d1..00000000 --- a/kernel/kernel/filesystem/ext2/directory_iterator.tests.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include -#include - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, - "Ext2 directory_iterator walks a real directory and terminates", "[filesystem][ext2][readdir]") -{ - auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; - - GIVEN("a mounted ext2 filesystem and its root directory") - { - REQUIRE(std::filesystem::exists(image_path)); - REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); - - auto boot_device = kernel::devices::storage::determine_boot_device(); - REQUIRE(boot_device != nullptr); - - auto dev_inode = kstd::make_shared(boot_device); - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - REQUIRE(mount); - - auto root = (*mount)->root_dentry()->inode(); - auto const & root_ext2_inode = static_cast(*root); - - auto mount_state = static_pointer_cast((*mount)->driver_data()); - - WHEN("iterating from begin to the default-constructed end sentinel") - { - auto names = std::vector{}; - auto guard = 0; - - for (auto it = kernel::filesystem::ext2::directory_iterator{root_ext2_inode, *mount_state}; - it != kernel::filesystem::ext2::directory_iterator{}; ++it) - { - REQUIRE(guard++ < 64); // fails loudly on a non-terminating loop rather than hanging the suite - names.emplace_back(&it->name_start, it->name_len); - } - - THEN("iteration terminates on its own and finds the expected entries") - { - REQUIRE(guard < 64); - REQUIRE(std::ranges::find(names, ".") != names.end()); - REQUIRE(std::ranges::find(names, "..") != names.end()); - REQUIRE(std::ranges::find(names, "information") != names.end()); - } - } - } -} - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, - "Ext2 directory_iterator satisfies the forward-iterator multi-pass guarantee", - "[filesystem][ext2][readdir]") -{ - auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; - - GIVEN("an iterator positioned at the first entry of the root directory") - { - REQUIRE(std::filesystem::exists(image_path)); - REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); - - auto boot_device = kernel::devices::storage::determine_boot_device(); - REQUIRE(boot_device != nullptr); - - auto dev_inode = kstd::make_shared(boot_device); - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - REQUIRE(mount); - - auto root = (*mount)->root_dentry()->inode(); - auto const & root_ext2_inode = static_cast(*root); - - auto mount_state = static_pointer_cast((*mount)->driver_data()); - - auto original = kernel::filesystem::ext2::directory_iterator{root_ext2_inode, *mount_state}; - auto const first_name = std::string{&original->name_start, original->name_len}; - - WHEN("the iterator is copied, then only the original is advanced") - { - auto copy = original; - ++original; - - THEN("the copy still refers to the first entry, unaffected by advancing the original") - { - auto const copy_name = std::string{©->name_start, copy->name_len}; - REQUIRE(copy_name == first_name); - - auto const advanced_name = std::string{&original->name_start, original->name_len}; - REQUIRE(advanced_name != first_name); - } - } - } -} - -SCENARIO("Ext2 directory_iterator's default-constructed value is a valid, comparable end sentinel", - "[filesystem][ext2][readdir]") -{ - GIVEN("two independently default-constructed iterators") - { - auto first = kernel::filesystem::ext2::directory_iterator{}; - auto second = kernel::filesystem::ext2::directory_iterator{}; - - THEN("they compare equal to each other") - { - REQUIRE(first == second); - } - } -} \ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/error.cpp b/kernel/kernel/filesystem/ext2/error.cpp deleted file mode 100644 index 73921379..00000000 --- a/kernel/kernel/filesystem/ext2/error.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include - -#include - -#include - -#include - -namespace kernel::filesystem::ext2 -{ - - namespace - { - struct ext2_category_t final : kstd::error_category - { - [[nodiscard]] constexpr auto name() const noexcept -> std::string_view override - { - return "ext2"; - } - - [[nodiscard]] constexpr auto message(int value) const noexcept -> std::string_view override - { - switch (static_cast(value)) - { - case ext2_errc::invalid_magic_number: - return "invalid filesystem magic"; - case ext2_errc::invalid_root_inode: - return "invalid root inode"; - case ext2_errc::invalid_block_group_index: - return "block group index out of bounds"; - case ext2_errc::invalid_block_index: - return "block index out of bounds"; - case ext2_errc::invalid_block_number: - return "block number out of bounds"; - case ext2_errc::failed_to_read_superblock: - return "failed to read superblock"; - case ext2_errc::failed_to_read_block_group_descriptors: - return "failed to read block group descriptors"; - case ext2_errc::not_enough_free_blocks: - return "not enough free blocks"; - case ext2_errc::not_enough_inodes: - return "not enough inodes"; - case ext2_errc::unsupported_features_present: - return "unsupported features present"; - case ext2_errc::name_too_long: - return "name too long"; - default: - return "unknown ext2 error"; - }; - } - - [[nodiscard]] constexpr auto equivalent(int code, kstd::error_condition const & condition) const noexcept - -> bool override - { - switch (static_cast(code)) - { - case ext2_errc::invalid_magic_number: - case ext2_errc::invalid_root_inode: - case ext2_errc::name_too_long: - if (condition.category() == kernel::filesystem::vfs_category()) - { - return condition.value() == static_cast(kernel::filesystem::vfs_errc::invalid_filesystem); - } - else if (condition.category() == kstd::generic_category()) - { - return condition.value() == static_cast(kstd::errc::invalid_argument); - } - break; - case ext2_errc::invalid_block_group_index: - case ext2_errc::invalid_block_index: - case ext2_errc::invalid_block_number: - case ext2_errc::failed_to_read_superblock: - case ext2_errc::failed_to_read_block_group_descriptors: - if (condition.category() == kstd::generic_category()) - { - return condition.value() == static_cast(kstd::errc::io_error); - } - break; - case ext2_errc::not_enough_free_blocks: - case ext2_errc::not_enough_inodes: - if (condition.category() == kstd::generic_category()) - { - return condition.value() == static_cast(kstd::errc::no_space_on_device); - } - break; - case ext2_errc::unsupported_features_present: - if (condition.category() == kstd::generic_category()) - { - return condition.value() == static_cast(kstd::errc::not_supported); - } - break; - } - return kstd::error_category::equivalent(code, condition); - } - } constexpr inline ext2_category_instance{}; - } // namespace - - [[nodiscard]] auto ext2_category() noexcept -> kstd::error_category const & - { - return ext2_category_instance; - } - -} // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/error.hpp b/kernel/kernel/filesystem/ext2/error.hpp deleted file mode 100644 index edd0d017..00000000 --- a/kernel/kernel/filesystem/ext2/error.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_ERROR_HPP -#define TEACHOS_KERNEL_FILESYSTEM_EXT2_ERROR_HPP - -#include - -#include - -#include - -namespace kernel::filesystem::ext2 -{ - - enum struct ext2_errc : int - { - invalid_magic_number = 1, - invalid_root_inode, - invalid_block_group_index, - invalid_block_index, - invalid_block_number, - failed_to_read_superblock, - failed_to_read_block_group_descriptors, - not_enough_free_blocks, - not_enough_inodes, - unsupported_features_present, - name_too_long, - }; - - [[nodiscard]] auto ext2_category() noexcept -> kstd::error_category const &; - - [[nodiscard]] constexpr auto inline make_error_code(ext2_errc error) noexcept -> kstd::error_code - { - return {static_cast(error), ext2_category()}; - } - -} // namespace kernel::filesystem::ext2 - -namespace kstd -{ - template<> - struct is_error_code_enum : std::true_type - { - }; -} // namespace kstd - -#endif diff --git a/kernel/kernel/filesystem/ext2/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp deleted file mode 100644 index 0ddbca79..00000000 --- a/kernel/kernel/filesystem/ext2/filesystem.cpp +++ /dev/null @@ -1,1103 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace kstd::units_literals; - -namespace kernel::filesystem::ext2 -{ - - //! Check that this driver class is stateless. - static_assert(sizeof(kernel::filesystem::filesystem) == sizeof(kernel::filesystem::ext2::filesystem)); - - //! Hidden implementation details. - namespace - { - struct indirect_level - { - uint32_t slot_index; - size_t capacity; - }; - - //! Check if the given bit has the bit at the given index set. - //! - //! @warning This function will panic if the bit index is out-of-bounds for the given bitmap. - //! - //! @param bitmap The bitmap to check. - //! @param index The index of the bit to check. - [[nodiscard]] constexpr auto bitmap_is_set(kstd::vector const & bitmap, size_t index) -> bool - { - auto const byte_index = index / 8; - auto const bit_index = index % 8; - return (bitmap.at(byte_index) & static_cast(1u << bit_index)) != std::byte{}; - } - - //! Set the bit at the given index in the given bitmap. - //! - //! @warning This function will panic if the bit index is out-of-bounds for the given bitmap. - //! - //! @param bitmap The bitmap to manipulate. - //! @param index The index of the bit to set. - constexpr auto bitmap_set(kstd::vector & bitmap, size_t index) -> void - { - auto const byte_index = index / 8; - auto const bit_index = index % 8; - bitmap.at(byte_index) |= static_cast(1u << bit_index); - } - - //! Get the number of block numbers storable on each level - //! - //! @param level The level to check. - //! @param block_size The size of a single block. - [[nodiscard]] constexpr auto block_numbers(std::size_t level, kstd::bytes block_size) noexcept -> std::size_t - { - if (level == 0) - { - return constants::direct_block_count; - } - - auto const numbers_per_block = block_size / kstd::size_of(); - auto result = numbers_per_block; - for (auto current_level = level; current_level > 1; --current_level) - { - result *= numbers_per_block; - } - - return result; - } - - //! Get mapping of block indirection levels to possible number of block on that level. - //! - //! @param block_size The size of a single block. - [[nodiscard]] constexpr auto indirect_levels(kstd::bytes block_size) -> std::array - { - return { - {{constants::singly_indirect_block_index, block_numbers(1, block_size)}, - {constants::doubly_indirect_block_index, block_numbers(2, block_size)}, - {constants::triply_indirect_block_index, block_numbers(3, block_size)}} - }; - } - - //! Extract the file type from the an ext2 file mode. - //! - //! @param mode The mode to extract the type from. - //! @return The file type encoded in the mode. - constexpr auto to_filetype(uint16_t mode) -> kapi::filesystem::file_type - { - switch (mode & constants::mode_mask) - { - case constants::mode_regular: - return kapi::filesystem::file_type::regular; - case constants::mode_directory: - return kapi::filesystem::file_type::directory; - case constants::mode_block_device: - return kapi::filesystem::file_type::block_device; - case constants::mode_character_device: - return kapi::filesystem::file_type::character_device; - case constants::mode_symbolic_link: - return kapi::filesystem::file_type::symbolic_link; - case constants::mode_fifo: - return kapi::filesystem::file_type::fifo; - case constants::mode_socket: - return kapi::filesystem::file_type::socket; - default: - return kapi::filesystem::file_type{0}; - } - } - - //! Combine a file type and an ext2 mode. - //! - //! @param type The type to encode. - //! @param mode The original ext2 file mode. - constexpr auto combine(kapi::filesystem::file_type type, uint16_t mode) -> uint16_t - { - if (mode & constants::mode_mask) - { - return mode; - } - - return mode | [=] { - switch (type) - { - case kapi::filesystem::file_type::directory: - return constants::mode_directory; - case kapi::filesystem::file_type::regular: - return constants::mode_regular; - case kapi::filesystem::file_type::block_device: - return constants::mode_block_device; - case kapi::filesystem::file_type::character_device: - return constants::mode_character_device; - case kapi::filesystem::file_type::symbolic_link: - return constants::mode_symbolic_link; - case kapi::filesystem::file_type::fifo: - return constants::mode_fifo; - case kapi::filesystem::file_type::socket: - return constants::mode_socket; - default: - kapi::system::panic("[EXT2] Not implemented."); - } - }(); - } - - //! Convert a file type to a directory entry file type. - //! - //! @param type The file type to convert. - //! @return The directory file type. - constexpr auto to_directory_file_type(kapi::filesystem::file_type type) -> std::uint8_t - { - switch (type) - { - case kapi::filesystem::file_type::regular: - return 1; - case kapi::filesystem::file_type::directory: - return 2; - case kapi::filesystem::file_type::character_device: - return 3; - case kapi::filesystem::file_type::block_device: - return 4; - case kapi::filesystem::file_type::fifo: - return 5; - case kapi::filesystem::file_type::socket: - return 6; - case kapi::filesystem::file_type::symbolic_link: - return 7; - default: - return 0; - } - } - - //! Write a new directory entry into the given buffer. - //! - //! @param buffer The buffer to write to. - //! @param offset The offset inside to the buffer. - //! @param target The inode the entry targets. - //! @param name The name for the entry. - //! @param type The filet type of the entry. - //! @return A pointer to the written entry on success, an error otherwise. - auto write_directory_entry_to_buffer(std::span buffer, kstd::bytes offset, inode & target, - std::string_view name, kapi::filesystem::file_type type) - -> kstd::result - { - auto const name_length = static_cast(name.size()); - auto const record_size = (sizeof(linked_directory_entry) - 1 + name_length + 3u) & ~3u; - if (buffer.size() - offset.value < record_size) - { - // TODO: improve error code. - return kstd::failure(vfs_errc::invalid_argument); - } - - auto * entry = reinterpret_cast(buffer.data() + offset); - entry->inode = target.number(); - entry->rec_len = record_size; - entry->name_len = name_length; - entry->file_type = to_directory_file_type(type); - kstd::libc::memcpy(&entry->name_start, name.data(), name_length); - return kstd::success(entry); - } - - //! Get the revision level of the filesystem. - //! - //! @param state The driver state to operate on. - //! @return The revision level. - [[nodiscard]] constexpr auto revision_level(mount_state const & state) -> uint32_t - { - return state.superblock.rev_level; - } - - //! Get the size of an inode in the filesystem. - //! - //! @param state The driver state to operate on. - //! @return The size of an inode in bytes. - [[nodiscard]] auto inode_size(mount_state const & state) -> kstd::bytes - { - return kstd::bytes(revision_level(state) == constants::good_old_revision ? 128 : state.superblock.inode_size); - } - - //! Read a block from the backing inode into the provided buffer. - //! - //! @param block_number The number of the block to read. - //! @param buffer The buffer to read the block data into. - //! @param state The state to operate on. - //! @return The number of bytes read. - [[nodiscard]] auto read_block(uint32_t block_number, std::span buffer, mount_state const & state) - -> kstd::result - { - if (buffer.size() < block_size(state).value) - { - return kstd::failure(vfs_errc::invalid_argument); - } - - auto const block_offset = static_cast(block_number) * block_size(state); - return state.backing_inode->read(buffer, block_offset); - } - - //! Write a block of data from the provided buffer to the backing inode. - //! - //! @param block_number The number of the block to write. - //! @param buffer The buffer containing the data to write. - //! @param state The state to operate on. - //! @return The number of bytes written. - [[nodiscard]] auto write_block(uint32_t block_number, std::span buffer, mount_state & state) - -> kstd::result - { - if (buffer.size() < block_size(state).value) - { - return kstd::failure(vfs_errc::invalid_argument); - } - - auto const block_offset = static_cast(block_number) * block_size(state); - return state.backing_inode->write(buffer, block_offset); - } - - //! Allocate a single inode in the file system. - //! - //! @param state The driver state to operate on. - //! @param batch The active write batch. - //! @return An inode number on success, an error otherwise. - auto allocate_inode(mount_state & state, write_batch & batch) -> kstd::result - { - auto & [superblock, block_group_descriptors, _, __] = state; - - if (block_group_descriptors.empty() || superblock.free_inodes_count == 0) - { - return kstd::failure(ext2_errc::not_enough_inodes); - } - - for (auto block_group_descriptor_index = 0uz; block_group_descriptor_index < block_group_descriptors.size(); - ++block_group_descriptor_index) - { - auto & block_group_descriptor = block_group_descriptors.at(block_group_descriptor_index); - if (block_group_descriptor.free_inodes_count == 0) - { - continue; - } - - auto inode_bitmap = kstd::vector{block_size(state).value}; - if (auto read_result = read_block(block_group_descriptor.inode_bitmap, inode_bitmap, state); !read_result) - { - return kstd::failure(read_result.error()); - } - - for (auto i = 0uz; i < static_cast(superblock.inodes_per_group); ++i) - { - if (!bitmap_is_set(inode_bitmap, i)) - { - bitmap_set(inode_bitmap, i); - block_group_descriptor.free_inodes_count--; - superblock.free_inodes_count--; - - if (auto write_result = write_block(block_group_descriptor.inode_bitmap, inode_bitmap, state); - !write_result) - { - return kstd::failure(write_result.error()); - } - - batch.mark_group(block_group_descriptor_index); - - return block_group_descriptor_index * superblock.inodes_per_group + i + 1; - } - } - } - - return kstd::failure(ext2_errc::not_enough_inodes); - } - - //! Add a new directory entry to a given directory. - //! - //! @param directory The directory to add the entry to. - //! @param name The name for the entry. - //! @param child The inode number of the child. - //! @param mode The file mode of the entry. - //! @param state The driver state to operate on. - //! @param batch The active write batch. - //! @return Nothing on success, an error code otherwise. - auto add_directory_entry(inode & directory, std::string_view name, inode & child, std::uint16_t mode, - mount_state & state, write_batch & batch) -> kstd::result - { - auto const last_block_index = block_count(directory, state) - 1; - auto const global_block = inode_block_number(last_block_index, directory, state); - - if (!global_block) - { - return kstd::failure(global_block.error()); - } - - auto buffer = kstd::vector{block_size(state).value}; - if (auto result = read_block(*global_block, buffer, state); !result) - { - return kstd::failure(result.error()); - } - - // TODO handle "gaps" in directory entries (e.g., after deletions) in the current implementation, we only - // add new entries at the end of the last block, but we could also reuse space from deleted entries - auto offset = 0_B; - while (true) - { - auto const * entry = reinterpret_cast(buffer.data() + offset); - if (offset + kstd::bytes{entry->rec_len} >= block_size(state)) - { - break; - } - offset += kstd::bytes{entry->rec_len}; - } - - auto const file_type = to_filetype(mode); - auto const name_len = static_cast(name.size()); - auto const needed_rec_len = static_cast((8u + name_len + 3u) & ~3u); - - auto * last_entry = reinterpret_cast(buffer.data() + offset); - auto const last_entry_actual_len = static_cast((8u + last_entry->name_len + 3u) & ~3u); - - if (kstd::bytes{last_entry->rec_len} - last_entry_actual_len >= needed_rec_len) - { - last_entry->rec_len = last_entry_actual_len.value; - auto entry_offset = offset + last_entry_actual_len; - auto entry = write_directory_entry_to_buffer(buffer, entry_offset, child, name, file_type); - if (!entry) - { - return kstd::failure(entry.error()); - } - - auto remainder = block_size(state) - entry_offset; - (*entry)->rec_len = remainder.value; - - return global_block.and_then([&](auto block_number) { return write_block(block_number, buffer, state); }) - .transform([](auto) {}); - } - else - { - if (!directory.append_blocks(1, batch)) - { - return kstd::failure(ext2_errc::not_enough_free_blocks); - } - - auto const new_block_index = block_count(directory, state) - 1; - auto const new_global_block = inode_block_number(new_block_index, directory, state); - if (!new_global_block) - { - return kstd::failure(new_global_block.error()); - } - - kstd::libc::memset(buffer.data(), 0, block_size(state).value); - auto entry = write_directory_entry_to_buffer(buffer, 0_B, child, name, file_type); - if (!entry) - { - return kstd::failure(entry.error()); - } - - auto remainder = block_size(state); - (*entry)->rec_len = remainder.value; - - if (auto result = - new_global_block.and_then([&](auto block_number) { return write_block(block_number, buffer, state); }); - !result) - { - return kstd::failure(result.error()); - } - } - - auto size = block_count(directory, state) * block_size(state); - directory.data().size = size.value; - batch.mark_inode(directory); - return kstd::success(); - } - - //! Initialize a new directory inode. - //! - //! This function initializes an inode to be an empty directory. On success, the inode will be a directory with only - //! two children, "." and "..". - //! - //! @param directory The inode to initialize. - //! @param parent The parent of this newly initialized directory. - //! @param state The driver state to operate on. - //! @param batch The active write batch. - //! @return Nothing on success, an error otherwise. - auto init_directory(inode & directory, inode & parent, mount_state & state, write_batch & batch) - -> kstd::result - { - if (!directory.append_blocks(1, batch)) - { - return kstd::failure(ext2_errc::not_enough_free_blocks); - } - - auto const global_block = inode_block_number(0, directory, state); - if (!global_block) - { - return kstd::failure(global_block.error()); - } - - auto buffer = kstd::vector{block_size(state).value}; - auto dot = write_directory_entry_to_buffer(buffer, 0_B, directory, ".", kapi::filesystem::file_type::directory); - - if (!dot) - { - return kstd::failure(dot.error()); - } - - auto dot_dot_offset = kstd::bytes{(*dot)->rec_len}; - auto dot_dot = - write_directory_entry_to_buffer(buffer, dot_dot_offset, parent, "..", kapi::filesystem::file_type::directory); - if (!dot_dot) - { - return kstd::failure(dot_dot.error()); - } - auto remainder = block_size(state) - dot_dot_offset; - (*dot_dot)->rec_len = remainder.value; - - // '..' inside the new dir counts as an extra hard link to the parent - auto & parent_inode_data = parent.data(); - parent_inode_data.links_count++; - - batch.mark_inode(parent); - - return global_block.and_then([&](auto number) { return write_block(number, buffer, state); }).transform([](auto) { - }); - } - - //! Calculate the offset of the inode with a given number in the filesystem. - //! - //! @param number The number of the inode. - //! @param state The driver state to operate on. - //! @return The offset to the filesystem start of the inode. - [[nodiscard]] auto calculate_inode_offset(std::uint32_t number, mount_state const & state) - -> kstd::result - { - auto const inodes_per_group = state.superblock.inodes_per_group; - auto const block_group_index = (number - 1) / inodes_per_group; - auto const inode_index_within_group = (number - 1) % inodes_per_group; - - if (block_group_index >= state.block_group_descriptors.size()) - { - return kstd::failure(ext2_errc::invalid_block_group_index); - } - - auto const & block_group_descriptor = state.block_group_descriptors.at(block_group_index); - auto const inode_table_start_block = block_group_descriptor.inode_table; - auto const inode_table_offset = inode_table_start_block * block_size(state); - auto const inode_offset = inode_table_offset + inode_index_within_group * inode_size(state); - - return inode_offset; - } - - //! Read the inode with the given number from the filesystem. - //! - //! @param number The number of the inode to read. - //! @param state The driver state to operate on. - //! @return The read inode on success, an error otherwise. - [[nodiscard]] auto read_inode(std::uint32_t number, mount_state const & state) - -> kstd::result> - { - if (auto inode_offset = calculate_inode_offset(number, state)) - { - auto new_inode_data = inode_data{}; - - if (auto read_result = state.backing_inode->read(kstd::raw_bytes(new_inode_data), *inode_offset); !read_result) - { - return kstd::failure(read_result.error()); - } - else - { - return kstd::make_shared(number, new_inode_data); - } - } - else - { - return kstd::failure(inode_offset.error()); - } - } - - //! Read the indirect block number from the specified block at the specified index. - //! - //! @param block The number of the indirect block to read from. - //! @param index The index into the indirect block to read from. - //! @param state The driver state to operate on. - //! @return The block number at the given index in the given indirect block on success, an error otherwise. - [[nodiscard]] auto read_block_number_at_index(std::uint32_t block, std::size_t index, mount_state const & state) - -> kstd::result - { - uint32_t block_number_buffer = 0; - - auto const block_offset = block * block_size(state); - auto const number_offset = block_offset + index * kstd::size_of(); - - if (auto read_result = state.backing_inode->read(kstd::raw_bytes(block_number_buffer), number_offset); - !read_result) - { - return kstd::failure(read_result.error()); - } - - return block_number_buffer; - } - - //! Get the offset of the Block Group Descriptor table. - //! - //! @param state The driver state to operate on. - //! @return The filesystem offset of the block group descriptor table. - [[nodiscard]] auto block_group_descriptor_table_offset(mount_state const & state) -> kstd::bytes - { - return block_size(state) == 1024_B ? 2 * block_size(state) : block_size(state); - } - - //! The set of "incompatible" Extended Filesystem features supported by this driver. - constexpr auto supported_incompatible_features = incompatible_features::file_types_in_directory_entries; - - //! The set of "read-only" Extended Filesystem features supported by this driver. - constexpr auto supported_ro_compatible_features = read_only_compatible_features::sparse_superblock_copies | // - read_only_compatible_features::large_file_support; - - //! A set of permissions allowing all access to everyone. - constexpr auto no_access_restrictions = std::uint16_t{0x01FF}; - } // namespace - - auto filesystem::probe(inode_ptr const & backing_inode) const -> kstd::result - { - auto superblock = ext2::superblock{}; - auto const bytes = kstd::raw_bytes(superblock); - auto const read_result = backing_inode->read(bytes, constants::superblock_offset); - - if (!read_result) - { - return kstd::failure(read_result.error()); - } - - if (superblock.magic != constants::magic_number) - { - return kstd::failure(ext2_errc::invalid_magic_number); - } - - if (std::to_underlying(superblock.feature_incompat & ~supported_incompatible_features) || - std::to_underlying(superblock.feature_ro_compat & ~supported_ro_compatible_features)) - { - return kstd::failure(ext2_errc::unsupported_features_present); - } - - return 0; - } - - auto filesystem::mount(inode_ptr const & backing_inode) -> kstd::result - { - auto const mount_state = kstd::make_shared(); - auto & [superblock, block_group_descriptors, backing, _] = *mount_state; - - backing = backing_inode; - - auto const superblock_bytes = kstd::raw_bytes(superblock); - if (auto read_result = backing->read(superblock_bytes, constants::superblock_offset); !read_result) - { - return kstd::failure(read_result.error()); - } - - if (superblock.magic != constants::magic_number) - { - return kstd::failure(ext2_errc::invalid_magic_number); - } - - auto const blocks_per_group = superblock.blocks_per_group; - auto const num_block_groups = (superblock.blocks_count + blocks_per_group - 1) / blocks_per_group; - - block_group_descriptors = kstd::vector(num_block_groups); - auto const block_group_descriptors_bytes = kstd::raw_bytes(block_group_descriptors); - if (auto read_result = - backing->read(block_group_descriptors_bytes, block_group_descriptor_table_offset(*mount_state)); - !read_result) - { - return kstd::failure(read_result.error()); - } - - auto const root = read_inode(constants::root_inode_number, *mount_state); - - if (!root) - { - return kstd::failure(root.error()); - } - - if (!(*root)->is_directory()) - { - return kstd::failure(ext2_errc::invalid_root_inode); - } - - return mount_result{*root, mount_state}; - } - - auto filesystem::lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const - -> kstd::result - { - auto const directory = static_pointer_cast(parent); - if (!directory) - { - return kstd::failure(vfs_errc::invalid_inode); - } - - if (!directory->is_directory()) - { - return kstd::failure(vfs_errc::not_a_directory); - } - - auto const mount_state = static_pointer_cast(driver_data); - if (!mount_state) - { - return kstd::failure(vfs_errc::not_mounted); - } - - auto guard = kstd::lock_guard{mount_state->lock}; - - for (auto it = directory_iterator{*directory, *mount_state}; it != directory_iterator{}; ++it) - { - if (it->inode == 0) - { - continue; - } - - if (it->name() == name) - { - return read_inode(it->inode, *mount_state); - } - } - - return kstd::failure(vfs_errc::no_such_file_or_directory); - } - - auto filesystem::create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, - driver_data_ptr driver_data, std::optional raw_device) - -> kstd::result> - { - auto const ext2_parent = static_pointer_cast(parent); - if (!ext2_parent) - { - return kstd::failure(vfs_errc::invalid_argument); - } - - if (!ext2_parent->is_directory()) - { - return kstd::failure(vfs_errc::not_a_directory); - } - - auto const is_device = kapi::filesystem::is_device(type); - if (is_device && !raw_device) - { - return kstd::failure(vfs_errc::invalid_argument); - } - - auto const mount_state = static_pointer_cast(driver_data); - if (!mount_state) - { - return kstd::failure(vfs_errc::invalid_driver_data); - } - - auto guard = kstd::lock_guard{mount_state->lock}; - auto batch = write_batch{*mount_state}; - - auto maybe_inode_number = allocate_inode(*mount_state, batch); - if (!maybe_inode_number) - { - return kstd::failure(maybe_inode_number.error()); - } - - auto inode_number = maybe_inode_number.value(); - - auto data = inode_data{}; - - // TODO: use correct access rights - data.mode = combine(type, no_access_restrictions); - data.size = 0; - data.dir_acl = 0; - data.links_count = 1; - data.blocks = 0; - data.block.fill(0); - - if (is_device) - { - data.block[0] = static_cast(raw_device->minor) | // - (static_cast(raw_device->major) << 8); - } - - auto created = kstd::make_shared(inode_number, data); - created->set_owning_mount(ext2_parent->owning_mount().lock()); - - // TODO: preallocate blocks depending on type and m_superblock.prealloc_dir_blocks or m_superblock.prealloc_blocks; - if (auto result = add_directory_entry(*ext2_parent, name, *created, data.mode, *mount_state, batch); !result) - { - return kstd::failure(result.error()); - } - - if (created->is_directory()) - { - // TODO increment used_dirs_count in block group descriptor - if (auto result = init_directory(*created, *ext2_parent, *mount_state, batch); !result) - { - return kstd::failure(result.error()); - } - set_data_size(*created, block_size(*mount_state), *mount_state); - } - - batch.mark_inode(*created); - - return created; - } - - //! @name Free Functions - //! @{ - - auto block_size(mount_state const & state) -> kstd::bytes - { - return kstd::bytes{constants::base_block_size.value << state.superblock.log_block_size}; - } - - auto block_count(inode const & inode, mount_state const & state) -> uint32_t - { - return inode.data().blocks / (2 << state.superblock.log_block_size); - } - - auto data_size(inode const & inode, mount_state const & state) -> kstd::bytes - { - uint64_t size = inode.data().size; - - if (revision_level(state) > constants::good_old_revision && inode.is_regular()) - { - size |= static_cast(inode.data().dir_acl) << 32; - } - - return kstd::bytes{size}; - } - - auto set_data_size(inode & inode, kstd::bytes new_size, mount_state const & state) -> void - { - if (revision_level(state) > constants::good_old_revision && inode.is_regular()) - { - inode.data().dir_acl = static_cast(new_size.value >> 32); - } - inode.data().size = static_cast(new_size.value); - } - - auto do_read(inode const & inode, std::span buffer, kstd::bytes offset, mount_state const & state) - -> kstd::result - { - auto const max_readable = data_size(inode, state) - offset; - auto const requested_size = std::min(kstd::bytes{buffer.size()}, max_readable); - - auto const block_size = ext2::block_size(state); - auto block_index = offset / block_size; - auto in_block_offset = offset % block_size; - - auto bytes_read = 0_B; - - while (bytes_read < requested_size) - { - auto const block_number = inode_block_number(block_index, inode, state); - if (!block_number) - { - break; - } - - auto const bytes_to_read = std::min(requested_size - bytes_read, block_size - in_block_offset); - if (block_number == 0) - { - kstd::libc::memset(buffer.data() + bytes_read, 0, bytes_to_read.value); - bytes_read += bytes_to_read; - } - else - { - auto const block_start_offset = block_number.value() * block_size; - auto const read_offset = block_start_offset + in_block_offset; - - auto const read_result = - state.backing_inode->read(buffer.subspan(bytes_read.value, bytes_to_read.value), read_offset); - if (!read_result) - { - return kstd::failure(read_result.error()); - } - - bytes_read += read_result.value(); - } - - block_index++; - in_block_offset = 0_B; - } - - return bytes_read; - } - - auto allocate_blocks(size_t count, mount_state & state, write_batch & batch) -> kstd::result> - { - auto & [superblock, block_group_descriptors, backing_inode, _] = state; - - if (block_group_descriptors.empty() || count > superblock.free_blocks_count) - { - return kstd::failure(ext2_errc::not_enough_free_blocks); - } - - struct pending_group - { - std::size_t index; - std::size_t claimed; - kstd::vector bitmap; - }; - - auto pending = kstd::vector{}; - auto allocated_blocks = kstd::vector{}; - - for (auto group_index = 0uz; group_index < block_group_descriptors.size() && allocated_blocks.size() < count; - ++group_index) - { - auto & descriptor = block_group_descriptors.at(group_index); - if (descriptor.free_blocks_count == 0) - { - continue; - } - - auto block_bitmap = kstd::vector{block_size(state).value}; - if (auto read_result = read_block(descriptor.block_bitmap, block_bitmap, state); !read_result) - { - return kstd::failure(read_result.error()); - } - - auto claimed = 0uz; - for (auto i = 0uz; i < static_cast(superblock.blocks_per_group) && allocated_blocks.size() < count; ++i) - { - if (!bitmap_is_set(block_bitmap, i)) - { - bitmap_set(block_bitmap, i); - ++claimed; - allocated_blocks.push_back(i + group_index * superblock.blocks_per_group); - } - } - - if (claimed > 0) - { - pending.push_back({group_index, claimed, std::move(block_bitmap)}); - } - } - - if (allocated_blocks.size() != count) - { - return kstd::failure(ext2_errc::not_enough_free_blocks); - } - - for (auto & group : pending) - { - auto & descriptor = block_group_descriptors.at(group.index); - descriptor.free_blocks_count -= group.claimed; - superblock.free_blocks_count -= group.claimed; - - if (auto result = write_block(descriptor.block_bitmap, group.bitmap, state); !result) - { - return kstd::failure(result.error()); - } - batch.mark_group(group.index); - } - - return allocated_blocks; - } - - auto update_inode_block_count(inode_data & data, uint32_t delta, mount_state & state) -> void - { - data.blocks += delta * (2 << state.superblock.log_block_size); - } - - auto write_inode(uint32_t inode_number, inode_data const & data, mount_state & state) -> kstd::result - { - if (auto inode_offset = calculate_inode_offset(inode_number, state)) - { - return state.backing_inode->write(kstd::raw_bytes(data), *inode_offset).transform([](auto) {}); - } - else - { - return kstd::failure(inode_offset.error()); - } - } - - auto inode_block_number(size_t index, inode const & inode, mount_state const & state) -> kstd::result - { - auto const & block_array = inode.data().block; - - if (index < constants::direct_block_count) - { - return block_array.at(index); - } - - index -= constants::direct_block_count; - auto const block_size = ext2::block_size(state); - auto const numbers_per_block = block_numbers(1, block_size); - for (auto const & level : indirect_levels(block_size)) - { - auto const capacity = level.capacity; - if (index >= capacity) - { - index -= capacity; - continue; - } - - auto block_number = block_array[level.slot_index]; - if (block_number == 0) - { - return 0; - } - - for (auto stride = capacity / numbers_per_block;; stride /= numbers_per_block) - { - auto const idx = index / stride; - index %= stride; - - if (auto read_result = read_block_number_at_index(block_number, idx, state); !read_result) - { - return kstd::failure(read_result.error()); - } - else - { - block_number = read_result.value(); - } - - if (block_number == 0) - { - return 0; - } - - if (stride == 1) - { - break; - } - } - - return block_number; - } - - return kstd::failure(ext2_errc::invalid_block_index); - } - - auto write_global_block_number_to_inode_block_index(size_t inode_block_index, inode_data & data, - uint32_t global_block_number, mount_state & state, - write_batch & batch) -> kstd::result - { - if (inode_block_index < constants::direct_block_count) - { - data.block[inode_block_index] = global_block_number; - return kstd::success(); - } - inode_block_index -= constants::direct_block_count; - - auto const block_size = ext2::block_size(state); - auto const numbers_per_block = block_numbers(1, block_size); - for (auto const & level : indirect_levels(block_size)) - { - if (inode_block_index >= level.capacity) - { - inode_block_index -= level.capacity; - continue; - } - - auto & backing_inode = state.backing_inode; - auto block_number = data.block[level.slot_index]; - auto parent_byte_offset = 0_B; - bool is_root = true; - - for (auto stride = level.capacity / numbers_per_block;; stride /= numbers_per_block) - { - auto const idx = inode_block_index / stride; - inode_block_index %= stride; - - if (idx == 0 && inode_block_index == 0) - { - auto allocated = allocate_blocks(1, state, batch); - if (!allocated) - { - return kstd::failure(allocated.error()); - } - - block_number = allocated->front(); - - if (is_root) - { - data.block[level.slot_index] = block_number; - } - else - { - if (auto write_result = backing_inode->write(kstd::raw_bytes(block_number), parent_byte_offset); - !write_result) - { - return kstd::failure(write_result.error()); - } - } - } - - auto const byte_offset = block_number * block_size + idx * kstd::size_of(); - - if (stride == 1) - { - if (auto write_result = backing_inode->write(kstd::raw_bytes(global_block_number), byte_offset); - !write_result) - { - return kstd::failure(write_result.error()); - } - return kstd::success(); - } - - parent_byte_offset = byte_offset; - is_root = false; - if (auto read_result = read_block_number_at_index(block_number, idx, state); !read_result) - { - return kstd::failure(read_result.error()); - } - else - { - block_number = read_result.value(); - } - } - } - - return kstd::success(); - } - - auto write_block_group_descriptor(block_group_descriptor const & block_group_descriptor, - size_t block_group_descriptor_index, mount_state const & state) - -> kstd::result - { - // TODO update all block group descriptors - return state.backing_inode - ->write(kstd::raw_bytes(block_group_descriptor), - block_group_descriptor_table_offset(state) + - block_group_descriptor_index * kstd::size_of(block_group_descriptor)) - .transform([](auto) {}); - } - - auto write_superblock(mount_state & state) -> kstd::result - { - // TODO update all superblock copies - return state.backing_inode->write(kstd::raw_bytes(state.superblock), constants::superblock_offset) - .transform([](auto) {}); - } - - //! @} - -} // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp deleted file mode 100644 index 85ce4624..00000000 --- a/kernel/kernel/filesystem/ext2/filesystem.hpp +++ /dev/null @@ -1,164 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_FILESYSTEM_HPP -#define TEACHOS_KERNEL_FILESYSTEM_EXT2_FILESYSTEM_HPP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -namespace kernel::filesystem::ext2 -{ - //! Constants related to the ext2 filesystem. - namespace constants - { - constexpr kstd::bytes inline base_block_size = kstd::bytes{1024}; - constexpr kstd::bytes inline superblock_offset = base_block_size; - constexpr uint16_t inline magic_number = 0xEF53; - - constexpr uint32_t inline good_old_revision = 0; - constexpr uint32_t inline dynamic_revision = 1; - - 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; - constexpr uint16_t inline mode_symbolic_link = 0xA000; - constexpr uint16_t inline mode_block_device = 0x6000; - constexpr uint16_t inline mode_character_device = 0x2000; - constexpr uint16_t inline mode_fifo = 0x1000; - constexpr uint16_t inline mode_socket = 0xC000; - - } // namespace constants - - //! The Second Extended Filesystem (ext2) - struct filesystem final : kernel::filesystem::filesystem - { - //! @name Driver Interface Implementation - //! @{ - - [[nodiscard]] auto probe(inode_ptr const & backing_inode) const -> kstd::result override; - - auto mount(inode_ptr const & backing_inode) -> kstd::result override; - - [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const - -> kstd::result override; - - [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, - driver_data_ptr driver_data, - std::optional raw_device = std::nullopt) - -> kstd::result override; - - //! @} - }; - - //! Get the size of a block in the filesystem. - //! - //! @param state The driver state to operate on. - //! @return The size of a block in bytes. - [[nodiscard]] auto block_size(mount_state const & state) -> kstd::bytes; - - //! Get the number of blocks allocated to an inode. - //! - //! @param inode The inode whose block count to query. - //! @param state The driver state to operate on. - //! @return The number of blocks allocated to the inode. - [[nodiscard]] auto block_count(inode const & inode, mount_state const & state) -> uint32_t; - - //! Determine the size of an inode's data on disk. - //! - //! @param inode The inode whose data size to determine. - //! @param state The driver state to operate on. - //! @return The size of the data referenced by the inode. - [[nodiscard]] auto data_size(inode const & inode, mount_state const & state) -> kstd::bytes; - - //! Set the size of an inode's data on disk. - //! - //! @param inode The inode whose data size to set. - //! @param new_size The size to set. - //! @param state The driver state to operate on. - auto set_data_size(inode & inode, kstd::bytes new_size, mount_state const & state) -> void; - - //! Perform the actual read of the requested data. - //! - //! @param inode The inode whose data to read. - //! @param buffer The buffer to read into. - //! @param offset The offset inside the inode's data to start at. - //! @param state The driver state to operate on. - //! @return The number of bytes read on success, an error otherwise. - [[nodiscard]] auto do_read(inode const & inode, std::span buffer, kstd::bytes offset, - mount_state const & state) -> kstd::result; - - //! Allocate a specified number of blocks. - //! - //! @param count The number of blocks to allocate. - //! @param state The state to operate on. - //! @param batch The active write batch. - //! @return A vector of the allocated block numbers. - auto allocate_blocks(size_t count, mount_state & state, write_batch & batch) -> kstd::result>; - - //! Update the number of blocks allocated to an inode. - //! - //! @param data The inode data. - //! @param state The state to operate on. - //! @param delta The change in the number of blocks. - auto update_inode_block_count(inode_data & data, uint32_t delta, mount_state & state) -> void; - - //! Write an inode to the backing inode. - //! - //! @param inode_number The number of the inode to write. - //! @param state The state to operate on. - //! @param data The inode data to write. - auto write_inode(uint32_t inode_number, inode_data const & data, mount_state & state) -> kstd::result; - - //! Get the block number associated with the nth block of an inode. - //! - //! @param index The index of the block within the inode. - //! @param inode The inode. - //! @param state The state to operate on. - //! @return The global block number on success, an error otherwise. - [[nodiscard]] auto inode_block_number(size_t index, inode const & inode, mount_state const & state) - -> kstd::result; - - //! Write a global block number to an inode block index. - //! - //! @param block_index The index of the block within the inode. - //! @param data The inode data. - //! @param global_block_number The global block number to write. - //! @param state The state to operate on. - //! @param batch The active write batch. - auto write_global_block_number_to_inode_block_index(size_t block_index, inode_data & data, - uint32_t global_block_number, mount_state & state, - write_batch & batch) -> kstd::result; - - [[nodiscard]] auto write_block_group_descriptor(block_group_descriptor const & block_group_descriptor, - size_t block_group_descriptor_index, mount_state const & state) - -> kstd::result; - [[nodiscard]] auto write_superblock(mount_state & state) -> kstd::result; -} // namespace kernel::filesystem::ext2 - -#endif diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp deleted file mode 100644 index 8565db4d..00000000 --- a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp +++ /dev/null @@ -1,360 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -using namespace kstd::units_literals; - -// NOLINTBEGIN(readability-magic-numbers) - -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::determine_boot_device(); - REQUIRE(boot_device); - - auto dev_inode = kstd::make_shared(boot_device); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - auto driver_data = (*mount)->driver_data(); - REQUIRE(mount); - - THEN("the root inode is available and is a directory") - { - REQUIRE(root); - REQUIRE((root)->is_directory()); - } - - THEN("lookup resolves known entries from the image") - { - auto information = fs->lookup(root, "information", driver_data); - REQUIRE(information); - REQUIRE(information.value()->is_directory()); - (*information)->set_owning_mount(*mount); - - auto info_1 = fs->lookup(*information, "info_1.txt", driver_data); - REQUIRE(info_1); - REQUIRE(info_1.value()->is_regular()); - (*info_1)->set_owning_mount(*mount); - } - - THEN("lookup returns null for invalid inputs") - { - REQUIRE(!fs->lookup(nullptr, "information", driver_data)); - - auto information = fs->lookup(root, "information", driver_data); - REQUIRE(information); - (*information)->set_owning_mount(*mount); - auto info_1 = fs->lookup(*information, "info_1.txt", driver_data); - REQUIRE(info_1); - (*info_1)->set_owning_mount(*mount); - - REQUIRE(!fs->lookup(*info_1, "anything", driver_data)); - REQUIRE(!fs->lookup(root, "does_not_exist", driver_data)); - } - } -} - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, - "Ext2 filesystem create new inodes 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::determine_boot_device(); - REQUIRE(boot_device); - - auto dev_inode = kstd::make_shared(boot_device); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - auto driver_data = (*mount)->driver_data(); - REQUIRE(mount); - - THEN("a file can be created") - { - auto lookup_result = fs->lookup(root, "blub", driver_data); - REQUIRE(!lookup_result); - - auto new_inode = fs->create_inode(root, "blub", kapi::filesystem::file_type::regular, driver_data); - REQUIRE(new_inode); - REQUIRE(new_inode.value()->is_regular()); - (*new_inode)->set_owning_mount(*mount); - - lookup_result = fs->lookup(root, "blub", driver_data); - REQUIRE(lookup_result); - (*lookup_result)->set_owning_mount(*mount); - } - - THEN("a directory can be created") - { - auto lookup_result = fs->lookup(root, "blub", driver_data); - REQUIRE(!lookup_result); - - auto new_directory = fs->create_inode(root, "blub", kapi::filesystem::file_type::directory, driver_data); - REQUIRE(new_directory); - REQUIRE(new_directory.value()->is_directory()); - - lookup_result = fs->lookup(root, "blub", driver_data); - REQUIRE(lookup_result); - } - - THEN("a directory and a file within can be created") - { - auto new_directory = fs->create_inode(root, "blub", kapi::filesystem::file_type::directory, driver_data); - REQUIRE(new_directory); - (*new_directory)->set_owning_mount(*mount); - - auto new_file = - fs->create_inode(new_directory.value(), "blub_file", kapi::filesystem::file_type::regular, driver_data); - REQUIRE(new_file); - (*new_file)->set_owning_mount(*mount); - - auto lookup_result = fs->lookup(new_directory.value(), "blub_file", driver_data); - REQUIRE(lookup_result); - REQUIRE(lookup_result.value()->is_regular()); - } - - THEN("a file can be created and written to") - { - auto new_inode = fs->create_inode(root, "blub", kapi::filesystem::file_type::regular, driver_data); - REQUIRE(new_inode); - - auto buffer = - std::vector{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'}, std::byte{'o'}, std::byte{' '}, - std::byte{'W'}, std::byte{'o'}, std::byte{'r'}, std::byte{'l'}, std::byte{'d'}, std::byte{'!'}}; - auto bytes_written = new_inode.value()->write(buffer, 0_B); - REQUIRE(bytes_written == kstd::bytes{buffer.size()}); - - auto read_buffer = std::vector(5, std::byte{0xAA}); - auto bytes_read = new_inode.value()->read(read_buffer, 0_B); - REQUIRE(bytes_read == kstd::bytes{read_buffer.size()}); - - std::string_view result{reinterpret_cast(read_buffer.data()), static_cast(*bytes_read)}; - REQUIRE(result == "Hello"); - } - } -} - -SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem]") -{ - auto const block_size = 1024_B; - GIVEN("a block device that does not contain an ext2 superblock") - { - auto device = kstd::make_shared("mock", block_size, 2 * block_size); - REQUIRE(device); - - auto dev_inode = kstd::make_shared(device); - - auto fs = kstd::make_shared(); - - THEN("mount fails with invalid_magic_number") - { - REQUIRE(fs->mount(dev_inode).error() == kernel::filesystem::ext2::ext2_errc::invalid_magic_number); - } - } -} - -SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesystem][ext2][filesystem]") -{ - auto const block_size = 1024_B; - - GIVEN("a minimally valid ext2 layout with configured indirect block tables") - { - auto device = kstd::make_shared("mock", block_size, 128 * block_size); - REQUIRE(device); - - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); - - auto dev_inode = kstd::make_shared(device); - - auto fs = kstd::make_shared(); - auto mount = fs->mount(dev_inode); - REQUIRE(mount); - auto mount_state = static_pointer_cast(mount->second); - REQUIRE(mount_state); - - 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(block_size / kstd::size_of()); - auto const singly_start = static_cast(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; - - auto inode = kernel::filesystem::ext2::inode{42, inode_data}; - - THEN("mapping resolves direct, singly, doubly and triply indirect indexes") - { - REQUIRE(inode_block_number(0, inode, *mount_state) == 7); - REQUIRE(inode_block_number(singly_start, inode, *mount_state) == 31); - REQUIRE(inode_block_number(doubly_start, inode, *mount_state) == 42); - REQUIRE(inode_block_number(triply_start, inode, *mount_state) == 53); - } - - THEN("mapping returns error for out-of-range indexes") - { - auto const beyond_triply = triply_start + numbers_per_block * numbers_per_block * numbers_per_block; - REQUIRE(!inode_block_number(beyond_triply, inode, *mount_state)); - } - } -} - -SCENARIO("Ext2 block writing includes direct and all indirect levels", "[filesystem][ext2][filesystem]") -{ - auto const block_size = 1024_B; - - GIVEN("a mounted ext2 filesystem with free blocks available for indirect table allocation") - { - auto device = kstd::make_shared("mock", block_size, 256 * block_size); - REQUIRE(device); - - auto superblock = kernel::filesystem::ext2::superblock{}; - superblock.magic = kernel::filesystem::ext2::constants::magic_number; - superblock.log_block_size = 0; - superblock.blocks_count = 256; - superblock.free_blocks_count = 128; - superblock.blocks_per_group = 256; - superblock.inodes_per_group = 32; - superblock.rev_level = kernel::filesystem::ext2::constants::dynamic_revision; - superblock.inode_size = 128; - - auto block_group_descriptor = kernel::filesystem::ext2::block_group_descriptor{}; - block_group_descriptor.block_bitmap = 255; - block_group_descriptor.inode_table = 5; - block_group_descriptor.free_blocks_count = 128; - - auto block_bitmap = std::vector(block_size.value, std::byte{0x00}); - block_bitmap[0] = std::byte{0b1111'1111}; - block_bitmap[1] = std::byte{0b1111'1111}; - - auto dev_inode = kstd::make_shared(device); - REQUIRE(dev_inode->write(block_bitmap, block_group_descriptor.block_bitmap * block_size)); - - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device, superblock, block_group_descriptor); - - auto fs = kstd::make_shared(); - auto mount = fs->mount(dev_inode); - REQUIRE(mount); - auto mount_state = static_pointer_cast(mount->second); - REQUIRE(mount_state); - - auto read_u32 = [&](kstd::bytes offset) -> uint32_t { - auto value = uint32_t{0}; - std::memcpy(&value, device->data.data() + offset, sizeof(value)); - return value; - }; - - auto inode_data = kernel::filesystem::ext2::inode_data{}; - - auto const numbers_per_block = static_cast(block_size / kstd::size_of()); - auto const singly_start = static_cast(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("writing resolves direct, singly, doubly and triply indirect indexes") - { - constexpr auto direct_global_block_number = 101; - constexpr auto singly_global_block_number = 202; - constexpr auto doubly_global_block_number = 303; - constexpr auto triply_global_block_number = 404; - - auto batch = kernel::filesystem::ext2::write_batch{*mount_state}; - - REQUIRE(write_global_block_number_to_inode_block_index(0, inode_data, direct_global_block_number, *mount_state, - batch)); - REQUIRE(inode_data.block[0] == direct_global_block_number); - - REQUIRE(write_global_block_number_to_inode_block_index(singly_start, inode_data, singly_global_block_number, - *mount_state, batch)); - REQUIRE(inode_data.block[12] == 16); - REQUIRE(read_u32(static_cast(inode_data.block[12]) * block_size.value) == - singly_global_block_number); - - REQUIRE(write_global_block_number_to_inode_block_index(singly_start + 1, inode_data, - singly_global_block_number + 1, *mount_state, batch)); - REQUIRE(read_u32(static_cast(inode_data.block[12]) * block_size.value + - 1 * kstd::size_of()) == singly_global_block_number + 1); - - REQUIRE(write_global_block_number_to_inode_block_index(doubly_start, inode_data, doubly_global_block_number, - *mount_state, batch)); - REQUIRE(inode_data.block[13] == 17); - auto const doubly_leaf_table = read_u32(static_cast(inode_data.block[13]) * block_size); - REQUIRE(doubly_leaf_table == 18); - REQUIRE(read_u32(static_cast(doubly_leaf_table) * block_size) == doubly_global_block_number); - - REQUIRE(write_global_block_number_to_inode_block_index(triply_start, inode_data, triply_global_block_number, - *mount_state, batch)); - REQUIRE(inode_data.block[14] == 19); - auto const triply_middle_table = read_u32(static_cast(inode_data.block[14]) * block_size); - REQUIRE(triply_middle_table == 20); - auto const triply_leaf_table = read_u32(static_cast(triply_middle_table) * block_size); - REQUIRE(triply_leaf_table == 21); - REQUIRE(read_u32(static_cast(triply_leaf_table) * block_size) == triply_global_block_number); - - auto inode = kernel::filesystem::ext2::inode{42, inode_data}; - - REQUIRE(inode_block_number(0, inode, *mount_state) == direct_global_block_number); - REQUIRE(inode_block_number(singly_start, inode, *mount_state) == singly_global_block_number); - REQUIRE(inode_block_number(doubly_start, inode, *mount_state) == doubly_global_block_number); - REQUIRE(inode_block_number(triply_start, inode, *mount_state) == triply_global_block_number); - } - } -} - -// NOLINTEND(readability-magic-numbers) \ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp deleted file mode 100644 index c57ced93..00000000 --- a/kernel/kernel/filesystem/ext2/inode.cpp +++ /dev/null @@ -1,361 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -using namespace kstd::units_literals; - -namespace kernel::filesystem::ext2 -{ - - namespace - { - constexpr auto to_file_type(std::uint8_t entry_type) noexcept -> kapi::filesystem::file_type - { - switch (entry_type) - { - case 1: - return kapi::filesystem::file_type::regular; - case 2: - return kapi::filesystem::file_type::directory; - case 3: - return kapi::filesystem::file_type::character_device; - case 4: - return kapi::filesystem::file_type::block_device; - case 5: - return kapi::filesystem::file_type::fifo; - case 6: - return kapi::filesystem::file_type::socket; - case 7: - return kapi::filesystem::file_type::symbolic_link; - default: - return kapi::filesystem::file_type{}; - } - } - } // namespace - - inode::inode(uint32_t inode_number, inode_data const & data) - : m_inode_number(inode_number) - , m_data(data) - {} - - auto inode::read(std::span buffer, kstd::bytes offset) const -> kstd::result - { - if (is_symbolic_link() && this->size() <= kstd::bytes{sizeof(m_data.block)}) - { - auto const max_readable = this->size() - offset; - auto const requested_size = std::min(kstd::bytes{buffer.size()}, max_readable); - auto inline_target = reinterpret_cast(m_data.block.data()); - kstd::libc::memcpy(buffer.data(), inline_target + offset, requested_size.value); - return requested_size; - } - - auto state = get_driver_data(); - if (!state) - { - return kstd::failure(state.error()); - } - - auto backing_inode = (*state)->backing_inode; - if (!(*state)->backing_inode) - { - return kstd::failure(vfs_errc::not_mounted); - } - - auto guard = kstd::lock_guard{(*state)->lock}; - return ext2::do_read(*this, buffer, offset, **state); - } - - auto inode::write(std::span buffer, kstd::bytes offset) -> kstd::result - { - if (!is_regular()) - { - kapi::system::panic("[EXT2] ext2::inode write called on inode that is not of type regular"); - } - - // TODO check maximum file size of filesystem - // TODO handle sparse files - - auto state = get_driver_data(); - if (!state) - { - return kstd::failure(state.error()); - } - - auto backing_inode = (*state)->backing_inode; - if (!(*state)->backing_inode) - { - return kstd::failure(vfs_errc::not_mounted); - } - - auto guard = kstd::lock_guard{(*state)->lock}; - auto batch = write_batch{**state}; - - auto const new_inode_size = std::max(ext2::data_size(*this, **state), offset + kstd::bytes{buffer.size()}); - - auto block_size = ext2::block_size(**state); - auto const current_block_count = ext2::block_count(*this, **state); - auto const max_new_inode_size_without_new_blocks = current_block_count * block_size; - - if (new_inode_size > max_new_inode_size_without_new_blocks) - { - auto const additional_blocks_needed = - (new_inode_size - max_new_inode_size_without_new_blocks + block_size - 1_B) / block_size; - - if (!append_blocks(additional_blocks_needed, batch)) - { - return kstd::failure(ext2_errc::not_enough_free_blocks); - } - } - - auto block_index = offset / block_size; - auto in_block_offset = offset % block_size; - - auto bytes_written = 0_B; - - while (bytes_written < kstd::bytes{buffer.size()}) - { - auto const block_number = ext2::inode_block_number(block_index, *this, **state); - // TODO BA-FS26 if blocknumber == 0 --> handle sparse file - if (!block_number) - { - return kstd::failure(block_number.error()); - } - - auto const bytes_to_write = std::min(kstd::bytes{buffer.size()} - bytes_written, block_size - in_block_offset); - - auto const block_start_offset = block_number.value() * block_size; - auto const write_offset = block_start_offset + in_block_offset; - - auto const write_result = - backing_inode->write(buffer.subspan(bytes_written.value, bytes_to_write.value), write_offset); - if (!write_result) - { - return kstd::failure(write_result.error()); - } - - bytes_written += write_result.value(); - - block_index++; - in_block_offset = 0_B; - } - - ext2::set_data_size(*this, std::max(ext2::data_size(*this, **state), offset + bytes_written), **state); - batch.mark_inode(*this); - return bytes_written; - } - - auto inode::is_block_device() const -> bool - { - return (m_data.mode & constants::mode_mask) == constants::mode_block_device; - } - - auto inode::is_character_device() const -> bool - { - return (m_data.mode & constants::mode_mask) == constants::mode_character_device; - } - - auto inode::is_directory() const -> bool - { - return (m_data.mode & constants::mode_mask) == constants::mode_directory; - } - - auto inode::is_regular() const -> bool - { - return (m_data.mode & constants::mode_mask) == constants::mode_regular; - } - - auto inode::is_symbolic_link() const -> bool - { - return (m_data.mode & constants::mode_mask) == constants::mode_symbolic_link; - } - - auto inode::raw_device() const -> std::optional - { - if (!is_device()) - { - return std::nullopt; - } - - auto const raw_device_number = m_data.block[0]; - - return kapi::filesystem::device_number{ - .major = static_cast((raw_device_number >> 8) & 0xff), - .minor = static_cast(raw_device_number & 0xff), - }; - } - - auto inode::status() const -> kstd::result - { - auto result = kernel::filesystem::inode::status(); - if (!result) - { - return result; - } - - result->mode = m_data.mode; - result->size = size(); - result->inode_number = m_inode_number; - result->link_count = m_data.links_count; - - return result; - } - - auto inode::read_directory(directory_listing_cursor position, std::span entries) const - -> kstd::result> - { - if (!is_directory()) - { - return kstd::failure(vfs_errc::not_a_directory); - } - - auto state = get_driver_data(); - if (!state) - { - return kstd::failure(state.error()); - } - - auto guard = kstd::lock_guard{(*state)->lock}; - - auto count = 0uz; - auto it = directory_iterator{*this, kstd::bytes{position.value}, **state}; - - for (; it != directory_iterator{} && count < entries.size(); ++it) - { - if (it->inode == 0) - { - continue; - } - - entries[count++] = directory_listing_entry{ - .name = kstd::string{it->name()}, - .type = to_file_type(it->file_type), - .inode_number = it->inode, - }; - - if (count == entries.size()) - { - break; - } - } - - return std::pair{count, directory_listing_cursor{it.offset().value}}; - } - - auto inode::append_blocks(size_t count, write_batch & batch) -> bool - { - auto state = get_driver_data(); - if (!state) - { - return false; - } - - auto new_blocks = ext2::allocate_blocks(count, **state, batch); - if (!new_blocks) - { - return false; - } - - for (auto i = 0uz; i < new_blocks->size(); ++i) - { - auto const block_index = ext2::block_count(*this, **state) + i; - auto const global_block_number = new_blocks->at(i); - - if (auto write_result = ext2::write_global_block_number_to_inode_block_index(block_index, m_data, - global_block_number, **state, batch); - !write_result) - { - return false; - } - } - - ext2::update_inode_block_count(m_data, count, **state); - return true; - } - - auto inode::set_size(kstd::bytes new_size) -> void - { - auto driver_data = get_driver_data(); - if (!driver_data) - { - return; - } - - auto state = static_pointer_cast(*driver_data); - auto guard = kstd::lock_guard{state->lock}; - return ext2::set_data_size(*this, new_size, *state); - } - - [[nodiscard]] auto inode::data() -> inode_data & - { - return m_data; - } - - [[nodiscard]] auto inode::data() const -> inode_data const & - { - return m_data; - } - - auto inode::size() const -> kstd::bytes - { - auto state = get_driver_data(); - if (!state) - { - return 0_B; - } - - auto guard = kstd::lock_guard{(*state)->lock}; - return ext2::data_size(*this, **state); - } - - [[nodiscard]] auto inode::number() const -> uint32_t - { - return m_inode_number; - } - - auto inode::get_driver_data() -> kstd::result> - { - if (auto mount = owning_mount().lock()) - { - return static_pointer_cast(mount->driver_data()); - } - - return kstd::failure(vfs_errc::not_mounted); - } - - auto inode::get_driver_data() const -> kstd::result> - { - if (auto mount = owning_mount().lock()) - { - return static_pointer_cast(mount->driver_data()); - } - - return kstd::failure(vfs_errc::not_mounted); - } - -} // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/inode.hpp b/kernel/kernel/filesystem/ext2/inode.hpp deleted file mode 100644 index 77164e28..00000000 --- a/kernel/kernel/filesystem/ext2/inode.hpp +++ /dev/null @@ -1,161 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_INODE_HPP -#define TEACHOS_KERNEL_FILESYSTEM_EXT2_INODE_HPP - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -namespace kernel::filesystem::ext2 -{ - struct filesystem; - - //! The data for a single ext2 inode. - struct [[gnu::packed]] inode_data - { - uint16_t mode; - uint16_t uid; - uint32_t size; - uint32_t atime; - uint32_t ctime; - uint32_t mtime; - uint32_t dtime; - uint16_t gid; - uint16_t links_count; - uint32_t blocks; - uint32_t flags; - uint32_t osd1; - std::array block; // NOLINT(readability-magic-numbers) - uint32_t generation; - uint32_t file_acl; - uint32_t dir_acl; - uint32_t faddr; - std::array osd2; // NOLINT(readability-magic-numbers) - }; - - struct inode : kernel::filesystem::inode - { - //! @name Construction/Destruction - //! @{ - - //! Create an ext2 inode associated with the given filesystem. - //! - //! @param inode_number The inode number on disk. - //! @param data The data associated with this inode, read from the disk. - explicit inode(uint32_t inode_number, inode_data const & data); - - //! @} - - //! @name I/O Operations - //! @{ - - [[nodiscard]] auto read(std::span buffer, kstd::bytes offset) const - -> kstd::result override; - - auto write(std::span buffer, kstd::bytes offset) -> kstd::result override; - - //! @} - - //! @name Property Checking - //! @{ - - [[nodiscard]] auto is_block_device() const -> bool override; - - [[nodiscard]] auto is_character_device() const -> bool override; - - [[nodiscard]] auto is_directory() const -> bool override; - - [[nodiscard]] auto is_regular() const -> bool override; - - [[nodiscard]] auto is_symbolic_link() const -> bool override; - - //! @} - - //! @name POSIX Information Access - //! @{ - - [[nodiscard]] auto raw_device() const -> std::optional override; - - [[nodiscard]] auto status() const -> kstd::result override; - - [[nodiscard]] auto read_directory(directory_listing_cursor position, - std::span entries) const - -> kstd::result> override; - - //! @} - - //! @name Property Manipulation - //! @{ - - //! Append the specified number of blocks to this inode. - //! - //! @param count The number of blocks to append. - //! @param batch The active write batch. - //! @return true if the blocks were successfully appended, false otherwise. - auto append_blocks(size_t count, write_batch & batch) -> bool; - - //! Set the size of the file represented by this inode. - //! - //! @param new_size The new size of the file in bytes. - auto set_size(kstd::bytes new_size) -> void; - - //! @} - - //! @name Ext2 Information Queries - //! @{ - - //! Get the data associated with this inode. - //! - //! @return A reference to the inode data. - [[nodiscard]] auto data() -> inode_data &; - - //! Get the data associated with this inode. - //! - //! @return A const reference to the inode data. - [[nodiscard]] auto data() const -> inode_data const &; - - //! Get the size of the file represented by this inode. - //! - //! @return The size of the file in bytes. - [[nodiscard]] auto size() const -> kstd::bytes; - - //! @brief Get the inode number of this inode. - //! - //! @return The inode number. - [[nodiscard]] auto number() const -> uint32_t; - - //! @} - - private: - //! Get the filesystem driver data associated with this inode. - //! - //! @return The filesystem driver data on success, and error otherwise. - [[nodiscard]] auto get_driver_data() -> kstd::result>; - - //! Get the filesystem driver data associated with this inode. - //! - //! @return The filesystem driver data on success, and error otherwise. - [[nodiscard]] auto get_driver_data() const -> kstd::result>; - - //! The inode number on disk. - uint32_t m_inode_number{}; - //! The inode data. - inode_data m_data{}; - }; -} // namespace kernel::filesystem::ext2 - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/inode.tests.cpp b/kernel/kernel/filesystem/ext2/inode.tests.cpp deleted file mode 100644 index 6b6525bc..00000000 --- a/kernel/kernel/filesystem/ext2/inode.tests.cpp +++ /dev/null @@ -1,874 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace kstd::units_literals; - -// NOLINTBEGIN(readability-magic-numbers) - -namespace -{ - auto write_entry(std::vector & block, std::size_t offset, uint32_t inode_number, std::string_view name, - uint8_t file_type, kstd::bytes block_size, bool extend_to_block_end) -> std::size_t - { - auto const header_and_name = 8uz + name.size(); - auto const rounded = (header_and_name + 3uz) & ~3uz; - auto const rec_len = extend_to_block_end ? static_cast(block_size.value) - offset : rounded; - - auto * entry = reinterpret_cast(block.data() + offset); - entry->inode = inode_number; - entry->rec_len = static_cast(rec_len); - entry->name_len = static_cast(name.size()); - entry->file_type = file_type; - std::copy(name.begin(), name.end(), &entry->name_start); - - return rounded; - } -} // namespace - -SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]") -{ - GIVEN("an ext2 filesystem") - { - auto fs = kernel::filesystem::ext2::filesystem{}; - auto data = kernel::filesystem::ext2::inode_data{}; - - THEN("the inode is initialized with regular file mode in data and has the kind regular") - { - data.mode = kernel::filesystem::ext2::constants::mode_regular; - auto inode = kernel::filesystem::ext2::inode(42, data); - - REQUIRE(inode.is_regular()); - REQUIRE_FALSE(inode.is_directory()); - REQUIRE_FALSE(inode.is_device()); - REQUIRE_FALSE(inode.is_symbolic_link()); - } - - THEN("the inode is initialized with directory mode in data and has the kind directory") - { - data.mode = kernel::filesystem::ext2::constants::mode_directory; - auto inode = kernel::filesystem::ext2::inode(42, data); - - REQUIRE_FALSE(inode.is_regular()); - REQUIRE(inode.is_directory()); - REQUIRE_FALSE(inode.is_device()); - REQUIRE_FALSE(inode.is_symbolic_link()); - } - - THEN("the inode is initialized with symbolic link mode in data and has the kind symbolic link") - { - data.mode = kernel::filesystem::ext2::constants::mode_symbolic_link; - auto inode = kernel::filesystem::ext2::inode(42, data); - - REQUIRE_FALSE(inode.is_regular()); - REQUIRE_FALSE(inode.is_directory()); - REQUIRE_FALSE(inode.is_device()); - REQUIRE(inode.is_symbolic_link()); - } - - THEN("the inode is initialized with zero mode in data and has no specific kind") - { - data.mode = 0; - auto inode = kernel::filesystem::ext2::inode(42, data); - - REQUIRE_FALSE(inode.is_regular()); - REQUIRE_FALSE(inode.is_directory()); - REQUIRE_FALSE(inode.is_device()); - REQUIRE_FALSE(inode.is_symbolic_link()); - } - } -} - -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::determine_boot_device(); - REQUIRE(boot_device != nullptr); - - auto dev_inode = kstd::make_shared(boot_device); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - auto driver_data = (*mount)->driver_data(); - REQUIRE(mount); - - auto information = fs->lookup(root, "information", driver_data); - REQUIRE(information); - (*information)->set_owning_mount(*mount); - - auto file = fs->lookup(*information, "info_1.txt", driver_data); - REQUIRE(file); - REQUIRE(file.value()->is_regular()); - - (*file)->set_owning_mount(*mount); - - THEN("reading from offset zero returns expected file prefix") - { - auto buffer = kstd::vector(6); - auto const bytes_read = file.value()->read(buffer, 0_B); - - REQUIRE(bytes_read == 6_B); - - auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read->value}; - REQUIRE(text == "info_1"); - } - - THEN("reading with an offset returns the expected byte") - { - auto buffer = kstd::vector(1); - auto const bytes_read = file.value()->read(buffer, 5_B); - - REQUIRE(bytes_read == 1_B); - REQUIRE(static_cast(buffer[0]) == '1'); - } - } -} - -SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesystem][ext2][inode]") -{ - auto const block_size = 1024_B; - GIVEN("an ext2 inode with only direct mapped data blocks") - { - auto device = kstd::make_shared("mock", block_size, 64 * block_size); - REQUIRE(device != nullptr); - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); - - auto dev_inode = kstd::make_shared(device); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - REQUIRE(mount); - - auto data = kernel::filesystem::ext2::inode_data{}; - data.block[0] = 30; - data.block[1] = 0; - data.block[2] = 31; - data.size = block_size.value * 3; - - kernel::tests::filesystem::ext2::write_bytes(*device, 30 * block_size, "Hello", 5_B); - kernel::tests::filesystem::ext2::write_bytes(*device, 31 * block_size, "World!", 6_B); - - auto inode = kernel::filesystem::ext2::inode{42, data}; - inode.set_owning_mount(*mount); - - auto buffer = kstd::vector(data.size, std::byte{0xAB}); - - THEN("correct number of bytes are read and holes are returned as zeros") - { - auto const bytes_read = inode.read(buffer, 0_B); - REQUIRE(bytes_read == kstd::bytes{data.size}); - - auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read->value}; - REQUIRE(text.substr(0, 5) == "Hello"); - REQUIRE(std::ranges::all_of(text.substr(5, block_size.value - 5), [](char c) { return c == '\0'; })); - REQUIRE(text.substr(2 * block_size.value, 6) == "World!"); - REQUIRE(std::ranges::all_of(text.substr(2 * block_size.value + 6, 3 * block_size.value), - [](char c) { return c == '\0'; })); - } - } - - GIVEN("an ext2 indode with file holes in singly indirect blocks") - { - auto device = kstd::make_shared("mock", block_size, 64 * block_size); - REQUIRE(device != nullptr); - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); - - auto dev_inode = kstd::make_shared(device); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - REQUIRE(mount); - - auto data = kernel::filesystem::ext2::inode_data{}; - data.block[0] = 30; - data.block[12] = 31; - data.size = block_size.value * 15; - - kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size, 50); - kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size + 4_B, 0); - kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size + 8_B, 51); - - kernel::tests::filesystem::ext2::write_bytes(*device, 30 * block_size, "Hello", 5_B); - kernel::tests::filesystem::ext2::write_bytes(*device, 50 * block_size, "Blub", 4_B); - kernel::tests::filesystem::ext2::write_bytes(*device, 51 * block_size, "World!", 6_B); - - auto inode = kernel::filesystem::ext2::inode{42, data}; - inode.set_owning_mount(*mount); - - auto buffer = kstd::vector(data.size, std::byte{0xAB}); - - THEN("correct number of bytes are read and holes are returned as zeros") - { - auto const bytes_read = inode.read(buffer, 0_B); - REQUIRE(bytes_read == kstd::bytes{data.size}); - - auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read->value}; - REQUIRE(text.substr(0, 5) == "Hello"); - REQUIRE(std::ranges::all_of(text.substr(5, 12 * block_size.value - 5), [](char c) { return c == '\0'; })); - REQUIRE(text.substr(12 * block_size.value, 4) == "Blub"); - REQUIRE(std::ranges::all_of(text.substr(12 * block_size.value + 4, 2 * block_size.value - 4), - [](char c) { return c == '\0'; })); - REQUIRE(text.substr(14 * block_size.value, 6) == "World!"); - REQUIRE(std::ranges::all_of(text.substr(14 * block_size.value + 6, 1 * block_size.value - 6), - [](char c) { return c == '\0'; })); - } - } - - GIVEN("an ext2 inode with zero singly indirect block pointer") - { - auto device = kstd::make_shared("mock", block_size, 64 * block_size); - REQUIRE(device != nullptr); - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); - - auto dev_inode = kstd::make_shared(device); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - REQUIRE(mount); - - auto data = kernel::filesystem::ext2::inode_data{}; - data.block[12] = 0; - data.size = block_size.value * 15; - - auto inode = kernel::filesystem::ext2::inode{42, data}; - inode.set_owning_mount(*mount); - - auto buffer = kstd::vector(block_size.value * 15, std::byte{0xAB}); - - THEN("all direct blocks are zero when singly indirect block pointer is zero") - { - auto const bytes_read = inode.read(buffer, 0_B); - REQUIRE(bytes_read == kstd::bytes{buffer.size()}); - REQUIRE(std::ranges::all_of(buffer, [](std::byte c) { return c == std::byte{0x00}; })); - } - } -} - -SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]") -{ - auto const block_size = 1024_B; - GIVEN("an ext2 inode with two direct blocks and a block size of 1024 bytes") - { - auto device = kstd::make_shared("mock", block_size, 64 * block_size); - REQUIRE(device != nullptr); - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); - - auto dev_inode = kstd::make_shared(device); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - REQUIRE(mount); - - auto inode_data = kernel::filesystem::ext2::inode_data{}; - inode_data.size = block_size.value * 2; - inode_data.block[0] = 20; - kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size - 6_B, "Hello ", 6_B); - inode_data.block[1] = 21; - kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size, "World!", 6_B); - auto inode = kernel::filesystem::ext2::inode{42, inode_data}; - inode.set_owning_mount(*mount); - - auto buffer = kstd::vector(12, std::byte{0x00}); - - THEN("reading across the block boundary returns the combined content") - { - auto const bytes_read = inode.read(buffer, block_size - 6_B); - REQUIRE(bytes_read == kstd::bytes{12}); - - auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read->value}; - REQUIRE(text == "Hello World!"); - } - } -} - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 inode writes to real image", - "[filesystem][ext2][inode]") -{ - 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::determine_boot_device(); - REQUIRE(boot_device != nullptr); - - auto dev_inode = kstd::make_shared(boot_device); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - auto driver_data = (*mount)->driver_data(); - REQUIRE(mount); - - auto information = fs->lookup(root, "information", driver_data); - REQUIRE(information); - (*information)->set_owning_mount(*mount); - - auto file = fs->lookup(*information, "info_1.txt", driver_data); - REQUIRE(file); - REQUIRE(file.value()->is_regular()); - (*file)->set_owning_mount(*mount); - - auto mount_state = static_pointer_cast(driver_data); - REQUIRE(mount_state); - - THEN("writing to the inode updates backing device and the inode size") - { - auto write_buffer = kstd::vector{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'}, - std::byte{'o'}, std::byte{' '}, std::byte{'W'}, std::byte{'o'}, - std::byte{'r'}, std::byte{'l'}, std::byte{'d'}, std::byte{'!'}}; - - auto const bytes_written = file.value()->write(write_buffer, 0_B); - REQUIRE(bytes_written == kstd::bytes{12}); - - auto read_buffer = kstd::vector(42, std::byte{0x00}); - auto const bytes_read = file.value()->read(read_buffer, 0_B); - REQUIRE(bytes_read == kstd::bytes{12}); - - auto const text = std::string_view{reinterpret_cast(read_buffer.data()), bytes_read->value}; - REQUIRE(text == "Hello World!"); - - auto inode_data = static_cast(file.value().get())->data(); - REQUIRE(inode_data.size == bytes_read->value); - } - - THEN("writing allocates new blocks and updates the inode size") - { - auto inode = static_pointer_cast(file.value()); - - auto const block_size = kernel::filesystem::ext2::block_size(*mount_state); - auto const expected_allocated_blocks = 32 * block_count(*inode, *mount_state); - - auto write_buffer = kstd::vector(block_size.value * expected_allocated_blocks, std::byte{'A'}); - - auto const bytes_written = inode->write(write_buffer, 0_B); - REQUIRE(bytes_written == block_size * expected_allocated_blocks); - - auto read_buffer = kstd::vector(block_size.value * expected_allocated_blocks, std::byte{0x00}); - auto const bytes_read = inode->read(read_buffer, 0_B); - REQUIRE(bytes_read == block_size * expected_allocated_blocks); - REQUIRE(std::ranges::all_of(read_buffer, [](std::byte c) { return c == std::byte{'A'}; })); - - REQUIRE(inode->size().value == write_buffer.size()); - REQUIRE(block_count(*inode, *mount_state) == expected_allocated_blocks); - } - - THEN("writing to a an inode of type directory panics") - { - auto write_buffer = kstd::vector(12, std::byte{0xAA}); - REQUIRE_THROWS_AS(information.value()->write(write_buffer, 0_B), kernel::tests::cpu::halt); - } - } -} - -SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]") -{ - auto const block_size = 1024_B; - - auto device = kstd::make_shared("mock", block_size, 128 * block_size); - REQUIRE(device != nullptr); - - 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.inode_size = 128; - superblock.rev_level = 1; - superblock.free_blocks_count = 64 - 15; - - auto block_group_descriptor = kernel::filesystem::ext2::block_group_descriptor{}; - block_group_descriptor.inode_table = 5; - block_group_descriptor.free_blocks_count = 64 - 15; - block_group_descriptor.block_bitmap = 10; - - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device, superblock, block_group_descriptor); - - auto dev_inode = kstd::make_shared(device); - - auto block_bitmap = std::vector(block_size.value, std::byte{0x00}); - block_bitmap[0] = std::byte{0b1111'1111}; - block_bitmap[1] = std::byte{0b1111'1111}; - block_bitmap[2] = std::byte{0b0011'0000}; - - REQUIRE(dev_inode->write(block_bitmap, block_group_descriptor.block_bitmap * block_size)); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - REQUIRE(mount); - auto mount_state = static_pointer_cast((*mount)->driver_data()); - REQUIRE(mount_state); - - auto inode_data = kernel::filesystem::ext2::inode_data{}; - inode_data.size = 5; - inode_data.blocks = 2 * block_size.value / 512; - inode_data.block[0] = 20; - inode_data.block[1] = 21; - inode_data.mode = kernel::filesystem::ext2::constants::mode_regular; - - uint32_t inode_number = 3; - auto const inode_data_offset = block_group_descriptor.inode_table * block_size + - kstd::bytes{(inode_number - 1) * mount_state->superblock.inode_size}; - - auto inode = kernel::filesystem::ext2::inode{inode_number, inode_data}; - inode.set_owning_mount(*mount); - REQUIRE(dev_inode->write(kstd::raw_bytes(inode), inode_data_offset)); - - auto const buffer = - kstd::vector{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'}, std::byte{'o'}, std::byte{' '}, - std::byte{'W'}, std::byte{'o'}, std::byte{'r'}, std::byte{'l'}, std::byte{'d'}, std::byte{'!'}}; - - GIVEN("mocked ext2 filesystem") - { - auto do_read_inode = [&]() { - auto inode_data = kernel::filesystem::ext2::inode_data{}; - REQUIRE(dev_inode->read(kstd::raw_bytes(inode_data), inode_data_offset)); - auto read_inode = kernel::filesystem::ext2::inode{inode_number, inode_data}; - read_inode.set_owning_mount(*mount); - return read_inode; - }; - - THEN("writing in the current size does not update the size and does not allocate new blocks") - { - auto const expected_bytes_written = 3_B; - auto const bytes_written = inode.write(std::span{buffer}.subspan(0, 3), 0_B); - REQUIRE(bytes_written == expected_bytes_written); - - auto const text = std::string_view{reinterpret_cast(device->data.data() + 20 * block_size), 3}; - REQUIRE(text == "Hel"); - - auto read_block_bitmap = std::vector(block_size.value, std::byte{0x00}); - REQUIRE(dev_inode->read(read_block_bitmap, block_group_descriptor.block_bitmap * block_size)); - REQUIRE(block_bitmap == read_block_bitmap); - - auto read_inode = do_read_inode(); - REQUIRE(inode_data.size == read_inode.size().value); - REQUIRE(inode_data.blocks == read_inode.data().blocks); - - auto const current_blocks = inode_data.block; - auto const read_blocks = read_inode.data().block; - REQUIRE(current_blocks == read_blocks); - } - - THEN("writing over the current size (within an existing block) updates the size but does not allocate additional " - "blocks") - { - auto const offset = block_size - 4_B; - auto const bytes_written = inode.write(buffer, offset); - REQUIRE(bytes_written == kstd::bytes{buffer.size()}); - - auto const text = std::string_view{ - reinterpret_cast(device->data.data() + 21 * block_size.value - 4), bytes_written->value}; - REQUIRE(text == "Hello World!"); - - auto read_block_bitmap = std::vector(block_size.value, std::byte{0x00}); - REQUIRE(dev_inode->read(read_block_bitmap, block_group_descriptor.block_bitmap * block_size)); - REQUIRE(block_bitmap == read_block_bitmap); - - auto read_inode = do_read_inode(); - REQUIRE(offset + kstd::bytes{buffer.size()} == read_inode.size()); - REQUIRE(inode_data.blocks == read_inode.data().blocks); - - auto const current_blocks = inode_data.block; - auto const read_blocks = read_inode.data().block; - REQUIRE(current_blocks == read_blocks); - } - - THEN("writing over the current size and over allocated blocks updates size and allocates a new block") - { - auto const offset = block_size * 2 - 4_B; - auto const bytes_written = inode.write(buffer, offset); - REQUIRE(bytes_written == kstd::bytes{buffer.size()}); - - auto read_buffer = std::vector(buffer.size(), std::byte{0x00}); - auto const bytes_read = inode.read(read_buffer, offset); - auto const text = std::string_view{reinterpret_cast(read_buffer.data()), bytes_read->value}; - REQUIRE(text == "Hello World!"); - - auto read_block_bitmap = std::vector(block_size.value, std::byte{0x00}); - REQUIRE(dev_inode->read(read_block_bitmap, block_group_descriptor.block_bitmap * block_size)); - - auto expected_block_bitmap = block_bitmap; - expected_block_bitmap[2] = std::byte{0b0011'0001}; - REQUIRE(expected_block_bitmap == read_block_bitmap); - - auto read_inode = do_read_inode(); - REQUIRE(offset + kstd::bytes{buffer.size()} == read_inode.size()); - REQUIRE(inode_data.blocks + 2 == read_inode.data().blocks); - - auto expected_blocks = inode_data.block; - expected_blocks[2] = 16; - auto const read_blocks = read_inode.data().block; - REQUIRE(expected_blocks == read_blocks); - } - - THEN("writing over the current size and over the number of free blocks") - { - auto const offset = 65 * block_size; - REQUIRE_FALSE(inode.write(buffer, offset)); - } - } -} - -SCENARIO("Ext2 inode get_size() and set_size() handles size correctly depending on revision level", - "[filesystem][ext2][inode]") -{ - auto const block_size = 1024_B; - - 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.inode_size = 128; - - GIVEN("an ext2 inode with good old revision and inode_data.size = 256, inode_data.dir_acl = 32") - { - superblock.rev_level = 0; - - auto device = kstd::make_shared("mock", block_size, 64 * block_size); - REQUIRE(device != nullptr); - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device, superblock); - - auto dev_inode = kstd::make_shared(device); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - REQUIRE(mount); - - auto data = kernel::filesystem::ext2::inode_data{}; - data.size = 256; - data.dir_acl = 32; - - auto const verify_size_and_set_size = [&](uint16_t mode, kstd::bytes expected_initial_size, - uint32_t expected_dir_acl_after_set_size) { - data.mode = mode; - - auto inode = kernel::filesystem::ext2::inode{42, data}; - inode.set_owning_mount(*mount); - - REQUIRE(inode.size() == expected_initial_size); - - inode.set_size(12_B); - - REQUIRE(inode.size() == 12_B); - REQUIRE(inode.data().size == 12); - REQUIRE(inode.data().dir_acl == expected_dir_acl_after_set_size); - }; - - THEN("the inode size is 256 if mode = regular") - { - verify_size_and_set_size(kernel::filesystem::ext2::constants::mode_regular, 256_B, 32); - } - - THEN("the inode size is 256 if mode = directory") - { - verify_size_and_set_size(kernel::filesystem::ext2::constants::mode_directory, 256_B, 32); - } - } - - GIVEN("an ext2 inode with good dynamic revision and inode_data.size = 256, inode_data.dir_acl = 32") - { - superblock.rev_level = 1; - - auto device = kstd::make_shared("mock", block_size, 64 * block_size); - REQUIRE(device != nullptr); - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device, superblock); - - auto dev_inode = kstd::make_shared(device); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - REQUIRE(mount); - - auto data = kernel::filesystem::ext2::inode_data{}; - data.size = 256; - data.dir_acl = 32; - - auto const verify_size_and_set_size = [&](uint16_t mode, kstd::bytes expected_initial_size, - uint32_t expected_dir_acl_after_set_size) { - data.mode = mode; - - auto inode = kernel::filesystem::ext2::inode{42, data}; - inode.set_owning_mount(*mount); - - REQUIRE(inode.size() == expected_initial_size); - - inode.set_size(12_B); - - REQUIRE(inode.size() == 12_B); - REQUIRE(inode.data().size == 12); - REQUIRE(inode.data().dir_acl == expected_dir_acl_after_set_size); - }; - - THEN("the inode size is 256 if mode = regular") - { - verify_size_and_set_size(kernel::filesystem::ext2::constants::mode_regular, 0x0000'0020'0000'0100_B, 0); - } - - THEN("the inode size is 256 if mode = directory") - { - verify_size_and_set_size(kernel::filesystem::ext2::constants::mode_directory, 256_B, 32); - } - } -} - -SCENARIO("Ext2 inode status()", "[filesystem][ext2][inode]") -{ - GIVEN("an ext block device inode with real metadata") - { - auto fs = kernel::filesystem::ext2::filesystem{}; - auto data = kernel::filesystem::ext2::inode_data{}; - data.mode = kernel::filesystem::ext2::constants::mode_block_device; - data.block[0] = (1 << 8); - data.links_count = 1; - - auto inode = kernel::filesystem::ext2::inode(42, data); - - THEN("status() reports the real inode number, link count, and decoded raw device") - { - auto result = inode.status(); - REQUIRE(result); - - REQUIRE(result->mode == std::to_underlying(kapi::filesystem::file_type::block_device)); - REQUIRE(result->inode_number == 42); - REQUIRE(result->link_count == 1); - REQUIRE(result->raw_device == kapi::filesystem::device_number{.major = 1, .minor = 0}); - } - } -} - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, - "Ext2 inode read_directory returns real directory entries", "[filesystem][ext2][inode][img][readdir]") -{ - auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; - - GIVEN("a mounted ext2 filesystem and the information directory inode") - { - REQUIRE(std::filesystem::exists(image_path)); - REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); - - auto boot_device = kernel::devices::storage::determine_boot_device(); - REQUIRE(boot_device != nullptr); - - auto dev_inode = kstd::make_shared(boot_device); - - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - auto root = (*mount)->root_dentry()->inode(); - auto driver_data = (*mount)->driver_data(); - REQUIRE(mount); - - auto information = fs->lookup(root, "information", driver_data); - REQUIRE(information); - (*information)->set_owning_mount(*mount); - - WHEN("read_directory is called with a buffer large enough for everything in it") - { - auto entries = kstd::vector(8); - auto result = (*information)->read_directory(kernel::filesystem::directory_listing_cursor{}, entries); - - THEN("known entries come back, correctly typed") - { - REQUIRE(result); - auto [count, next] = *result; - REQUIRE(count > 0); - - auto find_by_name = [&](std::string_view name) { - return std::ranges::find_if(entries.begin(), entries.begin() + static_cast(count), - [&](auto const & entry) { return entry.name == name; }); - }; - - auto dot = find_by_name("."); - REQUIRE(dot != entries.begin() + static_cast(count)); - REQUIRE(dot->type == kapi::filesystem::file_type::directory); - - auto info_1 = find_by_name("info_1.txt"); - REQUIRE(info_1 != entries.begin() + static_cast(count)); - REQUIRE(info_1->type == kapi::filesystem::file_type::regular); - - AND_THEN("a repeated call with the returned cursor reports exhaustion") - { - auto second = (*information)->read_directory(next, entries); - REQUIRE(second); - auto [second_count, second_cursor] = *second; - REQUIRE(second_count == 0); - REQUIRE(second_cursor == next); - } - } - } - } -} - -SCENARIO("Ext2 inode read_directory rejects non-directory inodes", "[filesystem][ext2][inode][readdir]") -{ - GIVEN("a regular file inode") - { - auto data = kernel::filesystem::ext2::inode_data{}; - data.mode = kernel::filesystem::ext2::constants::mode_regular; - auto inode = kernel::filesystem::ext2::inode{7, data}; - - THEN("read_directory fails without needing a mount at all") - { - auto entries = kstd::vector(1); - auto result = inode.read_directory(kernel::filesystem::directory_listing_cursor{}, entries); - - REQUIRE_FALSE(result); - } - } -} - -SCENARIO("Ext2 inode read_directory skips deleted entries and paginates correctly", - "[filesystem][ext2][inode][readdir]") -{ - auto const block_size = 1024_B; - - GIVEN("a directory block with four entries, one of them deleted") - { - auto device = kstd::make_shared("mock", block_size, 64 * block_size); - REQUIRE(device != nullptr); - - 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.inode_size = 128; - superblock.rev_level = 1; - - auto block_group_descriptor = kernel::filesystem::ext2::block_group_descriptor{}; - block_group_descriptor.inode_table = 5; - block_group_descriptor.block_bitmap = 10; - - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device, superblock, block_group_descriptor); - - auto dev_inode = kstd::make_shared(device); - auto fs = kstd::make_shared(); - auto mount = kernel::filesystem::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); - REQUIRE(mount); - - auto block = std::vector(block_size.value, std::byte{0}); - auto offset = 0uz; - offset += - write_entry(block, offset, 10, "alpha", - static_cast(kernel::filesystem::ext2::constants::mode_regular >> 8), block_size, false); - offset += write_entry(block, offset, 0, "deleted", 0, block_size, false); - offset += - write_entry(block, offset, 11, "beta", - static_cast(kernel::filesystem::ext2::constants::mode_regular >> 8), block_size, false); - write_entry(block, offset, 12, "gamma", - static_cast(kernel::filesystem::ext2::constants::mode_regular >> 8), block_size, true); - - kernel::tests::filesystem::ext2::write_bytes(*device, 30 * block_size, block.data(), block_size); - - auto data = kernel::filesystem::ext2::inode_data{}; - data.mode = kernel::filesystem::ext2::constants::mode_directory; - data.size = block_size.value; - data.block[0] = 30; - - auto directory = kernel::filesystem::ext2::inode{3, data}; - directory.set_owning_mount(*mount); - - WHEN("read_directory is called one entry at a time, chaining the returned cursor") - { - auto seen = std::vector{}; - auto position = kernel::filesystem::directory_listing_cursor{}; - - for (auto guard = 0; guard < 8; ++guard) - { - auto one = kstd::vector(1); - auto result = directory.read_directory(position, one); - REQUIRE(result); - auto [count, next] = *result; - if (count == 0) - { - break; - } - seen.push_back(std::string{one[0].name}); - position = next; - } - - THEN("the deleted entry never appears, and every real entry is found exactly once") - { - REQUIRE(seen.size() == 3); - REQUIRE(std::ranges::find(seen, "deleted") == seen.end()); - REQUIRE(std::ranges::find(seen, "alpha") != seen.end()); - REQUIRE(std::ranges::find(seen, "beta") != seen.end()); - REQUIRE(std::ranges::find(seen, "gamma") != seen.end()); - } - } - - WHEN("read_directory is called with a buffer that exactly fits the three real entries") - { - auto entries = kstd::vector(3); - auto result = directory.read_directory(kernel::filesystem::directory_listing_cursor{}, entries); - - THEN("all three come back, and the very next call reports exhaustion, not a fourth entry") - { - REQUIRE(result); - auto [count, next] = *result; - REQUIRE(count == 3); - - auto second = directory.read_directory(next, entries); - REQUIRE(second); - auto [second_count, second_cursor] = *second; - REQUIRE(second_count == 0); - REQUIRE(second_cursor == next); - } - } - } -} - -// NOLINTEND(readability-magic-numbers) \ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp b/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp deleted file mode 100644 index 2d364f05..00000000 --- a/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_LINKED_DIRECTORY_ENTRY_HPP -#define TEACHOS_KERNEL_FILESYSTEM_EXT2_LINKED_DIRECTORY_ENTRY_HPP - -#include -#include - -namespace kernel::filesystem::ext2 -{ - //! A linked directory entry in the ext2 filesystem. - struct [[gnu::packed]] linked_directory_entry - { - [[nodiscard]] constexpr auto name() const noexcept -> std::string_view - { - return std::string_view{&name_start, name_len}; - } - - uint32_t inode; - uint16_t rec_len; - uint8_t name_len; - uint8_t file_type; - char name_start; // NOLINT(readability-magic-numbers) - }; -} // namespace kernel::filesystem::ext2 - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/module.cpp b/kernel/kernel/filesystem/ext2/module.cpp deleted file mode 100644 index e6b14307..00000000 --- a/kernel/kernel/filesystem/ext2/module.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include -#include - -#include - -#include - -namespace -{ - struct descriptor final : kernel::filesystem::driver_descriptor - { - [[nodiscard]] auto name() const noexcept -> std::string_view override - { - return "ext2"; - } - - [[nodiscard]] auto make_instance() const -> kernel::filesystem::filesystem::filesystem_ptr override - { - return kstd::make_shared(); - } - }; - - [[gnu::used]] - constexpr auto registration = kernel::filesystem::driver_module{}; -} // namespace diff --git a/kernel/kernel/filesystem/ext2/mount_state.hpp b/kernel/kernel/filesystem/ext2/mount_state.hpp deleted file mode 100644 index ba705382..00000000 --- a/kernel/kernel/filesystem/ext2/mount_state.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_MOUNT_STATE_HPP -#define TEACHOS_KERNEL_FILESYSTEM_EXT2_MOUNT_STATE_HPP - -#include -#include -#include - -#include - -#include -#include - -namespace kernel::filesystem::ext2 -{ - - struct mount_state - { - struct superblock superblock{}; - kstd::vector block_group_descriptors{}; - kstd::shared_ptr backing_inode{}; - mutable kapi::tracked_mutex lock{}; - }; - -} // namespace kernel::filesystem::ext2 - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/superblock.hpp b/kernel/kernel/filesystem/ext2/superblock.hpp deleted file mode 100644 index dce3cb29..00000000 --- a/kernel/kernel/filesystem/ext2/superblock.hpp +++ /dev/null @@ -1,122 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_SUPERBLOCK_HPP -#define TEACHOS_KERNEL_FILESYSTEM_EXT2_SUPERBLOCK_HPP - -#include - -#include -#include -#include - -namespace kernel::filesystem::ext2 -{ - - //! Ext2 compatible features. - //! - //! These enumerators describe the flags that may be ignored by the an ext2 driver. - enum struct compatible_features : std::uint32_t - { - directory_preallocation = 1 << 0, - magic_inodes = 1 << 1, - ext3_journal = 1 << 2, - extended_attributes = 1 << 3, - non_standard_inode_size = 1 << 4, - hashed_directory_indexing = 1 << 5, - }; - - //! Ext2 incompatibility features - //! - //! These enumerators describe the flags that might not be supported by an ext2 driver. - enum struct incompatible_features : std::uint32_t - { - compression = 1 << 0, - file_types_in_directory_entries = 1 << 1, - journal_recovery_required = 1 << 2, - external_journal_device = 1 << 3, - meta_block_group_layout = 1 << 4, - }; - - //! Ext2 read-only compatible features - //! - //! There enumerators describe the flags that might prevent read-write access for a driver. - enum struct read_only_compatible_features : std::uint32_t - { - sparse_superblock_copies = 1 << 0, - large_file_support = 1 << 1, - binary_tree_directories = 1 << 2, - }; - - //! The superblock in the ext2 filesystem. - struct [[gnu::packed]] superblock - { - uint32_t inodes_count; - uint32_t blocks_count; - uint32_t reserved_blocks_count; - uint32_t free_blocks_count; - uint32_t free_inodes_count; - uint32_t first_data_block; - uint32_t log_block_size; - uint32_t log_frag_size; - uint32_t blocks_per_group; - uint32_t frags_per_group; - uint32_t inodes_per_group; - uint32_t mtime; - uint32_t wtime; - uint16_t mnt_count; - uint16_t max_mnt_count; - uint16_t magic; - uint16_t state; - uint16_t errors; - uint16_t minor_rev_level; - uint32_t lastcheck; - uint32_t checkinterval; - uint32_t creator_os; - uint32_t rev_level; - uint16_t def_resuid; - uint16_t def_resgid; - - // EXT2_DYNAMIC_REV superblock only - uint32_t first_ino; - uint16_t inode_size; - uint16_t block_group_nr; - compatible_features feature_compat; - incompatible_features feature_incompat; - read_only_compatible_features feature_ro_compat; - std::array uuid; - std::array volume_name; - std::array last_mounted; - uint32_t algorithm_usage_bitmap; - - // Performance Hints - uint8_t prealloc_blocks; - uint8_t prealloc_dir_blocks; - uint16_t padding1; - - // Journaling Support - std::array journal_uuid; - uint32_t journal_inum; - uint32_t journal_dev; - uint32_t last_orphan; - - // Directory Indexing Support - std::array hash_seed; - uint8_t def_hash_version; - std::array padding2; - - // Other options - uint32_t default_mount_options; - uint32_t first_meta_bg; - std::array unused; // NOLINT(readability-magic-numbers) - }; -} // namespace kernel::filesystem::ext2 - -template<> -struct kstd::is_bitfield_enum : std::true_type -{ -}; - -template<> -struct kstd::is_bitfield_enum : std::true_type -{ -}; - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/ext2/write_batch.cpp b/kernel/kernel/filesystem/ext2/write_batch.cpp deleted file mode 100644 index 3ea6ef19..00000000 --- a/kernel/kernel/filesystem/ext2/write_batch.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include -#include -#include - -namespace kernel::filesystem::ext2 -{ - - write_batch::write_batch(mount_state & state) - : m_state{state} - {} - - write_batch::~write_batch() - { - std::ranges::for_each(m_dirty_groups, [this](auto index) { - auto & descriptor = m_state.block_group_descriptors.at(index); - auto result = ext2::write_block_group_descriptor(descriptor, index, m_state); - - if (!result) - { - kapi::system::panic("[FS:ext2] failed to write block groups descriptor", result.error()); - } - }); - - if (!m_dirty_groups.empty()) - { - auto result = ext2::write_superblock(m_state); - if (!result) - { - kapi::system::panic("[FS:ext2] failed to write superblock", result.error()); - } - } - - std::ranges::for_each(m_dirty_inodes, [this](auto inode) { - auto result = ext2::write_inode(inode->number(), inode->data(), m_state); - if (!result) - { - kapi::system::panic("[FS:ext2] failed to write inode", result.error()); - } - }); - } - - auto write_batch::mark_group(std::size_t index) -> void - { - if (std::ranges::find(m_dirty_groups, index) == std::ranges::end(m_dirty_groups)) - { - m_dirty_groups.push_back(index); - } - } - - auto write_batch::mark_inode(inode & inode) -> void - { - if (std::ranges::find(m_dirty_inodes, &inode) == std::ranges::end(m_dirty_inodes)) - { - m_dirty_inodes.push_back(&inode); - } - } - -} // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/write_batch.hpp b/kernel/kernel/filesystem/ext2/write_batch.hpp deleted file mode 100644 index 110d089c..00000000 --- a/kernel/kernel/filesystem/ext2/write_batch.hpp +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_WRITE_BATCH_HPP -#define TEACHOS_KERNEL_FILESYSTEM_EXT2_WRITE_BATCH_HPP - -#include - -#include - -namespace kernel::filesystem::ext2 -{ - - struct filesystem; - struct inode; - struct mount_state; - - //! A RAII write batching type. - //! - //! Any metadata writes are first appended to the batch. On destruction, the write is committed to the filesystem. - struct write_batch - { - //! Construct a new write batch for the given filesystem and mount state. - //! - //! @param state The driver state to operate on. - explicit write_batch(mount_state & state); - - write_batch(write_batch const &) = delete; - write_batch(write_batch &&) = delete; - auto operator=(write_batch const &) = delete; - auto operator=(write_batch &&) = delete; - - //! Commit and destroy the batch. - ~write_batch(); - - //! Mark the group with the given index as dirty. - //! - //! @param index The index of the group to mark. - auto mark_group(std::size_t index) -> void; - - //! Mark the given inode as dirty. - //! - //! @param inode The inode to mark. - auto mark_inode(inode & inode) -> void; - - private: - mount_state & m_state; - kstd::vector m_dirty_groups{}; - kstd::vector m_dirty_inodes{}; - }; - -} // namespace kernel::filesystem::ext2 - -#endif diff --git a/kernel/kernel/filesystem/file_type_bindings.hpp b/kernel/kernel/filesystem/file_type_bindings.hpp deleted file mode 100644 index da8e4efc..00000000 --- a/kernel/kernel/filesystem/file_type_bindings.hpp +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_FILE_TYPE_BINDINGS_HPP -#define TEACHOS_KERNEL_FILESYSTEM_FILE_TYPE_BINDINGS_HPP - -#include - -#include - -#include - -namespace kernel::filesystem -{ - - //! A binding between a facet, a posix special file type - struct file_type_binding // NOLINT(cppcoreguidelines-pro-type-member-init) - { - kapi::capabilities::facet_id facet; - kapi::filesystem::file_type posix_type; - }; - - //! Predefined bindings for facet + file type - //! - //! This array associates published facets with files types. - constexpr auto inline file_type_bindings = std::array{ - file_type_binding{kapi::filesystem::character_special_file::id, kapi::filesystem::file_type::character_device}, - file_type_binding{ kapi::filesystem::block_special_file::id, kapi::filesystem::file_type::block_device}, - }; - -} // namespace kernel::filesystem - -#endif diff --git a/kernel/kernel/filesystem/filesystem.hpp b/kernel/kernel/filesystem/filesystem.hpp deleted file mode 100644 index f9fb2859..00000000 --- a/kernel/kernel/filesystem/filesystem.hpp +++ /dev/null @@ -1,77 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_FILESYSTEM_HPP -#define TEACHOS_KERNEL_FILESYSTEM_FILESYSTEM_HPP - -#include - -#include - -#include -#include -#include -#include - -#include -#include -#include -#include - -namespace kernel::filesystem -{ - //! The base class for all filesystems. - //! - //! This class provides a common interface for managing files and directories within the virtual filesystem. - //! Filesystem implementations must derive from this class. - struct filesystem - { - using filesystem_ptr = kstd::shared_ptr; - using driver_data_ptr = kstd::shared_ptr; - using inode_ptr = kstd::shared_ptr; - using mount_result = std::pair; - - //! Virtual destructor enabling polymorphic destruction. - virtual ~filesystem() = default; - - //! Check if the data represented by the given inode is recognized as this filesystem. - //! - //! @param inode The inode to probe, if any. - //! @return the priority (higher is better) of the match on success, an error otherwise. - [[nodiscard]] virtual auto probe(inode_ptr const & inode) const -> kstd::result = 0; - - //! Initializes the filesystem with the given inode. - //! - //! Typically, the backing inode is the inode representing the block device or another inode which contains the - //! filesystem data (e.g. a file on an already mounted filesystem). - //! - //! @param backing_inode The inode to use as the backing inode for the filesystem. - //! @return The root inode of the mounted filesystem on success, and error otherwise. - virtual auto mount(inode_ptr const & backing_inode) -> kstd::result = 0; - - //! Find a child inode below the given parent inode with the specified 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. - //! @param driver_data The per-mount data used by the driver. - //! @return A pointer to the requested child inode on success, an error otherwise. - [[nodiscard]] virtual auto lookup(inode_ptr const & parent, std::string_view name, - driver_data_ptr driver_data) const -> kstd::result = 0; - - //! Create a new inode with the given name below a given parent inode. - //! - //! @param parent The parent inode. - //! @param name The name of the inode to create. - //! @param type The file type of inode to be created. - //! @param driver_data The per-mount data used by the driver. - //! @param raw_device The device number the new inode should represent, if any. - //! @return A pointer to the created inode on success, an error otherwise. - [[nodiscard]] virtual auto create_inode(inode_ptr const & parent, std::string_view name, - kapi::filesystem::file_type type, driver_data_ptr driver_data, - std::optional raw_device = std::nullopt) - -> kstd::result> = 0; - }; - -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/inode.cpp b/kernel/kernel/filesystem/inode.cpp deleted file mode 100644 index 7b290479..00000000 --- a/kernel/kernel/filesystem/inode.cpp +++ /dev/null @@ -1,103 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include - -namespace kernel::filesystem -{ - auto inode::is_directory() const -> bool - { - return false; - } - - auto inode::is_regular() const -> bool - { - return false; - } - - auto inode::is_block_device() const -> bool - { - return false; - } - - auto inode::is_character_device() const -> bool - { - return false; - } - - auto inode::is_device() const -> bool - { - return is_block_device() || is_character_device(); - } - - auto inode::is_symbolic_link() const -> bool - { - return false; - } - - auto inode::status() const -> kstd::result - { - auto result = kapi::filesystem::file_status{}; - - if (is_directory()) - { - result.mode = std::to_underlying(kapi::filesystem::file_type::directory); - } - else if (is_regular()) - { - result.mode = std::to_underlying(kapi::filesystem::file_type::regular); - } - else if (is_block_device()) - { - result.mode = std::to_underlying(kapi::filesystem::file_type::block_device); - } - else if (is_character_device()) - { - result.mode = std::to_underlying(kapi::filesystem::file_type::character_device); - } - else if (is_symbolic_link()) - { - result.mode = std::to_underlying(kapi::filesystem::file_type::symbolic_link); - } - - if (auto const number = raw_device()) - { - result.raw_device = *number; - } - - return result; - } - - auto inode::raw_device() const -> std::optional - { - return std::nullopt; - } - - auto inode::read_directory(directory_listing_cursor, std::span) const - -> kstd::result> - { - return kstd::failure(vfs_errc::not_a_directory); - } - - auto inode::owning_mount() const noexcept -> kstd::weak_ptr - { - return m_owning_mount; - } - - auto inode::set_owning_mount(kstd::shared_ptr const & source) noexcept -> void - { - m_owning_mount = source; - } - -} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/kernel/filesystem/inode.hpp b/kernel/kernel/filesystem/inode.hpp deleted file mode 100644 index e7de9276..00000000 --- a/kernel/kernel/filesystem/inode.hpp +++ /dev/null @@ -1,142 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_INODE_HPP -#define TEACHOS_KERNEL_FILESYSTEM_INODE_HPP - -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include - -namespace kernel::filesystem -{ - struct mount; - - //! An inode in the filesystem. - //! - //! Specific filesystems, or other subsystems, may provide their own implementation of this abstract base class. For - //! example, the ext2 filesystem provides its own inode implementation derived from this class, performing ext2 - //! specific operations to fulfill the contract of this class. - struct inode - { - //! Create an inode. - inode() = default; - - //! Virtual destructor for the inode. - virtual ~inode() = default; - - //! @name I/O Operations - //! @{ - - //! Read from this inode into a buffer - //! - //! This function reads data from this inode into a given buffer. Implementations must guarantee, that if buffer is - //! larger than the entity represented by this inode, the buffer bytes beyond the range of the read bytes are left - //! untouched. - //! - //! @param buffer Destination buffer. - //! @param offset Read offset in bytes. - //! @return The number of bytes read on success, an error otherwise. - [[nodiscard]] virtual auto read(std::span buffer, kstd::bytes offset) const - -> kstd::result = 0; - - //! Writes data from a buffer into this inode. - //! - //! This function writes data from a buffer into this inode. Implementations must handle buffer sizes larger than - //! inode entity sizes gracefully. Specifically, all bytes in the buffer that do not fit into the entity represented - //! by this inode must be ignored. - //! - //! @param buffer Source buffer. - //! @param offset Write offset in bytes. - //! @return The number of bytes written on success, an error otherwise. - virtual auto write(std::span buffer, kstd::bytes offset) -> kstd::result = 0; - - //! @} - - //! @name Property Checking - //! @{ - - //! Check if this inode describes a block device node. - //! - //! @return @c true iff. this inode describes a block device node, @c false otherwise. - [[nodiscard]] virtual auto is_block_device() const -> bool; - - //! Check if this inode describes a character device node. - //! - //! @return @c true iff. this inode describes a character device node, @c false otherwise. - [[nodiscard]] virtual auto is_character_device() const -> bool; - - //! Check if this inode describes a device. - //! - //! @return @c true iff. this inode is either a block or character device node, @c false otherwise. - [[nodiscard]] auto is_device() const -> bool; - - //! Check if this inode describes a directory. - //! - //! @return @c true iff. this inode describes a directory, @c false otherwise. - [[nodiscard]] virtual auto is_directory() const -> bool; - - //! Check if this inode describes a regular file. - //! - //! @return @c true iff. this inode describes a regular file, @c false otherwise. - [[nodiscard]] virtual auto is_regular() const -> bool; - - //! Check if this inode describes a symbolic link. - //! - //! @return @c true iff. this inode describes a symbolic link, @c false otherwise. - [[nodiscard]] virtual auto is_symbolic_link() const -> bool; - - //! @} - - //! @name POSIX Information Access - //! @{ - - //! Get POSIX status information for this inode. - //! - //! @return a populated status information object, or an error or failure. - [[nodiscard]] virtual auto status() const -> kstd::result; - - //! Get the raw device, if any, this inode refers to. - //! - //! @return a device number if this inode refers to a device, an empty optional otherwise. - [[nodiscard]] virtual auto raw_device() const -> std::optional; - - //! List the contents of a directory. - //! - //! @param position The current directory listing position. - //! @param entries An output span to store directory entries. - //! @return A pair of the number or read entries and the next listing position on success, an error otherwise. - [[nodiscard]] virtual auto read_directory(directory_listing_cursor position, - std::span entries) const - -> kstd::result>; - - //! @} - - //! @name Mount Resolution - //! @{ - - //! Get the mount this inode belongs to. - //! - //! @return The mount this inode belongs to. - [[nodiscard]] auto owning_mount() const noexcept -> kstd::weak_ptr; - - //! Define which mount this inode belongs to. - //! - //! @param source The mount this inode belongs to. - auto set_owning_mount(kstd::shared_ptr const & source) noexcept -> void; - - //! @} - - private: - kstd::weak_ptr m_owning_mount{}; - }; -} // namespace kernel::filesystem - -#endif diff --git a/kernel/kernel/filesystem/mount.cpp b/kernel/kernel/filesystem/mount.cpp deleted file mode 100644 index 7381a160..00000000 --- a/kernel/kernel/filesystem/mount.cpp +++ /dev/null @@ -1,135 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - mount::mount(dentry_ptr const & mount_dentry, dentry_ptr const & root_dentry, filesystem_ptr const & fs, - mount_ptr const & parent_mount, mount_ptr const & source_mount, kstd::shared_ptr driver_data) - : m_mount_dentry(mount_dentry) - , m_root_dentry(root_dentry) - , m_filesystem(fs) - , m_parent_mount(parent_mount) - , m_source_mount(source_mount) - , m_driver_data(driver_data) - , m_ref_count(0) - { - if (!m_filesystem) - { - kapi::system::panic("[FILESYSTEM] mount initialized with null filesystem."); - } - } - - auto mount::attach(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, - kstd::shared_ptr const & root_inode, kstd::shared_ptr driver_data, - mount_ptr const & parent_mount, mount_ptr const & source_mount) -> mount_ptr - { - auto root_dentry = mount_dentry - ? kstd::make_shared(mount_dentry->parent(), root_inode, mount_dentry->name()) - : kstd::make_shared(nullptr, root_inode, "/"); - - auto instance = mount_ptr{ - new mount{mount_dentry, root_dentry, fs, parent_mount, source_mount, driver_data} - }; - - root_inode->set_owning_mount(instance); - - return instance; - } - - auto mount::create(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, mount_ptr const & parent_mount, - mount_ptr const & source_mount, kstd::shared_ptr const & backing_inode) - -> kstd::result - { - if (!fs) - { - kapi::system::panic("[OS:FS] Tried to mount a null filesystem!"); - } - - auto mount_result = fs->mount(backing_inode); - if (!mount_result) - { - return kstd::failure(mount_result.error()); - } - - auto [root_inode, driver_data] = *mount_result; - - auto instance = attach(mount_dentry, fs, root_inode, driver_data, parent_mount, source_mount); - - return instance; - } - - auto mount::mount_dentry() const -> dentry_ptr const & - { - return m_mount_dentry; - } - - auto mount::filesystem() const -> filesystem_ptr const & - { - return m_filesystem; - } - - auto mount::root_dentry() const -> dentry_ptr const & - { - return m_root_dentry; - } - - auto mount::mount_path() const -> kstd::string - { - if (m_mount_dentry) - { - return m_mount_dentry->absolute_path(); - } - return "/"; - } - - auto mount::parent_mount() const -> mount_ptr const & - { - return m_parent_mount; - } - - auto mount::source_mount() const -> mount_ptr - { - return m_source_mount.lock(); - } - - auto mount::driver_data() const -> kstd::shared_ptr - { - return m_driver_data; - } - - auto mount::increment_ref_count() -> void - { - m_ref_count += 1; - } - - auto mount::decrement_ref_count() -> void - { - if (m_ref_count == 0) - { - kapi::system::panic("[FILESYSTEM] decrement_ref_count() was called but ref_count is 0"); - } - - m_ref_count -= 1; - } - - auto mount::is_ready_to_unmount() const -> bool - { - return m_ref_count == 0; - } - - auto mount::ref_count() const -> size_t - { - return m_ref_count; - } -} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/kernel/filesystem/mount.hpp b/kernel/kernel/filesystem/mount.hpp deleted file mode 100644 index cc032874..00000000 --- a/kernel/kernel/filesystem/mount.hpp +++ /dev/null @@ -1,109 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_MOUNT_HPP -#define TEACHOS_KERNEL_FILESYSTEM_MOUNT_HPP - -#include -#include - -#include -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - struct inode; - - //! A mounted filesystem - struct mount - { - using dentry_ptr = kstd::shared_ptr; - using filesystem_ptr = kstd::shared_ptr; - using mount_ptr = kstd::shared_ptr; - using inode_ptr = kstd::shared_ptr; - - //! Attach a mounted filesystem with a known root inode - //! - //! @param mount_dentry The directory entry where the filesystem is mounted. - //! @param fs The filesystem instance being mounted. - //! @param root_inode The backing inode for the filesystem, must not be null. - //! @param driver_data The filesystem specific driver data. - //! @param parent_mount The parent mount which contains the mount_dentry. - //! @param source_mount The mount that the filesystem originates from. - auto static attach(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, inode_ptr const & root_inode, - kstd::shared_ptr driver_data, mount_ptr const & parent_mount, - mount_ptr const & source_mount) -> mount_ptr; - - //! Create a new mount with the given parameters. - //! - //! @param mount_dentry The directory entry where the filesystem is mounted. - //! @param fs The filesystem instance being mounted. - //! @param parent_mount The parent mount which contains the mount_dentry. - //! @param source_mount The mount that the filesystem originates from. - //! @param backing_inode The backing inode for the filesystem, if any. - auto static create(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, mount_ptr const & parent_mount, - mount_ptr const & source_mount, inode_ptr const & backing_inode) -> kstd::result; - - //! Get the directory entry where the filesystem is mounted. - [[nodiscard]] auto mount_dentry() const -> dentry_ptr const &; - - //! Get the root directory entry of the mounted filesystem. - [[nodiscard]] auto root_dentry() const -> dentry_ptr const &; - - //! Get the filesystem instance being mounted. - [[nodiscard]] auto filesystem() const -> filesystem_ptr const &; - - //! Get the path at which the filesystem is mounted. - [[nodiscard]] auto mount_path() const -> kstd::string; - - //! Get the parent mount that this mount was attached beneath. - [[nodiscard]] auto parent_mount() const -> mount_ptr const &; - - //! Get the source mount where this mount originates from. - [[nodiscard]] auto source_mount() const -> mount_ptr; - - //! Get the data of the attached filesystem driver. - [[nodiscard]] auto driver_data() const -> kstd::shared_ptr; - - //! Increment the reference count for this mount. - auto increment_ref_count() -> void; - - //! Decrement the reference count for this mount. - //! - //! @warning This function panics if the reference count is already zero. - auto decrement_ref_count() -> void; - - //! Check if the mount is ready to be unmounted. - //! - //! @return True if the mount is ready to be unmounted, false otherwise. - [[nodiscard]] auto is_ready_to_unmount() const -> bool; - - //! Get the current reference count for this mount. - //! - //! @return The current reference count. - [[nodiscard]] auto ref_count() const -> size_t; - - private: - //! Create a new mount with the given parameters. - //! - //! @param mount_dentry The directory entry where the filesystem is mounted. - //! @param root_dentry The root directory entry of the mounted filesystem. - //! @param fs The filesystem instance being mounted. - //! @param parent_mount The parent mount which contains the mount_dentry. - //! @param source_mount The mount that the filesystem originates from. - //! @param driver_data The driver specific state data. - mount(dentry_ptr const & mount_dentry, dentry_ptr const & root_dentry, filesystem_ptr const & fs, - mount_ptr const & parent_mount, mount_ptr const & source_mount, kstd::shared_ptr driver_data); - - dentry_ptr m_mount_dentry{}; - dentry_ptr m_root_dentry{}; - filesystem_ptr m_filesystem{}; - mount_ptr m_parent_mount{}; - kstd::weak_ptr m_source_mount{}; - kstd::shared_ptr m_driver_data{}; - std::atomic_size_t m_ref_count{0}; - }; -} // namespace kernel::filesystem - -#endif diff --git a/kernel/kernel/filesystem/mount.tests.cpp b/kernel/kernel/filesystem/mount.tests.cpp deleted file mode 100644 index a77134e5..00000000 --- a/kernel/kernel/filesystem/mount.tests.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include - -SCENARIO("Mount construction", "[filesystem][mount]") -{ - GIVEN("a filesystem and a root inode") - { - auto fs = kstd::make_shared(); - auto root_inode = kstd::make_shared(); - - WHEN("attaching a mount with no mount point") - { - auto mount = kernel::filesystem::mount::attach(nullptr, fs, root_inode, nullptr, nullptr, nullptr); - - THEN("the mount has the correct filesystem, root dentry, mount dentry, and mount path") - { - REQUIRE(mount->filesystem() == fs); - REQUIRE(mount->root_dentry()->inode() == root_inode); - REQUIRE(mount->root_dentry()->name() == "/"); - REQUIRE(mount->mount_dentry() == nullptr); - REQUIRE(mount->mount_path() == "/"); - REQUIRE(mount->is_ready_to_unmount()); - } - - THEN("the mount has no parent mount and no source mount") - { - REQUIRE(mount->parent_mount() == nullptr); - REQUIRE(mount->source_mount() == nullptr); - } - } - - WHEN("constructing a mount with a null filesystem") - { - THEN("the constructor panics") - { - REQUIRE_THROWS_AS((kernel::filesystem::mount::create(nullptr, nullptr, nullptr, nullptr, nullptr)), - kernel::tests::cpu::halt); - } - } - } -} - -SCENARIO("Mount reference counting", "[filesystem][mount]") -{ - GIVEN("a filesystem and a root dentry") - { - auto fs = kstd::make_shared(); - auto root_inode = kstd::make_shared(); - auto root_dentry = kstd::make_shared(nullptr, root_inode, "/"); - - THEN("reference count can be incremented and decremented, the mount is ready to unmount when the reference " - "count == 0") - { - auto mount = kernel::filesystem::mount::create(root_dentry, fs, nullptr, nullptr, nullptr); - REQUIRE(mount); - - (*mount)->increment_ref_count(); - REQUIRE((*mount)->ref_count() == 1); - REQUIRE_FALSE((*mount)->is_ready_to_unmount()); - - (*mount)->increment_ref_count(); - REQUIRE((*mount)->ref_count() == 2); - REQUIRE_FALSE((*mount)->is_ready_to_unmount()); - - (*mount)->decrement_ref_count(); - REQUIRE((*mount)->ref_count() == 1); - REQUIRE_FALSE((*mount)->is_ready_to_unmount()); - - (*mount)->decrement_ref_count(); - REQUIRE((*mount)->ref_count() == 0); - REQUIRE((*mount)->is_ready_to_unmount()); - } - - THEN("decrementing reference count when it is already zero does not decrement it below zero") - { - auto mount = kernel::filesystem::mount::create(root_dentry, fs, nullptr, nullptr, nullptr); - - REQUIRE_THROWS_AS((*mount)->decrement_ref_count(), std::runtime_error); - REQUIRE((*mount)->ref_count() == 0); - REQUIRE((*mount)->is_ready_to_unmount()); - } - } -} \ No newline at end of file diff --git a/kernel/kernel/filesystem/mount_table.cpp b/kernel/kernel/filesystem/mount_table.cpp deleted file mode 100644 index d3f252b4..00000000 --- a/kernel/kernel/filesystem/mount_table.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include - -#include -#include - -#include -#include - -#include -#include -#include - -namespace kernel::filesystem -{ - auto mount_table::has_child_mounts(kstd::shared_ptr const & parent_mount) const -> bool - { - return std::ranges::any_of(m_mounts, - [&parent_mount](auto const & mount) { return mount->parent_mount() == parent_mount; }); - } - - auto mount_table::add_mount(kstd::shared_ptr const & mount) -> void - { - m_mounts.push_back(mount); - - if (auto source_mount = mount->source_mount()) - { - source_mount->increment_ref_count(); - } - - if (auto mount_dentry = mount->mount_dentry()) - { - mount_dentry->set_flag(dentry::dentry_flags::is_mount_point); - } - } - - auto mount_table::remove_mount(std::string_view path) -> operation_result - { - auto mount_it = find_mount_iterator(path); - if (mount_it == m_mounts.end()) - { - return operation_result::mount_not_found; - } - - auto const & mount = *mount_it; - if (!mount->is_ready_to_unmount()) - { - return operation_result::cannot_be_unmounted; - } - if (has_child_mounts(mount)) - { - return operation_result::has_child_mounts; - } - - if (auto source_mount = mount->source_mount()) - { - source_mount->decrement_ref_count(); - } - - if (auto mount_dentry = mount->mount_dentry()) - { - mount_dentry->unset_flag(dentry::dentry_flags::is_mount_point); - } - - m_mounts.erase(mount_it); - return operation_result::removed; - } - - auto mount_table::find_mount(std::string_view path) const -> kstd::shared_ptr - { - auto mount_it = find_mount_iterator(path); - return (mount_it != m_mounts.end()) ? *mount_it : nullptr; - } - - auto mount_table::find_mount_iterator(std::string_view path) const - -> kstd::vector>::const_iterator - { - return std::ranges::find_last_if(m_mounts, [&](auto const & mount) { return mount->mount_path() == path; }).begin(); - } -} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/kernel/filesystem/mount_table.hpp b/kernel/kernel/filesystem/mount_table.hpp deleted file mode 100644 index 678a5927..00000000 --- a/kernel/kernel/filesystem/mount_table.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_MOUNT_TABLE_HPP -#define TEACHOS_KERNEL_FILESYSTEM_MOUNT_TABLE_HPP - -#include - -#include -#include - -#include - -namespace kernel::filesystem -{ - //! A table managing all mounted filesystems. - struct mount_table - { - //! Results for mount table operations. - enum class operation_result : int - { - removed = 0, - has_child_mounts = -1, - mount_not_found = -2, - cannot_be_unmounted = -3 - }; - - //! Add a mount to the table. - //! - //! @param mount The mount to add. - auto add_mount(kstd::shared_ptr const & mount) -> void; - - //! Remove 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; // TODO: replace return type with kstd::result - - //! Find the mount with the exact mount path matching the given path. - //! - //! @param path The path to match against the mount paths in the table. - //! @return A pointer to the mount with the exact matching path on success, nullpointer otherwise. - [[nodiscard]] auto find_mount(std::string_view path) const -> kstd::shared_ptr; - - private: - [[nodiscard]] auto has_child_mounts(kstd::shared_ptr const & parent_mount) const -> bool; - [[nodiscard]] auto find_mount_iterator(std::string_view path) const - -> kstd::vector>::const_iterator; - - kstd::vector> m_mounts; - }; -} // namespace kernel::filesystem - -#endif diff --git a/kernel/kernel/filesystem/mount_table.tests.cpp b/kernel/kernel/filesystem/mount_table.tests.cpp deleted file mode 100644 index bed6cd9d..00000000 --- a/kernel/kernel/filesystem/mount_table.tests.cpp +++ /dev/null @@ -1,184 +0,0 @@ -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include - -SCENARIO("Mount table construction", "[filesystem][mount_table]") -{ - GIVEN("an empty mount table") - { - kernel::filesystem::mount_table table; - - 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(); - auto root_dentry1 = kstd::make_shared( - nullptr, kstd::make_shared(), "/"); - auto mount_dentry1 = kstd::make_shared( - nullptr, kstd::make_shared(), "/"); - auto mount1 = kernel::filesystem::mount::create(mount_dentry1, fs1, nullptr, nullptr, nullptr); - - auto fs2 = kstd::make_shared(); - auto root_dentry2 = kstd::make_shared( - nullptr, kstd::make_shared(), "/"); - auto mount_dentry2 = kstd::make_shared( - nullptr, kstd::make_shared(), "/mnt"); - auto mount2 = kernel::filesystem::mount::create(mount_dentry2, fs2, nullptr, nullptr, nullptr); - - table.add_mount(*mount1); - table.add_mount(*mount2); - - THEN("dentry flags are set correctly for mounted dentries") - { - REQUIRE(mount_dentry1->has_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point)); - REQUIRE(mount_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point)); - } - - THEN("finding mounts by exact valid path returns the correct mount") - { - REQUIRE(table.find_mount("/") == mount1); - REQUIRE(table.find_mount("/mnt") == mount2); - } - - THEN("finding mounts by exact invalid path returns null") - { - REQUIRE(table.find_mount("/nonexistent") == nullptr); - REQUIRE(table.find_mount("/mnt/file") == nullptr); - } - - THEN("removing a mount that has no child mounts succeeds") - { - REQUIRE(table.remove_mount("/mnt") == kernel::filesystem::mount_table::operation_result::removed); - REQUIRE_FALSE(root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point)); - } - - 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(); - auto root_dentry1 = kstd::make_shared( - nullptr, kstd::make_shared(), "/"); - auto mount_dentry1 = kstd::make_shared( - nullptr, kstd::make_shared(), "/"); - auto mount1 = kernel::filesystem::mount::create(mount_dentry1, fs1, nullptr, nullptr, nullptr); - - auto fs2 = kstd::make_shared(); - auto root_dentry2 = kstd::make_shared( - nullptr, kstd::make_shared(), "/"); - auto mount_dentry2 = kstd::make_shared( - nullptr, kstd::make_shared(), "/"); - auto mount2 = kernel::filesystem::mount::create(mount_dentry2, fs2, nullptr, nullptr, nullptr); - - table.add_mount(*mount1); - table.add_mount(*mount2); - - THEN("finding mounts by exact valid path returns the correct mount") - { - REQUIRE(table.find_mount("/") == mount2); - } - - THEN("removing the topmost mount with the same path succeeds") - { - REQUIRE(table.remove_mount("/") == kernel::filesystem::mount_table::operation_result::removed); - REQUIRE_FALSE(root_dentry2->has_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point)); - } - } - - GIVEN("a mount with child mounts") - { - kernel::filesystem::mount_table table; - - auto fs1 = kstd::make_shared(); - auto root_dentry1 = kstd::make_shared( - nullptr, kstd::make_shared(), "/"); - auto mount_dentry1 = kstd::make_shared( - nullptr, kstd::make_shared(), "/"); - auto mount1 = kernel::filesystem::mount::create(mount_dentry1, fs1, nullptr, nullptr, nullptr); - - auto fs2 = kstd::make_shared(); - auto root_dentry2 = kstd::make_shared( - nullptr, kstd::make_shared(), "/"); - auto mount_dentry2 = kstd::make_shared( - mount_dentry1, kstd::make_shared(), "mnt"); - auto mount2 = kernel::filesystem::mount::create(mount_dentry2, fs2, *mount1, nullptr, nullptr); - - auto fs3 = kstd::make_shared(); - auto root_dentry3 = kstd::make_shared( - nullptr, kstd::make_shared(), "/"); - auto mount_dentry3 = kstd::make_shared( - mount_dentry2, kstd::make_shared(), "submnt"); - auto mount3 = kernel::filesystem::mount::create(mount_dentry3, fs3, *mount2, nullptr, nullptr); - - table.add_mount(*mount1); - table.add_mount(*mount2); - table.add_mount(*mount3); - - 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_FALSE(root_dentry3->has_flag(kernel::filesystem::dentry::dentry_flags::is_mount_point)); - } - } -} - -SCENARIO("Mount reference counting", "[filesystem][mount_table]") -{ - kernel::filesystem::mount_table table; - - GIVEN("a filesystem and a root dentry") - { - auto fs = kstd::make_shared(); - auto root_inode = kstd::make_shared(); - auto root_dentry = kstd::make_shared(nullptr, root_inode, "/"); - - auto source_mount = kernel::filesystem::mount::create(root_dentry, fs, nullptr, nullptr, nullptr); - auto mount = kernel::filesystem::mount::create(root_dentry, fs, nullptr, *source_mount, nullptr); - - THEN("reference count of source mount is incremented when a mount is added to the mount table and decremented when " - "the mount is removed") - { - REQUIRE((*source_mount)->ref_count() == 0); - - table.add_mount(*mount); - REQUIRE((*source_mount)->ref_count() == 1); - - REQUIRE(table.remove_mount("/") == kernel::filesystem::mount_table::operation_result::removed); - REQUIRE((*source_mount)->ref_count() == 0); - } - } -} diff --git a/kernel/kernel/filesystem/open_file_descriptor.cpp b/kernel/kernel/filesystem/open_file_descriptor.cpp deleted file mode 100644 index d18ae141..00000000 --- a/kernel/kernel/filesystem/open_file_descriptor.cpp +++ /dev/null @@ -1,53 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - open_file_descriptor::open_file_descriptor(kstd::shared_ptr const & dentry) - : m_dentry(dentry) - { - if (!dentry) - { - kstd::os::panic("[FILESYSTEM] open_file_descriptor constructed with null dentry."); - } - } - - auto open_file_descriptor::read(std::span) -> kstd::result - { - return kstd::failure(vfs_errc::is_a_directory); - } - - auto open_file_descriptor::write(std::span) -> kstd::result - { - return kstd::failure(vfs_errc::is_a_directory); - } - - auto open_file_descriptor::seek(kstd::offset, kapi::filesystem::seek_origin) -> kstd::result - { - return kstd::failure(vfs_errc::is_a_directory); - } - - auto open_file_descriptor::read_directory(std::span) -> kstd::result - { - return kstd::failure(vfs_errc::not_a_directory); - } - - auto open_file_descriptor::get_dentry() const -> kstd::shared_ptr const & - { - return m_dentry; - } - -} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/kernel/filesystem/open_file_descriptor.hpp b/kernel/kernel/filesystem/open_file_descriptor.hpp deleted file mode 100644 index 3f3722b4..00000000 --- a/kernel/kernel/filesystem/open_file_descriptor.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_OPEN_FILE_DESCRIPTOR_HPP -#define TEACHOS_KERNEL_FILESYSTEM_OPEN_FILE_DESCRIPTOR_HPP - -#include -#include - -#include - -#include -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - //! An open file. - //! - //! This class encapsulates the state of an open file, including a reference to the associated directory and the - //! current byte offset within the file. - struct open_file_descriptor - { - //! Create an new open file descriptor for a given directory entry. - //! - //! @param dentry The directory entry to associate with the open file descriptor. - explicit open_file_descriptor(kstd::shared_ptr const & dentry); - - //! Virtual destructor to enable safe destruction through base pointers. - virtual ~open_file_descriptor() = default; - - //! Read data from the open file descriptor into a buffer. - //! - //! @param buffer The buffer to read data into. - //! @return The number of bytes read on success, an error otherwise. - virtual auto read(std::span buffer) -> kstd::result; - - //! Write data to the open file descriptor from a buffer. - //! - //! @param buffer The buffer to write data from. - //! @return The number of bytes written on success, an error otherwise. - virtual auto write(std::span buffer) -> kstd::result; - - //! Move the read/write offset of the file. - //! - //! @param offset The offset to apply relative to the given origin. - //! @param origin The origin of the offset. - //! @return the new offset on success, an error otherwise. - virtual auto seek(kstd::offset offset, kapi::filesystem::seek_origin origin) -> kstd::result; - - //! Read directory entries from the file. - //! - //! @param entries A buffer to read the entries into. - //! @return The number of read entries on success, an error otherwise. - virtual auto read_directory(std::span entries) -> kstd::result; - - //! Get a reference to the directory entry associated with this open file descriptor. - //! - //! @return A reference to the associated directory entry. - [[nodiscard]] auto get_dentry() const -> kstd::shared_ptr const &; - - private: - kstd::shared_ptr m_dentry; - }; - -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/open_file_descriptor.tests.cpp b/kernel/kernel/filesystem/open_file_descriptor.tests.cpp deleted file mode 100644 index 2fbffbf8..00000000 --- a/kernel/kernel/filesystem/open_file_descriptor.tests.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include - -#include -#include - -using namespace kstd::units_literals; - -SCENARIO("open_file_descriptor's base defaults fail every operation, unconditionally", - "[filesystem][open_file_descriptor]") -{ - GIVEN("a base open_file_descriptor constructed directly, not through a derived type") - { - auto inode = kstd::make_shared(); - auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); - auto fd = kernel::filesystem::open_file_descriptor{dentry}; - - THEN("read fails as 'this is a directory', not silently succeeding with zero bytes") - { - auto buffer = kstd::vector(4); - auto result = fd.read(buffer); - - REQUIRE_FALSE(result); - REQUIRE(result.error() == kernel::filesystem::vfs_errc::is_a_directory); - } - - THEN("write fails the same way") - { - auto buffer = kstd::vector{std::byte{'x'}}; - auto result = fd.write(buffer); - - REQUIRE_FALSE(result); - REQUIRE(result.error() == kernel::filesystem::vfs_errc::is_a_directory); - } - - THEN("seek fails the same way") - { - auto result = fd.seek(kstd::offset{0}, kapi::filesystem::seek_origin::beginning); - - REQUIRE_FALSE(result); - REQUIRE(result.error() == kernel::filesystem::vfs_errc::is_a_directory); - } - - THEN("read_directory fails as 'this is not a directory' — the opposite error, and the only one of " - "the four that's correct for it") - { - auto entries = kstd::vector(1); - auto result = fd.read_directory(entries); - - REQUIRE_FALSE(result); - REQUIRE(result.error() == kernel::filesystem::vfs_errc::not_a_directory); - } - } -} - -SCENARIO("Cross-type calls through open_file_descriptor fall through to the correct base default, not " - "the other subclass's behavior", - "[filesystem][open_file_descriptor]") -{ - GIVEN("a byte_offset_file_descriptor, accessed through a base pointer") - { - auto inode = kstd::make_shared(); - auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); - kstd::shared_ptr fd = - kstd::make_shared(dentry); - - THEN("read_directory falls through to the base — byte_offset_file_descriptor never overrode it") - { - auto entries = kstd::vector(1); - auto result = fd->read_directory(entries); - - REQUIRE_FALSE(result); - REQUIRE(result.error() == kernel::filesystem::vfs_errc::not_a_directory); - } - } - - GIVEN("a directory_file_descriptor, accessed through a base pointer") - { - auto inode = kstd::make_shared(); - auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); - kstd::shared_ptr fd = - kstd::make_shared(dentry); - - THEN("read/write/seek all fall through to the base — directory_file_descriptor never overrode any " - "of them") - { - auto buffer = kstd::vector(4); - REQUIRE_FALSE(fd->read(buffer)); - - auto write_buffer = kstd::vector{std::byte{'x'}}; - REQUIRE_FALSE(fd->write(write_buffer)); - - REQUIRE_FALSE(fd->seek(kstd::offset{0}, kapi::filesystem::seek_origin::beginning)); - } - } -} \ No newline at end of file diff --git a/kernel/kernel/filesystem/open_file_table.cpp b/kernel/kernel/filesystem/open_file_table.cpp deleted file mode 100644 index dea504e4..00000000 --- a/kernel/kernel/filesystem/open_file_table.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include - -namespace -{ - constinit auto static global_open_file_table = std::optional{}; -} // namespace - -namespace kernel::filesystem -{ - auto open_file_table::init() -> void - { - if (global_open_file_table) - { - kapi::system::panic("[FILESYSTEM] Open file table has already been initialized."); - } - - global_open_file_table.emplace(open_file_table{}); - } - - auto open_file_table::get() -> open_file_table & - { - if (!global_open_file_table) - { - kapi::system::panic("[FILESYSTEM] Open file table has not been initialized."); - } - - return *global_open_file_table; - } - - auto open_file_table::add_file(kstd::shared_ptr const & file_descriptor) - -> kstd::result - { - if (!file_descriptor) - { - return kstd::failure(vfs_errc::invalid_file_descriptor); - } - - auto it = std::ranges::find_if(m_open_files, [](auto const & open_file) { return open_file == nullptr; }); - if (it != m_open_files.end()) - { - *it = file_descriptor; - return it - m_open_files.begin(); - } - - m_open_files.push_back(file_descriptor); - return m_open_files.size() - 1; - } - - auto open_file_table::file(size_t fd) const -> kstd::result> - { - if (fd >= m_open_files.size() || m_open_files.at(fd) == nullptr) - { - return kstd::failure(vfs_errc::invalid_file_descriptor); - } - - return kstd::success(m_open_files.at(fd)); - } - - auto open_file_table::remove_file(size_t fd) -> kstd::result - { - if (fd >= m_open_files.size() || m_open_files.at(fd) == nullptr) - { - return kstd::failure(vfs_errc::invalid_file_descriptor); - } - - m_open_files.at(fd) = nullptr; - return kstd::success(); - } -} // namespace kernel::filesystem - -namespace kernel::tests::filesystem::open_file_table -{ - auto deinit() -> void - { - global_open_file_table.reset(); - } -} // namespace kernel::tests::filesystem::open_file_table diff --git a/kernel/kernel/filesystem/open_file_table.hpp b/kernel/kernel/filesystem/open_file_table.hpp deleted file mode 100644 index ddc1ea16..00000000 --- a/kernel/kernel/filesystem/open_file_table.hpp +++ /dev/null @@ -1,57 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_OPEN_FILE_TABLE_HPP -#define TEACHOS_KERNEL_FILESYSTEM_OPEN_FILE_TABLE_HPP - -#include - -#include -#include -#include -#include - -#include - -namespace kernel::filesystem -{ - //! @brief A table for managing file descriptors in the filesystem. - //! - //! This class provides methods for adding, retrieving, and removing open file descriptors. - struct open_file_table - { - //! Initialize the global open file table. - //! - //! @warning This function panics if called more than once. - auto static init() -> void; - - //! Get the global open file table instance. - //! - //! @warning Panics if the open file table has not been initialized. - //! - //! @return A reference to the global open file table. - auto static get() -> open_file_table &; - - //! Add a file to the open file table. - //! - //! @param fd The file descriptor to add. - //! @return The file descriptor index assigned to the file on success, an error otherwise. - auto add_file(kstd::shared_ptr const & fd) -> kstd::result; - - //! Get a file from the open file table. - //! - //! @param fd The file descriptor index to retrieve. - //! @return The requested file descriptor on success, an error otherwise. - [[nodiscard]] auto file(size_t fd) const -> kstd::result>; - - //! Remove a file from the open file table. - //! - //! @param fd The file descriptor index to remove. - //! @return Nothin on success, an error otherwise. - auto remove_file(size_t fd) -> kstd::result; - - private: - open_file_table() = default; - - kstd::vector> m_open_files{}; - }; -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/open_file_table.tests.cpp b/kernel/kernel/filesystem/open_file_table.tests.cpp deleted file mode 100644 index 69188528..00000000 --- a/kernel/kernel/filesystem/open_file_table.tests.cpp +++ /dev/null @@ -1,107 +0,0 @@ -#include - -#include -#include -#include - -#include -#include -#include -#include - -#include - -SCENARIO("Open file table add/get file", "[filesystem][open_file_table]") -{ - GIVEN("a open file table and an open file descriptor") - { - auto & table = kernel::filesystem::open_file_table::get(); - auto inode = kstd::make_shared(); - auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); - - auto file_descriptor_1 = kstd::make_shared(dentry); - auto file_descriptor_2 = kstd::make_shared(dentry); - - WHEN("adding the open file descriptor to the open file table") - { - auto fd_1 = table.add_file(file_descriptor_1); - auto fd_2 = table.add_file(file_descriptor_2); - auto fd_3 = table.add_file(file_descriptor_2); - - THEN("a valid file descriptor is returned") - { - REQUIRE(fd_1 == 0); - REQUIRE(fd_2 == 1); - REQUIRE(fd_3 == 2); - } - - THEN("the file descriptor can be retrieved using the returned file descriptor") - { - auto retrieved_descriptor = table.file(*fd_1); - REQUIRE(retrieved_descriptor == file_descriptor_1); - } - } - } - - GIVEN("a invalid open file descriptor") - { - auto & table = kernel::filesystem::open_file_table::get(); - - THEN("adding a null file descriptor returns an error") - { - auto fd = table.add_file(nullptr); - REQUIRE(fd.error() == kstd::errc::bad_file_descriptor); - } - - THEN("retrieving a file descriptor with an out-of-bounds file descriptor returns an error") - { - auto retrieved_descriptor = table.file(1000); - REQUIRE(retrieved_descriptor.error() == kstd::errc::bad_file_descriptor); - } - } -} - -SCENARIO("Open file table remove file", "[filesystem][open_file_table]") -{ - GIVEN("a open file table with an open file descriptor") - { - auto & table = kernel::filesystem::open_file_table::get(); - auto inode = kstd::make_shared(); - auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); - auto file_descriptor = kstd::make_shared(dentry); - auto fd = table.add_file(file_descriptor); - - WHEN("removing the file descriptor using the file descriptor") - { - CHECK(table.remove_file(*fd)); - - THEN("the file descriptor can no longer be retrieved using the file descriptor") - { - auto retrieved_descriptor = table.file(*fd); - REQUIRE(retrieved_descriptor.error() == kstd::errc::bad_file_descriptor); - } - } - - WHEN("removing a file descriptor the other file descriptor keep the same index") - { - auto fd2 = table.add_file(file_descriptor); - CHECK(table.remove_file(*fd)); - - THEN("the second file descriptor can still be retrieved using its file descriptor") - { - auto retrieved_descriptor = table.file(*fd2); - REQUIRE(retrieved_descriptor == file_descriptor); - } - } - } - - GIVEN("an invalid file descriptor") - { - auto & table = kernel::filesystem::open_file_table::get(); - - 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/kernel/filesystem/path.hpp b/kernel/kernel/filesystem/path.hpp deleted file mode 100644 index 4dc0dbce..00000000 --- a/kernel/kernel/filesystem/path.hpp +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_PATH_HPP -#define TEACHOS_KERNEL_FILESYSTEM_PATH_HPP - -#include - -#include - -#include -#include -#include - -namespace kernel::filesystem::path -{ - - //! Check if the given path is within the maximum allowed length. - //! - //! @param path The path to check. - //! @return @c true if the path length is valid, @c false otherwise. - auto inline is_valid_path_length(std::string_view path) -> bool - { - return path.length() < kernel::filesystem::constants::max_path_length; - } - - //! Check if the given path is an absolute path. - //! - //! @param path The path to check. - //! @return @c true if the path is an absolute path, @c false otherwise. - auto inline is_valid_absolute_path(std::string_view path) -> bool - { - return !path.empty() && path.front() == '/' && is_valid_path_length(path); - } - - //! Check if the given path is a relative path. - //! - //! @param path The path to check. - //! @return @c true if the path is a relative path, @c false otherwise. - auto inline is_valid_relative_path(std::string_view path) -> bool - { - return !path.empty() && path.front() != '/' && is_valid_path_length(path); - } - - //! Check if the given path is a valid path. - //! - //! @param path The path to check. - //! @return @c true if the path is a valid path, @c false otherwise. - auto inline is_valid_path(std::string_view path) -> bool - { - return is_valid_absolute_path(path) || is_valid_relative_path(path); - } - - //! Split the given path into its components. - //! - //! @param path The path to split. - //! @return A range of strings representing the components of the path. - auto inline split(std::string_view path) - { - return std::views::split(path, '/') | std::views::filter([](auto const & part) { return !part.empty(); }) | - std::views::transform( - [](auto const & part) { return kstd::string(std::string_view(part.begin(), part.end())); }); - } - - //! Split the given path into its parent path and filename components. - //! - //! @param path The path to split. - //! @return A pair of string views representing the parent path and filename. - auto inline split_into_path_and_filename(std::string_view path) -> std::pair - { - if (path.empty()) - { - return {"", ""}; - } - - if (path.length() > 1 && path.back() == '/') - { - path = path.substr(0, path.length() - 1); - } - - auto last_separator_index = path.rfind('/'); - if (last_separator_index == std::string_view::npos) - { - return {"", path}; - } - else - { - auto parent_path = path.substr(0, last_separator_index); - if (parent_path.empty()) - { - parent_path = "/"; - } - auto filename = path.substr(last_separator_index + 1); - return {parent_path, filename}; - } - } - -} // namespace kernel::filesystem::path - -#endif // TEACHOS_KERNEL_FILESYSTEM_PATH_HPP \ No newline at end of file diff --git a/kernel/kernel/filesystem/path.tests.cpp b/kernel/kernel/filesystem/path.tests.cpp deleted file mode 100644 index 7bba371b..00000000 --- a/kernel/kernel/filesystem/path.tests.cpp +++ /dev/null @@ -1,128 +0,0 @@ -#include - -#include - -#include -#include -#include -#include - -SCENARIO("path utilities", "[filesystem][path]") -{ - GIVEN("valid and invalid paths") - { - THEN("valid absolute paths are recognized as valid") - { - REQUIRE(kernel::filesystem::path::is_valid_path("/valid/absolute/path")); - REQUIRE(kernel::filesystem::path::is_valid_path("/")); - } - - THEN("valid relative paths are recognized as valid") - { - REQUIRE(kernel::filesystem::path::is_valid_path("valid/../relative/.././path")); - REQUIRE(kernel::filesystem::path::is_valid_path("valid/relative/path")); - REQUIRE(kernel::filesystem::path::is_valid_path("file.txt")); - } - - THEN("invalid paths are recognized as invalid") - { - REQUIRE_FALSE(kernel::filesystem::path::is_valid_path("")); - REQUIRE_FALSE(kernel::filesystem::path::is_valid_path(std::string(4096, 'a'))); - } - - THEN("valid absolute paths are recognized as absolute") - { - REQUIRE(kernel::filesystem::path::is_valid_absolute_path("/valid/absolute/path")); - REQUIRE_FALSE(kernel::filesystem::path::is_valid_absolute_path("valid/relative/path")); - } - - THEN("invalid paths are not recognized as absolute") - { - REQUIRE_FALSE(kernel::filesystem::path::is_valid_absolute_path("")); - REQUIRE_FALSE(kernel::filesystem::path::is_valid_absolute_path(std::string(4096, 'a'))); - REQUIRE_FALSE(kernel::filesystem::path::is_valid_absolute_path("invalid/absolute/path")); - } - - THEN("valid relative paths are recognized as relative") - { - REQUIRE(kernel::filesystem::path::is_valid_relative_path("valid/relative/path")); - REQUIRE_FALSE(kernel::filesystem::path::is_valid_relative_path("/valid/absolute/path")); - } - - THEN("invalid paths are not recognized as relative") - { - REQUIRE_FALSE(kernel::filesystem::path::is_valid_relative_path("")); - REQUIRE_FALSE(kernel::filesystem::path::is_valid_relative_path(std::string(4096, 'a'))); - REQUIRE_FALSE(kernel::filesystem::path::is_valid_relative_path("/invalid/absolute/path")); - } - } - - GIVEN("a valid path") - { - THEN("it can be split into components") - { - auto components = kernel::filesystem::path::split("/a/b///c/d.txt"); - std::vector expected = {"a", "b", "c", "d.txt"}; - REQUIRE(std::ranges::equal(components, expected)); - } - } - - GIVEN("file paths") - { - THEN("path is split into parent path and filename") - { - auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("/a/b/c/d.txt"); - REQUIRE(parent == "/a/b/c"); - REQUIRE(filename == "d.txt"); - } - - THEN("root path is split into root and empty filename") - { - auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("/"); - REQUIRE(parent == "/"); - REQUIRE(filename == ""); - } - - THEN("file directly under root is split correctly") - { - auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("/file.txt"); - REQUIRE(parent == "/"); - REQUIRE(filename == "file.txt"); - } - - THEN("filename without parent path is split into empty parent and filename") - { - auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("file.txt"); - REQUIRE(parent == ""); - REQUIRE(filename == "file.txt"); - } - - THEN("path with trailing slash is split correctly") - { - auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("/a/b/c/"); - REQUIRE(parent == "/a/b"); - REQUIRE(filename == "c"); - } - - THEN("single component with trailing slash has root as parent") - { - auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("/a/"); - REQUIRE(parent == "/"); - REQUIRE(filename == "a"); - } - - THEN("relative path is split into relative parent and filename") - { - auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename("a/b/c.txt"); - REQUIRE(parent == "a/b"); - REQUIRE(filename == "c.txt"); - } - - THEN("an empty path is split into empty parent and filename") - { - auto [parent, filename] = kernel::filesystem::path::split_into_path_and_filename(""); - REQUIRE(parent == ""); - REQUIRE(filename == ""); - } - } -} \ No newline at end of file diff --git a/kernel/kernel/filesystem/reserved_numbers.hpp b/kernel/kernel/filesystem/reserved_numbers.hpp deleted file mode 100644 index 871d93f8..00000000 --- a/kernel/kernel/filesystem/reserved_numbers.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_RESERVED_NUMBERS_HPP -#define TEACHOS_KERNEL_FILESYSTEM_RESERVED_NUMBERS_HPP - -#include - -//! Constants defining major numbers used in the VFS layer. -namespace kernel::filesystem -{ - - namespace block_major_numbers - { - //! Reserved devices. - constexpr auto inline internal = std::uint8_t{0}; - - //! RAM disk devices. - constexpr auto inline ram_disk = std::uint8_t{1}; - } // namespace block_major_numbers - - namespace character_major_numbers - { - //! Memory devices. - constexpr auto inline memory = std::uint8_t{1}; - } // namespace character_major_numbers - - namespace character_minor_numbers - { - //! The 'null' device - constexpr auto inline null = std::uint8_t{3}; - - //! The 'zero' device - constexpr auto inline zero = std::uint8_t{5}; - } // namespace character_minor_numbers - -} // namespace kernel::filesystem - -#endif diff --git a/kernel/kernel/filesystem/rootfs/filesystem.cpp b/kernel/kernel/filesystem/rootfs/filesystem.cpp deleted file mode 100644 index 4618fd69..00000000 --- a/kernel/kernel/filesystem/rootfs/filesystem.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include - -namespace kernel::filesystem::rootfs -{ - static_assert(sizeof(kernel::filesystem::filesystem) == sizeof(rootfs::filesystem)); - - auto filesystem::probe(inode_ptr const & inode) const -> kstd::result - { - if (inode) - { - return kstd::failure(vfs_errc::invalid_argument); - } - - return 0; - } - - auto filesystem::mount(inode_ptr const &) -> kstd::result - { - auto root = kstd::make_shared(); - return mount_result{root, nullptr}; - } - - auto filesystem::lookup(inode_ptr const &, std::string_view, driver_data_ptr) const -> kstd::result - { - return kstd::failure(vfs_errc::no_such_file_or_directory); - } - - auto filesystem::create_inode(inode_ptr const &, std::string_view, kapi::filesystem::file_type, driver_data_ptr, - std::optional) -> kstd::result - { - return kstd::failure(vfs_errc::read_only_file_system); - } -} // namespace kernel::filesystem::rootfs diff --git a/kernel/kernel/filesystem/rootfs/filesystem.hpp b/kernel/kernel/filesystem/rootfs/filesystem.hpp deleted file mode 100644 index 0195b250..00000000 --- a/kernel/kernel/filesystem/rootfs/filesystem.hpp +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_ROOTFS_FILESYSTEM_HPP -#define TEACHOS_KERNEL_FILESYSTEM_ROOTFS_FILESYSTEM_HPP - -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace kernel::filesystem::rootfs -{ - //! A filesystem for the filesystem root. - //! - //! 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 - { - [[nodiscard]] auto probe(inode_ptr const & inode) const -> kstd::result override; - - //! @copydoc kernel::filesystem::filesystem::mount - auto mount(inode_ptr const & backing_inode) -> kstd::result override; - - //! @copydoc kernel::filesystem::filesystem::lookup. - //! @return Always returns an error, as rootfs is always empty. - [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const - -> kstd::result override; - - //! @copydoc kernel::filesystem::filesystem::create_inode. - //! @return Always returns an error, as creating new inodes is not supported in rootfs. - [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, - driver_data_ptr driver_data, - std::optional raw_device = std::nullopt) - -> kstd::result override; - }; -} // namespace kernel::filesystem::rootfs - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp b/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp deleted file mode 100644 index 15c8e313..00000000 --- a/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include - -#include - -#include - -#include -#include -#include - -#include - -SCENARIO("Rootfs filesystem mount and lookup", "[filesystem][rootfs][filesystem]") -{ - GIVEN("a mounted rootfs filesystem") - { - auto fs = kstd::make_shared(); - auto mount = fs->mount(nullptr); - REQUIRE(mount); - auto [root, driver_data] = *mount; - REQUIRE(root); - - THEN("the filesystem can be mounted successfully") - { - REQUIRE(mount); - } - - THEN("looking up a non-existent directory returns an error") - { - auto non_existent_inode_1 = fs->lookup(root, "", driver_data); - REQUIRE(!non_existent_inode_1); - auto non_existent_inode_2 = fs->lookup(root, "nonexistent", driver_data); - REQUIRE(!non_existent_inode_2); - } - - THEN("looking up with a null parent inode returns an error") - { - auto result = fs->lookup(nullptr, "dev", driver_data); - REQUIRE(!result); - } - - THEN("create_inode always returns an error") - { - auto result = fs->create_inode(root, "new_device", kapi::filesystem::file_type::directory, driver_data); - REQUIRE(!result); - } - } -} diff --git a/kernel/kernel/filesystem/rootfs/inode.cpp b/kernel/kernel/filesystem/rootfs/inode.cpp deleted file mode 100644 index f2751afc..00000000 --- a/kernel/kernel/filesystem/rootfs/inode.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include - -namespace kernel::filesystem::rootfs -{ - auto inode::read(std::span, kstd::bytes) const -> kstd::result - { - return kstd::bytes{}; - } - - auto inode::write(std::span, kstd::bytes) -> kstd::result - { - return kstd::bytes{}; - } - - auto inode::is_directory() const -> bool - { - return true; - } - - auto inode::read_directory(directory_listing_cursor position, std::span) const - -> kstd::result> - { - return std::pair{0, position}; - } -} // namespace kernel::filesystem::rootfs diff --git a/kernel/kernel/filesystem/rootfs/inode.hpp b/kernel/kernel/filesystem/rootfs/inode.hpp deleted file mode 100644 index fdef0df4..00000000 --- a/kernel/kernel/filesystem/rootfs/inode.hpp +++ /dev/null @@ -1,36 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_ROOTFS_INODE_HPP -#define TEACHOS_KERNEL_FILESYSTEM_ROOTFS_INODE_HPP - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -namespace kernel::filesystem::rootfs -{ - //! An inode in the rootfs filesystem. - struct inode : kernel::filesystem::inode - { - [[nodiscard]] auto read(std::span buffer, kstd::bytes offset) const - -> kstd::result override; - - auto write(std::span buffer, kstd::bytes offset) -> kstd::result override; - - [[nodiscard]] auto is_directory() const -> bool override; - - [[nodiscard]] auto read_directory(directory_listing_cursor position, - std::span entries) const - -> kstd::result> override; - }; -} // namespace kernel::filesystem::rootfs - -#endif diff --git a/kernel/kernel/filesystem/rootfs/inode.tests.cpp b/kernel/kernel/filesystem/rootfs/inode.tests.cpp deleted file mode 100644 index 560e7953..00000000 --- a/kernel/kernel/filesystem/rootfs/inode.tests.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include - -#include -#include -#include -#include - -#include - -#include - -using namespace kstd::units_literals; - -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 buffer(10); - auto bytes_read = inode.read(buffer, 0_B); - - THEN("no bytes are read") - { - REQUIRE(bytes_read == 0_B); - } - } - - WHEN("writing to the inode") - { - kstd::vector buffer(10, std::byte{'x'}); - auto bytes_written = inode.write(buffer, 0_B); - - THEN("no bytes are written") - { - REQUIRE(bytes_written == 0_B); - } - } - } -} diff --git a/kernel/kernel/filesystem/rootfs/module.cpp b/kernel/kernel/filesystem/rootfs/module.cpp deleted file mode 100644 index 464120b2..00000000 --- a/kernel/kernel/filesystem/rootfs/module.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include -#include - -#include - -#include - -namespace -{ - - struct descriptor final : kernel::filesystem::driver_descriptor - { - [[nodiscard]] auto name() const noexcept -> std::string_view override - { - return "rootfs"; - } - - [[nodiscard]] auto make_instance() const -> kernel::filesystem::filesystem::filesystem_ptr override - { - return kstd::make_shared(); - } - }; - - [[gnu::used]] - constexpr auto registration = kernel::filesystem::driver_module{}; - -} // namespace \ No newline at end of file diff --git a/kernel/kernel/filesystem/vfs.cpp b/kernel/kernel/filesystem/vfs.cpp deleted file mode 100644 index 4645fd54..00000000 --- a/kernel/kernel/filesystem/vfs.cpp +++ /dev/null @@ -1,481 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace kstd::units_literals; - -namespace -{ - constinit auto static active_vfs = std::optional{}; -} // namespace - -namespace kernel::filesystem -{ - auto vfs::init() -> void - { - if (active_vfs) - { - kapi::system::panic("[FILESYSTEM] vfs has already been initialized."); - } - - active_vfs.emplace(); - } - - vfs::vfs() - { - // mount rootfs at / - auto rootfs = driver_registry::get().find("rootfs"); - if (!rootfs) - { - kapi::system::panic("[OS:FS] rootfs driver is missing!"); - } - - auto root_mount_result = (*rootfs)->mount(nullptr); - if (!root_mount_result) - { - kapi::system::panic("[FILESYSTEM] failed to mount root FS", root_mount_result.error()); - } - - auto [root_inode, root_state] = *root_mount_result; - - auto root_mount = mount::attach(nullptr, *rootfs, root_inode, root_state, nullptr, nullptr); - m_mount_table.add_mount(root_mount); - - // mount devfs at /dev (inside rootfs, temporary, will be shadowed) - auto devfs = driver_registry::get().find("devfs"); - if (!devfs) - { - kapi::system::panic("[OS:FS] devfs driver is missing!"); - } - - auto device_fs_mount_result = (*devfs)->mount(nullptr); - if (!device_fs_mount_result) - { - kapi::system::panic("[FILESYSTEM] failed to mount device FS", device_fs_mount_result.error()); - } - - auto [device_fs_root, device_fs_data] = *device_fs_mount_result; - graft_persistent_device_fs(*devfs, device_fs_root, device_fs_data); - - // mount boot fs at / (shadows rootfs), re-graft devfs - auto [boot_device_dentry, boot_device_mount_context] = - resolve_path_internal("/dev/ram0").value_or(std::pair{nullptr, nullptr}); - if (boot_device_dentry && boot_device_mount_context) - { - auto driver = driver_registry::get().match(boot_device_dentry->inode()); - if (!driver) - { - kstd::println(kstd::print_sink::stderr, "[OS:FS] Missing driver for root disk!"); - return; - } - - auto root_dentry = resolve_path("/"); - if (!root_dentry) - { - kapi::system::panic("[OS:FS] No root directory found!"); - } - - auto mount = - mount::create(*root_dentry, *driver, root_mount, boot_device_mount_context, boot_device_dentry->inode()); - - if (!mount) - { - kapi::system::panic("[OS:FS] failed to mount boot FS", mount.error()); - } - m_mount_table.add_mount(*mount); - - graft_persistent_device_fs(*devfs, device_fs_root, device_fs_data); - } - } - - auto vfs::get() -> vfs & - { - if (!active_vfs) - { - kapi::system::panic("[FILESYSTEM] vfs has not been initialized."); - } - - return *active_vfs; - } - - auto vfs::open(std::string_view path) -> kstd::result - { - auto resolved_path = resolve_path_internal(path); - if (!resolved_path) - { - return kstd::failure(resolved_path.error()); - } - - auto [dentry, mount] = resolved_path.value(); - auto inode = dentry->inode(); - - if (inode->is_device()) - { - auto number = inode->raw_device(); - if (!number) - { - return kstd::failure(vfs_errc::no_such_device); - } - - auto type = inode->is_block_device() ? kapi::filesystem::file_type::block_device - : kapi::filesystem::file_type::character_device; - auto resolved_device = device_number_registry::get().resolve(*number, type); - if (!resolved_device) - { - return kstd::failure(resolved_device.error()); - } - - auto device_dentry = kstd::make_shared( - dentry->parent(), kstd::make_shared(*resolved_device, inode), dentry->name()); - - mount->increment_ref_count(); - return device_dentry; - } - - mount->increment_ref_count(); - return dentry; - } - - auto vfs::close(std::string_view path) -> kstd::result - { - if (auto mount = find_mount(path)) - { - mount.value()->decrement_ref_count(); - return kstd::success(); - } - return kstd::failure(vfs_errc::invalid_path); - } - - auto vfs::mount(std::string_view source, std::string_view target) -> kstd::result - { - if (!path::is_valid_path(source) || !path::is_valid_path(target)) - { - return kstd::failure(vfs_errc::invalid_path); - } - - auto resolved_target = resolve_path_internal(target); - if (!resolved_target) - { - return kstd::failure(resolved_target.error()); - } - auto [mount_point_dentry, mount_context] = *resolved_target; - - auto resolved_source = resolve_path_internal(source); - if (!resolved_source) - { - return kstd::failure(resolved_source.error()); - } - auto [source_dentry, source_mount_context] = *resolved_source; - - auto driver = driver_registry::get().match(source_dentry->inode()); - if (!driver) - { - return kstd::failure(driver.error()); - } - - auto mount = - mount::create(mount_point_dentry, *driver, mount_context, source_mount_context, source_dentry->inode()); - if (!mount) - { - return kstd::failure(mount.error()); - } - m_mount_table.add_mount(*mount); - - return kstd::success(); - } - - auto vfs::unmount(std::string_view path) -> kstd::result - { - if (!path::is_valid_path(path)) - { - return kstd::failure(vfs_errc::invalid_path); - } - - auto remove_result = m_mount_table.remove_mount(path); - if (remove_result == mount_table::operation_result::removed) - { - return kstd::success(); - } - else if (remove_result == mount_table::operation_result::mount_not_found) - { - return kstd::failure(vfs_errc::mount_point_not_found); - } - else if (remove_result == mount_table::operation_result::cannot_be_unmounted) - { - return kstd::failure(vfs_errc::mount_busy); - } - else if (remove_result == mount_table::operation_result::has_child_mounts) - { - return kstd::failure(vfs_errc::has_child_mounts); - } - - return kstd::failure(vfs_errc::unmount_failed); - } - - auto vfs::mkdir(std::string_view path) -> kstd::result - { - return create_inode(path, kapi::filesystem::file_type::directory); - } - - auto vfs::create(std::string_view path) -> kstd::result - { - return create_inode(path, kapi::filesystem::file_type::regular); - } - - auto vfs::status(std::string_view path) const -> kstd::result - { - auto resolved = resolve_path(path); - if (!resolved) - { - return kstd::failure(resolved.error()); - } - - return (*resolved)->inode()->status(); - } - - auto vfs::create_device_node(std::string_view path, std::uint32_t mode, kapi::filesystem::device_number device) - -> kstd::result - { - if (kapi::filesystem::is_block_device(mode)) - { - return create_inode(path, kapi::filesystem::file_type::block_device, device); - } - else if (kapi::filesystem::is_character_device(mode)) - { - return create_inode(path, kapi::filesystem::file_type::character_device, device); - } - - return kstd::failure(vfs_errc::invalid_argument); - } - - auto vfs::create_inode(std::string_view path, kapi::filesystem::file_type type, - std::optional raw_device) -> kstd::result - { - if (!path::is_valid_path(path)) - { - return kstd::failure(vfs_errc::invalid_path); - } - - if (resolve_path(path)) - { - return kstd::failure(vfs_errc::file_exists); - } - - auto [parent_path, name] = path::split_into_path_and_filename(path); - if (name.empty()) - { - return kstd::failure(vfs_errc::invalid_path); - } - - auto resolved_parent = resolve_path_internal(parent_path); - if (!resolved_parent) - { - return kstd::failure(resolved_parent.error()); - } - - auto [parent_dentry, mount_context] = *resolved_parent; - - if (auto fs = mount_context->filesystem()) - { - if (auto new_inode = - fs->create_inode(parent_dentry->inode(), name, type, mount_context->driver_data(), raw_device)) - { - (*new_inode)->set_owning_mount(mount_context); - - auto new_dentry = kstd::make_shared(parent_dentry, *new_inode, name); - parent_dentry->add_child(new_dentry); - - return kstd::success(); - } - } - - return kstd::failure(vfs_errc::no_such_file_or_directory); - } - - auto vfs::graft_persistent_device_fs(kstd::shared_ptr const & device_fs, - kstd::shared_ptr const & root_inode, kstd::shared_ptr driver_data) - -> void - { - auto [root_mount_point_dentry, root_mount] = resolve_path_internal("/").value_or(std::pair{nullptr, nullptr}); - if (root_mount_point_dentry && root_mount) - { - auto dev_dentry = root_mount_point_dentry->find_child("dev"); - if (!dev_dentry) - { - dev_dentry = kstd::make_shared(root_mount_point_dentry, root_inode, "dev"); - root_mount_point_dentry->add_child(dev_dentry); - } - - auto new_mount = mount::attach(dev_dentry, device_fs, root_inode, driver_data, root_mount, nullptr); - m_mount_table.add_mount(new_mount); - } - } - - auto vfs::resolve_path_internal(std::string_view path) const -> kstd::result> - { - if (!path::is_valid_absolute_path(path)) - { - return kstd::failure(vfs_errc::invalid_path); - } - - auto current_mount = m_mount_table.find_mount("/"); - if (!current_mount) - { - kapi::system::panic("[FILESYSTEM] no root mount found."); - } - - auto current_dentry = current_mount->root_dentry(); - - auto path_parts = path::split(path); - kstd::vector path_parts_vector(path_parts.begin(), path_parts.end()); - std::ranges::reverse(path_parts_vector); - - auto symlink_counter = 0uz; - - while (!path_parts_vector.empty()) - { - auto part = path_parts_vector.back(); - path_parts_vector.pop_back(); - - if (!current_dentry->inode()->is_directory()) - { - return kstd::failure(vfs_errc::not_a_directory); - } - - if (part == ".") - { - continue; - } - - if (part == "..") - { - auto parent_dentry = current_dentry->parent(); - - if (current_dentry == current_mount->root_dentry()) - { - if (current_mount->mount_path() == "/") - { - continue; - } - - if (auto parent_mount = current_mount->parent_mount()) - { - current_mount = parent_mount; - current_dentry = parent_dentry; - } - } - - current_dentry = parent_dentry; - continue; - } - - auto next_dentry = current_dentry->find_child(part); - if (!next_dentry) - { - auto current_fs = current_mount->filesystem(); - auto found_inode = current_fs->lookup(current_dentry->inode(), part, current_mount->driver_data()); - if (!found_inode) - { - return kstd::failure(found_inode.error()); - } - - (*found_inode)->set_owning_mount(current_mount); - - next_dentry = kstd::make_shared(current_dentry, *found_inode, part); - current_dentry->add_child(next_dentry); - } - else if (next_dentry->has_flag(dentry::dentry_flags::is_mount_point)) - { - current_mount = m_mount_table.find_mount(next_dentry->absolute_path()); - if (!current_mount) - { - kapi::system::panic("[FILESYSTEM] mount for dentry with mounted flag not found."); - } - - next_dentry = current_mount->root_dentry(); - } - - if (next_dentry->inode()->is_symbolic_link()) - { - if (symlink_counter++ > constants::symloop_max) - { - return kstd::failure(vfs_errc::too_many_symbolic_link_levels); - } - - kstd::vector buffer(constants::symlink_max_path_length); - auto const bytes_read = next_dentry->inode()->read(buffer, 0_B); - if (!bytes_read) - { - return kstd::failure(bytes_read.error()); - } - - auto const symbolic_link_path = - std::string_view{reinterpret_cast(buffer.data()), bytes_read->value}; - - auto symbolic_link_parts = path::split(symbolic_link_path); - kstd::vector symbolic_link_parts_vector(symbolic_link_parts.begin(), symbolic_link_parts.end()); - std::ranges::reverse(symbolic_link_parts_vector); - - path_parts_vector.insert_range(path_parts_vector.end(), symbolic_link_parts_vector); - - if (path::is_valid_absolute_path(symbolic_link_path)) - { - current_mount = m_mount_table.find_mount("/"); - current_dentry = current_mount->root_dentry(); - } - continue; - } - - current_dentry = next_dentry; - } - return std::pair{current_dentry, current_mount}; - } - - auto vfs::resolve_path(std::string_view path) const -> kstd::result - { - return resolve_path_internal(path).transform([](auto result) { return result.first; }); - } - - auto vfs::find_mount(std::string_view path) const -> kstd::result - { - return resolve_path_internal(path).transform([](auto result) { return result.second; }); - } - -} // namespace kernel::filesystem - -namespace kernel::tests::filesystem::vfs -{ - auto deinit() -> void - { - active_vfs.reset(); - } -} // namespace kernel::tests::filesystem::vfs diff --git a/kernel/kernel/filesystem/vfs.hpp b/kernel/kernel/filesystem/vfs.hpp deleted file mode 100644 index a53ab532..00000000 --- a/kernel/kernel/filesystem/vfs.hpp +++ /dev/null @@ -1,125 +0,0 @@ -#ifndef TEACHOS_KERNEL_FILESYSTEM_VFS_HPP -#define TEACHOS_KERNEL_FILESYSTEM_VFS_HPP - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include - -namespace kernel::filesystem -{ - //! The virtual filesystem interface. - //! - //! 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 involves - //! resolving the path to the appropriate mounted filesystem and delegating the file operation to that filesystem's - //! implementation. - struct vfs - { - using dentry_ptr = kstd::shared_ptr; - using mount_ptr = kstd::shared_ptr; - using fs_ptr = kstd::shared_ptr; - - vfs(); - - //! Initialize the virtual filesystem. - //! - //! @warning This function panics if the VFS has already been initialized. - auto static init() -> void; - - //! Get the singleton instance of the virtual filesystem. - //! - //! @warning This function panics if the VFS has not been initialized yet. - //! - //! @return A reference to the VFS instance. - auto static get() -> vfs &; - - //! Open a file by its path. - //! - //! @param path The path to the file to open. - //! @return A shared pointer to the dentry on success or an error code on failure. - auto open(std::string_view path) -> kstd::result; - - //! Close a file by its associated path. - //! - //! @param path The path to the file to close. - //! @return Nothing on success or an error code on failure. - auto close(std::string_view path) -> kstd::result; - - //! Mount a source path to a specific target path. - //! - //! @param source The source of the filesystem to mount. - //! @param target The path where the filesystem should be mounted. - //! @return Nothing on success or an error code on failure. - auto mount(std::string_view source, std::string_view target) -> kstd::result; - - //! Unmount the filesystem mounted at the specified path. - //! - //! @param path The path where the filesystem is mounted. - //! @return Nothing on success or an error code on failure. - auto unmount(std::string_view path) -> kstd::result; - - //! Create a new directory at the specified path. - //! - //! @param path The path where the new directory should be created. - //! @return Nothing on success, an error otherwise. - auto mkdir(std::string_view path) -> kstd::result; - - //! Create a new file at the specified path. - //! - //! @param path The path where the new file should be created. - //! @return Nothing on success, an error otherwise. - // TODO remove after the open method supports flags. - auto create(std::string_view path) -> kstd::result; - - //! Get the status of the file at a given path. - //! - //! @return A populated file status object on success, an error otherwise. - [[nodiscard]] auto status(std::string_view path) const -> kstd::result; - - //! Create a new device node at a given path. - //! - //! @return Nothing on success, an error otherwise. - auto create_device_node(std::string_view path, std::uint32_t mode, kapi::filesystem::device_number device) - -> kstd::result; - - private: - // Note: Resolving a dentry requires traversing mount points; since the associated 'mount' object is discovered as a - // byproduct of this traversal, we return it alongside the dentry to avoid redundant lookups in callers that require - // mount context. - // - // If only one component is needed, the convenience wrappers can be used: - // - resolve_path() for the dentry only. - // - find_mount() for the mount context only. - [[nodiscard]] auto resolve_path_internal(std::string_view path) const - -> kstd::result>; - - [[nodiscard]] auto resolve_path(std::string_view path) const -> kstd::result; - - [[nodiscard]] auto find_mount(std::string_view path) const -> kstd::result; - - auto graft_persistent_device_fs(kstd::shared_ptr const & device_fs, - kstd::shared_ptr const & root_inode, kstd::shared_ptr driver_data) - -> void; - - auto create_inode(std::string_view path, kapi::filesystem::file_type type, - std::optional raw_device = std::nullopt) -> kstd::result; - - mount_table m_mount_table{}; - }; -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/vfs.tests.cpp b/kernel/kernel/filesystem/vfs.tests.cpp deleted file mode 100644 index aac384fb..00000000 --- a/kernel/kernel/filesystem/vfs.tests.cpp +++ /dev/null @@ -1,703 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -using namespace kstd::units_literals; - -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("a real image file containing a /dev directory") - { - REQUIRE(std::filesystem::exists(image_path_2)); - REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_2"}, {image_path_2})); - - THEN("vfs hides the image's /dev behind the devfs mount") - { - auto & vfs = kernel::filesystem::vfs::get(); - - auto image_1 = vfs.open("/dev/image_1.txt"); - REQUIRE(image_1.error() == kstd::errc::no_such_file_or_directory); - - auto dev = vfs.open("/dev/ram0"); - REQUIRE(dev != 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(); - - 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.mount("/dev/ram1", "/information")); - - auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); - REQUIRE(mounted_monkey_1); - REQUIRE(vfs.close(mounted_monkey_1.value()->absolute_path())); - - REQUIRE(vfs.unmount("/information")); - auto unmounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); - REQUIRE(unmounted_monkey_1.error() == kstd::errc::no_such_file_or_directory); - - 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.mount("/dev/ram1", "/information")); - REQUIRE(vfs.mount("/dev/ram2", "/information/monkey_house/infrastructure")); - - 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); - REQUIRE(mounted_fish1); - - REQUIRE(vfs.close(mounted_monkey_1.value()->absolute_path())); - REQUIRE(vfs.close(mounted_fish1.value()->absolute_path())); - - REQUIRE(!vfs.unmount("/information")); - REQUIRE(vfs.unmount("/information/monkey_house/infrastructure")); - REQUIRE(vfs.unmount("/information")); - } - - THEN("image can be mounted, unmount only if no files are open") - { - REQUIRE(vfs.mount("/dev/ram1", "/information")); - - auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); - REQUIRE(mounted_monkey_1); - - REQUIRE(!vfs.unmount("/information")); - - REQUIRE(vfs.close(mounted_monkey_1.value()->absolute_path())); - - REQUIRE(vfs.unmount("/information")); - } - - THEN("file with invalid path or not opened file cannot be closed") - { - REQUIRE(!vfs.close("invalid_path")); - REQUIRE_THROWS_AS(vfs.close("/information/info_1.txt"), std::runtime_error); - } - - THEN("file cannot be closed twice") - { - auto info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1); - - REQUIRE(vfs.close(info_1.value()->absolute_path())); - REQUIRE_THROWS_AS(vfs.close(info_1.value()->absolute_path()), std::runtime_error); - } - - THEN("images can be stacked mounted and correct file system is unmounted again") - { - REQUIRE(vfs.mount("/dev/ram1", "/information")); - REQUIRE(vfs.mount("/dev/ram2", "/information")); - - auto mounted_tickets = vfs.open("/information/entrance/tickets.txt"); - REQUIRE(mounted_tickets); - - REQUIRE(vfs.close(mounted_tickets.value()->absolute_path())); - - REQUIRE(vfs.unmount("/information")); - mounted_tickets = vfs.open("/information/entrance/tickets.txt"); - REQUIRE(mounted_tickets.error() == kstd::errc::no_such_file_or_directory); - - auto mounted_monkey = vfs.open("/information/monkey_house/monkey_1.txt"); - REQUIRE(mounted_monkey); - } - - THEN("image can be mounted on / file opened and unmounted again") - { - auto info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1 != nullptr); - - REQUIRE(vfs.mount("/dev/ram1", "/")); - - info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1.error() == kstd::errc::no_such_file_or_directory); - - auto water = vfs.open("/monkey_house/infrastructure/water.txt"); - REQUIRE(water != nullptr); - - REQUIRE(vfs.close(water.value()->absolute_path())); - - REQUIRE(vfs.unmount("/")); - - info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1 != nullptr); - } - - THEN("image can be mounted on / just the boot root has /dev") - { - auto info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1 != nullptr); - - REQUIRE(vfs.mount("/dev/ram1", "/")); - - info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1.error() == kstd::errc::no_such_file_or_directory); - - auto water = vfs.open("/monkey_house/infrastructure/water.txt"); - REQUIRE(water != nullptr); - - REQUIRE(vfs.close(water.value()->absolute_path())); - - auto dev_ram_16 = vfs.open("/dev/ram16"); - REQUIRE(dev_ram_16.error() == kstd::errc::no_such_file_or_directory); - - REQUIRE(vfs.mount("/dev/ram32", "/").error() == kstd::errc::no_such_file_or_directory); - - REQUIRE(vfs.unmount("/")); - - auto dev_ram_32 = vfs.open("/dev/ram32"); - REQUIRE(dev_ram_32 != nullptr); - } - - THEN("boot root can be unmounted and remounted again but /dev is not re-grafted") - { - auto info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1 != nullptr); - - REQUIRE(vfs.close(info_1.value()->absolute_path())); - - REQUIRE(vfs.unmount("/dev")); - REQUIRE(vfs.unmount("/")); - - info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1.error() == kstd::errc::no_such_file_or_directory); - - REQUIRE(vfs.mount("/dev/ram0", "/")); - - info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1 != nullptr); - - auto dev_ram_0 = vfs.open("/dev/ram0"); - REQUIRE(dev_ram_0.error() == kstd::errc::no_such_file_or_directory); - } - - THEN("mount with null file system fails") - { - REQUIRE(vfs.mount("/closed.txt", "/information").error() == kstd::errc::not_supported); - } - - THEN("mount with invalid path fails") - { - REQUIRE(vfs.mount("/dev/ram16", "").error() == kstd::errc::invalid_argument); - REQUIRE(vfs.mount("/dev/ram16", "information").error() == kstd::errc::invalid_argument); - } - - THEN("mount with non-existent source path fails") - { - REQUIRE(vfs.mount("/dev/nonexistent", "/information").error() == kstd::errc::no_such_file_or_directory); - } - - THEN("mount with non-existent mount point fails") - { - REQUIRE(vfs.mount("/dev/ram16", "/information/nonexistent").error() == kstd::errc::no_such_file_or_directory); - } - - THEN("unmount with invalid path fails") - { - REQUIRE(vfs.unmount("").error() == kstd::errc::invalid_argument); - REQUIRE(vfs.unmount("information").error() == kstd::errc::no_such_file_or_directory); - } - - THEN("unmounting non-existent mount point returns expected error code") - { - REQUIRE(vfs.unmount("/information/nonexistent").error() == kstd::errc::no_such_file_or_directory); - } - - THEN("a file can be access if . in the path") - { - auto info_1 = vfs.open("/information/./info_1.txt"); - REQUIRE(info_1 != nullptr); - } - - THEN("a file can be accessed within the same mount if path contains .. ") - { - auto info_1 = vfs.open("/archiv/../information/info_1.txt"); - REQUIRE(info_1 != nullptr); - - auto img = vfs.open("/archiv/../information/../archiv/2024.img"); - REQUIRE(img != nullptr); - } - - THEN("a file can be accessed over multiple mounts if path contains .. or . ") - { - REQUIRE(vfs.mount("/dev/ram1", "/information")); - - auto img = vfs.open("/information/monkey_house/caretaker/../../../../../../archiv/2024.img"); - REQUIRE(img != nullptr); - - auto dev_32 = vfs.open("/information/monkey_house/caretaker/../../../dev/ram32"); - REQUIRE(dev_32 != nullptr); - - auto water = vfs.open("/information/./monkey_house/infrastructure/water.txt"); - REQUIRE(water != nullptr); - } - - THEN("a file can be accessed over multiple mounts (device and file) if path contains .. ") - { - REQUIRE(vfs.mount("/dev/ram1", "/information")); - REQUIRE(vfs.mount("/archiv/2024.img", "/information/monkey_house/infrastructure")); - - auto pig_1 = vfs.open("/information/monkey_house/infrastructure/stable/pig_1.txt"); - REQUIRE(pig_1 != nullptr); - - auto isabelle = - vfs.open("/information/monkey_house/infrastructure/stable/../../../monkey_house/caretaker/isabelle.txt"); - REQUIRE(isabelle != nullptr); - - auto closed = vfs.open("/information/monkey_house/infrastructure/stable/../../../../closed.txt"); - REQUIRE(closed != nullptr); - } - } - - GIVEN("A real image file within which new files and directories can be created") - { - REQUIRE(std::filesystem::exists(image_path_1)); - REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1})); - - THEN("a new file can be created and read again") - { - auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(vfs.create("/information/new_file.txt")); - - auto new_file = vfs.open("/information/new_file.txt"); - REQUIRE(new_file != nullptr); - } - - THEN("a new directory can be created and read again") - { - auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(vfs.mkdir("/information/new_directory")); - } - - THEN("a new file cannot be created if it already exists") - { - auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(!vfs.create("/information/info_1.txt")); - } - - THEN("a new directory cannot be created if it already exists") - { - auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(!vfs.mkdir("/information")); - } - - THEN("a new file cannot be created if path does not exist or is invalid") - { - auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(!vfs.create("")); - REQUIRE(!vfs.create("invalid_path")); - } - - THEN("a new directory cannot be created if path does not exist or is invalid") - { - auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(!vfs.mkdir("")); - REQUIRE(!vfs.mkdir("invalid_path")); - } - } - - GIVEN("A real image file containing as filesystem formatted files") - { - REQUIRE(std::filesystem::exists(image_path_1)); - REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1})); - - THEN("the file-filesystem in the image can be mounted, files can be read and unmounted again") - { - auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(vfs.mount("/archiv/2024.img", "/information")); - - auto info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1.error() == kstd::errc::no_such_file_or_directory); - - auto dentry = vfs.open("/information/sheep_1.txt"); - REQUIRE(dentry != nullptr); - auto sheep_1_ofd = kstd::make_shared(dentry.value()); - - kstd::vector buffer(7); - auto bytes_read = sheep_1_ofd->read(buffer); - std::string_view buffer_as_str{reinterpret_cast(buffer.data()), bytes_read->value}; - REQUIRE(buffer_as_str == "sheep_1"); - - REQUIRE(vfs.close(dentry.value()->absolute_path())); - - REQUIRE(vfs.unmount("/information")); - auto unmounted_sheep_1 = vfs.open("/information/sheep_1.txt"); - REQUIRE(unmounted_sheep_1.error() == kstd::errc::no_such_file_or_directory); - } - - THEN("the file-filesystem in the image can be mounted and in this filesystem can another file-filesystem be " - "mounted, files can be read and unmounted again") - { - auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(vfs.mount("/archiv/2024.img", "/information")); - REQUIRE(vfs.mount("/archiv/2025.img", "/information/stable")); - - auto sheep_1 = vfs.open("/information/sheep_1.txt"); - auto goat_1 = vfs.open("/information/stable/petting_zoo/goat_1.txt"); - REQUIRE(sheep_1 != nullptr); - REQUIRE(goat_1 != nullptr); - - auto sheep_1_ofd = kstd::make_shared(sheep_1.value()); - auto goat_1_ofd = kstd::make_shared(goat_1.value()); - - kstd::vector sheep_buffer(7); - auto bytes_read = sheep_1_ofd->read(sheep_buffer); - std::string_view buffer_as_str{reinterpret_cast(sheep_buffer.data()), bytes_read->value}; - REQUIRE(buffer_as_str == "sheep_1"); - - kstd::vector goat_buffer(6); - bytes_read = goat_1_ofd->read(goat_buffer); - buffer_as_str = std::string_view{reinterpret_cast(goat_buffer.data()), bytes_read->value}; - REQUIRE(buffer_as_str == "goat_1"); - - REQUIRE(vfs.close(sheep_1.value()->absolute_path())); - REQUIRE(vfs.close(goat_1.value()->absolute_path())); - - REQUIRE(vfs.unmount("/information").error() == kstd::errc::device_or_resource_busy); - - REQUIRE(vfs.unmount("/information/stable")); - auto unmounted_goat_1 = vfs.open("/information/stable/petting_zoo/goat_1.txt"); - REQUIRE(unmounted_goat_1.error() == kstd::errc::no_such_file_or_directory); - - auto still_mounted_sheep_1 = vfs.open("/information/sheep_1.txt"); - REQUIRE(still_mounted_sheep_1 != nullptr); - - REQUIRE(vfs.close(still_mounted_sheep_1.value()->absolute_path())); - - REQUIRE(vfs.unmount("/information")); - auto unmounted_sheep_1 = vfs.open("/information/sheep_1.txt"); - REQUIRE(unmounted_sheep_1.error() == kstd::errc::no_such_file_or_directory); - } - } - - GIVEN("two real image files where the second contains as filesystem formatted files") - { - REQUIRE(std::filesystem::exists(image_path_1)); - REQUIRE(std::filesystem::exists(image_path_3)); - REQUIRE_NOTHROW( - setup_modules_from_img_and_init_vfs({"test_img_module_3", "test_img_module_1"}, {image_path_3, image_path_1})); - - auto & vfs = kernel::filesystem::vfs::get(); - - THEN("cannot unmount a filesystem if files are mounted") - { - REQUIRE(vfs.mount("/dev/ram1", "/entrance")); - REQUIRE(vfs.mount("/entrance/archiv/2024.img", "/enclosures")); - - REQUIRE(vfs.unmount("/entrance").error() == kstd::errc::device_or_resource_busy); - REQUIRE(vfs.unmount("/enclosures")); - REQUIRE(vfs.unmount("/entrance")); - } - - THEN("can mount filesystem onto the directory that contains it") - { - REQUIRE(vfs.mount("/dev/ram1", "/entrance")); - REQUIRE(vfs.mount("/entrance/archiv/2024.img", "/entrance")); - - REQUIRE(vfs.unmount("/entrance")); - REQUIRE(vfs.unmount("/entrance")); - } - } - - GIVEN("A real image files, containing symbolic links") - { - REQUIRE(std::filesystem::exists(image_path_1)); - REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1})); - - THEN("file can be opened through absolute symbolic link") - { - auto & vfs = kernel::filesystem::vfs::get(); - auto info_1 = vfs.open("/symlinks/info_1_absolute"); - REQUIRE(info_1 != nullptr); - } - - THEN("file can be opened through relative symbolic link") - { - auto & vfs = kernel::filesystem::vfs::get(); - auto info_1 = vfs.open("/symlinks/info_1_relative"); - REQUIRE(info_1 != nullptr); - } - - THEN("file can be opened through symbolic link pointing absolute to the directory containing the file") - { - auto & vfs = kernel::filesystem::vfs::get(); - auto info_1 = vfs.open("/symlinks/information_directory_absolute/info_1.txt"); - REQUIRE(info_1 != nullptr); - } - - THEN("file can be opened through symbolic link pointing relative to the directory containing the file") - { - auto & vfs = kernel::filesystem::vfs::get(); - auto info_1 = vfs.open("/symlinks/information_directory_relative/info_1.txt"); - REQUIRE(info_1 != nullptr); - } - - THEN("symbolic link with path traversing back to the root") - { - auto & vfs = kernel::filesystem::vfs::get(); - auto info_1 = vfs.open("/symlinks/traverse_back_5_times/information/info_1.txt"); - REQUIRE(info_1 != nullptr); - } - - THEN("symbolic link containing an invalid absolute path is handled correctly") - { - auto & vfs = kernel::filesystem::vfs::get(); - auto invalid_symlink = vfs.open("/symlinks/invalid_absolute"); - REQUIRE(invalid_symlink.error() == kstd::errc::no_such_file_or_directory); - } - - THEN("symbolic link containing an invalid relative path is handled correctly") - { - auto & vfs = kernel::filesystem::vfs::get(); - auto invalid_symlink = vfs.open("/symlinks/invalid_relative"); - REQUIRE(invalid_symlink.error() == kstd::errc::no_such_file_or_directory); - } - - THEN("circular symbolic links are detected and handled correctly") - { - auto & vfs = kernel::filesystem::vfs::get(); - auto circular_symlink = vfs.open("/symlinks/symloop_a"); - REQUIRE(circular_symlink.error() == kstd::errc::too_many_symbolic_link_levels); - } - } - - GIVEN("A real image file containing as filesystem formatted files and this filesystem contains a symbolic link") - { - REQUIRE(std::filesystem::exists(image_path_1)); - REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1})); - - auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(vfs.mount("/archiv/2024.img", "/information")); - - THEN("file can be opened through symbolic link pointing to the parent filesystem") - { - auto closed_file = vfs.open("/information/symlinks/traverse_back_twice/closed.txt"); - REQUIRE(closed_file != nullptr); - } - } - - GIVEN("Two real images, one containing a symbolic link leaving and reentering filesystem again") - { - REQUIRE(std::filesystem::exists(image_path_1)); - REQUIRE(std::filesystem::exists(image_path_2)); - REQUIRE_NOTHROW( - setup_modules_from_img_and_init_vfs({"test_img_module_1", "test_img_module_2"}, {image_path_1, image_path_2})); - - auto & vfs = kernel::filesystem::vfs::get(); - REQUIRE(vfs.mount("/dev/ram1", "/information")); - - THEN("file can be opened through symbolic link pointing to the parent filesystem and back into the mounted " - "filesystem again") - { - auto monkey_1 = vfs.open("/information/symlinks/leave_and_reenter_mount/monkey_1.txt"); - REQUIRE(monkey_1 != nullptr); - } - } - - GIVEN("One real image containing a very long symbolic link") - { - REQUIRE(std::filesystem::exists(image_path_3)); - REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_3"}, {image_path_3})); - - auto & vfs = kernel::filesystem::vfs::get(); - - THEN("file can be opened through symbolic link with a long path") - { - auto fish_30 = vfs.open("/symlinks/very_long_symlink"); - REQUIRE(fish_30 != nullptr); - } - } - - GIVEN("One real image containing a valid symbolic link chain") - { - REQUIRE(std::filesystem::exists(image_path_3)); - REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_3"}, {image_path_3})); - - auto & vfs = kernel::filesystem::vfs::get(); - - THEN("file can be opened through symbolic link chain") - { - auto map = vfs.open("/symlinks/symlink_chain_1/map.txt"); - REQUIRE(map != nullptr); - } - } -} - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "VFS status()", "[filesystem][vfs][img]") -{ - auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; - - GIVEN("a real image file") - { - 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(); - - THEN("status(/dev/ram0) reports it as the correct block device") - { - auto result = vfs.status("/dev/ram0"); - REQUIRE(result); - REQUIRE(kapi::filesystem::is_block_device(result->mode)); - REQUIRE(result->raw_device.major == kernel::filesystem::block_major_numbers::ram_disk); - } - - THEN("status(/information/info_1.txt) reports it as a regular file") - { - auto result = vfs.status("/information/info_1.txt"); - REQUIRE(result); - REQUIRE(kapi::filesystem::is_regular(result->mode)); - REQUIRE(result->raw_device == kapi::filesystem::device_number{}); - } - - WHEN("creating a persistent device node on the ext2 filesystem, resolving to /dev/ram0") - { - auto ram0_status = vfs.status("/dev/ram0"); - REQUIRE(ram0_status); - - auto number = kapi::filesystem::device_number{ - ram0_status->raw_device.major, - ram0_status->raw_device.minor, - }; - - REQUIRE(vfs.create_device_node("/persistent_ram0", - std::to_underlying(kapi::filesystem::file_type::block_device) | 0660, number)); - - THEN("opening it reaches the same device as /dev/ram0") - { - auto persistent_node_status = vfs.status("/persistent_ram0"); - REQUIRE(persistent_node_status); - REQUIRE(persistent_node_status->raw_device == ram0_status->raw_device); - - auto via_devfs = vfs.open("/dev/ram0"); - REQUIRE(via_devfs != nullptr); - - auto via_persistent_fs = vfs.open("/persistent_ram0"); - REQUIRE(via_persistent_fs != nullptr); - - auto marker = kstd::vector{std::byte{'M'}, std::byte{'K'}, std::byte{'N'}, std::byte{'O'}, std::byte{'D'}}; - auto written = (*via_persistent_fs)->inode()->write(marker, 0_B); - REQUIRE(written == kstd::bytes{marker.size()}); - - auto read_back = kstd::vector(marker.size()); - auto read = (*via_devfs)->inode()->read(read_back, 0_B); - REQUIRE(read == kstd::bytes{marker.size()}); - - REQUIRE(read_back == marker); - } - } - - WHEN("create_device_node() is called with a number that matches no live device") - { - auto const number = kapi::filesystem::device_number{.major = 250, .minor = 250}; - REQUIRE(vfs.create_device_node("/nowhere", std::to_underlying(kapi::filesystem::file_type::block_device) | 0660, - number)); - - THEN("opening it fails with ENODEV, not a panic") - { - auto result = vfs.open("/nowhere"); - REQUIRE(!result); - REQUIRE(result.error() == kstd::errc::no_such_device); - } - } - - THEN("create_device_node() with a mode that isn't a device type fails with invalid_argument") - { - auto const number = kapi::filesystem::device_number{.major = 1, .minor = 0}; - auto result = vfs.create_device_node("/not_a_device", - std::to_underlying(kapi::filesystem::file_type::regular) | 0644, number); - REQUIRE(!result); - REQUIRE(result.error() == kstd::errc::invalid_argument); - } - } -} diff --git a/kernel/kernel/filesystems/devfs/filesystem.cpp b/kernel/kernel/filesystems/devfs/filesystem.cpp new file mode 100644 index 00000000..b6a3c084 --- /dev/null +++ b/kernel/kernel/filesystems/devfs/filesystem.cpp @@ -0,0 +1,82 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace kernel::filesystems::devfs +{ + + static_assert(sizeof(kernel::vfs::filesystem) == sizeof(devfs::filesystem)); + + auto filesystem::probe(inode_ptr const & inode) const -> kstd::result + { + if (inode) + { + return kstd::failure(vfs::errc::invalid_argument); + } + + return 0; + } + + auto filesystem::mount(inode_ptr const &) -> kstd::result + { + auto root = kstd::make_shared(); + return mount_result{root, root}; + } + + auto filesystem::lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const + -> kstd::result + { + if (!parent) + { + return kstd::failure(vfs::errc::invalid_inode); + } + + if (!parent->is_directory()) + { + return kstd::failure(vfs::errc::not_a_directory); + } + + if (parent.get() != driver_data.get()) + { + return kstd::failure(vfs::errc::invalid_inode); + } + + auto entries = vfs::device_number_registry::get().all(); + auto found = std::ranges::find_if(entries, [&](auto const & entry) { return entry.name == name; }); + + if (found == entries.end()) + { + return kstd::failure(vfs::errc::no_such_file_or_directory); + } + + if (auto device = found->device.lock()) + { + return kstd::make_shared(device); + } + + return kstd::failure(vfs::errc::no_such_file_or_directory); + } + + auto filesystem::create_inode(inode_ptr const &, std::string_view, kapi::filesystem::file_type, driver_data_ptr, + std::optional) -> kstd::result + { + return kstd::failure(vfs::errc::read_only_file_system); + } + +} // namespace kernel::filesystems::devfs \ No newline at end of file diff --git a/kernel/kernel/filesystems/devfs/filesystem.hpp b/kernel/kernel/filesystems/devfs/filesystem.hpp new file mode 100644 index 00000000..af9a973a --- /dev/null +++ b/kernel/kernel/filesystems/devfs/filesystem.hpp @@ -0,0 +1,46 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_DEVFS_FILESYSTEM_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_DEVFS_FILESYSTEM_HPP + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include + +namespace kernel::filesystems::devfs +{ + + //! 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 final : kernel::vfs::filesystem + { + [[nodiscard]] auto probe(inode_ptr const & inode) const -> kstd::result override; + + auto mount(inode_ptr const & backing_inode) -> kstd::result override; + + [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const + -> kstd::result override; + + [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, + driver_data_ptr driver_data, + std::optional raw_device = std::nullopt) + -> kstd::result override; + }; +} // namespace kernel::filesystems::devfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/devfs/filesystem.tests.cpp b/kernel/kernel/filesystems/devfs/filesystem.tests.cpp new file mode 100644 index 00000000..771ba247 --- /dev/null +++ b/kernel/kernel/filesystems/devfs/filesystem.tests.cpp @@ -0,0 +1,134 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include + +#include + +using namespace kstd::units_literals; + +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_fixture, + "Devfs filesystem lookup uses storage management devices", "[filesystem][devfs]") +{ + GIVEN("a boot module registry with one module") + { + setup_modules(1); + + auto fs = kstd::make_shared(); + auto mount = fs->mount(nullptr); + REQUIRE(mount); + auto [root, driver_data] = *mount; + REQUIRE(root); + + THEN("lookup on root finds ram0 device inode") + { + auto inode = fs->lookup(root, "ram0", driver_data); + REQUIRE(inode); + REQUIRE(inode.value()->is_device()); + } + + THEN("lookup of an unknown device returns error") + { + auto inode = fs->lookup(root, "ram99", driver_data); + REQUIRE(!inode); + } + + THEN("lookup with wrong parent returns error") + { + auto other_fs = kstd::make_shared(); + auto other_root = other_fs->mount(nullptr); + CHECK(other_root); + + auto inode = fs->lookup(other_root->first, "ram0", driver_data); + REQUIRE(!inode); + } + + THEN("lookup with a non-directory parent returns error") + { + auto non_directory_inode = fs->lookup(root, "ram0", driver_data); + REQUIRE(non_directory_inode); + REQUIRE_FALSE(non_directory_inode.value()->is_directory()); + + auto result = fs->lookup(*non_directory_inode, "anything", driver_data); + REQUIRE(!result); + } + + THEN("create_inode always returns an error") + { + auto result = fs->create_inode(root, "new_device", kapi::filesystem::file_type::regular, driver_data); + REQUIRE(!result); + } + } + + GIVEN("a boot module registry with three modules") + { + setup_modules(3, 2048); + + auto fs = kstd::make_shared(); + auto mount = fs->mount(nullptr); + REQUIRE(mount); + auto [root, driver_data] = *mount; + REQUIRE(root); + + THEN("lookup finds all generated RAM devices") + { + REQUIRE(fs->lookup(root, "ram0", driver_data)); + REQUIRE(fs->lookup(root, "ram1", driver_data)); + REQUIRE(fs->lookup(root, "ram2", driver_data)); + } + } +} + +SCENARIO("Devfs filesystem updates", "[filesystem][devfs]") +{ + GIVEN("A mounted devfs, and a bus attached to the root without a device attached") + { + auto fs = kstd::make_shared(); + auto mount = fs->mount(nullptr); + REQUIRE(mount); + auto [root, driver_data] = *mount; + REQUIRE(root); + + auto bus = kstd::make_shared("devfs_live_update_bus"); + kapi::devices::get_root_bus()->add_child(bus); + + REQUIRE_FALSE(fs->lookup(root, "devfs_live_update_device_node", driver_data)); + + WHEN("a device is attached, bound, and published") + { + auto device = kstd::make_shared("devfs_live_update_device", 512_B); + bus->add_child(device); + CHECK(kernel::tests::devices::bind(*device, std::uint8_t{99})); + + REQUIRE( + kapi::devices::publish_facet(device, "devfs_live_update_device_node")); + + THEN("devfs finds it immediately") + { + REQUIRE(fs->lookup(root, "devfs_live_update_device_node", driver_data)); + } + + WHEN("the device is detached") + { + REQUIRE(kapi::devices::remove_device(*device)); + + THEN("devfs does not find it anymore") + { + REQUIRE_FALSE(fs->lookup(root, "devfs_live_update_device_node", driver_data)); + } + } + } + + REQUIRE(kapi::devices::remove_device(*bus)); + } +} \ No newline at end of file diff --git a/kernel/kernel/filesystems/devfs/inode.cpp b/kernel/kernel/filesystems/devfs/inode.cpp new file mode 100644 index 00000000..01ce185f --- /dev/null +++ b/kernel/kernel/filesystems/devfs/inode.cpp @@ -0,0 +1,102 @@ +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace kstd::units_literals; + +namespace kernel::filesystems::devfs +{ + namespace + { + constexpr auto static sentinel = vfs::directory_listing_cursor{std::numeric_limits::max()}; + } + + auto inode::read(std::span, kstd::bytes) const -> kstd::result + { + return 0_B; + } + + auto inode::write(std::span, kstd::bytes) -> kstd::result + { + return 0_B; + } + + auto inode::is_directory() const -> bool + { + return true; + } + + auto inode::read_directory(vfs::directory_listing_cursor position, + std::span entries) const + -> kstd::result> + { + auto const requested = entries.size(); + if (requested == 0) + { + return kstd::failure(vfs::errc::invalid_argument); + } + + if (position == sentinel) + { + return std::pair{0, sentinel}; + } + + auto devices = vfs::device_number_registry::get().all(); + if (devices.empty()) + { + return std::pair{0, position}; + } + + std::ranges::stable_sort(devices, std::ranges::less{}, &vfs::device_number_registry::entry::number); + + auto const device_number = kapi::filesystem::device_number{static_cast(position.value >> 8), + static_cast(position.value & 0xff)}; + auto const begin = std::ranges::lower_bound(devices, device_number, std::ranges::less{}, + &vfs::device_number_registry::entry::number); + auto const end = std::ranges::end(devices); + auto const to_read = std::min(requested, static_cast(std::ranges::distance(begin, end))); + + if (to_read == 0) + { + return std::pair{0, position}; + } + + std::ranges::transform(begin, begin + to_read, std::ranges::begin(entries), [](auto const & entry) { + return vfs::directory_listing_entry{ + .name = entry.name, + .type = entry.type, + .inode_number = std::bit_cast(entry.number), + }; + }); + + auto const last_copied = begin + to_read; + if (last_copied == end) + { + return std::pair{to_read, sentinel}; + } + + auto const new_device_number = last_copied->number; + auto const new_cursor = vfs::directory_listing_cursor{static_cast(new_device_number.minor) | + (static_cast(new_device_number.major) << 8)}; + return std::pair{to_read, new_cursor}; + } +} // namespace kernel::filesystems::devfs \ No newline at end of file diff --git a/kernel/kernel/filesystems/devfs/inode.hpp b/kernel/kernel/filesystems/devfs/inode.hpp new file mode 100644 index 00000000..a4bbccfa --- /dev/null +++ b/kernel/kernel/filesystems/devfs/inode.hpp @@ -0,0 +1,35 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_DEVFS_INODE_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_DEVFS_INODE_HPP + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace kernel::filesystems::devfs +{ + //! Inode implementation for the devfs filesystem. + //! + //! This inode represents root device node in the /dev directory. + struct inode final : kernel::vfs::inode + { + [[nodiscard]] auto read(std::span buffer, kstd::bytes offset) const + -> kstd::result override; + + auto write(std::span buffer, kstd::bytes offset) -> kstd::result override; + + [[nodiscard]] auto is_directory() const -> bool override; + + [[nodiscard]] auto read_directory(vfs::directory_listing_cursor position, + std::span entries) const + -> kstd::result> override; + }; +} // namespace kernel::filesystems::devfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/devfs/inode.tests.cpp b/kernel/kernel/filesystems/devfs/inode.tests.cpp new file mode 100644 index 00000000..956db26b --- /dev/null +++ b/kernel/kernel/filesystems/devfs/inode.tests.cpp @@ -0,0 +1,235 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +using namespace kstd::units_literals; + +namespace +{ + auto make_and_publish(kstd::shared_ptr const & bus, kstd::string name) + -> kstd::shared_ptr + + { + auto device = kstd::make_shared(name, 512_B); + bus->add_child(device); + REQUIRE(kernel::tests::devices::bind(*device)); + REQUIRE(kapi::devices::publish_facet(device, name)); + return device; + } +} // namespace + +SCENARIO("Devfs inode creation", "[filesystem][devfs][inode]") +{ + GIVEN("a devfs inode") + { + auto inode = kernel::filesystems::devfs::inode{}; + + THEN("the inode has the correct kind") + { + REQUIRE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_symbolic_link()); + } + } +} + +SCENARIO("Devfs inode read/write", "[filesystem][devfs][inode]") +{ + GIVEN("a devfs inode") + { + auto inode = kernel::filesystems::devfs::inode{}; + + WHEN("attempting to read from the devfs inode") + { + kstd::vector buffer(512); + auto bytes_read = inode.read(buffer, 0_B); + + THEN("no bytes are read") + { + REQUIRE(bytes_read == 0_B); + } + } + + WHEN("attempting to write to the devfs inode") + { + kstd::vector buffer(512); + auto bytes_written = inode.write(buffer, 0_B); + + THEN("no bytes are written") + { + REQUIRE(bytes_written == 0_B); + } + } + } +} + +SCENARIO("Devfs inode directory listing", "[filesystem][devfs][inode]") +{ + GIVEN("a devfs inode and no registered devices") + { + auto root = kernel::filesystems::devfs::inode{}; + + THEN("the directory listing is empty") + { + auto entries = kstd::vector{4}; + auto result = root.read_directory(kernel::vfs::directory_listing_cursor{}, entries); + + REQUIRE(result); + auto [count, next] = *result; + REQUIRE(count == 0); + } + } + + GIVEN("a devfs inode and three registered devices") + { + auto root = kernel::filesystems::devfs::inode{}; + + auto bus = kstd::make_shared("devfs_readdir_bus"); + kapi::devices::get_root_bus()->add_child(bus); + + auto device1 = make_and_publish(bus, "devfs_readdir_device1"); + auto device2 = make_and_publish(bus, "devfs_readdir_device2"); + auto device3 = make_and_publish(bus, "devfs_readdir_device3"); + + WHEN("the directory listing is requested with a buffer large enough for all of them") + { + auto entries = kstd::vector{4}; + auto result = root.read_directory(kernel::vfs::directory_listing_cursor{}, entries); + + THEN("all devices are listed") + { + REQUIRE(result); + auto [count, next] = *result; + REQUIRE(count == 3); + + auto names = kstd::vector{3}; + for (auto i = 0uz; i < count; ++i) + { + REQUIRE(entries[i].type == kapi::filesystem::file_type::block_device); + names.push_back(entries[i].name); + } + + REQUIRE(std::ranges::find(names, "devfs_readdir_device1") != names.end()); + REQUIRE(std::ranges::find(names, "devfs_readdir_device2") != names.end()); + REQUIRE(std::ranges::find(names, "devfs_readdir_device3") != names.end()); + + AND_THEN("listing again with the returned cursor reports 'end-of-directory'") + { + auto result = root.read_directory(next, entries); + + REQUIRE(result); + auto [count, cursor] = *result; + REQUIRE(count == 0); + REQUIRE(cursor == next); + } + } + } + + WHEN("read_directory is called one entry at a time, chaining the returned cursor") + { + auto seen = kstd::vector{}; + auto position = kernel::vfs::directory_listing_cursor{}; + + for (auto guard = 0; guard < 4; ++guard) + { + auto one = kstd::vector(1); + auto result = root.read_directory(position, one); + REQUIRE(result); + auto [count, next] = *result; + if (count == 0) + { + break; + } + seen.push_back(one[0].name); + position = next; + } + + THEN("nothing is skipped or duplicated across the paginated calls") + { + REQUIRE(seen.size() == 3); + auto sorted = seen; + std::ranges::sort(sorted); + std::ignore = std::ranges::unique(sorted); + REQUIRE(sorted.size() == 3); + } + } + + REQUIRE(kapi::devices::remove_device(*bus)); + } +} + +SCENARIO("Devfs inode read_directory orders by device number, not registration order", + "[filesystem][devfs][inode][readdir]") +{ + GIVEN("a device that frees the lowest minor number before a later device reuses it") + { + auto root = kernel::filesystems::devfs::inode{}; + + auto bus = kstd::make_shared("devfs_readdir_reuse_bus"); + kapi::devices::get_root_bus()->add_child(bus); + + auto first_out = make_and_publish(bus, "readdir_reuse_first_out"); + auto still_in = make_and_publish(bus, "readdir_reuse_still_in"); + REQUIRE(kapi::devices::remove_device(*first_out)); + auto later_in = make_and_publish(bus, "readdir_reuse_later_in"); + + WHEN("read_directory is called with room for both remaining entries") + { + auto entries = kstd::vector(2); + auto result = root.read_directory(kernel::vfs::directory_listing_cursor{}, entries); + + THEN("later_in (the reused, lower device number) comes back before still_in, by number, not by " + "when each was registered") + { + REQUIRE(result); + auto [count, next] = *result; + REQUIRE(count == 2); + REQUIRE(entries[0].name == "readdir_reuse_later_in"); + REQUIRE(entries[1].name == "readdir_reuse_still_in"); + } + } + + REQUIRE(kapi::devices::remove_device(*bus)); + } +} + +SCENARIO("Devfs read_directory rejects non-directory inodes", "[filesystem][devfs][inode][readdir]") +{ + GIVEN("a device_inode, which does not represent a directory") + { + auto bus = kstd::make_shared("devfs_readdir_nondir_bus"); + kapi::devices::get_root_bus()->add_child(bus); + auto device = make_and_publish(bus, "readdir_nondir_device"); + auto node = kernel::vfs::device_inode{device}; + + THEN("read_directory falls through to the base class default rather than enumerating anything") + { + auto entries = kstd::vector(1); + auto result = node.read_directory(kernel::vfs::directory_listing_cursor{}, entries); + + REQUIRE_FALSE(result); + } + + REQUIRE(kapi::devices::remove_device(*bus)); + } +} \ No newline at end of file diff --git a/kernel/kernel/filesystems/devfs/module.cpp b/kernel/kernel/filesystems/devfs/module.cpp new file mode 100644 index 00000000..2048caa1 --- /dev/null +++ b/kernel/kernel/filesystems/devfs/module.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +#include + +#include + +namespace +{ + + struct descriptor final : kernel::vfs::driver_descriptor + { + [[nodiscard]] auto name() const noexcept -> std::string_view override + { + return "devfs"; + } + + [[nodiscard]] auto make_instance() const -> kernel::vfs::filesystem::filesystem_ptr override + { + return kstd::make_shared(); + } + }; + + [[gnu::used]] + constexpr auto registration = kernel::vfs::driver_module{}; + +} // namespace \ No newline at end of file diff --git a/kernel/kernel/filesystems/ext2/block_group_descriptor.hpp b/kernel/kernel/filesystems/ext2/block_group_descriptor.hpp new file mode 100644 index 00000000..dd9c3317 --- /dev/null +++ b/kernel/kernel/filesystems/ext2/block_group_descriptor.hpp @@ -0,0 +1,22 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_EXT2_BLOCK_GROUP_DESCRIPTOR_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_EXT2_BLOCK_GROUP_DESCRIPTOR_HPP + +#include +#include + +namespace kernel::filesystems::ext2 +{ + //! A block group descriptor in the ext2 filesystem. + struct [[gnu::packed]] block_group_descriptor + { + uint32_t block_bitmap; + uint32_t inode_bitmap; + uint32_t inode_table; + uint16_t free_blocks_count; + uint16_t free_inodes_count; + uint16_t used_dirs_count; + std::array padding; + std::array reserved; // NOLINT(readability-magic-numbers) + }; +} // namespace kernel::filesystems::ext2 +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/ext2/directory_iterator.cpp b/kernel/kernel/filesystems/ext2/directory_iterator.cpp new file mode 100644 index 00000000..1c94a863 --- /dev/null +++ b/kernel/kernel/filesystems/ext2/directory_iterator.cpp @@ -0,0 +1,109 @@ +#include + +#include +#include +#include + +#include + +#include +#include + +using namespace kstd::literals; + +namespace kernel::filesystems::ext2 +{ + + directory_iterator::directory_iterator(inode const & inode, mount_state const & state) + : directory_iterator{inode, 0_B, state} + {} + + directory_iterator::directory_iterator(inode const & inode, kstd::bytes offset, mount_state const & state) + : m_inode{&inode} + , m_state{&state} + , m_file_offset{offset} + , m_buffer{sizeof(value_type)} + { + if (!inode.is_directory()) + { + kapi::system::panic("[FS:ext2] Tried perform directory iteration on non-directory inode {}", inode.number()); + } + + if (m_file_offset >= data_size(*m_inode, *m_state)) + { + m_inode = nullptr; + return; + } + + read(); + } + + auto directory_iterator::operator*() const -> reference + { + return *reinterpret_cast(m_buffer.data()); + } + + auto directory_iterator::operator->() const -> pointer + { + return reinterpret_cast(m_buffer.data()); + } + + auto directory_iterator::operator++() -> directory_iterator & + { + if (!m_inode || m_file_offset >= data_size(*m_inode, *m_state)) + { + m_inode = nullptr; + return *this; + } + read(); + return *this; + } + + auto directory_iterator::operator++(int) -> directory_iterator + { + auto copy = *this; + ++(*this); + return copy; + } + + [[nodiscard]] auto directory_iterator::offset() const noexcept -> kstd::bytes + { + return m_file_offset; + } + + auto operator==(directory_iterator const & lhs, directory_iterator const & rhs) -> bool + { + if (lhs.m_inode == rhs.m_inode) + { + return lhs.m_inode == nullptr || lhs.m_file_offset == rhs.m_file_offset; + } + return false; + } + + auto directory_iterator::read() -> void + { + if (auto result = do_read(*m_inode, m_buffer, m_file_offset, *m_state); !result) + { + kapi::system::panic("[FS:ext2] failed to read directory entry", result.error()); + } + + auto entry = reinterpret_cast(m_buffer.data()); + + auto const remainder = entry->name_len - 1; + + if (remainder > 0) + { + m_buffer.resize(m_buffer.size() + remainder); + + if (auto result = do_read(*m_inode, m_buffer, m_file_offset, *m_state); !result) + { + kapi::system::panic("[FS:ext2] failed to read directory entry", result.error()); + } + + entry = reinterpret_cast(m_buffer.data()); + } + + m_file_offset = m_file_offset + static_cast(entry->rec_len); + } + +} // namespace kernel::filesystems::ext2 \ No newline at end of file diff --git a/kernel/kernel/filesystems/ext2/directory_iterator.hpp b/kernel/kernel/filesystems/ext2/directory_iterator.hpp new file mode 100644 index 00000000..41a37e74 --- /dev/null +++ b/kernel/kernel/filesystems/ext2/directory_iterator.hpp @@ -0,0 +1,56 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_EXT2_DIRECTORY_ITERATOR_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_EXT2_DIRECTORY_ITERATOR_HPP + +#include +#include +#include + +#include +#include +#include + +#include +#include + +namespace kernel::filesystems::ext2 +{ + + struct directory_iterator + { + using iterator_category = std::forward_iterator_tag; + using iterator_concept = std::forward_iterator_tag; + using value_type = linked_directory_entry; + using pointer = linked_directory_entry const *; + using reference = linked_directory_entry const &; + using difference_type = std::ptrdiff_t; + + constexpr directory_iterator() = default; + + directory_iterator(inode const & inode, mount_state const & state); + + directory_iterator(inode const & inode, kstd::bytes offset, mount_state const & state); + + auto operator*() const -> reference; + + auto operator->() const -> pointer; + + auto operator++() -> directory_iterator &; + + auto operator++(int) -> directory_iterator; + + [[nodiscard]] auto offset() const noexcept -> kstd::bytes; + + auto friend operator==(directory_iterator const & lhs, directory_iterator const & rhs) -> bool; + + private: + auto read() -> void; + + kstd::observer_ptr m_inode{}; + kstd::observer_ptr m_state{}; + kstd::bytes m_file_offset{}; + kstd::vector m_buffer{}; + }; + +} // namespace kernel::filesystems::ext2 + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/ext2/directory_iterator.tests.cpp b/kernel/kernel/filesystems/ext2/directory_iterator.tests.cpp new file mode 100644 index 00000000..795c5cbf --- /dev/null +++ b/kernel/kernel/filesystems/ext2/directory_iterator.tests.cpp @@ -0,0 +1,123 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include + +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_fixture, + "Ext2 directory_iterator walks a real directory and terminates", "[filesystem][ext2][readdir]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("a mounted ext2 filesystem and its root directory") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); + + auto boot_device = kernel::devices::storage::determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto dev_inode = kstd::make_shared(boot_device); + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + REQUIRE(mount); + + auto root = (*mount)->root_dentry()->inode(); + auto const & root_ext2_inode = static_cast(*root); + + auto mount_state = static_pointer_cast((*mount)->driver_data()); + + WHEN("iterating from begin to the default-constructed end sentinel") + { + auto names = std::vector{}; + auto guard = 0; + + for (auto it = kernel::filesystems::ext2::directory_iterator{root_ext2_inode, *mount_state}; + it != kernel::filesystems::ext2::directory_iterator{}; ++it) + { + REQUIRE(guard++ < 64); // fails loudly on a non-terminating loop rather than hanging the suite + names.emplace_back(&it->name_start, it->name_len); + } + + THEN("iteration terminates on its own and finds the expected entries") + { + REQUIRE(guard < 64); + REQUIRE(std::ranges::find(names, ".") != names.end()); + REQUIRE(std::ranges::find(names, "..") != names.end()); + REQUIRE(std::ranges::find(names, "information") != names.end()); + } + } + } +} + +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_fixture, + "Ext2 directory_iterator satisfies the forward-iterator multi-pass guarantee", + "[filesystem][ext2][readdir]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("an iterator positioned at the first entry of the root directory") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); + + auto boot_device = kernel::devices::storage::determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto dev_inode = kstd::make_shared(boot_device); + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + REQUIRE(mount); + + auto root = (*mount)->root_dentry()->inode(); + auto const & root_ext2_inode = static_cast(*root); + + auto mount_state = static_pointer_cast((*mount)->driver_data()); + + auto original = kernel::filesystems::ext2::directory_iterator{root_ext2_inode, *mount_state}; + auto const first_name = std::string{&original->name_start, original->name_len}; + + WHEN("the iterator is copied, then only the original is advanced") + { + auto copy = original; + ++original; + + THEN("the copy still refers to the first entry, unaffected by advancing the original") + { + auto const copy_name = std::string{©->name_start, copy->name_len}; + REQUIRE(copy_name == first_name); + + auto const advanced_name = std::string{&original->name_start, original->name_len}; + REQUIRE(advanced_name != first_name); + } + } + } +} + +SCENARIO("Ext2 directory_iterator's default-constructed value is a valid, comparable end sentinel", + "[filesystem][ext2][readdir]") +{ + GIVEN("two independently default-constructed iterators") + { + auto first = kernel::filesystems::ext2::directory_iterator{}; + auto second = kernel::filesystems::ext2::directory_iterator{}; + + THEN("they compare equal to each other") + { + REQUIRE(first == second); + } + } +} \ No newline at end of file diff --git a/kernel/kernel/filesystems/ext2/error.cpp b/kernel/kernel/filesystems/ext2/error.cpp new file mode 100644 index 00000000..23b01f4e --- /dev/null +++ b/kernel/kernel/filesystems/ext2/error.cpp @@ -0,0 +1,103 @@ +#include + +#include + +#include + +#include + +namespace kernel::filesystems::ext2 +{ + + namespace + { + struct category_t final : kstd::error_category + { + [[nodiscard]] constexpr auto name() const noexcept -> std::string_view override + { + return "ext2"; + } + + [[nodiscard]] constexpr auto message(int value) const noexcept -> std::string_view override + { + switch (static_cast(value)) + { + case errc::invalid_magic_number: + return "invalid filesystem magic"; + case errc::invalid_root_inode: + return "invalid root inode"; + case errc::invalid_block_group_index: + return "block group index out of bounds"; + case errc::invalid_block_index: + return "block index out of bounds"; + case errc::invalid_block_number: + return "block number out of bounds"; + case errc::failed_to_read_superblock: + return "failed to read superblock"; + case errc::failed_to_read_block_group_descriptors: + return "failed to read block group descriptors"; + case errc::not_enough_free_blocks: + return "not enough free blocks"; + case errc::not_enough_inodes: + return "not enough inodes"; + case errc::unsupported_features_present: + return "unsupported features present"; + case errc::name_too_long: + return "name too long"; + default: + return "unknown ext2 error"; + }; + } + + [[nodiscard]] constexpr auto equivalent(int code, kstd::error_condition const & condition) const noexcept + -> bool override + { + switch (static_cast(code)) + { + case errc::invalid_magic_number: + case errc::invalid_root_inode: + case errc::name_too_long: + if (condition.category() == kernel::vfs::category()) + { + return condition.value() == static_cast(kernel::vfs::errc::invalid_filesystem); + } + else if (condition.category() == kstd::generic_category()) + { + return condition.value() == static_cast(kstd::errc::invalid_argument); + } + break; + case errc::invalid_block_group_index: + case errc::invalid_block_index: + case errc::invalid_block_number: + case errc::failed_to_read_superblock: + case errc::failed_to_read_block_group_descriptors: + if (condition.category() == kstd::generic_category()) + { + return condition.value() == static_cast(kstd::errc::io_error); + } + break; + case errc::not_enough_free_blocks: + case errc::not_enough_inodes: + if (condition.category() == kstd::generic_category()) + { + return condition.value() == static_cast(kstd::errc::no_space_on_device); + } + break; + case errc::unsupported_features_present: + if (condition.category() == kstd::generic_category()) + { + return condition.value() == static_cast(kstd::errc::not_supported); + } + break; + } + return kstd::error_category::equivalent(code, condition); + } + } constexpr inline ext2_category_instance{}; + } // namespace + + [[nodiscard]] auto category() noexcept -> kstd::error_category const & + { + return ext2_category_instance; + } + +} // namespace kernel::filesystems::ext2 diff --git a/kernel/kernel/filesystems/ext2/error.hpp b/kernel/kernel/filesystems/ext2/error.hpp new file mode 100644 index 00000000..3c767051 --- /dev/null +++ b/kernel/kernel/filesystems/ext2/error.hpp @@ -0,0 +1,45 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_EXT2_ERROR_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_EXT2_ERROR_HPP + +#include + +#include + +#include + +namespace kernel::filesystems::ext2 +{ + + enum struct errc : int + { + invalid_magic_number = 1, + invalid_root_inode, + invalid_block_group_index, + invalid_block_index, + invalid_block_number, + failed_to_read_superblock, + failed_to_read_block_group_descriptors, + not_enough_free_blocks, + not_enough_inodes, + unsupported_features_present, + name_too_long, + }; + + [[nodiscard]] auto category() noexcept -> kstd::error_category const &; + + [[nodiscard]] constexpr auto inline make_error_code(errc error) noexcept -> kstd::error_code + { + return {static_cast(error), category()}; + } + +} // namespace kernel::filesystems::ext2 + +namespace kstd +{ + template<> + struct is_error_code_enum : std::true_type + { + }; +} // namespace kstd + +#endif diff --git a/kernel/kernel/filesystems/ext2/filesystem.cpp b/kernel/kernel/filesystems/ext2/filesystem.cpp new file mode 100644 index 00000000..a2468fcb --- /dev/null +++ b/kernel/kernel/filesystems/ext2/filesystem.cpp @@ -0,0 +1,1103 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace kstd::units_literals; + +namespace kernel::filesystems::ext2 +{ + + //! Check that this driver class is stateless. + static_assert(sizeof(kernel::vfs::filesystem) == sizeof(kernel::filesystems::ext2::filesystem)); + + //! Hidden implementation details. + namespace + { + struct indirect_level + { + uint32_t slot_index; + size_t capacity; + }; + + //! Check if the given bit has the bit at the given index set. + //! + //! @warning This function will panic if the bit index is out-of-bounds for the given bitmap. + //! + //! @param bitmap The bitmap to check. + //! @param index The index of the bit to check. + [[nodiscard]] constexpr auto bitmap_is_set(kstd::vector const & bitmap, size_t index) -> bool + { + auto const byte_index = index / 8; + auto const bit_index = index % 8; + return (bitmap.at(byte_index) & static_cast(1u << bit_index)) != std::byte{}; + } + + //! Set the bit at the given index in the given bitmap. + //! + //! @warning This function will panic if the bit index is out-of-bounds for the given bitmap. + //! + //! @param bitmap The bitmap to manipulate. + //! @param index The index of the bit to set. + constexpr auto bitmap_set(kstd::vector & bitmap, size_t index) -> void + { + auto const byte_index = index / 8; + auto const bit_index = index % 8; + bitmap.at(byte_index) |= static_cast(1u << bit_index); + } + + //! Get the number of block numbers storable on each level + //! + //! @param level The level to check. + //! @param block_size The size of a single block. + [[nodiscard]] constexpr auto block_numbers(std::size_t level, kstd::bytes block_size) noexcept -> std::size_t + { + if (level == 0) + { + return constants::direct_block_count; + } + + auto const numbers_per_block = block_size / kstd::size_of(); + auto result = numbers_per_block; + for (auto current_level = level; current_level > 1; --current_level) + { + result *= numbers_per_block; + } + + return result; + } + + //! Get mapping of block indirection levels to possible number of block on that level. + //! + //! @param block_size The size of a single block. + [[nodiscard]] constexpr auto indirect_levels(kstd::bytes block_size) -> std::array + { + return { + {{constants::singly_indirect_block_index, block_numbers(1, block_size)}, + {constants::doubly_indirect_block_index, block_numbers(2, block_size)}, + {constants::triply_indirect_block_index, block_numbers(3, block_size)}} + }; + } + + //! Extract the file type from the an ext2 file mode. + //! + //! @param mode The mode to extract the type from. + //! @return The file type encoded in the mode. + constexpr auto to_filetype(uint16_t mode) -> kapi::filesystem::file_type + { + switch (mode & constants::mode_mask) + { + case constants::mode_regular: + return kapi::filesystem::file_type::regular; + case constants::mode_directory: + return kapi::filesystem::file_type::directory; + case constants::mode_block_device: + return kapi::filesystem::file_type::block_device; + case constants::mode_character_device: + return kapi::filesystem::file_type::character_device; + case constants::mode_symbolic_link: + return kapi::filesystem::file_type::symbolic_link; + case constants::mode_fifo: + return kapi::filesystem::file_type::fifo; + case constants::mode_socket: + return kapi::filesystem::file_type::socket; + default: + return kapi::filesystem::file_type{0}; + } + } + + //! Combine a file type and an ext2 mode. + //! + //! @param type The type to encode. + //! @param mode The original ext2 file mode. + constexpr auto combine(kapi::filesystem::file_type type, uint16_t mode) -> uint16_t + { + if (mode & constants::mode_mask) + { + return mode; + } + + return mode | [=] { + switch (type) + { + case kapi::filesystem::file_type::directory: + return constants::mode_directory; + case kapi::filesystem::file_type::regular: + return constants::mode_regular; + case kapi::filesystem::file_type::block_device: + return constants::mode_block_device; + case kapi::filesystem::file_type::character_device: + return constants::mode_character_device; + case kapi::filesystem::file_type::symbolic_link: + return constants::mode_symbolic_link; + case kapi::filesystem::file_type::fifo: + return constants::mode_fifo; + case kapi::filesystem::file_type::socket: + return constants::mode_socket; + default: + kapi::system::panic("[EXT2] Not implemented."); + } + }(); + } + + //! Convert a file type to a directory entry file type. + //! + //! @param type The file type to convert. + //! @return The directory file type. + constexpr auto to_directory_file_type(kapi::filesystem::file_type type) -> std::uint8_t + { + switch (type) + { + case kapi::filesystem::file_type::regular: + return 1; + case kapi::filesystem::file_type::directory: + return 2; + case kapi::filesystem::file_type::character_device: + return 3; + case kapi::filesystem::file_type::block_device: + return 4; + case kapi::filesystem::file_type::fifo: + return 5; + case kapi::filesystem::file_type::socket: + return 6; + case kapi::filesystem::file_type::symbolic_link: + return 7; + default: + return 0; + } + } + + //! Write a new directory entry into the given buffer. + //! + //! @param buffer The buffer to write to. + //! @param offset The offset inside to the buffer. + //! @param target The inode the entry targets. + //! @param name The name for the entry. + //! @param type The filet type of the entry. + //! @return A pointer to the written entry on success, an error otherwise. + auto write_directory_entry_to_buffer(std::span buffer, kstd::bytes offset, inode & target, + std::string_view name, kapi::filesystem::file_type type) + -> kstd::result + { + auto const name_length = static_cast(name.size()); + auto const record_size = (sizeof(linked_directory_entry) - 1 + name_length + 3u) & ~3u; + if (buffer.size() - offset.value < record_size) + { + // TODO: improve error code. + return kstd::failure(vfs::errc::invalid_argument); + } + + auto * entry = reinterpret_cast(buffer.data() + offset); + entry->inode = target.number(); + entry->rec_len = record_size; + entry->name_len = name_length; + entry->file_type = to_directory_file_type(type); + kstd::libc::memcpy(&entry->name_start, name.data(), name_length); + return kstd::success(entry); + } + + //! Get the revision level of the filesystem. + //! + //! @param state The driver state to operate on. + //! @return The revision level. + [[nodiscard]] constexpr auto revision_level(mount_state const & state) -> uint32_t + { + return state.superblock.rev_level; + } + + //! Get the size of an inode in the filesystem. + //! + //! @param state The driver state to operate on. + //! @return The size of an inode in bytes. + [[nodiscard]] auto inode_size(mount_state const & state) -> kstd::bytes + { + return kstd::bytes(revision_level(state) == constants::good_old_revision ? 128 : state.superblock.inode_size); + } + + //! Read a block from the backing inode into the provided buffer. + //! + //! @param block_number The number of the block to read. + //! @param buffer The buffer to read the block data into. + //! @param state The state to operate on. + //! @return The number of bytes read. + [[nodiscard]] auto read_block(uint32_t block_number, std::span buffer, mount_state const & state) + -> kstd::result + { + if (buffer.size() < block_size(state).value) + { + return kstd::failure(vfs::errc::invalid_argument); + } + + auto const block_offset = static_cast(block_number) * block_size(state); + return state.backing_inode->read(buffer, block_offset); + } + + //! Write a block of data from the provided buffer to the backing inode. + //! + //! @param block_number The number of the block to write. + //! @param buffer The buffer containing the data to write. + //! @param state The state to operate on. + //! @return The number of bytes written. + [[nodiscard]] auto write_block(uint32_t block_number, std::span buffer, mount_state & state) + -> kstd::result + { + if (buffer.size() < block_size(state).value) + { + return kstd::failure(vfs::errc::invalid_argument); + } + + auto const block_offset = static_cast(block_number) * block_size(state); + return state.backing_inode->write(buffer, block_offset); + } + + //! Allocate a single inode in the file system. + //! + //! @param state The driver state to operate on. + //! @param batch The active write batch. + //! @return An inode number on success, an error otherwise. + auto allocate_inode(mount_state & state, write_batch & batch) -> kstd::result + { + auto & [superblock, block_group_descriptors, _, __] = state; + + if (block_group_descriptors.empty() || superblock.free_inodes_count == 0) + { + return kstd::failure(errc::not_enough_inodes); + } + + for (auto block_group_descriptor_index = 0uz; block_group_descriptor_index < block_group_descriptors.size(); + ++block_group_descriptor_index) + { + auto & block_group_descriptor = block_group_descriptors.at(block_group_descriptor_index); + if (block_group_descriptor.free_inodes_count == 0) + { + continue; + } + + auto inode_bitmap = kstd::vector{block_size(state).value}; + if (auto read_result = read_block(block_group_descriptor.inode_bitmap, inode_bitmap, state); !read_result) + { + return kstd::failure(read_result.error()); + } + + for (auto i = 0uz; i < static_cast(superblock.inodes_per_group); ++i) + { + if (!bitmap_is_set(inode_bitmap, i)) + { + bitmap_set(inode_bitmap, i); + block_group_descriptor.free_inodes_count--; + superblock.free_inodes_count--; + + if (auto write_result = write_block(block_group_descriptor.inode_bitmap, inode_bitmap, state); + !write_result) + { + return kstd::failure(write_result.error()); + } + + batch.mark_group(block_group_descriptor_index); + + return block_group_descriptor_index * superblock.inodes_per_group + i + 1; + } + } + } + + return kstd::failure(errc::not_enough_inodes); + } + + //! Add a new directory entry to a given directory. + //! + //! @param directory The directory to add the entry to. + //! @param name The name for the entry. + //! @param child The inode number of the child. + //! @param mode The file mode of the entry. + //! @param state The driver state to operate on. + //! @param batch The active write batch. + //! @return Nothing on success, an error code otherwise. + auto add_directory_entry(inode & directory, std::string_view name, inode & child, std::uint16_t mode, + mount_state & state, write_batch & batch) -> kstd::result + { + auto const last_block_index = block_count(directory, state) - 1; + auto const global_block = inode_block_number(last_block_index, directory, state); + + if (!global_block) + { + return kstd::failure(global_block.error()); + } + + auto buffer = kstd::vector{block_size(state).value}; + if (auto result = read_block(*global_block, buffer, state); !result) + { + return kstd::failure(result.error()); + } + + // TODO handle "gaps" in directory entries (e.g., after deletions) in the current implementation, we only + // add new entries at the end of the last block, but we could also reuse space from deleted entries + auto offset = 0_B; + while (true) + { + auto const * entry = reinterpret_cast(buffer.data() + offset); + if (offset + kstd::bytes{entry->rec_len} >= block_size(state)) + { + break; + } + offset += kstd::bytes{entry->rec_len}; + } + + auto const file_type = to_filetype(mode); + auto const name_len = static_cast(name.size()); + auto const needed_rec_len = static_cast((8u + name_len + 3u) & ~3u); + + auto * last_entry = reinterpret_cast(buffer.data() + offset); + auto const last_entry_actual_len = static_cast((8u + last_entry->name_len + 3u) & ~3u); + + if (kstd::bytes{last_entry->rec_len} - last_entry_actual_len >= needed_rec_len) + { + last_entry->rec_len = last_entry_actual_len.value; + auto entry_offset = offset + last_entry_actual_len; + auto entry = write_directory_entry_to_buffer(buffer, entry_offset, child, name, file_type); + if (!entry) + { + return kstd::failure(entry.error()); + } + + auto remainder = block_size(state) - entry_offset; + (*entry)->rec_len = remainder.value; + + return global_block.and_then([&](auto block_number) { return write_block(block_number, buffer, state); }) + .transform([](auto) {}); + } + else + { + if (!directory.append_blocks(1, batch)) + { + return kstd::failure(errc::not_enough_free_blocks); + } + + auto const new_block_index = block_count(directory, state) - 1; + auto const new_global_block = inode_block_number(new_block_index, directory, state); + if (!new_global_block) + { + return kstd::failure(new_global_block.error()); + } + + kstd::libc::memset(buffer.data(), 0, block_size(state).value); + auto entry = write_directory_entry_to_buffer(buffer, 0_B, child, name, file_type); + if (!entry) + { + return kstd::failure(entry.error()); + } + + auto remainder = block_size(state); + (*entry)->rec_len = remainder.value; + + if (auto result = + new_global_block.and_then([&](auto block_number) { return write_block(block_number, buffer, state); }); + !result) + { + return kstd::failure(result.error()); + } + } + + auto size = block_count(directory, state) * block_size(state); + directory.data().size = size.value; + batch.mark_inode(directory); + return kstd::success(); + } + + //! Initialize a new directory inode. + //! + //! This function initializes an inode to be an empty directory. On success, the inode will be a directory with only + //! two children, "." and "..". + //! + //! @param directory The inode to initialize. + //! @param parent The parent of this newly initialized directory. + //! @param state The driver state to operate on. + //! @param batch The active write batch. + //! @return Nothing on success, an error otherwise. + auto init_directory(inode & directory, inode & parent, mount_state & state, write_batch & batch) + -> kstd::result + { + if (!directory.append_blocks(1, batch)) + { + return kstd::failure(errc::not_enough_free_blocks); + } + + auto const global_block = inode_block_number(0, directory, state); + if (!global_block) + { + return kstd::failure(global_block.error()); + } + + auto buffer = kstd::vector{block_size(state).value}; + auto dot = write_directory_entry_to_buffer(buffer, 0_B, directory, ".", kapi::filesystem::file_type::directory); + + if (!dot) + { + return kstd::failure(dot.error()); + } + + auto dot_dot_offset = kstd::bytes{(*dot)->rec_len}; + auto dot_dot = + write_directory_entry_to_buffer(buffer, dot_dot_offset, parent, "..", kapi::filesystem::file_type::directory); + if (!dot_dot) + { + return kstd::failure(dot_dot.error()); + } + auto remainder = block_size(state) - dot_dot_offset; + (*dot_dot)->rec_len = remainder.value; + + // '..' inside the new dir counts as an extra hard link to the parent + auto & parent_inode_data = parent.data(); + parent_inode_data.links_count++; + + batch.mark_inode(parent); + + return global_block.and_then([&](auto number) { return write_block(number, buffer, state); }).transform([](auto) { + }); + } + + //! Calculate the offset of the inode with a given number in the filesystem. + //! + //! @param number The number of the inode. + //! @param state The driver state to operate on. + //! @return The offset to the filesystem start of the inode. + [[nodiscard]] auto calculate_inode_offset(std::uint32_t number, mount_state const & state) + -> kstd::result + { + auto const inodes_per_group = state.superblock.inodes_per_group; + auto const block_group_index = (number - 1) / inodes_per_group; + auto const inode_index_within_group = (number - 1) % inodes_per_group; + + if (block_group_index >= state.block_group_descriptors.size()) + { + return kstd::failure(errc::invalid_block_group_index); + } + + auto const & block_group_descriptor = state.block_group_descriptors.at(block_group_index); + auto const inode_table_start_block = block_group_descriptor.inode_table; + auto const inode_table_offset = inode_table_start_block * block_size(state); + auto const inode_offset = inode_table_offset + inode_index_within_group * inode_size(state); + + return inode_offset; + } + + //! Read the inode with the given number from the filesystem. + //! + //! @param number The number of the inode to read. + //! @param state The driver state to operate on. + //! @return The read inode on success, an error otherwise. + [[nodiscard]] auto read_inode(std::uint32_t number, mount_state const & state) + -> kstd::result> + { + if (auto inode_offset = calculate_inode_offset(number, state)) + { + auto new_inode_data = inode_data{}; + + if (auto read_result = state.backing_inode->read(kstd::raw_bytes(new_inode_data), *inode_offset); !read_result) + { + return kstd::failure(read_result.error()); + } + else + { + return kstd::make_shared(number, new_inode_data); + } + } + else + { + return kstd::failure(inode_offset.error()); + } + } + + //! Read the indirect block number from the specified block at the specified index. + //! + //! @param block The number of the indirect block to read from. + //! @param index The index into the indirect block to read from. + //! @param state The driver state to operate on. + //! @return The block number at the given index in the given indirect block on success, an error otherwise. + [[nodiscard]] auto read_block_number_at_index(std::uint32_t block, std::size_t index, mount_state const & state) + -> kstd::result + { + uint32_t block_number_buffer = 0; + + auto const block_offset = block * block_size(state); + auto const number_offset = block_offset + index * kstd::size_of(); + + if (auto read_result = state.backing_inode->read(kstd::raw_bytes(block_number_buffer), number_offset); + !read_result) + { + return kstd::failure(read_result.error()); + } + + return block_number_buffer; + } + + //! Get the offset of the Block Group Descriptor table. + //! + //! @param state The driver state to operate on. + //! @return The filesystem offset of the block group descriptor table. + [[nodiscard]] auto block_group_descriptor_table_offset(mount_state const & state) -> kstd::bytes + { + return block_size(state) == 1024_B ? 2 * block_size(state) : block_size(state); + } + + //! The set of "incompatible" Extended Filesystem features supported by this driver. + constexpr auto supported_incompatible_features = incompatible_features::file_types_in_directory_entries; + + //! The set of "read-only" Extended Filesystem features supported by this driver. + constexpr auto supported_ro_compatible_features = read_only_compatible_features::sparse_superblock_copies | // + read_only_compatible_features::large_file_support; + + //! A set of permissions allowing all access to everyone. + constexpr auto no_access_restrictions = std::uint16_t{0x01FF}; + } // namespace + + auto filesystem::probe(inode_ptr const & backing_inode) const -> kstd::result + { + auto superblock = ext2::superblock{}; + auto const bytes = kstd::raw_bytes(superblock); + auto const read_result = backing_inode->read(bytes, constants::superblock_offset); + + if (!read_result) + { + return kstd::failure(read_result.error()); + } + + if (superblock.magic != constants::magic_number) + { + return kstd::failure(errc::invalid_magic_number); + } + + if (std::to_underlying(superblock.feature_incompat & ~supported_incompatible_features) || + std::to_underlying(superblock.feature_ro_compat & ~supported_ro_compatible_features)) + { + return kstd::failure(errc::unsupported_features_present); + } + + return 0; + } + + auto filesystem::mount(inode_ptr const & backing_inode) -> kstd::result + { + auto const mount_state = kstd::make_shared(); + auto & [superblock, block_group_descriptors, backing, _] = *mount_state; + + backing = backing_inode; + + auto const superblock_bytes = kstd::raw_bytes(superblock); + if (auto read_result = backing->read(superblock_bytes, constants::superblock_offset); !read_result) + { + return kstd::failure(read_result.error()); + } + + if (superblock.magic != constants::magic_number) + { + return kstd::failure(errc::invalid_magic_number); + } + + auto const blocks_per_group = superblock.blocks_per_group; + auto const num_block_groups = (superblock.blocks_count + blocks_per_group - 1) / blocks_per_group; + + block_group_descriptors = kstd::vector(num_block_groups); + auto const block_group_descriptors_bytes = kstd::raw_bytes(block_group_descriptors); + if (auto read_result = + backing->read(block_group_descriptors_bytes, block_group_descriptor_table_offset(*mount_state)); + !read_result) + { + return kstd::failure(read_result.error()); + } + + auto const root = read_inode(constants::root_inode_number, *mount_state); + + if (!root) + { + return kstd::failure(root.error()); + } + + if (!(*root)->is_directory()) + { + return kstd::failure(errc::invalid_root_inode); + } + + return mount_result{*root, mount_state}; + } + + auto filesystem::lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const + -> kstd::result + { + auto const directory = static_pointer_cast(parent); + if (!directory) + { + return kstd::failure(vfs::errc::invalid_inode); + } + + if (!directory->is_directory()) + { + return kstd::failure(vfs::errc::not_a_directory); + } + + auto const mount_state = static_pointer_cast(driver_data); + if (!mount_state) + { + return kstd::failure(vfs::errc::not_mounted); + } + + auto guard = kstd::lock_guard{mount_state->lock}; + + for (auto it = directory_iterator{*directory, *mount_state}; it != directory_iterator{}; ++it) + { + if (it->inode == 0) + { + continue; + } + + if (it->name() == name) + { + return read_inode(it->inode, *mount_state); + } + } + + return kstd::failure(vfs::errc::no_such_file_or_directory); + } + + auto filesystem::create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, + driver_data_ptr driver_data, std::optional raw_device) + -> kstd::result> + { + auto const ext2_parent = static_pointer_cast(parent); + if (!ext2_parent) + { + return kstd::failure(vfs::errc::invalid_argument); + } + + if (!ext2_parent->is_directory()) + { + return kstd::failure(vfs::errc::not_a_directory); + } + + auto const is_device = kapi::filesystem::is_device(type); + if (is_device && !raw_device) + { + return kstd::failure(vfs::errc::invalid_argument); + } + + auto const mount_state = static_pointer_cast(driver_data); + if (!mount_state) + { + return kstd::failure(vfs::errc::invalid_driver_data); + } + + auto guard = kstd::lock_guard{mount_state->lock}; + auto batch = write_batch{*mount_state}; + + auto maybe_inode_number = allocate_inode(*mount_state, batch); + if (!maybe_inode_number) + { + return kstd::failure(maybe_inode_number.error()); + } + + auto inode_number = maybe_inode_number.value(); + + auto data = inode_data{}; + + // TODO: use correct access rights + data.mode = combine(type, no_access_restrictions); + data.size = 0; + data.dir_acl = 0; + data.links_count = 1; + data.blocks = 0; + data.block.fill(0); + + if (is_device) + { + data.block[0] = static_cast(raw_device->minor) | // + (static_cast(raw_device->major) << 8); + } + + auto created = kstd::make_shared(inode_number, data); + created->set_owning_mount(ext2_parent->owning_mount().lock()); + + // TODO: preallocate blocks depending on type and m_superblock.prealloc_dir_blocks or m_superblock.prealloc_blocks; + if (auto result = add_directory_entry(*ext2_parent, name, *created, data.mode, *mount_state, batch); !result) + { + return kstd::failure(result.error()); + } + + if (created->is_directory()) + { + // TODO increment used_dirs_count in block group descriptor + if (auto result = init_directory(*created, *ext2_parent, *mount_state, batch); !result) + { + return kstd::failure(result.error()); + } + set_data_size(*created, block_size(*mount_state), *mount_state); + } + + batch.mark_inode(*created); + + return created; + } + + //! @name Free Functions + //! @{ + + auto block_size(mount_state const & state) -> kstd::bytes + { + return kstd::bytes{constants::base_block_size.value << state.superblock.log_block_size}; + } + + auto block_count(inode const & inode, mount_state const & state) -> uint32_t + { + return inode.data().blocks / (2 << state.superblock.log_block_size); + } + + auto data_size(inode const & inode, mount_state const & state) -> kstd::bytes + { + uint64_t size = inode.data().size; + + if (revision_level(state) > constants::good_old_revision && inode.is_regular()) + { + size |= static_cast(inode.data().dir_acl) << 32; + } + + return kstd::bytes{size}; + } + + auto set_data_size(inode & inode, kstd::bytes new_size, mount_state const & state) -> void + { + if (revision_level(state) > constants::good_old_revision && inode.is_regular()) + { + inode.data().dir_acl = static_cast(new_size.value >> 32); + } + inode.data().size = static_cast(new_size.value); + } + + auto do_read(inode const & inode, std::span buffer, kstd::bytes offset, mount_state const & state) + -> kstd::result + { + auto const max_readable = data_size(inode, state) - offset; + auto const requested_size = std::min(kstd::bytes{buffer.size()}, max_readable); + + auto const block_size = ext2::block_size(state); + auto block_index = offset / block_size; + auto in_block_offset = offset % block_size; + + auto bytes_read = 0_B; + + while (bytes_read < requested_size) + { + auto const block_number = inode_block_number(block_index, inode, state); + if (!block_number) + { + break; + } + + auto const bytes_to_read = std::min(requested_size - bytes_read, block_size - in_block_offset); + if (block_number == 0) + { + kstd::libc::memset(buffer.data() + bytes_read, 0, bytes_to_read.value); + bytes_read += bytes_to_read; + } + else + { + auto const block_start_offset = block_number.value() * block_size; + auto const read_offset = block_start_offset + in_block_offset; + + auto const read_result = + state.backing_inode->read(buffer.subspan(bytes_read.value, bytes_to_read.value), read_offset); + if (!read_result) + { + return kstd::failure(read_result.error()); + } + + bytes_read += read_result.value(); + } + + block_index++; + in_block_offset = 0_B; + } + + return bytes_read; + } + + auto allocate_blocks(size_t count, mount_state & state, write_batch & batch) -> kstd::result> + { + auto & [superblock, block_group_descriptors, backing_inode, _] = state; + + if (block_group_descriptors.empty() || count > superblock.free_blocks_count) + { + return kstd::failure(errc::not_enough_free_blocks); + } + + struct pending_group + { + std::size_t index; + std::size_t claimed; + kstd::vector bitmap; + }; + + auto pending = kstd::vector{}; + auto allocated_blocks = kstd::vector{}; + + for (auto group_index = 0uz; group_index < block_group_descriptors.size() && allocated_blocks.size() < count; + ++group_index) + { + auto & descriptor = block_group_descriptors.at(group_index); + if (descriptor.free_blocks_count == 0) + { + continue; + } + + auto block_bitmap = kstd::vector{block_size(state).value}; + if (auto read_result = read_block(descriptor.block_bitmap, block_bitmap, state); !read_result) + { + return kstd::failure(read_result.error()); + } + + auto claimed = 0uz; + for (auto i = 0uz; i < static_cast(superblock.blocks_per_group) && allocated_blocks.size() < count; ++i) + { + if (!bitmap_is_set(block_bitmap, i)) + { + bitmap_set(block_bitmap, i); + ++claimed; + allocated_blocks.push_back(i + group_index * superblock.blocks_per_group); + } + } + + if (claimed > 0) + { + pending.push_back({group_index, claimed, std::move(block_bitmap)}); + } + } + + if (allocated_blocks.size() != count) + { + return kstd::failure(errc::not_enough_free_blocks); + } + + for (auto & group : pending) + { + auto & descriptor = block_group_descriptors.at(group.index); + descriptor.free_blocks_count -= group.claimed; + superblock.free_blocks_count -= group.claimed; + + if (auto result = write_block(descriptor.block_bitmap, group.bitmap, state); !result) + { + return kstd::failure(result.error()); + } + batch.mark_group(group.index); + } + + return allocated_blocks; + } + + auto update_inode_block_count(inode_data & data, uint32_t delta, mount_state & state) -> void + { + data.blocks += delta * (2 << state.superblock.log_block_size); + } + + auto write_inode(uint32_t inode_number, inode_data const & data, mount_state & state) -> kstd::result + { + if (auto inode_offset = calculate_inode_offset(inode_number, state)) + { + return state.backing_inode->write(kstd::raw_bytes(data), *inode_offset).transform([](auto) {}); + } + else + { + return kstd::failure(inode_offset.error()); + } + } + + auto inode_block_number(size_t index, inode const & inode, mount_state const & state) -> kstd::result + { + auto const & block_array = inode.data().block; + + if (index < constants::direct_block_count) + { + return block_array.at(index); + } + + index -= constants::direct_block_count; + auto const block_size = ext2::block_size(state); + auto const numbers_per_block = block_numbers(1, block_size); + for (auto const & level : indirect_levels(block_size)) + { + auto const capacity = level.capacity; + if (index >= capacity) + { + index -= capacity; + continue; + } + + auto block_number = block_array[level.slot_index]; + if (block_number == 0) + { + return 0; + } + + for (auto stride = capacity / numbers_per_block;; stride /= numbers_per_block) + { + auto const idx = index / stride; + index %= stride; + + if (auto read_result = read_block_number_at_index(block_number, idx, state); !read_result) + { + return kstd::failure(read_result.error()); + } + else + { + block_number = read_result.value(); + } + + if (block_number == 0) + { + return 0; + } + + if (stride == 1) + { + break; + } + } + + return block_number; + } + + return kstd::failure(errc::invalid_block_index); + } + + auto write_global_block_number_to_inode_block_index(size_t inode_block_index, inode_data & data, + uint32_t global_block_number, mount_state & state, + write_batch & batch) -> kstd::result + { + if (inode_block_index < constants::direct_block_count) + { + data.block[inode_block_index] = global_block_number; + return kstd::success(); + } + inode_block_index -= constants::direct_block_count; + + auto const block_size = ext2::block_size(state); + auto const numbers_per_block = block_numbers(1, block_size); + for (auto const & level : indirect_levels(block_size)) + { + if (inode_block_index >= level.capacity) + { + inode_block_index -= level.capacity; + continue; + } + + auto & backing_inode = state.backing_inode; + auto block_number = data.block[level.slot_index]; + auto parent_byte_offset = 0_B; + bool is_root = true; + + for (auto stride = level.capacity / numbers_per_block;; stride /= numbers_per_block) + { + auto const idx = inode_block_index / stride; + inode_block_index %= stride; + + if (idx == 0 && inode_block_index == 0) + { + auto allocated = allocate_blocks(1, state, batch); + if (!allocated) + { + return kstd::failure(allocated.error()); + } + + block_number = allocated->front(); + + if (is_root) + { + data.block[level.slot_index] = block_number; + } + else + { + if (auto write_result = backing_inode->write(kstd::raw_bytes(block_number), parent_byte_offset); + !write_result) + { + return kstd::failure(write_result.error()); + } + } + } + + auto const byte_offset = block_number * block_size + idx * kstd::size_of(); + + if (stride == 1) + { + if (auto write_result = backing_inode->write(kstd::raw_bytes(global_block_number), byte_offset); + !write_result) + { + return kstd::failure(write_result.error()); + } + return kstd::success(); + } + + parent_byte_offset = byte_offset; + is_root = false; + if (auto read_result = read_block_number_at_index(block_number, idx, state); !read_result) + { + return kstd::failure(read_result.error()); + } + else + { + block_number = read_result.value(); + } + } + } + + return kstd::success(); + } + + auto write_block_group_descriptor(block_group_descriptor const & block_group_descriptor, + size_t block_group_descriptor_index, mount_state const & state) + -> kstd::result + { + // TODO update all block group descriptors + return state.backing_inode + ->write(kstd::raw_bytes(block_group_descriptor), + block_group_descriptor_table_offset(state) + + block_group_descriptor_index * kstd::size_of(block_group_descriptor)) + .transform([](auto) {}); + } + + auto write_superblock(mount_state & state) -> kstd::result + { + // TODO update all superblock copies + return state.backing_inode->write(kstd::raw_bytes(state.superblock), constants::superblock_offset) + .transform([](auto) {}); + } + + //! @} + +} // namespace kernel::filesystems::ext2 diff --git a/kernel/kernel/filesystems/ext2/filesystem.hpp b/kernel/kernel/filesystems/ext2/filesystem.hpp new file mode 100644 index 00000000..cdf552c9 --- /dev/null +++ b/kernel/kernel/filesystems/ext2/filesystem.hpp @@ -0,0 +1,164 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_EXT2_FILESYSTEM_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_EXT2_FILESYSTEM_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace kernel::filesystems::ext2 +{ + //! Constants related to the ext2 filesystem. + namespace constants + { + constexpr kstd::bytes inline base_block_size = kstd::bytes{1024}; + constexpr kstd::bytes inline superblock_offset = base_block_size; + constexpr uint16_t inline magic_number = 0xEF53; + + constexpr uint32_t inline good_old_revision = 0; + constexpr uint32_t inline dynamic_revision = 1; + + 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; + constexpr uint16_t inline mode_symbolic_link = 0xA000; + constexpr uint16_t inline mode_block_device = 0x6000; + constexpr uint16_t inline mode_character_device = 0x2000; + constexpr uint16_t inline mode_fifo = 0x1000; + constexpr uint16_t inline mode_socket = 0xC000; + + } // namespace constants + + //! The Second Extended Filesystem (ext2) + struct filesystem final : kernel::vfs::filesystem + { + //! @name Driver Interface Implementation + //! @{ + + [[nodiscard]] auto probe(inode_ptr const & backing_inode) const -> kstd::result override; + + auto mount(inode_ptr const & backing_inode) -> kstd::result override; + + [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const + -> kstd::result override; + + [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, + driver_data_ptr driver_data, + std::optional raw_device = std::nullopt) + -> kstd::result override; + + //! @} + }; + + //! Get the size of a block in the filesystem. + //! + //! @param state The driver state to operate on. + //! @return The size of a block in bytes. + [[nodiscard]] auto block_size(mount_state const & state) -> kstd::bytes; + + //! Get the number of blocks allocated to an inode. + //! + //! @param inode The inode whose block count to query. + //! @param state The driver state to operate on. + //! @return The number of blocks allocated to the inode. + [[nodiscard]] auto block_count(inode const & inode, mount_state const & state) -> uint32_t; + + //! Determine the size of an inode's data on disk. + //! + //! @param inode The inode whose data size to determine. + //! @param state The driver state to operate on. + //! @return The size of the data referenced by the inode. + [[nodiscard]] auto data_size(inode const & inode, mount_state const & state) -> kstd::bytes; + + //! Set the size of an inode's data on disk. + //! + //! @param inode The inode whose data size to set. + //! @param new_size The size to set. + //! @param state The driver state to operate on. + auto set_data_size(inode & inode, kstd::bytes new_size, mount_state const & state) -> void; + + //! Perform the actual read of the requested data. + //! + //! @param inode The inode whose data to read. + //! @param buffer The buffer to read into. + //! @param offset The offset inside the inode's data to start at. + //! @param state The driver state to operate on. + //! @return The number of bytes read on success, an error otherwise. + [[nodiscard]] auto do_read(inode const & inode, std::span buffer, kstd::bytes offset, + mount_state const & state) -> kstd::result; + + //! Allocate a specified number of blocks. + //! + //! @param count The number of blocks to allocate. + //! @param state The state to operate on. + //! @param batch The active write batch. + //! @return A vector of the allocated block numbers. + auto allocate_blocks(size_t count, mount_state & state, write_batch & batch) -> kstd::result>; + + //! Update the number of blocks allocated to an inode. + //! + //! @param data The inode data. + //! @param state The state to operate on. + //! @param delta The change in the number of blocks. + auto update_inode_block_count(inode_data & data, uint32_t delta, mount_state & state) -> void; + + //! Write an inode to the backing inode. + //! + //! @param inode_number The number of the inode to write. + //! @param state The state to operate on. + //! @param data The inode data to write. + auto write_inode(uint32_t inode_number, inode_data const & data, mount_state & state) -> kstd::result; + + //! Get the block number associated with the nth block of an inode. + //! + //! @param index The index of the block within the inode. + //! @param inode The inode. + //! @param state The state to operate on. + //! @return The global block number on success, an error otherwise. + [[nodiscard]] auto inode_block_number(size_t index, inode const & inode, mount_state const & state) + -> kstd::result; + + //! Write a global block number to an inode block index. + //! + //! @param block_index The index of the block within the inode. + //! @param data The inode data. + //! @param global_block_number The global block number to write. + //! @param state The state to operate on. + //! @param batch The active write batch. + auto write_global_block_number_to_inode_block_index(size_t block_index, inode_data & data, + uint32_t global_block_number, mount_state & state, + write_batch & batch) -> kstd::result; + + [[nodiscard]] auto write_block_group_descriptor(block_group_descriptor const & block_group_descriptor, + size_t block_group_descriptor_index, mount_state const & state) + -> kstd::result; + [[nodiscard]] auto write_superblock(mount_state & state) -> kstd::result; +} // namespace kernel::filesystems::ext2 + +#endif diff --git a/kernel/kernel/filesystems/ext2/filesystem.tests.cpp b/kernel/kernel/filesystems/ext2/filesystem.tests.cpp new file mode 100644 index 00000000..4ace4e3d --- /dev/null +++ b/kernel/kernel/filesystems/ext2/filesystem.tests.cpp @@ -0,0 +1,360 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +using namespace kstd::units_literals; + +// NOLINTBEGIN(readability-magic-numbers) + +SCENARIO_METHOD(kernel::tests::vfs::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::determine_boot_device(); + REQUIRE(boot_device); + + auto dev_inode = kstd::make_shared(boot_device); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + auto driver_data = (*mount)->driver_data(); + REQUIRE(mount); + + THEN("the root inode is available and is a directory") + { + REQUIRE(root); + REQUIRE((root)->is_directory()); + } + + THEN("lookup resolves known entries from the image") + { + auto information = fs->lookup(root, "information", driver_data); + REQUIRE(information); + REQUIRE(information.value()->is_directory()); + (*information)->set_owning_mount(*mount); + + auto info_1 = fs->lookup(*information, "info_1.txt", driver_data); + REQUIRE(info_1); + REQUIRE(info_1.value()->is_regular()); + (*info_1)->set_owning_mount(*mount); + } + + THEN("lookup returns null for invalid inputs") + { + REQUIRE(!fs->lookup(nullptr, "information", driver_data)); + + auto information = fs->lookup(root, "information", driver_data); + REQUIRE(information); + (*information)->set_owning_mount(*mount); + auto info_1 = fs->lookup(*information, "info_1.txt", driver_data); + REQUIRE(info_1); + (*info_1)->set_owning_mount(*mount); + + REQUIRE(!fs->lookup(*info_1, "anything", driver_data)); + REQUIRE(!fs->lookup(root, "does_not_exist", driver_data)); + } + } +} + +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_fixture, "Ext2 filesystem create new inodes 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::determine_boot_device(); + REQUIRE(boot_device); + + auto dev_inode = kstd::make_shared(boot_device); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + auto driver_data = (*mount)->driver_data(); + REQUIRE(mount); + + THEN("a file can be created") + { + auto lookup_result = fs->lookup(root, "blub", driver_data); + REQUIRE(!lookup_result); + + auto new_inode = fs->create_inode(root, "blub", kapi::filesystem::file_type::regular, driver_data); + REQUIRE(new_inode); + REQUIRE(new_inode.value()->is_regular()); + (*new_inode)->set_owning_mount(*mount); + + lookup_result = fs->lookup(root, "blub", driver_data); + REQUIRE(lookup_result); + (*lookup_result)->set_owning_mount(*mount); + } + + THEN("a directory can be created") + { + auto lookup_result = fs->lookup(root, "blub", driver_data); + REQUIRE(!lookup_result); + + auto new_directory = fs->create_inode(root, "blub", kapi::filesystem::file_type::directory, driver_data); + REQUIRE(new_directory); + REQUIRE(new_directory.value()->is_directory()); + + lookup_result = fs->lookup(root, "blub", driver_data); + REQUIRE(lookup_result); + } + + THEN("a directory and a file within can be created") + { + auto new_directory = fs->create_inode(root, "blub", kapi::filesystem::file_type::directory, driver_data); + REQUIRE(new_directory); + (*new_directory)->set_owning_mount(*mount); + + auto new_file = + fs->create_inode(new_directory.value(), "blub_file", kapi::filesystem::file_type::regular, driver_data); + REQUIRE(new_file); + (*new_file)->set_owning_mount(*mount); + + auto lookup_result = fs->lookup(new_directory.value(), "blub_file", driver_data); + REQUIRE(lookup_result); + REQUIRE(lookup_result.value()->is_regular()); + } + + THEN("a file can be created and written to") + { + auto new_inode = fs->create_inode(root, "blub", kapi::filesystem::file_type::regular, driver_data); + REQUIRE(new_inode); + + auto buffer = + std::vector{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'}, std::byte{'o'}, std::byte{' '}, + std::byte{'W'}, std::byte{'o'}, std::byte{'r'}, std::byte{'l'}, std::byte{'d'}, std::byte{'!'}}; + auto bytes_written = new_inode.value()->write(buffer, 0_B); + REQUIRE(bytes_written == kstd::bytes{buffer.size()}); + + auto read_buffer = std::vector(5, std::byte{0xAA}); + auto bytes_read = new_inode.value()->read(read_buffer, 0_B); + REQUIRE(bytes_read == kstd::bytes{read_buffer.size()}); + + std::string_view result{reinterpret_cast(read_buffer.data()), static_cast(*bytes_read)}; + REQUIRE(result == "Hello"); + } + } +} + +SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem]") +{ + auto const block_size = 1024_B; + GIVEN("a block device that does not contain an ext2 superblock") + { + auto device = kstd::make_shared("mock", block_size, 2 * block_size); + REQUIRE(device); + + auto dev_inode = kstd::make_shared(device); + + auto fs = kstd::make_shared(); + + THEN("mount fails with invalid_magic_number") + { + REQUIRE(fs->mount(dev_inode).error() == kernel::filesystems::ext2::errc::invalid_magic_number); + } + } +} + +SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesystem][ext2][filesystem]") +{ + auto const block_size = 1024_B; + + GIVEN("a minimally valid ext2 layout with configured indirect block tables") + { + auto device = kstd::make_shared("mock", block_size, 128 * block_size); + REQUIRE(device); + + kernel::tests::filesystems::ext2::setup_mock_ext2_layout(*device); + + auto dev_inode = kstd::make_shared(device); + + auto fs = kstd::make_shared(); + auto mount = fs->mount(dev_inode); + REQUIRE(mount); + auto mount_state = static_pointer_cast(mount->second); + REQUIRE(mount_state); + + auto inode_data = kernel::filesystems::ext2::inode_data{}; + inode_data.block[0] = 7; + + inode_data.block[12] = 30; + kernel::tests::filesystems::ext2::write_u32(*device, 30 * block_size, 31); + + inode_data.block[13] = 40; + kernel::tests::filesystems::ext2::write_u32(*device, 40 * block_size, 41); + kernel::tests::filesystems::ext2::write_u32(*device, 41 * block_size, 42); + + inode_data.block[14] = 50; + kernel::tests::filesystems::ext2::write_u32(*device, 50 * block_size, 51); + kernel::tests::filesystems::ext2::write_u32(*device, 51 * block_size, 52); + kernel::tests::filesystems::ext2::write_u32(*device, 52 * block_size, 53); + + auto const numbers_per_block = static_cast(block_size / kstd::size_of()); + auto const singly_start = static_cast(kernel::filesystems::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; + + auto inode = kernel::filesystems::ext2::inode{42, inode_data}; + + THEN("mapping resolves direct, singly, doubly and triply indirect indexes") + { + REQUIRE(inode_block_number(0, inode, *mount_state) == 7); + REQUIRE(inode_block_number(singly_start, inode, *mount_state) == 31); + REQUIRE(inode_block_number(doubly_start, inode, *mount_state) == 42); + REQUIRE(inode_block_number(triply_start, inode, *mount_state) == 53); + } + + THEN("mapping returns error for out-of-range indexes") + { + auto const beyond_triply = triply_start + numbers_per_block * numbers_per_block * numbers_per_block; + REQUIRE(!inode_block_number(beyond_triply, inode, *mount_state)); + } + } +} + +SCENARIO("Ext2 block writing includes direct and all indirect levels", "[filesystem][ext2][filesystem]") +{ + auto const block_size = 1024_B; + + GIVEN("a mounted ext2 filesystem with free blocks available for indirect table allocation") + { + auto device = kstd::make_shared("mock", block_size, 256 * block_size); + REQUIRE(device); + + auto superblock = kernel::filesystems::ext2::superblock{}; + superblock.magic = kernel::filesystems::ext2::constants::magic_number; + superblock.log_block_size = 0; + superblock.blocks_count = 256; + superblock.free_blocks_count = 128; + superblock.blocks_per_group = 256; + superblock.inodes_per_group = 32; + superblock.rev_level = kernel::filesystems::ext2::constants::dynamic_revision; + superblock.inode_size = 128; + + auto block_group_descriptor = kernel::filesystems::ext2::block_group_descriptor{}; + block_group_descriptor.block_bitmap = 255; + block_group_descriptor.inode_table = 5; + block_group_descriptor.free_blocks_count = 128; + + auto block_bitmap = std::vector(block_size.value, std::byte{0x00}); + block_bitmap[0] = std::byte{0b1111'1111}; + block_bitmap[1] = std::byte{0b1111'1111}; + + auto dev_inode = kstd::make_shared(device); + REQUIRE(dev_inode->write(block_bitmap, block_group_descriptor.block_bitmap * block_size)); + + kernel::tests::filesystems::ext2::setup_mock_ext2_layout(*device, superblock, block_group_descriptor); + + auto fs = kstd::make_shared(); + auto mount = fs->mount(dev_inode); + REQUIRE(mount); + auto mount_state = static_pointer_cast(mount->second); + REQUIRE(mount_state); + + auto read_u32 = [&](kstd::bytes offset) -> uint32_t { + auto value = uint32_t{0}; + std::memcpy(&value, device->data.data() + offset, sizeof(value)); + return value; + }; + + auto inode_data = kernel::filesystems::ext2::inode_data{}; + + auto const numbers_per_block = static_cast(block_size / kstd::size_of()); + auto const singly_start = static_cast(kernel::filesystems::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("writing resolves direct, singly, doubly and triply indirect indexes") + { + constexpr auto direct_global_block_number = 101; + constexpr auto singly_global_block_number = 202; + constexpr auto doubly_global_block_number = 303; + constexpr auto triply_global_block_number = 404; + + auto batch = kernel::filesystems::ext2::write_batch{*mount_state}; + + REQUIRE(write_global_block_number_to_inode_block_index(0, inode_data, direct_global_block_number, *mount_state, + batch)); + REQUIRE(inode_data.block[0] == direct_global_block_number); + + REQUIRE(write_global_block_number_to_inode_block_index(singly_start, inode_data, singly_global_block_number, + *mount_state, batch)); + REQUIRE(inode_data.block[12] == 16); + REQUIRE(read_u32(static_cast(inode_data.block[12]) * block_size.value) == + singly_global_block_number); + + REQUIRE(write_global_block_number_to_inode_block_index(singly_start + 1, inode_data, + singly_global_block_number + 1, *mount_state, batch)); + REQUIRE(read_u32(static_cast(inode_data.block[12]) * block_size.value + + 1 * kstd::size_of()) == singly_global_block_number + 1); + + REQUIRE(write_global_block_number_to_inode_block_index(doubly_start, inode_data, doubly_global_block_number, + *mount_state, batch)); + REQUIRE(inode_data.block[13] == 17); + auto const doubly_leaf_table = read_u32(static_cast(inode_data.block[13]) * block_size); + REQUIRE(doubly_leaf_table == 18); + REQUIRE(read_u32(static_cast(doubly_leaf_table) * block_size) == doubly_global_block_number); + + REQUIRE(write_global_block_number_to_inode_block_index(triply_start, inode_data, triply_global_block_number, + *mount_state, batch)); + REQUIRE(inode_data.block[14] == 19); + auto const triply_middle_table = read_u32(static_cast(inode_data.block[14]) * block_size); + REQUIRE(triply_middle_table == 20); + auto const triply_leaf_table = read_u32(static_cast(triply_middle_table) * block_size); + REQUIRE(triply_leaf_table == 21); + REQUIRE(read_u32(static_cast(triply_leaf_table) * block_size) == triply_global_block_number); + + auto inode = kernel::filesystems::ext2::inode{42, inode_data}; + + REQUIRE(inode_block_number(0, inode, *mount_state) == direct_global_block_number); + REQUIRE(inode_block_number(singly_start, inode, *mount_state) == singly_global_block_number); + REQUIRE(inode_block_number(doubly_start, inode, *mount_state) == doubly_global_block_number); + REQUIRE(inode_block_number(triply_start, inode, *mount_state) == triply_global_block_number); + } + } +} + +// NOLINTEND(readability-magic-numbers) \ No newline at end of file diff --git a/kernel/kernel/filesystems/ext2/inode.cpp b/kernel/kernel/filesystems/ext2/inode.cpp new file mode 100644 index 00000000..8e5c4559 --- /dev/null +++ b/kernel/kernel/filesystems/ext2/inode.cpp @@ -0,0 +1,362 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace kstd::units_literals; + +namespace kernel::filesystems::ext2 +{ + + namespace + { + constexpr auto to_file_type(std::uint8_t entry_type) noexcept -> kapi::filesystem::file_type + { + switch (entry_type) + { + case 1: + return kapi::filesystem::file_type::regular; + case 2: + return kapi::filesystem::file_type::directory; + case 3: + return kapi::filesystem::file_type::character_device; + case 4: + return kapi::filesystem::file_type::block_device; + case 5: + return kapi::filesystem::file_type::fifo; + case 6: + return kapi::filesystem::file_type::socket; + case 7: + return kapi::filesystem::file_type::symbolic_link; + default: + return kapi::filesystem::file_type{}; + } + } + } // namespace + + inode::inode(uint32_t inode_number, inode_data const & data) + : m_inode_number(inode_number) + , m_data(data) + {} + + auto inode::read(std::span buffer, kstd::bytes offset) const -> kstd::result + { + if (is_symbolic_link() && this->size() <= kstd::bytes{sizeof(m_data.block)}) + { + auto const max_readable = this->size() - offset; + auto const requested_size = std::min(kstd::bytes{buffer.size()}, max_readable); + auto inline_target = reinterpret_cast(m_data.block.data()); + kstd::libc::memcpy(buffer.data(), inline_target + offset, requested_size.value); + return requested_size; + } + + auto state = get_driver_data(); + if (!state) + { + return kstd::failure(state.error()); + } + + auto backing_inode = (*state)->backing_inode; + if (!(*state)->backing_inode) + { + return kstd::failure(vfs::errc::not_mounted); + } + + auto guard = kstd::lock_guard{(*state)->lock}; + return ext2::do_read(*this, buffer, offset, **state); + } + + auto inode::write(std::span buffer, kstd::bytes offset) -> kstd::result + { + if (!is_regular()) + { + kapi::system::panic("[EXT2] ext2::inode write called on inode that is not of type regular"); + } + + // TODO check maximum file size of filesystem + // TODO handle sparse files + + auto state = get_driver_data(); + if (!state) + { + return kstd::failure(state.error()); + } + + auto backing_inode = (*state)->backing_inode; + if (!(*state)->backing_inode) + { + return kstd::failure(vfs::errc::not_mounted); + } + + auto guard = kstd::lock_guard{(*state)->lock}; + auto batch = write_batch{**state}; + + auto const new_inode_size = std::max(ext2::data_size(*this, **state), offset + kstd::bytes{buffer.size()}); + + auto block_size = ext2::block_size(**state); + auto const current_block_count = ext2::block_count(*this, **state); + auto const max_new_inode_size_without_new_blocks = current_block_count * block_size; + + if (new_inode_size > max_new_inode_size_without_new_blocks) + { + auto const additional_blocks_needed = + (new_inode_size - max_new_inode_size_without_new_blocks + block_size - 1_B) / block_size; + + if (!append_blocks(additional_blocks_needed, batch)) + { + return kstd::failure(errc::not_enough_free_blocks); + } + } + + auto block_index = offset / block_size; + auto in_block_offset = offset % block_size; + + auto bytes_written = 0_B; + + while (bytes_written < kstd::bytes{buffer.size()}) + { + auto const block_number = ext2::inode_block_number(block_index, *this, **state); + // TODO BA-FS26 if blocknumber == 0 --> handle sparse file + if (!block_number) + { + return kstd::failure(block_number.error()); + } + + auto const bytes_to_write = std::min(kstd::bytes{buffer.size()} - bytes_written, block_size - in_block_offset); + + auto const block_start_offset = block_number.value() * block_size; + auto const write_offset = block_start_offset + in_block_offset; + + auto const write_result = + backing_inode->write(buffer.subspan(bytes_written.value, bytes_to_write.value), write_offset); + if (!write_result) + { + return kstd::failure(write_result.error()); + } + + bytes_written += write_result.value(); + + block_index++; + in_block_offset = 0_B; + } + + ext2::set_data_size(*this, std::max(ext2::data_size(*this, **state), offset + bytes_written), **state); + batch.mark_inode(*this); + return bytes_written; + } + + auto inode::is_block_device() const -> bool + { + return (m_data.mode & constants::mode_mask) == constants::mode_block_device; + } + + auto inode::is_character_device() const -> bool + { + return (m_data.mode & constants::mode_mask) == constants::mode_character_device; + } + + auto inode::is_directory() const -> bool + { + return (m_data.mode & constants::mode_mask) == constants::mode_directory; + } + + auto inode::is_regular() const -> bool + { + return (m_data.mode & constants::mode_mask) == constants::mode_regular; + } + + auto inode::is_symbolic_link() const -> bool + { + return (m_data.mode & constants::mode_mask) == constants::mode_symbolic_link; + } + + auto inode::raw_device() const -> std::optional + { + if (!is_device()) + { + return std::nullopt; + } + + auto const raw_device_number = m_data.block[0]; + + return kapi::filesystem::device_number{ + .major = static_cast((raw_device_number >> 8) & 0xff), + .minor = static_cast(raw_device_number & 0xff), + }; + } + + auto inode::status() const -> kstd::result + { + auto result = kernel::vfs::inode::status(); + if (!result) + { + return result; + } + + result->mode = m_data.mode; + result->size = size(); + result->inode_number = m_inode_number; + result->link_count = m_data.links_count; + + return result; + } + + auto inode::read_directory(vfs::directory_listing_cursor position, + std::span entries) const + -> kstd::result> + { + if (!is_directory()) + { + return kstd::failure(vfs::errc::not_a_directory); + } + + auto state = get_driver_data(); + if (!state) + { + return kstd::failure(state.error()); + } + + auto guard = kstd::lock_guard{(*state)->lock}; + + auto count = 0uz; + auto it = directory_iterator{*this, kstd::bytes{position.value}, **state}; + + for (; it != directory_iterator{} && count < entries.size(); ++it) + { + if (it->inode == 0) + { + continue; + } + + entries[count++] = vfs::directory_listing_entry{ + .name = kstd::string{it->name()}, + .type = to_file_type(it->file_type), + .inode_number = it->inode, + }; + + if (count == entries.size()) + { + break; + } + } + + return std::pair{count, vfs::directory_listing_cursor{it.offset().value}}; + } + + auto inode::append_blocks(size_t count, write_batch & batch) -> bool + { + auto state = get_driver_data(); + if (!state) + { + return false; + } + + auto new_blocks = ext2::allocate_blocks(count, **state, batch); + if (!new_blocks) + { + return false; + } + + for (auto i = 0uz; i < new_blocks->size(); ++i) + { + auto const block_index = ext2::block_count(*this, **state) + i; + auto const global_block_number = new_blocks->at(i); + + if (auto write_result = ext2::write_global_block_number_to_inode_block_index(block_index, m_data, + global_block_number, **state, batch); + !write_result) + { + return false; + } + } + + ext2::update_inode_block_count(m_data, count, **state); + return true; + } + + auto inode::set_size(kstd::bytes new_size) -> void + { + auto driver_data = get_driver_data(); + if (!driver_data) + { + return; + } + + auto state = static_pointer_cast(*driver_data); + auto guard = kstd::lock_guard{state->lock}; + return ext2::set_data_size(*this, new_size, *state); + } + + [[nodiscard]] auto inode::data() -> inode_data & + { + return m_data; + } + + [[nodiscard]] auto inode::data() const -> inode_data const & + { + return m_data; + } + + auto inode::size() const -> kstd::bytes + { + auto state = get_driver_data(); + if (!state) + { + return 0_B; + } + + auto guard = kstd::lock_guard{(*state)->lock}; + return ext2::data_size(*this, **state); + } + + [[nodiscard]] auto inode::number() const -> uint32_t + { + return m_inode_number; + } + + auto inode::get_driver_data() -> kstd::result> + { + if (auto mount = owning_mount().lock()) + { + return static_pointer_cast(mount->driver_data()); + } + + return kstd::failure(vfs::errc::not_mounted); + } + + auto inode::get_driver_data() const -> kstd::result> + { + if (auto mount = owning_mount().lock()) + { + return static_pointer_cast(mount->driver_data()); + } + + return kstd::failure(vfs::errc::not_mounted); + } + +} // namespace kernel::filesystems::ext2 diff --git a/kernel/kernel/filesystems/ext2/inode.hpp b/kernel/kernel/filesystems/ext2/inode.hpp new file mode 100644 index 00000000..e2cb74a1 --- /dev/null +++ b/kernel/kernel/filesystems/ext2/inode.hpp @@ -0,0 +1,161 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEM_EXT2_INODE_HPP +#define TEACHOS_KERNEL_FILESYSTEM_EXT2_INODE_HPP + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace kernel::filesystems::ext2 +{ + struct filesystem; + + //! The data for a single ext2 inode. + struct [[gnu::packed]] inode_data + { + uint16_t mode; + uint16_t uid; + uint32_t size; + uint32_t atime; + uint32_t ctime; + uint32_t mtime; + uint32_t dtime; + uint16_t gid; + uint16_t links_count; + uint32_t blocks; + uint32_t flags; + uint32_t osd1; + std::array block; // NOLINT(readability-magic-numbers) + uint32_t generation; + uint32_t file_acl; + uint32_t dir_acl; + uint32_t faddr; + std::array osd2; // NOLINT(readability-magic-numbers) + }; + + struct inode : kernel::vfs::inode + { + //! @name Construction/Destruction + //! @{ + + //! Create an ext2 inode associated with the given filesystem. + //! + //! @param inode_number The inode number on disk. + //! @param data The data associated with this inode, read from the disk. + explicit inode(uint32_t inode_number, inode_data const & data); + + //! @} + + //! @name I/O Operations + //! @{ + + [[nodiscard]] auto read(std::span buffer, kstd::bytes offset) const + -> kstd::result override; + + auto write(std::span buffer, kstd::bytes offset) -> kstd::result override; + + //! @} + + //! @name Property Checking + //! @{ + + [[nodiscard]] auto is_block_device() const -> bool override; + + [[nodiscard]] auto is_character_device() const -> bool override; + + [[nodiscard]] auto is_directory() const -> bool override; + + [[nodiscard]] auto is_regular() const -> bool override; + + [[nodiscard]] auto is_symbolic_link() const -> bool override; + + //! @} + + //! @name POSIX Information Access + //! @{ + + [[nodiscard]] auto raw_device() const -> std::optional override; + + [[nodiscard]] auto status() const -> kstd::result override; + + [[nodiscard]] auto read_directory(vfs::directory_listing_cursor position, + std::span entries) const + -> kstd::result> override; + + //! @} + + //! @name Property Manipulation + //! @{ + + //! Append the specified number of blocks to this inode. + //! + //! @param count The number of blocks to append. + //! @param batch The active write batch. + //! @return true if the blocks were successfully appended, false otherwise. + auto append_blocks(size_t count, write_batch & batch) -> bool; + + //! Set the size of the file represented by this inode. + //! + //! @param new_size The new size of the file in bytes. + auto set_size(kstd::bytes new_size) -> void; + + //! @} + + //! @name Ext2 Information Queries + //! @{ + + //! Get the data associated with this inode. + //! + //! @return A reference to the inode data. + [[nodiscard]] auto data() -> inode_data &; + + //! Get the data associated with this inode. + //! + //! @return A const reference to the inode data. + [[nodiscard]] auto data() const -> inode_data const &; + + //! Get the size of the file represented by this inode. + //! + //! @return The size of the file in bytes. + [[nodiscard]] auto size() const -> kstd::bytes; + + //! @brief Get the inode number of this inode. + //! + //! @return The inode number. + [[nodiscard]] auto number() const -> uint32_t; + + //! @} + + private: + //! Get the filesystem driver data associated with this inode. + //! + //! @return The filesystem driver data on success, and error otherwise. + [[nodiscard]] auto get_driver_data() -> kstd::result>; + + //! Get the filesystem driver data associated with this inode. + //! + //! @return The filesystem driver data on success, and error otherwise. + [[nodiscard]] auto get_driver_data() const -> kstd::result>; + + //! The inode number on disk. + uint32_t m_inode_number{}; + //! The inode data. + inode_data m_data{}; + }; +} // namespace kernel::filesystems::ext2 + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/ext2/inode.tests.cpp b/kernel/kernel/filesystems/ext2/inode.tests.cpp new file mode 100644 index 00000000..aa8a8da5 --- /dev/null +++ b/kernel/kernel/filesystems/ext2/inode.tests.cpp @@ -0,0 +1,874 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace kstd::units_literals; + +// NOLINTBEGIN(readability-magic-numbers) + +namespace +{ + auto write_entry(std::vector & block, std::size_t offset, uint32_t inode_number, std::string_view name, + uint8_t file_type, kstd::bytes block_size, bool extend_to_block_end) -> std::size_t + { + auto const header_and_name = 8uz + name.size(); + auto const rounded = (header_and_name + 3uz) & ~3uz; + auto const rec_len = extend_to_block_end ? static_cast(block_size.value) - offset : rounded; + + auto * entry = reinterpret_cast(block.data() + offset); + entry->inode = inode_number; + entry->rec_len = static_cast(rec_len); + entry->name_len = static_cast(name.size()); + entry->file_type = file_type; + std::copy(name.begin(), name.end(), &entry->name_start); + + return rounded; + } +} // namespace + +SCENARIO("Ext2 inode initialization and properties", "[filesystem][ext2][inode]") +{ + GIVEN("an ext2 filesystem") + { + auto fs = kernel::filesystems::ext2::filesystem{}; + auto data = kernel::filesystems::ext2::inode_data{}; + + THEN("the inode is initialized with regular file mode in data and has the kind regular") + { + data.mode = kernel::filesystems::ext2::constants::mode_regular; + auto inode = kernel::filesystems::ext2::inode(42, data); + + REQUIRE(inode.is_regular()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_symbolic_link()); + } + + THEN("the inode is initialized with directory mode in data and has the kind directory") + { + data.mode = kernel::filesystems::ext2::constants::mode_directory; + auto inode = kernel::filesystems::ext2::inode(42, data); + + REQUIRE_FALSE(inode.is_regular()); + REQUIRE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_symbolic_link()); + } + + THEN("the inode is initialized with symbolic link mode in data and has the kind symbolic link") + { + data.mode = kernel::filesystems::ext2::constants::mode_symbolic_link; + auto inode = kernel::filesystems::ext2::inode(42, data); + + REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE(inode.is_symbolic_link()); + } + + THEN("the inode is initialized with zero mode in data and has no specific kind") + { + data.mode = 0; + auto inode = kernel::filesystems::ext2::inode(42, data); + + REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_symbolic_link()); + } + } +} + +SCENARIO_METHOD(kernel::tests::vfs::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::determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto dev_inode = kstd::make_shared(boot_device); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + auto driver_data = (*mount)->driver_data(); + REQUIRE(mount); + + auto information = fs->lookup(root, "information", driver_data); + REQUIRE(information); + (*information)->set_owning_mount(*mount); + + auto file = fs->lookup(*information, "info_1.txt", driver_data); + REQUIRE(file); + REQUIRE(file.value()->is_regular()); + + (*file)->set_owning_mount(*mount); + + THEN("reading from offset zero returns expected file prefix") + { + auto buffer = kstd::vector(6); + auto const bytes_read = file.value()->read(buffer, 0_B); + + REQUIRE(bytes_read == 6_B); + + auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read->value}; + REQUIRE(text == "info_1"); + } + + THEN("reading with an offset returns the expected byte") + { + auto buffer = kstd::vector(1); + auto const bytes_read = file.value()->read(buffer, 5_B); + + REQUIRE(bytes_read == 1_B); + REQUIRE(static_cast(buffer[0]) == '1'); + } + } +} + +SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesystem][ext2][inode]") +{ + auto const block_size = 1024_B; + GIVEN("an ext2 inode with only direct mapped data blocks") + { + auto device = kstd::make_shared("mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystems::ext2::setup_mock_ext2_layout(*device); + + auto dev_inode = kstd::make_shared(device); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); + + auto data = kernel::filesystems::ext2::inode_data{}; + data.block[0] = 30; + data.block[1] = 0; + data.block[2] = 31; + data.size = block_size.value * 3; + + kernel::tests::filesystems::ext2::write_bytes(*device, 30 * block_size, "Hello", 5_B); + kernel::tests::filesystems::ext2::write_bytes(*device, 31 * block_size, "World!", 6_B); + + auto inode = kernel::filesystems::ext2::inode{42, data}; + inode.set_owning_mount(*mount); + + auto buffer = kstd::vector(data.size, std::byte{0xAB}); + + THEN("correct number of bytes are read and holes are returned as zeros") + { + auto const bytes_read = inode.read(buffer, 0_B); + REQUIRE(bytes_read == kstd::bytes{data.size}); + + auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read->value}; + REQUIRE(text.substr(0, 5) == "Hello"); + REQUIRE(std::ranges::all_of(text.substr(5, block_size.value - 5), [](char c) { return c == '\0'; })); + REQUIRE(text.substr(2 * block_size.value, 6) == "World!"); + REQUIRE(std::ranges::all_of(text.substr(2 * block_size.value + 6, 3 * block_size.value), + [](char c) { return c == '\0'; })); + } + } + + GIVEN("an ext2 indode with file holes in singly indirect blocks") + { + auto device = kstd::make_shared("mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystems::ext2::setup_mock_ext2_layout(*device); + + auto dev_inode = kstd::make_shared(device); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); + + auto data = kernel::filesystems::ext2::inode_data{}; + data.block[0] = 30; + data.block[12] = 31; + data.size = block_size.value * 15; + + kernel::tests::filesystems::ext2::write_u32(*device, 31 * block_size, 50); + kernel::tests::filesystems::ext2::write_u32(*device, 31 * block_size + 4_B, 0); + kernel::tests::filesystems::ext2::write_u32(*device, 31 * block_size + 8_B, 51); + + kernel::tests::filesystems::ext2::write_bytes(*device, 30 * block_size, "Hello", 5_B); + kernel::tests::filesystems::ext2::write_bytes(*device, 50 * block_size, "Blub", 4_B); + kernel::tests::filesystems::ext2::write_bytes(*device, 51 * block_size, "World!", 6_B); + + auto inode = kernel::filesystems::ext2::inode{42, data}; + inode.set_owning_mount(*mount); + + auto buffer = kstd::vector(data.size, std::byte{0xAB}); + + THEN("correct number of bytes are read and holes are returned as zeros") + { + auto const bytes_read = inode.read(buffer, 0_B); + REQUIRE(bytes_read == kstd::bytes{data.size}); + + auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read->value}; + REQUIRE(text.substr(0, 5) == "Hello"); + REQUIRE(std::ranges::all_of(text.substr(5, 12 * block_size.value - 5), [](char c) { return c == '\0'; })); + REQUIRE(text.substr(12 * block_size.value, 4) == "Blub"); + REQUIRE(std::ranges::all_of(text.substr(12 * block_size.value + 4, 2 * block_size.value - 4), + [](char c) { return c == '\0'; })); + REQUIRE(text.substr(14 * block_size.value, 6) == "World!"); + REQUIRE(std::ranges::all_of(text.substr(14 * block_size.value + 6, 1 * block_size.value - 6), + [](char c) { return c == '\0'; })); + } + } + + GIVEN("an ext2 inode with zero singly indirect block pointer") + { + auto device = kstd::make_shared("mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystems::ext2::setup_mock_ext2_layout(*device); + + auto dev_inode = kstd::make_shared(device); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); + + auto data = kernel::filesystems::ext2::inode_data{}; + data.block[12] = 0; + data.size = block_size.value * 15; + + auto inode = kernel::filesystems::ext2::inode{42, data}; + inode.set_owning_mount(*mount); + + auto buffer = kstd::vector(block_size.value * 15, std::byte{0xAB}); + + THEN("all direct blocks are zero when singly indirect block pointer is zero") + { + auto const bytes_read = inode.read(buffer, 0_B); + REQUIRE(bytes_read == kstd::bytes{buffer.size()}); + REQUIRE(std::ranges::all_of(buffer, [](std::byte c) { return c == std::byte{0x00}; })); + } + } +} + +SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]") +{ + auto const block_size = 1024_B; + GIVEN("an ext2 inode with two direct blocks and a block size of 1024 bytes") + { + auto device = kstd::make_shared("mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystems::ext2::setup_mock_ext2_layout(*device); + + auto dev_inode = kstd::make_shared(device); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); + + auto inode_data = kernel::filesystems::ext2::inode_data{}; + inode_data.size = block_size.value * 2; + inode_data.block[0] = 20; + kernel::tests::filesystems::ext2::write_bytes(*device, 21 * block_size - 6_B, "Hello ", 6_B); + inode_data.block[1] = 21; + kernel::tests::filesystems::ext2::write_bytes(*device, 21 * block_size, "World!", 6_B); + auto inode = kernel::filesystems::ext2::inode{42, inode_data}; + inode.set_owning_mount(*mount); + + auto buffer = kstd::vector(12, std::byte{0x00}); + + THEN("reading across the block boundary returns the combined content") + { + auto const bytes_read = inode.read(buffer, block_size - 6_B); + REQUIRE(bytes_read == kstd::bytes{12}); + + auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read->value}; + REQUIRE(text == "Hello World!"); + } + } +} + +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_fixture, "Ext2 inode writes to real image", + "[filesystem][ext2][inode]") +{ + 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::determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto dev_inode = kstd::make_shared(boot_device); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + auto driver_data = (*mount)->driver_data(); + REQUIRE(mount); + + auto information = fs->lookup(root, "information", driver_data); + REQUIRE(information); + (*information)->set_owning_mount(*mount); + + auto file = fs->lookup(*information, "info_1.txt", driver_data); + REQUIRE(file); + REQUIRE(file.value()->is_regular()); + (*file)->set_owning_mount(*mount); + + auto mount_state = static_pointer_cast(driver_data); + REQUIRE(mount_state); + + THEN("writing to the inode updates backing device and the inode size") + { + auto write_buffer = kstd::vector{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'}, + std::byte{'o'}, std::byte{' '}, std::byte{'W'}, std::byte{'o'}, + std::byte{'r'}, std::byte{'l'}, std::byte{'d'}, std::byte{'!'}}; + + auto const bytes_written = file.value()->write(write_buffer, 0_B); + REQUIRE(bytes_written == kstd::bytes{12}); + + auto read_buffer = kstd::vector(42, std::byte{0x00}); + auto const bytes_read = file.value()->read(read_buffer, 0_B); + REQUIRE(bytes_read == kstd::bytes{12}); + + auto const text = std::string_view{reinterpret_cast(read_buffer.data()), bytes_read->value}; + REQUIRE(text == "Hello World!"); + + auto inode_data = static_cast(file.value().get())->data(); + REQUIRE(inode_data.size == bytes_read->value); + } + + THEN("writing allocates new blocks and updates the inode size") + { + auto inode = static_pointer_cast(file.value()); + + auto const block_size = kernel::filesystems::ext2::block_size(*mount_state); + auto const expected_allocated_blocks = 32 * block_count(*inode, *mount_state); + + auto write_buffer = kstd::vector(block_size.value * expected_allocated_blocks, std::byte{'A'}); + + auto const bytes_written = inode->write(write_buffer, 0_B); + REQUIRE(bytes_written == block_size * expected_allocated_blocks); + + auto read_buffer = kstd::vector(block_size.value * expected_allocated_blocks, std::byte{0x00}); + auto const bytes_read = inode->read(read_buffer, 0_B); + REQUIRE(bytes_read == block_size * expected_allocated_blocks); + REQUIRE(std::ranges::all_of(read_buffer, [](std::byte c) { return c == std::byte{'A'}; })); + + REQUIRE(inode->size().value == write_buffer.size()); + REQUIRE(block_count(*inode, *mount_state) == expected_allocated_blocks); + } + + THEN("writing to a an inode of type directory panics") + { + auto write_buffer = kstd::vector(12, std::byte{0xAA}); + REQUIRE_THROWS_AS(information.value()->write(write_buffer, 0_B), kernel::tests::cpu::halt); + } + } +} + +SCENARIO("Ext2 inode write across block boundaries", "[filesystem][ext2][inode]") +{ + auto const block_size = 1024_B; + + auto device = kstd::make_shared("mock", block_size, 128 * block_size); + REQUIRE(device != nullptr); + + auto superblock = kernel::filesystems::ext2::superblock{}; + superblock.magic = kernel::filesystems::ext2::constants::magic_number; + superblock.log_block_size = 0; + superblock.blocks_count = 64; + superblock.blocks_per_group = 64; + superblock.inodes_per_group = 32; + superblock.inode_size = 128; + superblock.rev_level = 1; + superblock.free_blocks_count = 64 - 15; + + auto block_group_descriptor = kernel::filesystems::ext2::block_group_descriptor{}; + block_group_descriptor.inode_table = 5; + block_group_descriptor.free_blocks_count = 64 - 15; + block_group_descriptor.block_bitmap = 10; + + kernel::tests::filesystems::ext2::setup_mock_ext2_layout(*device, superblock, block_group_descriptor); + + auto dev_inode = kstd::make_shared(device); + + auto block_bitmap = std::vector(block_size.value, std::byte{0x00}); + block_bitmap[0] = std::byte{0b1111'1111}; + block_bitmap[1] = std::byte{0b1111'1111}; + block_bitmap[2] = std::byte{0b0011'0000}; + + REQUIRE(dev_inode->write(block_bitmap, block_group_descriptor.block_bitmap * block_size)); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); + auto mount_state = static_pointer_cast((*mount)->driver_data()); + REQUIRE(mount_state); + + auto inode_data = kernel::filesystems::ext2::inode_data{}; + inode_data.size = 5; + inode_data.blocks = 2 * block_size.value / 512; + inode_data.block[0] = 20; + inode_data.block[1] = 21; + inode_data.mode = kernel::filesystems::ext2::constants::mode_regular; + + uint32_t inode_number = 3; + auto const inode_data_offset = block_group_descriptor.inode_table * block_size + + kstd::bytes{(inode_number - 1) * mount_state->superblock.inode_size}; + + auto inode = kernel::filesystems::ext2::inode{inode_number, inode_data}; + inode.set_owning_mount(*mount); + REQUIRE(dev_inode->write(kstd::raw_bytes(inode), inode_data_offset)); + + auto const buffer = + kstd::vector{std::byte{'H'}, std::byte{'e'}, std::byte{'l'}, std::byte{'l'}, std::byte{'o'}, std::byte{' '}, + std::byte{'W'}, std::byte{'o'}, std::byte{'r'}, std::byte{'l'}, std::byte{'d'}, std::byte{'!'}}; + + GIVEN("mocked ext2 filesystem") + { + auto do_read_inode = [&]() { + auto inode_data = kernel::filesystems::ext2::inode_data{}; + REQUIRE(dev_inode->read(kstd::raw_bytes(inode_data), inode_data_offset)); + auto read_inode = kernel::filesystems::ext2::inode{inode_number, inode_data}; + read_inode.set_owning_mount(*mount); + return read_inode; + }; + + THEN("writing in the current size does not update the size and does not allocate new blocks") + { + auto const expected_bytes_written = 3_B; + auto const bytes_written = inode.write(std::span{buffer}.subspan(0, 3), 0_B); + REQUIRE(bytes_written == expected_bytes_written); + + auto const text = std::string_view{reinterpret_cast(device->data.data() + 20 * block_size), 3}; + REQUIRE(text == "Hel"); + + auto read_block_bitmap = std::vector(block_size.value, std::byte{0x00}); + REQUIRE(dev_inode->read(read_block_bitmap, block_group_descriptor.block_bitmap * block_size)); + REQUIRE(block_bitmap == read_block_bitmap); + + auto read_inode = do_read_inode(); + REQUIRE(inode_data.size == read_inode.size().value); + REQUIRE(inode_data.blocks == read_inode.data().blocks); + + auto const current_blocks = inode_data.block; + auto const read_blocks = read_inode.data().block; + REQUIRE(current_blocks == read_blocks); + } + + THEN("writing over the current size (within an existing block) updates the size but does not allocate additional " + "blocks") + { + auto const offset = block_size - 4_B; + auto const bytes_written = inode.write(buffer, offset); + REQUIRE(bytes_written == kstd::bytes{buffer.size()}); + + auto const text = std::string_view{ + reinterpret_cast(device->data.data() + 21 * block_size.value - 4), bytes_written->value}; + REQUIRE(text == "Hello World!"); + + auto read_block_bitmap = std::vector(block_size.value, std::byte{0x00}); + REQUIRE(dev_inode->read(read_block_bitmap, block_group_descriptor.block_bitmap * block_size)); + REQUIRE(block_bitmap == read_block_bitmap); + + auto read_inode = do_read_inode(); + REQUIRE(offset + kstd::bytes{buffer.size()} == read_inode.size()); + REQUIRE(inode_data.blocks == read_inode.data().blocks); + + auto const current_blocks = inode_data.block; + auto const read_blocks = read_inode.data().block; + REQUIRE(current_blocks == read_blocks); + } + + THEN("writing over the current size and over allocated blocks updates size and allocates a new block") + { + auto const offset = block_size * 2 - 4_B; + auto const bytes_written = inode.write(buffer, offset); + REQUIRE(bytes_written == kstd::bytes{buffer.size()}); + + auto read_buffer = std::vector(buffer.size(), std::byte{0x00}); + auto const bytes_read = inode.read(read_buffer, offset); + auto const text = std::string_view{reinterpret_cast(read_buffer.data()), bytes_read->value}; + REQUIRE(text == "Hello World!"); + + auto read_block_bitmap = std::vector(block_size.value, std::byte{0x00}); + REQUIRE(dev_inode->read(read_block_bitmap, block_group_descriptor.block_bitmap * block_size)); + + auto expected_block_bitmap = block_bitmap; + expected_block_bitmap[2] = std::byte{0b0011'0001}; + REQUIRE(expected_block_bitmap == read_block_bitmap); + + auto read_inode = do_read_inode(); + REQUIRE(offset + kstd::bytes{buffer.size()} == read_inode.size()); + REQUIRE(inode_data.blocks + 2 == read_inode.data().blocks); + + auto expected_blocks = inode_data.block; + expected_blocks[2] = 16; + auto const read_blocks = read_inode.data().block; + REQUIRE(expected_blocks == read_blocks); + } + + THEN("writing over the current size and over the number of free blocks") + { + auto const offset = 65 * block_size; + REQUIRE_FALSE(inode.write(buffer, offset)); + } + } +} + +SCENARIO("Ext2 inode get_size() and set_size() handles size correctly depending on revision level", + "[filesystem][ext2][inode]") +{ + auto const block_size = 1024_B; + + auto superblock = kernel::filesystems::ext2::superblock{}; + superblock.magic = kernel::filesystems::ext2::constants::magic_number; + superblock.log_block_size = 0; + superblock.blocks_count = 64; + superblock.blocks_per_group = 64; + superblock.inodes_per_group = 32; + superblock.inode_size = 128; + + GIVEN("an ext2 inode with good old revision and inode_data.size = 256, inode_data.dir_acl = 32") + { + superblock.rev_level = 0; + + auto device = kstd::make_shared("mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystems::ext2::setup_mock_ext2_layout(*device, superblock); + + auto dev_inode = kstd::make_shared(device); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); + + auto data = kernel::filesystems::ext2::inode_data{}; + data.size = 256; + data.dir_acl = 32; + + auto const verify_size_and_set_size = [&](uint16_t mode, kstd::bytes expected_initial_size, + uint32_t expected_dir_acl_after_set_size) { + data.mode = mode; + + auto inode = kernel::filesystems::ext2::inode{42, data}; + inode.set_owning_mount(*mount); + + REQUIRE(inode.size() == expected_initial_size); + + inode.set_size(12_B); + + REQUIRE(inode.size() == 12_B); + REQUIRE(inode.data().size == 12); + REQUIRE(inode.data().dir_acl == expected_dir_acl_after_set_size); + }; + + THEN("the inode size is 256 if mode = regular") + { + verify_size_and_set_size(kernel::filesystems::ext2::constants::mode_regular, 256_B, 32); + } + + THEN("the inode size is 256 if mode = directory") + { + verify_size_and_set_size(kernel::filesystems::ext2::constants::mode_directory, 256_B, 32); + } + } + + GIVEN("an ext2 inode with good dynamic revision and inode_data.size = 256, inode_data.dir_acl = 32") + { + superblock.rev_level = 1; + + auto device = kstd::make_shared("mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + kernel::tests::filesystems::ext2::setup_mock_ext2_layout(*device, superblock); + + auto dev_inode = kstd::make_shared(device); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + REQUIRE(mount); + + auto data = kernel::filesystems::ext2::inode_data{}; + data.size = 256; + data.dir_acl = 32; + + auto const verify_size_and_set_size = [&](uint16_t mode, kstd::bytes expected_initial_size, + uint32_t expected_dir_acl_after_set_size) { + data.mode = mode; + + auto inode = kernel::filesystems::ext2::inode{42, data}; + inode.set_owning_mount(*mount); + + REQUIRE(inode.size() == expected_initial_size); + + inode.set_size(12_B); + + REQUIRE(inode.size() == 12_B); + REQUIRE(inode.data().size == 12); + REQUIRE(inode.data().dir_acl == expected_dir_acl_after_set_size); + }; + + THEN("the inode size is 256 if mode = regular") + { + verify_size_and_set_size(kernel::filesystems::ext2::constants::mode_regular, 0x0000'0020'0000'0100_B, 0); + } + + THEN("the inode size is 256 if mode = directory") + { + verify_size_and_set_size(kernel::filesystems::ext2::constants::mode_directory, 256_B, 32); + } + } +} + +SCENARIO("Ext2 inode status()", "[filesystem][ext2][inode]") +{ + GIVEN("an ext block device inode with real metadata") + { + auto fs = kernel::filesystems::ext2::filesystem{}; + auto data = kernel::filesystems::ext2::inode_data{}; + data.mode = kernel::filesystems::ext2::constants::mode_block_device; + data.block[0] = (1 << 8); + data.links_count = 1; + + auto inode = kernel::filesystems::ext2::inode(42, data); + + THEN("status() reports the real inode number, link count, and decoded raw device") + { + auto result = inode.status(); + REQUIRE(result); + + REQUIRE(result->mode == std::to_underlying(kapi::filesystem::file_type::block_device)); + REQUIRE(result->inode_number == 42); + REQUIRE(result->link_count == 1); + REQUIRE(result->raw_device == kapi::filesystem::device_number{.major = 1, .minor = 0}); + } + } +} + +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_fixture, + "Ext2 inode read_directory returns real directory entries", "[filesystem][ext2][inode][img][readdir]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("a mounted ext2 filesystem and the information directory inode") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); + + auto boot_device = kernel::devices::storage::determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto dev_inode = kstd::make_shared(boot_device); + + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + auto root = (*mount)->root_dentry()->inode(); + auto driver_data = (*mount)->driver_data(); + REQUIRE(mount); + + auto information = fs->lookup(root, "information", driver_data); + REQUIRE(information); + (*information)->set_owning_mount(*mount); + + WHEN("read_directory is called with a buffer large enough for everything in it") + { + auto entries = kstd::vector(8); + auto result = (*information)->read_directory(kernel::vfs::directory_listing_cursor{}, entries); + + THEN("known entries come back, correctly typed") + { + REQUIRE(result); + auto [count, next] = *result; + REQUIRE(count > 0); + + auto find_by_name = [&](std::string_view name) { + return std::ranges::find_if(entries.begin(), entries.begin() + static_cast(count), + [&](auto const & entry) { return entry.name == name; }); + }; + + auto dot = find_by_name("."); + REQUIRE(dot != entries.begin() + static_cast(count)); + REQUIRE(dot->type == kapi::filesystem::file_type::directory); + + auto info_1 = find_by_name("info_1.txt"); + REQUIRE(info_1 != entries.begin() + static_cast(count)); + REQUIRE(info_1->type == kapi::filesystem::file_type::regular); + + AND_THEN("a repeated call with the returned cursor reports exhaustion") + { + auto second = (*information)->read_directory(next, entries); + REQUIRE(second); + auto [second_count, second_cursor] = *second; + REQUIRE(second_count == 0); + REQUIRE(second_cursor == next); + } + } + } + } +} + +SCENARIO("Ext2 inode read_directory rejects non-directory inodes", "[filesystem][ext2][inode][readdir]") +{ + GIVEN("a regular file inode") + { + auto data = kernel::filesystems::ext2::inode_data{}; + data.mode = kernel::filesystems::ext2::constants::mode_regular; + auto inode = kernel::filesystems::ext2::inode{7, data}; + + THEN("read_directory fails without needing a mount at all") + { + auto entries = kstd::vector(1); + auto result = inode.read_directory(kernel::vfs::directory_listing_cursor{}, entries); + + REQUIRE_FALSE(result); + } + } +} + +SCENARIO("Ext2 inode read_directory skips deleted entries and paginates correctly", + "[filesystem][ext2][inode][readdir]") +{ + auto const block_size = 1024_B; + + GIVEN("a directory block with four entries, one of them deleted") + { + auto device = kstd::make_shared("mock", block_size, 64 * block_size); + REQUIRE(device != nullptr); + + auto superblock = kernel::filesystems::ext2::superblock{}; + superblock.magic = kernel::filesystems::ext2::constants::magic_number; + superblock.log_block_size = 0; + superblock.blocks_count = 64; + superblock.blocks_per_group = 64; + superblock.inodes_per_group = 32; + superblock.inode_size = 128; + superblock.rev_level = 1; + + auto block_group_descriptor = kernel::filesystems::ext2::block_group_descriptor{}; + block_group_descriptor.inode_table = 5; + block_group_descriptor.block_bitmap = 10; + + kernel::tests::filesystems::ext2::setup_mock_ext2_layout(*device, superblock, block_group_descriptor); + + auto dev_inode = kstd::make_shared(device); + auto fs = kstd::make_shared(); + auto mount = kernel::vfs::mount::create(nullptr, fs, nullptr, nullptr, dev_inode); + REQUIRE(mount); + + auto block = std::vector(block_size.value, std::byte{0}); + auto offset = 0uz; + offset += + write_entry(block, offset, 10, "alpha", + static_cast(kernel::filesystems::ext2::constants::mode_regular >> 8), block_size, false); + offset += write_entry(block, offset, 0, "deleted", 0, block_size, false); + offset += + write_entry(block, offset, 11, "beta", + static_cast(kernel::filesystems::ext2::constants::mode_regular >> 8), block_size, false); + write_entry(block, offset, 12, "gamma", + static_cast(kernel::filesystems::ext2::constants::mode_regular >> 8), block_size, true); + + kernel::tests::filesystems::ext2::write_bytes(*device, 30 * block_size, block.data(), block_size); + + auto data = kernel::filesystems::ext2::inode_data{}; + data.mode = kernel::filesystems::ext2::constants::mode_directory; + data.size = block_size.value; + data.block[0] = 30; + + auto directory = kernel::filesystems::ext2::inode{3, data}; + directory.set_owning_mount(*mount); + + WHEN("read_directory is called one entry at a time, chaining the returned cursor") + { + auto seen = std::vector{}; + auto position = kernel::vfs::directory_listing_cursor{}; + + for (auto guard = 0; guard < 8; ++guard) + { + auto one = kstd::vector(1); + auto result = directory.read_directory(position, one); + REQUIRE(result); + auto [count, next] = *result; + if (count == 0) + { + break; + } + seen.push_back(std::string{one[0].name}); + position = next; + } + + THEN("the deleted entry never appears, and every real entry is found exactly once") + { + REQUIRE(seen.size() == 3); + REQUIRE(std::ranges::find(seen, "deleted") == seen.end()); + REQUIRE(std::ranges::find(seen, "alpha") != seen.end()); + REQUIRE(std::ranges::find(seen, "beta") != seen.end()); + REQUIRE(std::ranges::find(seen, "gamma") != seen.end()); + } + } + + WHEN("read_directory is called with a buffer that exactly fits the three real entries") + { + auto entries = kstd::vector(3); + auto result = directory.read_directory(kernel::vfs::directory_listing_cursor{}, entries); + + THEN("all three come back, and the very next call reports exhaustion, not a fourth entry") + { + REQUIRE(result); + auto [count, next] = *result; + REQUIRE(count == 3); + + auto second = directory.read_directory(next, entries); + REQUIRE(second); + auto [second_count, second_cursor] = *second; + REQUIRE(second_count == 0); + REQUIRE(second_cursor == next); + } + } + } +} + +// NOLINTEND(readability-magic-numbers) \ No newline at end of file diff --git a/kernel/kernel/filesystems/ext2/linked_directory_entry.hpp b/kernel/kernel/filesystems/ext2/linked_directory_entry.hpp new file mode 100644 index 00000000..9530a586 --- /dev/null +++ b/kernel/kernel/filesystems/ext2/linked_directory_entry.hpp @@ -0,0 +1,25 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_EXT2_LINKED_DIRECTORY_ENTRY_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_EXT2_LINKED_DIRECTORY_ENTRY_HPP + +#include +#include + +namespace kernel::filesystems::ext2 +{ + //! A linked directory entry in the ext2 filesystem. + struct [[gnu::packed]] linked_directory_entry + { + [[nodiscard]] constexpr auto name() const noexcept -> std::string_view + { + return std::string_view{&name_start, name_len}; + } + + uint32_t inode; + uint16_t rec_len; + uint8_t name_len; + uint8_t file_type; + char name_start; // NOLINT(readability-magic-numbers) + }; +} // namespace kernel::filesystems::ext2 + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/ext2/module.cpp b/kernel/kernel/filesystems/ext2/module.cpp new file mode 100644 index 00000000..9032109a --- /dev/null +++ b/kernel/kernel/filesystems/ext2/module.cpp @@ -0,0 +1,26 @@ +#include +#include +#include + +#include + +#include + +namespace +{ + struct descriptor final : kernel::vfs::driver_descriptor + { + [[nodiscard]] auto name() const noexcept -> std::string_view override + { + return "ext2"; + } + + [[nodiscard]] auto make_instance() const -> kernel::vfs::filesystem::filesystem_ptr override + { + return kstd::make_shared(); + } + }; + + [[gnu::used]] + constexpr auto registration = kernel::vfs::driver_module{}; +} // namespace diff --git a/kernel/kernel/filesystems/ext2/mount_state.hpp b/kernel/kernel/filesystems/ext2/mount_state.hpp new file mode 100644 index 00000000..8103f4fb --- /dev/null +++ b/kernel/kernel/filesystems/ext2/mount_state.hpp @@ -0,0 +1,26 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_EXT2_MOUNT_STATE_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_EXT2_MOUNT_STATE_HPP + +#include +#include +#include + +#include + +#include +#include + +namespace kernel::filesystems::ext2 +{ + + struct mount_state + { + struct superblock superblock{}; + kstd::vector block_group_descriptors{}; + kstd::shared_ptr backing_inode{}; + mutable kapi::tracked_mutex lock{}; + }; + +} // namespace kernel::filesystems::ext2 + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/ext2/superblock.hpp b/kernel/kernel/filesystems/ext2/superblock.hpp new file mode 100644 index 00000000..92c73513 --- /dev/null +++ b/kernel/kernel/filesystems/ext2/superblock.hpp @@ -0,0 +1,122 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_EXT2_SUPERBLOCK_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_EXT2_SUPERBLOCK_HPP + +#include + +#include +#include +#include + +namespace kernel::filesystems::ext2 +{ + + //! Ext2 compatible features. + //! + //! These enumerators describe the flags that may be ignored by the an ext2 driver. + enum struct compatible_features : std::uint32_t + { + directory_preallocation = 1 << 0, + magic_inodes = 1 << 1, + ext3_journal = 1 << 2, + extended_attributes = 1 << 3, + non_standard_inode_size = 1 << 4, + hashed_directory_indexing = 1 << 5, + }; + + //! Ext2 incompatibility features + //! + //! These enumerators describe the flags that might not be supported by an ext2 driver. + enum struct incompatible_features : std::uint32_t + { + compression = 1 << 0, + file_types_in_directory_entries = 1 << 1, + journal_recovery_required = 1 << 2, + external_journal_device = 1 << 3, + meta_block_group_layout = 1 << 4, + }; + + //! Ext2 read-only compatible features + //! + //! There enumerators describe the flags that might prevent read-write access for a driver. + enum struct read_only_compatible_features : std::uint32_t + { + sparse_superblock_copies = 1 << 0, + large_file_support = 1 << 1, + binary_tree_directories = 1 << 2, + }; + + //! The superblock in the ext2 filesystem. + struct [[gnu::packed]] superblock + { + uint32_t inodes_count; + uint32_t blocks_count; + uint32_t reserved_blocks_count; + uint32_t free_blocks_count; + uint32_t free_inodes_count; + uint32_t first_data_block; + uint32_t log_block_size; + uint32_t log_frag_size; + uint32_t blocks_per_group; + uint32_t frags_per_group; + uint32_t inodes_per_group; + uint32_t mtime; + uint32_t wtime; + uint16_t mnt_count; + uint16_t max_mnt_count; + uint16_t magic; + uint16_t state; + uint16_t errors; + uint16_t minor_rev_level; + uint32_t lastcheck; + uint32_t checkinterval; + uint32_t creator_os; + uint32_t rev_level; + uint16_t def_resuid; + uint16_t def_resgid; + + // EXT2_DYNAMIC_REV superblock only + uint32_t first_ino; + uint16_t inode_size; + uint16_t block_group_nr; + compatible_features feature_compat; + incompatible_features feature_incompat; + read_only_compatible_features feature_ro_compat; + std::array uuid; + std::array volume_name; + std::array last_mounted; + uint32_t algorithm_usage_bitmap; + + // Performance Hints + uint8_t prealloc_blocks; + uint8_t prealloc_dir_blocks; + uint16_t padding1; + + // Journaling Support + std::array journal_uuid; + uint32_t journal_inum; + uint32_t journal_dev; + uint32_t last_orphan; + + // Directory Indexing Support + std::array hash_seed; + uint8_t def_hash_version; + std::array padding2; + + // Other options + uint32_t default_mount_options; + uint32_t first_meta_bg; + std::array unused; // NOLINT(readability-magic-numbers) + }; +} // namespace kernel::filesystems::ext2 + +template<> +struct kstd::is_bitfield_enum : std::true_type +{ +}; + +template<> +struct kstd::is_bitfield_enum : std::true_type +{ +}; + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/ext2/write_batch.cpp b/kernel/kernel/filesystems/ext2/write_batch.cpp new file mode 100644 index 00000000..f9a86a82 --- /dev/null +++ b/kernel/kernel/filesystems/ext2/write_batch.cpp @@ -0,0 +1,66 @@ +#include + +#include +#include +#include + +#include + +#include +#include +#include + +namespace kernel::filesystems::ext2 +{ + + write_batch::write_batch(mount_state & state) + : m_state{state} + {} + + write_batch::~write_batch() + { + std::ranges::for_each(m_dirty_groups, [this](auto index) { + auto & descriptor = m_state.block_group_descriptors.at(index); + auto result = ext2::write_block_group_descriptor(descriptor, index, m_state); + + if (!result) + { + kapi::system::panic("[FS:ext2] failed to write block groups descriptor", result.error()); + } + }); + + if (!m_dirty_groups.empty()) + { + auto result = ext2::write_superblock(m_state); + if (!result) + { + kapi::system::panic("[FS:ext2] failed to write superblock", result.error()); + } + } + + std::ranges::for_each(m_dirty_inodes, [this](auto inode) { + auto result = ext2::write_inode(inode->number(), inode->data(), m_state); + if (!result) + { + kapi::system::panic("[FS:ext2] failed to write inode", result.error()); + } + }); + } + + auto write_batch::mark_group(std::size_t index) -> void + { + if (std::ranges::find(m_dirty_groups, index) == std::ranges::end(m_dirty_groups)) + { + m_dirty_groups.push_back(index); + } + } + + auto write_batch::mark_inode(inode & inode) -> void + { + if (std::ranges::find(m_dirty_inodes, &inode) == std::ranges::end(m_dirty_inodes)) + { + m_dirty_inodes.push_back(&inode); + } + } + +} // namespace kernel::filesystems::ext2 diff --git a/kernel/kernel/filesystems/ext2/write_batch.hpp b/kernel/kernel/filesystems/ext2/write_batch.hpp new file mode 100644 index 00000000..926936df --- /dev/null +++ b/kernel/kernel/filesystems/ext2/write_batch.hpp @@ -0,0 +1,50 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_EXT2_WRITE_BATCH_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_EXT2_WRITE_BATCH_HPP + +#include + +#include + +namespace kernel::filesystems::ext2 +{ + + struct inode; + struct mount_state; + + //! A RAII write batching type. + //! + //! Any metadata writes are first appended to the batch. On destruction, the write is committed to the filesystem. + struct write_batch + { + //! Construct a new write batch for the given filesystem and mount state. + //! + //! @param state The driver state to operate on. + explicit write_batch(mount_state & state); + + write_batch(write_batch const &) = delete; + write_batch(write_batch &&) = delete; + auto operator=(write_batch const &) = delete; + auto operator=(write_batch &&) = delete; + + //! Commit and destroy the batch. + ~write_batch(); + + //! Mark the group with the given index as dirty. + //! + //! @param index The index of the group to mark. + auto mark_group(std::size_t index) -> void; + + //! Mark the given inode as dirty. + //! + //! @param inode The inode to mark. + auto mark_inode(inode & inode) -> void; + + private: + mount_state & m_state; + kstd::vector m_dirty_groups{}; + kstd::vector m_dirty_inodes{}; + }; + +} // namespace kernel::filesystems::ext2 + +#endif diff --git a/kernel/kernel/filesystems/rootfs/filesystem.cpp b/kernel/kernel/filesystems/rootfs/filesystem.cpp new file mode 100644 index 00000000..01dfddc1 --- /dev/null +++ b/kernel/kernel/filesystems/rootfs/filesystem.cpp @@ -0,0 +1,47 @@ +#include + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +namespace kernel::filesystems::rootfs +{ + static_assert(sizeof(kernel::vfs::filesystem) == sizeof(rootfs::filesystem)); + + auto filesystem::probe(inode_ptr const & inode) const -> kstd::result + { + if (inode) + { + return kstd::failure(vfs::errc::invalid_argument); + } + + return 0; + } + + auto filesystem::mount(inode_ptr const &) -> kstd::result + { + auto root = kstd::make_shared(); + return mount_result{root, nullptr}; + } + + auto filesystem::lookup(inode_ptr const &, std::string_view, driver_data_ptr) const -> kstd::result + { + return kstd::failure(vfs::errc::no_such_file_or_directory); + } + + auto filesystem::create_inode(inode_ptr const &, std::string_view, kapi::filesystem::file_type, driver_data_ptr, + std::optional) -> kstd::result + { + return kstd::failure(vfs::errc::read_only_file_system); + } +} // namespace kernel::filesystems::rootfs diff --git a/kernel/kernel/filesystems/rootfs/filesystem.hpp b/kernel/kernel/filesystems/rootfs/filesystem.hpp new file mode 100644 index 00000000..6f4db776 --- /dev/null +++ b/kernel/kernel/filesystems/rootfs/filesystem.hpp @@ -0,0 +1,42 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_ROOTFS_FILESYSTEM_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_ROOTFS_FILESYSTEM_HPP + +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace kernel::filesystems::rootfs +{ + //! A filesystem for the filesystem root. + //! + //! 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::vfs::filesystem + { + [[nodiscard]] auto probe(inode_ptr const & inode) const -> kstd::result override; + + auto mount(inode_ptr const & backing_inode) -> kstd::result override; + + [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, driver_data_ptr driver_data) const + -> kstd::result override; + + [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, + driver_data_ptr driver_data, + std::optional raw_device = std::nullopt) + -> kstd::result override; + }; +} // namespace kernel::filesystems::rootfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystems/rootfs/filesystem.tests.cpp b/kernel/kernel/filesystems/rootfs/filesystem.tests.cpp new file mode 100644 index 00000000..1cc4c10f --- /dev/null +++ b/kernel/kernel/filesystems/rootfs/filesystem.tests.cpp @@ -0,0 +1,48 @@ +#include + +#include + +#include + +#include +#include +#include + +#include + +SCENARIO("Rootfs filesystem mount and lookup", "[filesystem][rootfs][filesystem]") +{ + GIVEN("a mounted rootfs filesystem") + { + auto fs = kstd::make_shared(); + auto mount = fs->mount(nullptr); + REQUIRE(mount); + auto [root, driver_data] = *mount; + REQUIRE(root); + + THEN("the filesystem can be mounted successfully") + { + REQUIRE(mount); + } + + THEN("looking up a non-existent directory returns an error") + { + auto non_existent_inode_1 = fs->lookup(root, "", driver_data); + REQUIRE(!non_existent_inode_1); + auto non_existent_inode_2 = fs->lookup(root, "nonexistent", driver_data); + REQUIRE(!non_existent_inode_2); + } + + THEN("looking up with a null parent inode returns an error") + { + auto result = fs->lookup(nullptr, "dev", driver_data); + REQUIRE(!result); + } + + THEN("create_inode always returns an error") + { + auto result = fs->create_inode(root, "new_device", kapi::filesystem::file_type::directory, driver_data); + REQUIRE(!result); + } + } +} diff --git a/kernel/kernel/filesystems/rootfs/inode.cpp b/kernel/kernel/filesystems/rootfs/inode.cpp new file mode 100644 index 00000000..4c5b5384 --- /dev/null +++ b/kernel/kernel/filesystems/rootfs/inode.cpp @@ -0,0 +1,36 @@ +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include + +namespace kernel::filesystems::rootfs +{ + auto inode::read(std::span, kstd::bytes) const -> kstd::result + { + return kstd::bytes{}; + } + + auto inode::write(std::span, kstd::bytes) -> kstd::result + { + return kstd::bytes{}; + } + + auto inode::is_directory() const -> bool + { + return true; + } + + auto inode::read_directory(vfs::directory_listing_cursor position, std::span) const + -> kstd::result> + { + return std::pair{0, position}; + } +} // namespace kernel::filesystems::rootfs diff --git a/kernel/kernel/filesystems/rootfs/inode.hpp b/kernel/kernel/filesystems/rootfs/inode.hpp new file mode 100644 index 00000000..37b2fc54 --- /dev/null +++ b/kernel/kernel/filesystems/rootfs/inode.hpp @@ -0,0 +1,36 @@ +#ifndef TEACHOS_KERNEL_FILESYSTEMS_ROOTFS_INODE_HPP +#define TEACHOS_KERNEL_FILESYSTEMS_ROOTFS_INODE_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace kernel::filesystems::rootfs +{ + //! An inode in the rootfs filesystem. + struct inode : kernel::vfs::inode + { + [[nodiscard]] auto read(std::span buffer, kstd::bytes offset) const + -> kstd::result override; + + auto write(std::span buffer, kstd::bytes offset) -> kstd::result override; + + [[nodiscard]] auto is_directory() const -> bool override; + + [[nodiscard]] auto read_directory(vfs::directory_listing_cursor position, + std::span entries) const + -> kstd::result> override; + }; +} // namespace kernel::filesystems::rootfs + +#endif diff --git a/kernel/kernel/filesystems/rootfs/inode.tests.cpp b/kernel/kernel/filesystems/rootfs/inode.tests.cpp new file mode 100644 index 00000000..7e151d9f --- /dev/null +++ b/kernel/kernel/filesystems/rootfs/inode.tests.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include +#include +#include + +#include + +#include + +using namespace kstd::units_literals; + +SCENARIO("Rootfs inode read/write", "[filesystem][rootfs][inode]") +{ + GIVEN("a rootfs inode") + { + auto inode = kernel::filesystems::rootfs::inode{}; + + WHEN("reading from the inode") + { + kstd::vector buffer(10); + auto bytes_read = inode.read(buffer, 0_B); + + THEN("no bytes are read") + { + REQUIRE(bytes_read == 0_B); + } + } + + WHEN("writing to the inode") + { + kstd::vector buffer(10, std::byte{'x'}); + auto bytes_written = inode.write(buffer, 0_B); + + THEN("no bytes are written") + { + REQUIRE(bytes_written == 0_B); + } + } + } +} diff --git a/kernel/kernel/filesystems/rootfs/module.cpp b/kernel/kernel/filesystems/rootfs/module.cpp new file mode 100644 index 00000000..5975e9ba --- /dev/null +++ b/kernel/kernel/filesystems/rootfs/module.cpp @@ -0,0 +1,28 @@ +#include +#include +#include + +#include + +#include + +namespace +{ + + struct descriptor final : kernel::vfs::driver_descriptor + { + [[nodiscard]] auto name() const noexcept -> std::string_view override + { + return "rootfs"; + } + + [[nodiscard]] auto make_instance() const -> kernel::vfs::filesystem::filesystem_ptr override + { + return kstd::make_shared(); + } + }; + + [[gnu::used]] + constexpr auto registration = kernel::vfs::driver_module{}; + +} // namespace \ No newline at end of file diff --git a/kernel/kernel/main.cpp b/kernel/kernel/main.cpp index f8322b31..fd5ac5f1 100644 --- a/kernel/kernel/main.cpp +++ b/kernel/kernel/main.cpp @@ -1,10 +1,8 @@ -#include "kernel/filesystem/driver_registry.hpp" #include #include #include -#include -#include #include +#include #include #include @@ -245,13 +243,13 @@ auto main() -> int kapi::boot_modules::init(*modules_bus); kstd::println("[OS] Boot modules attached."); - kernel::filesystem::open_file_table::init(); + kernel::vfs::open_file_table::init(); kstd::println("[OS] Global open file table initialized."); - kernel::filesystem::driver_registry::init(); + kernel::vfs::driver_registry::init(); kstd::println("[OS] Builtin filesystems registered."); - kernel::filesystem::vfs::init(); + kernel::vfs::vfs::init(); kstd::println("[OS] Virtual filesystem initialized."); // TODO BA-FS26 remove demo code? diff --git a/kernel/kernel/test_support/filesystem/device_number_registry.hpp b/kernel/kernel/test_support/filesystem/device_number_registry.hpp deleted file mode 100644 index 5722837f..00000000 --- a/kernel/kernel/test_support/filesystem/device_number_registry.hpp +++ /dev/null @@ -1,9 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_DEVICE_NUMBER_REGISTRY_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_DEVICE_NUMBER_REGISTRY_HPP - -namespace kernel::tests::filesystem::device_number_registry -{ - auto deinit() -> void; -} - -#endif diff --git a/kernel/kernel/test_support/filesystem/driver_registry.hpp b/kernel/kernel/test_support/filesystem/driver_registry.hpp deleted file mode 100644 index b91acd57..00000000 --- a/kernel/kernel/test_support/filesystem/driver_registry.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_DRIVER_REGISTRY_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_DRIVER_REGISTRY_HPP - -namespace kernel::tests::filesystem::driver_registry -{ - //! Deinitialize the FS type registry singleton. - auto deinit() -> void; -} // namespace kernel::tests::filesystem::driver_registry - -#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/ext2.cpp b/kernel/kernel/test_support/filesystem/ext2.cpp deleted file mode 100644 index 96d94ea1..00000000 --- a/kernel/kernel/test_support/filesystem/ext2.cpp +++ /dev/null @@ -1,79 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -#include - -#include -#include - -using namespace kstd::units_literals; - -namespace kernel::tests::filesystem::ext2 -{ - namespace - { - constexpr uint32_t root_directory_data_block = 20; - } // namespace - - auto write_bytes(kernel::tests::devices::block_device & device, kstd::bytes offset, void const * source, - kstd::bytes size) -> void - { - auto const required_size = offset + size; - if (kstd::bytes{device.data.size()} < required_size) - { - device.data.resize(required_size.value, 0); - } - - std::memcpy(device.data.data() + offset, source, size.value); - } - - auto write_u32(kernel::tests::devices::block_device & device, kstd::bytes offset, uint32_t value) -> void - { - write_bytes(device, offset, &value, kstd::size_of(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; - setup_mock_ext2_layout(device, superblock); - } - - auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, - kernel::filesystem::ext2::superblock const & superblock) -> void - { - auto group_descriptor = kernel::filesystem::ext2::block_group_descriptor{}; - group_descriptor.inode_table = 5; - - setup_mock_ext2_layout(device, superblock, group_descriptor); - } - - auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, - kernel::filesystem::ext2::superblock const & superblock, - kernel::filesystem::ext2::block_group_descriptor const & group_descriptor) -> void - { - write_bytes(device, kernel::filesystem::ext2::constants::superblock_offset, &superblock, kstd::size_of(superblock)); - write_bytes(device, 2048_B, &group_descriptor, kstd::size_of(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(group_descriptor.inode_table) * kernel::filesystem::ext2::constants::base_block_size + - (kernel::filesystem::ext2::constants::root_inode_number - 1) * kstd::bytes{superblock.inode_size}; - write_bytes(device, root_inode_offset, &root_inode_data, kstd::size_of(root_inode_data)); - } -} // namespace kernel::tests::filesystem::ext2 \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/ext2.hpp b/kernel/kernel/test_support/filesystem/ext2.hpp deleted file mode 100644 index a83bdd94..00000000 --- a/kernel/kernel/test_support/filesystem/ext2.hpp +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP - -#include -#include -#include - -#include - -#include - -namespace kernel::tests::filesystem::ext2 -{ - auto write_bytes(kernel::tests::devices::block_device & device, kstd::bytes offset, void const * source, - kstd::bytes size) -> void; - auto write_u32(kernel::tests::devices::block_device & device, kstd::bytes offset, uint32_t value) -> void; - auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void; - auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, - kernel::filesystem::ext2::superblock const & superblock) -> void; - auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, - kernel::filesystem::ext2::superblock const & superblock, - kernel::filesystem::ext2::block_group_descriptor const & group_descriptor) -> void; - -} // namespace kernel::tests::filesystem::ext2 - -#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/filesystem.cpp b/kernel/kernel/test_support/filesystem/filesystem.cpp deleted file mode 100644 index 0d4610ab..00000000 --- a/kernel/kernel/test_support/filesystem/filesystem.cpp +++ /dev/null @@ -1,39 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include - -namespace kernel::tests::filesystem -{ - auto filesystem::probe(inode_ptr const &) const -> kstd::result - { - return 0; - } - - auto filesystem::mount(inode_ptr const &) -> kstd::result - { - return std::pair{kstd::make_shared(), nullptr}; - } - - auto filesystem::lookup(inode_ptr const &, std::string_view, driver_data_ptr) const -> kstd::result - { - return kstd::make_shared(); - } - - auto filesystem::create_inode(inode_ptr const &, std::string_view, kapi::filesystem::file_type, driver_data_ptr, - std::optional) -> kstd::result - { - return kstd::make_shared(); - } -} // namespace kernel::tests::filesystem \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/filesystem.hpp b/kernel/kernel/test_support/filesystem/filesystem.hpp deleted file mode 100644 index 3df2cccb..00000000 --- a/kernel/kernel/test_support/filesystem/filesystem.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILESYSTEM_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILESYSTEM_HPP - -#include -#include - -#include - -#include -#include -#include - -#include -#include -#include - -namespace kernel::tests::filesystem -{ - struct filesystem : kernel::filesystem::filesystem - { - filesystem() = default; - - [[nodiscard]] auto probe(inode_ptr const & inode) const -> kstd::result override; - - auto mount(kstd::shared_ptr const & backing_inode) - -> kstd::result override; - - [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, - driver_data_ptr driver_data = nullptr) const - -> kstd::result> override; - - [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, - driver_data_ptr driver_data = nullptr, - std::optional = std::nullopt) - -> kstd::result override; - }; -} // namespace kernel::tests::filesystem - -#endif diff --git a/kernel/kernel/test_support/filesystem/inode.cpp b/kernel/kernel/test_support/filesystem/inode.cpp deleted file mode 100644 index 409efe77..00000000 --- a/kernel/kernel/test_support/filesystem/inode.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -#include - -#include -#include - -#include -#include - -namespace kernel::tests::filesystem -{ - auto inode::read(std::span buffer, kstd::bytes) const -> kstd::result - { - return kstd::bytes{buffer.size()}; - } - - auto inode::write(std::span buffer, kstd::bytes) -> kstd::result - { - return kstd::bytes{buffer.size()}; - } - - auto inode::is_regular() const -> bool - { - return true; - } -} // namespace kernel::tests::filesystem \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/inode.hpp b/kernel/kernel/test_support/filesystem/inode.hpp deleted file mode 100644 index 51773f15..00000000 --- a/kernel/kernel/test_support/filesystem/inode.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_INODE_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_INODE_HPP - -#include - -#include -#include - -#include -#include - -namespace kernel::tests::filesystem -{ - struct inode : kernel::filesystem::inode - { - auto read(std::span buffer, kstd::bytes offset) const -> kstd::result override; - auto write(std::span buffer, kstd::bytes offset) -> kstd::result override; - - [[nodiscard]] auto is_regular() const -> bool override; - }; -} // namespace kernel::tests::filesystem - -#endif diff --git a/kernel/kernel/test_support/filesystem/open_file_table.hpp b/kernel/kernel/test_support/filesystem/open_file_table.hpp deleted file mode 100644 index 46b03341..00000000 --- a/kernel/kernel/test_support/filesystem/open_file_table.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_OPEN_FILE_TABLE_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_OPEN_FILE_TABLE_HPP - -namespace kernel::tests::filesystem::open_file_table -{ - //! Deinitialize the open file table singleton. - auto deinit() -> void; -} // namespace kernel::tests::filesystem::open_file_table - -#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.cpp b/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.cpp deleted file mode 100644 index b9228c69..00000000 --- a/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.cpp +++ /dev/null @@ -1,151 +0,0 @@ -#include - -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace kernel::tests::filesystem -{ - storage_boot_module_fixture::mapped_image::mapped_image(std::filesystem::path path) - : file_descriptor(::open(path.c_str(), O_RDWR)) - { - if (file_descriptor < 0) - { - throw std::runtime_error{"Failed to open image file for test boot module: " + path.string()}; - } - - struct stat statistics{}; - if (::fstat(file_descriptor, &statistics) < 0) - { - throw std::runtime_error{"Failed to get statistics for image file: " + path.string()}; - } - - size = static_cast(statistics.st_size); - - mapping = static_cast(::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, file_descriptor, 0)); - if (mapping == MAP_FAILED) - { - throw std::runtime_error{"Failed to map image file for test boot module: " + path.string()}; - } - } - - storage_boot_module_fixture::mapped_image::~mapped_image() - { - if (mapping != nullptr) - { - ::munmap(mapping, size); - } - if (file_descriptor >= 0) - { - ::close(file_descriptor); - } - } - - storage_boot_module_fixture::mapped_image::mapped_image(mapped_image && other) noexcept - : file_descriptor{std::exchange(other.file_descriptor, -1)} - , mapping{std::exchange(other.mapping, nullptr)} - , size{std::exchange(other.size, 0)} - {} - - auto storage_boot_module_fixture::mapped_image::operator=(mapped_image && other) noexcept -> mapped_image & - { - if (this != &other) - { - file_descriptor = std::exchange(other.file_descriptor, -1); - mapping = std::exchange(other.mapping, nullptr); - size = std::exchange(other.size, 0); - } - return *this; - } - - storage_boot_module_fixture::storage_boot_module_fixture() - { - m_boot_module_bus = kstd::make_shared(); - } - - 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_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(std::format("test_mod{} type=ramdisk", i)); - m_module_data.emplace_back(module_size, std::byte{static_cast(0x40 + (i % 16))}); - - auto module = kapi::boot_modules::module{ - .name = m_module_names[i].c_str(), - .start_address = kapi::memory::linear_address{m_module_data[i].data()}, - .size = kstd::bytes{m_module_data[i].size()}, - }; - m_boot_module_bus->add_child(kstd::make_shared(i, module)); - } - - if (module_count > 0 && kapi::devices::facet_registry::get().all(kapi::filesystem::block_special_file::id).empty()) - { - throw std::runtime_error{"No RAM disk driver bound to any of the test fixture's boot modules."}; - } - } - - auto storage_boot_module_fixture::setup_modules_from_img(std::vector const & module_names, - std::vector const & img_paths) -> void - { - m_module_names.clear(); - m_module_data.clear(); - - 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(i, module_names[i], img_paths[i]); - } - - if (!module_names.empty() && - kapi::devices::facet_registry::get().all(kapi::filesystem::block_special_file::id).empty()) - { - throw std::runtime_error{"No RAM disk driver bound to any of the test fixture's boot modules."}; - } - } - - auto storage_boot_module_fixture::setup_module_from_img(std::size_t index, std::string const & module_name, - std::filesystem::path const & img_path) -> void - { - m_module_names.push_back(module_name + " type=ramdisk"); - auto & mapped_image = m_mapped_images.emplace_back(img_path); - - auto module = kapi::boot_modules::module{ - .name = m_module_names.back().c_str(), - .start_address = kapi::memory::linear_address{mapped_image.mapping}, - .size = kstd::bytes{mapped_image.size}, - }; - - m_boot_module_bus->add_child(kstd::make_shared(index, module)); - } -} // namespace kernel::tests::filesystem \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.hpp b/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.hpp deleted file mode 100644 index 05830a91..00000000 --- a/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_STORAGE_BOOT_MODULE_FIXTURE_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_STORAGE_BOOT_MODULE_FIXTURE_HPP - -#include -#include - -#include - -#include -#include -#include -#include - -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(std::vector const & module_names, - std::vector const & img_paths) -> void; - - protected: - struct mapped_image - { - explicit mapped_image(std::filesystem::path path); - ~mapped_image(); - - mapped_image(mapped_image const &) = delete; - auto operator=(mapped_image const &) -> mapped_image & = delete; - - mapped_image(mapped_image &&) noexcept; - auto operator=(mapped_image &&) noexcept -> mapped_image &; - - int file_descriptor; - std::byte * mapping; - std::size_t size; - }; - - std::vector m_module_names{}; - std::vector> m_module_data{}; - std::vector m_mapped_images{}; - kstd::shared_ptr m_boot_module_bus{}; - - private: - auto setup_module_from_img(std::size_t index, std::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/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.cpp b/kernel/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.cpp deleted file mode 100644 index 02ccfec3..00000000 --- a/kernel/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include - -#include -#include - -#include -#include -#include -#include - -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( - std::vector const & module_names, std::vector 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/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp b/kernel/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp deleted file mode 100644 index 55f4b290..00000000 --- a/kernel/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#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 - -#include -#include -#include -#include - -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(std::vector const & module_names, - std::vector const & img_paths) -> void; - }; -} // namespace kernel::tests::filesystem - -#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/test_assets/README.md b/kernel/kernel/test_support/filesystem/test_assets/README.md deleted file mode 120000 index 718a2275..00000000 --- a/kernel/kernel/test_support/filesystem/test_assets/README.md +++ /dev/null @@ -1 +0,0 @@ -/arch/x86_64/support/modules/README.md \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/test_assets/ext2_1KB_devnode_fs.img b/kernel/kernel/test_support/filesystem/test_assets/ext2_1KB_devnode_fs.img deleted file mode 100644 index 09e59a4f..00000000 --- a/kernel/kernel/test_support/filesystem/test_assets/ext2_1KB_devnode_fs.img +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:393dcda08e381c4c0f86f8c7c04fd73e2e0b14891cea90d5e9e507adfe438435 -size 61440 diff --git a/kernel/kernel/test_support/filesystem/test_assets/ext2_1KB_fs.img b/kernel/kernel/test_support/filesystem/test_assets/ext2_1KB_fs.img deleted file mode 100644 index a5202ca3..00000000 --- a/kernel/kernel/test_support/filesystem/test_assets/ext2_1KB_fs.img +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:98ac3c1be872806e25fb14eea168ca79a91959f4e6a5ac3d00c5d8224c1f73a3 -size 10485760 diff --git a/kernel/kernel/test_support/filesystem/test_assets/ext2_2KB_fs.img b/kernel/kernel/test_support/filesystem/test_assets/ext2_2KB_fs.img deleted file mode 100644 index 7f297f07..00000000 --- a/kernel/kernel/test_support/filesystem/test_assets/ext2_2KB_fs.img +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6d9e872916e7d9107b321cc007e151899d5f19400a694666c0b24d482aef61ca -size 5242880 diff --git a/kernel/kernel/test_support/filesystem/test_assets/ext2_4KB_fs.img b/kernel/kernel/test_support/filesystem/test_assets/ext2_4KB_fs.img deleted file mode 100644 index c3f6dafb..00000000 --- a/kernel/kernel/test_support/filesystem/test_assets/ext2_4KB_fs.img +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:026ca30269dbd80beb2dd74677c94676d1d4a7f6b5fe406c4ddb82836ba2dc00 -size 10485760 diff --git a/kernel/kernel/test_support/filesystem/vfs.hpp b/kernel/kernel/test_support/filesystem/vfs.hpp deleted file mode 100644 index 739e3530..00000000 --- a/kernel/kernel/test_support/filesystem/vfs.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#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/kernel/test_support/filesystems/ext2.cpp b/kernel/kernel/test_support/filesystems/ext2.cpp new file mode 100644 index 00000000..d9008ade --- /dev/null +++ b/kernel/kernel/test_support/filesystems/ext2.cpp @@ -0,0 +1,80 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +using namespace kstd::units_literals; + +namespace kernel::tests::filesystems::ext2 +{ + namespace + { + constexpr uint32_t root_directory_data_block = 20; + } // namespace + + auto write_bytes(kernel::tests::devices::block_device & device, kstd::bytes offset, void const * source, + kstd::bytes size) -> void + { + auto const required_size = offset + size; + if (kstd::bytes{device.data.size()} < required_size) + { + device.data.resize(required_size.value, 0); + } + + std::memcpy(device.data.data() + offset, source, size.value); + } + + auto write_u32(kernel::tests::devices::block_device & device, kstd::bytes offset, uint32_t value) -> void + { + write_bytes(device, offset, &value, kstd::size_of(value)); + } + + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void + { + auto superblock = kernel::filesystems::ext2::superblock{}; + superblock.magic = kernel::filesystems::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; + setup_mock_ext2_layout(device, superblock); + } + + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, + kernel::filesystems::ext2::superblock const & superblock) -> void + { + auto group_descriptor = kernel::filesystems::ext2::block_group_descriptor{}; + group_descriptor.inode_table = 5; + + setup_mock_ext2_layout(device, superblock, group_descriptor); + } + + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, + kernel::filesystems::ext2::superblock const & superblock, + kernel::filesystems::ext2::block_group_descriptor const & group_descriptor) -> void + { + write_bytes(device, kernel::filesystems::ext2::constants::superblock_offset, &superblock, + kstd::size_of(superblock)); + write_bytes(device, 2048_B, &group_descriptor, kstd::size_of(group_descriptor)); + + auto root_inode_data = kernel::filesystems::ext2::inode_data{}; + root_inode_data.mode = kernel::filesystems::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(group_descriptor.inode_table) * kernel::filesystems::ext2::constants::base_block_size + + (kernel::filesystems::ext2::constants::root_inode_number - 1) * kstd::bytes{superblock.inode_size}; + write_bytes(device, root_inode_offset, &root_inode_data, kstd::size_of(root_inode_data)); + } +} // namespace kernel::tests::filesystems::ext2 \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystems/ext2.hpp b/kernel/kernel/test_support/filesystems/ext2.hpp new file mode 100644 index 00000000..7509f1e1 --- /dev/null +++ b/kernel/kernel/test_support/filesystems/ext2.hpp @@ -0,0 +1,26 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEMS_EXT2_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEMS_EXT2_HPP + +#include +#include +#include + +#include + +#include + +namespace kernel::tests::filesystems::ext2 +{ + auto write_bytes(kernel::tests::devices::block_device & device, kstd::bytes offset, void const * source, + kstd::bytes size) -> void; + auto write_u32(kernel::tests::devices::block_device & device, kstd::bytes offset, uint32_t value) -> void; + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void; + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, + kernel::filesystems::ext2::superblock const & superblock) -> void; + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, + kernel::filesystems::ext2::superblock const & superblock, + kernel::filesystems::ext2::block_group_descriptor const & group_descriptor) -> void; + +} // namespace kernel::tests::filesystems::ext2 + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystems/filesystem.cpp b/kernel/kernel/test_support/filesystems/filesystem.cpp new file mode 100644 index 00000000..219d85f8 --- /dev/null +++ b/kernel/kernel/test_support/filesystems/filesystem.cpp @@ -0,0 +1,39 @@ +#include + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace kernel::tests::filesystems +{ + auto filesystem::probe(inode_ptr const &) const -> kstd::result + { + return 0; + } + + auto filesystem::mount(inode_ptr const &) -> kstd::result + { + return std::pair{kstd::make_shared(), nullptr}; + } + + auto filesystem::lookup(inode_ptr const &, std::string_view, driver_data_ptr) const -> kstd::result + { + return kstd::make_shared(); + } + + auto filesystem::create_inode(inode_ptr const &, std::string_view, kapi::filesystem::file_type, driver_data_ptr, + std::optional) -> kstd::result + { + return kstd::make_shared(); + } +} // namespace kernel::tests::filesystems \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystems/filesystem.hpp b/kernel/kernel/test_support/filesystems/filesystem.hpp new file mode 100644 index 00000000..cfbfc550 --- /dev/null +++ b/kernel/kernel/test_support/filesystems/filesystem.hpp @@ -0,0 +1,38 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEMS_FILESYSTEM_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEMS_FILESYSTEM_HPP + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +namespace kernel::tests::filesystems +{ + struct filesystem : kernel::vfs::filesystem + { + filesystem() = default; + + [[nodiscard]] auto probe(inode_ptr const & inode) const -> kstd::result override; + + auto mount(kstd::shared_ptr const & backing_inode) -> kstd::result override; + + [[nodiscard]] auto lookup(inode_ptr const & parent, std::string_view name, + driver_data_ptr driver_data = nullptr) const + -> kstd::result> override; + + [[nodiscard]] auto create_inode(inode_ptr const & parent, std::string_view name, kapi::filesystem::file_type type, + driver_data_ptr driver_data = nullptr, + std::optional = std::nullopt) + -> kstd::result override; + }; +} // namespace kernel::tests::filesystems + +#endif diff --git a/kernel/kernel/test_support/filesystems/inode.cpp b/kernel/kernel/test_support/filesystems/inode.cpp new file mode 100644 index 00000000..3bba081d --- /dev/null +++ b/kernel/kernel/test_support/filesystems/inode.cpp @@ -0,0 +1,27 @@ +#include + +#include + +#include +#include + +#include +#include + +namespace kernel::tests::filesystems +{ + auto inode::read(std::span buffer, kstd::bytes) const -> kstd::result + { + return kstd::bytes{buffer.size()}; + } + + auto inode::write(std::span buffer, kstd::bytes) -> kstd::result + { + return kstd::bytes{buffer.size()}; + } + + auto inode::is_regular() const -> bool + { + return true; + } +} // namespace kernel::tests::filesystems \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystems/inode.hpp b/kernel/kernel/test_support/filesystems/inode.hpp new file mode 100644 index 00000000..cdb489de --- /dev/null +++ b/kernel/kernel/test_support/filesystems/inode.hpp @@ -0,0 +1,23 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEMS_INODE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEMS_INODE_HPP + +#include + +#include +#include + +#include +#include + +namespace kernel::tests::filesystems +{ + struct inode : kernel::vfs::inode + { + auto read(std::span buffer, kstd::bytes offset) const -> kstd::result override; + auto write(std::span buffer, kstd::bytes offset) -> kstd::result override; + + [[nodiscard]] auto is_regular() const -> bool override; + }; +} // namespace kernel::tests::filesystems + +#endif diff --git a/kernel/kernel/test_support/state_reset_listener.cpp b/kernel/kernel/test_support/state_reset_listener.cpp index 59f103b7..e7d17219 100644 --- a/kernel/kernel/test_support/state_reset_listener.cpp +++ b/kernel/kernel/test_support/state_reset_listener.cpp @@ -1,16 +1,16 @@ #include #include -#include -#include -#include -#include #include #include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -42,17 +42,17 @@ struct state_reset_listener : Catch::EventListenerBase kernel::drivers::init(); std::ignore = kernel::devices::storage::init(); - kernel::filesystem::open_file_table::init(); - std::ignore = kernel::filesystem::device_number_registry::get(); - kernel::filesystem::driver_registry::init(); + kernel::vfs::open_file_table::init(); + std::ignore = kernel::vfs::device_number_registry::get(); + kernel::vfs::driver_registry::init(); } void testCaseEnded(Catch::TestCaseStats const &) override { - kernel::tests::filesystem::driver_registry::deinit(); - kernel::tests::filesystem::device_number_registry::deinit(); - kernel::tests::filesystem::vfs::deinit(); - kernel::tests::filesystem::open_file_table::deinit(); + kernel::tests::vfs::driver_registry::deinit(); + kernel::tests::vfs::device_number_registry::deinit(); + kernel::tests::vfs::deinit(); + kernel::tests::vfs::open_file_table::deinit(); kapi::test_support::devices::deinit(); kernel::tests::memory::deinit(); diff --git a/kernel/kernel/test_support/vfs/device_number_registry.hpp b/kernel/kernel/test_support/vfs/device_number_registry.hpp new file mode 100644 index 00000000..8a85e7bd --- /dev/null +++ b/kernel/kernel/test_support/vfs/device_number_registry.hpp @@ -0,0 +1,9 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_VFS_DEVICE_NUMBER_REGISTRY_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_VFS_DEVICE_NUMBER_REGISTRY_HPP + +namespace kernel::tests::vfs::device_number_registry +{ + auto deinit() -> void; +} + +#endif diff --git a/kernel/kernel/test_support/vfs/driver_registry.hpp b/kernel/kernel/test_support/vfs/driver_registry.hpp new file mode 100644 index 00000000..117d2824 --- /dev/null +++ b/kernel/kernel/test_support/vfs/driver_registry.hpp @@ -0,0 +1,10 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_VFS_DRIVER_REGISTRY_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_VFS_DRIVER_REGISTRY_HPP + +namespace kernel::tests::vfs::driver_registry +{ + //! Deinitialize the FS type registry singleton. + auto deinit() -> void; +} // namespace kernel::tests::vfs::driver_registry + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/vfs/open_file_table.hpp b/kernel/kernel/test_support/vfs/open_file_table.hpp new file mode 100644 index 00000000..b36e4d6f --- /dev/null +++ b/kernel/kernel/test_support/vfs/open_file_table.hpp @@ -0,0 +1,10 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_VFS_OPEN_FILE_TABLE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_VFS_OPEN_FILE_TABLE_HPP + +namespace kernel::tests::vfs::open_file_table +{ + //! Deinitialize the open file table singleton. + auto deinit() -> void; +} // namespace kernel::tests::vfs::open_file_table + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/vfs/storage_boot_module_fixture.cpp b/kernel/kernel/test_support/vfs/storage_boot_module_fixture.cpp new file mode 100644 index 00000000..69dec962 --- /dev/null +++ b/kernel/kernel/test_support/vfs/storage_boot_module_fixture.cpp @@ -0,0 +1,151 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace kernel::tests::vfs +{ + storage_boot_module_fixture::mapped_image::mapped_image(std::filesystem::path path) + : file_descriptor(::open(path.c_str(), O_RDWR)) + { + if (file_descriptor < 0) + { + throw std::runtime_error{"Failed to open image file for test boot module: " + path.string()}; + } + + struct stat statistics{}; + if (::fstat(file_descriptor, &statistics) < 0) + { + throw std::runtime_error{"Failed to get statistics for image file: " + path.string()}; + } + + size = static_cast(statistics.st_size); + + mapping = static_cast(::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_PRIVATE, file_descriptor, 0)); + if (mapping == MAP_FAILED) + { + throw std::runtime_error{"Failed to map image file for test boot module: " + path.string()}; + } + } + + storage_boot_module_fixture::mapped_image::~mapped_image() + { + if (mapping != nullptr) + { + ::munmap(mapping, size); + } + if (file_descriptor >= 0) + { + ::close(file_descriptor); + } + } + + storage_boot_module_fixture::mapped_image::mapped_image(mapped_image && other) noexcept + : file_descriptor{std::exchange(other.file_descriptor, -1)} + , mapping{std::exchange(other.mapping, nullptr)} + , size{std::exchange(other.size, 0)} + {} + + auto storage_boot_module_fixture::mapped_image::operator=(mapped_image && other) noexcept -> mapped_image & + { + if (this != &other) + { + file_descriptor = std::exchange(other.file_descriptor, -1); + mapping = std::exchange(other.mapping, nullptr); + size = std::exchange(other.size, 0); + } + return *this; + } + + storage_boot_module_fixture::storage_boot_module_fixture() + { + m_boot_module_bus = kstd::make_shared(); + } + + 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_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(std::format("test_mod{} type=ramdisk", i)); + m_module_data.emplace_back(module_size, std::byte{static_cast(0x40 + (i % 16))}); + + auto module = kapi::boot_modules::module{ + .name = m_module_names[i].c_str(), + .start_address = kapi::memory::linear_address{m_module_data[i].data()}, + .size = kstd::bytes{m_module_data[i].size()}, + }; + m_boot_module_bus->add_child(kstd::make_shared(i, module)); + } + + if (module_count > 0 && kapi::devices::facet_registry::get().all(kapi::filesystem::block_special_file::id).empty()) + { + throw std::runtime_error{"No RAM disk driver bound to any of the test fixture's boot modules."}; + } + } + + auto storage_boot_module_fixture::setup_modules_from_img(std::vector const & module_names, + std::vector const & img_paths) -> void + { + m_module_names.clear(); + m_module_data.clear(); + + 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(i, module_names[i], img_paths[i]); + } + + if (!module_names.empty() && + kapi::devices::facet_registry::get().all(kapi::filesystem::block_special_file::id).empty()) + { + throw std::runtime_error{"No RAM disk driver bound to any of the test fixture's boot modules."}; + } + } + + auto storage_boot_module_fixture::setup_module_from_img(std::size_t index, std::string const & module_name, + std::filesystem::path const & img_path) -> void + { + m_module_names.push_back(module_name + " type=ramdisk"); + auto & mapped_image = m_mapped_images.emplace_back(img_path); + + auto module = kapi::boot_modules::module{ + .name = m_module_names.back().c_str(), + .start_address = kapi::memory::linear_address{mapped_image.mapping}, + .size = kstd::bytes{mapped_image.size}, + }; + + m_boot_module_bus->add_child(kstd::make_shared(index, module)); + } +} // namespace kernel::tests::vfs \ No newline at end of file diff --git a/kernel/kernel/test_support/vfs/storage_boot_module_fixture.hpp b/kernel/kernel/test_support/vfs/storage_boot_module_fixture.hpp new file mode 100644 index 00000000..34fa5356 --- /dev/null +++ b/kernel/kernel/test_support/vfs/storage_boot_module_fixture.hpp @@ -0,0 +1,52 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_VFS_STORAGE_BOOT_MODULE_FIXTURE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_VFS_STORAGE_BOOT_MODULE_FIXTURE_HPP + +#include +#include + +#include + +#include +#include +#include +#include + +namespace kernel::tests::vfs +{ + 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(std::vector const & module_names, + std::vector const & img_paths) -> void; + + protected: + struct mapped_image + { + explicit mapped_image(std::filesystem::path path); + ~mapped_image(); + + mapped_image(mapped_image const &) = delete; + auto operator=(mapped_image const &) -> mapped_image & = delete; + + mapped_image(mapped_image &&) noexcept; + auto operator=(mapped_image &&) noexcept -> mapped_image &; + + int file_descriptor; + std::byte * mapping; + std::size_t size; + }; + + std::vector m_module_names{}; + std::vector> m_module_data{}; + std::vector m_mapped_images{}; + kstd::shared_ptr m_boot_module_bus{}; + + private: + auto setup_module_from_img(std::size_t index, std::string const & module_name, + std::filesystem::path const & img_path) -> void; + }; +} // namespace kernel::tests::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/vfs/storage_boot_module_vfs_fixture.cpp b/kernel/kernel/test_support/vfs/storage_boot_module_vfs_fixture.cpp new file mode 100644 index 00000000..d21e35e5 --- /dev/null +++ b/kernel/kernel/test_support/vfs/storage_boot_module_vfs_fixture.cpp @@ -0,0 +1,31 @@ +#include + +#include +#include + +#include +#include +#include +#include + +namespace kernel::tests::vfs +{ + storage_boot_module_vfs_fixture::~storage_boot_module_vfs_fixture() + { + kernel::tests::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::vfs::vfs::init(); + } + + auto storage_boot_module_vfs_fixture::setup_modules_from_img_and_init_vfs( + std::vector const & module_names, std::vector const & img_paths) -> void + { + setup_modules_from_img(module_names, img_paths); + kernel::vfs::vfs::init(); + } +} // namespace kernel::tests::vfs \ No newline at end of file diff --git a/kernel/kernel/test_support/vfs/storage_boot_module_vfs_fixture.hpp b/kernel/kernel/test_support/vfs/storage_boot_module_vfs_fixture.hpp new file mode 100644 index 00000000..ad346e85 --- /dev/null +++ b/kernel/kernel/test_support/vfs/storage_boot_module_vfs_fixture.hpp @@ -0,0 +1,23 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_VFS_STORAGE_BOOT_MODULE_VFS_FIXTURE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_VFS_STORAGE_BOOT_MODULE_VFS_FIXTURE_HPP + +#include + +#include +#include +#include +#include + +namespace kernel::tests::vfs +{ + 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(std::vector const & module_names, + std::vector const & img_paths) -> void; + }; +} // namespace kernel::tests::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/vfs/test_assets/README.md b/kernel/kernel/test_support/vfs/test_assets/README.md new file mode 120000 index 00000000..718a2275 --- /dev/null +++ b/kernel/kernel/test_support/vfs/test_assets/README.md @@ -0,0 +1 @@ +/arch/x86_64/support/modules/README.md \ No newline at end of file diff --git a/kernel/kernel/test_support/vfs/test_assets/ext2_1KB_devnode_fs.img b/kernel/kernel/test_support/vfs/test_assets/ext2_1KB_devnode_fs.img new file mode 100644 index 00000000..09e59a4f --- /dev/null +++ b/kernel/kernel/test_support/vfs/test_assets/ext2_1KB_devnode_fs.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:393dcda08e381c4c0f86f8c7c04fd73e2e0b14891cea90d5e9e507adfe438435 +size 61440 diff --git a/kernel/kernel/test_support/vfs/test_assets/ext2_1KB_fs.img b/kernel/kernel/test_support/vfs/test_assets/ext2_1KB_fs.img new file mode 100644 index 00000000..a5202ca3 --- /dev/null +++ b/kernel/kernel/test_support/vfs/test_assets/ext2_1KB_fs.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:98ac3c1be872806e25fb14eea168ca79a91959f4e6a5ac3d00c5d8224c1f73a3 +size 10485760 diff --git a/kernel/kernel/test_support/vfs/test_assets/ext2_2KB_fs.img b/kernel/kernel/test_support/vfs/test_assets/ext2_2KB_fs.img new file mode 100644 index 00000000..7f297f07 --- /dev/null +++ b/kernel/kernel/test_support/vfs/test_assets/ext2_2KB_fs.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d9e872916e7d9107b321cc007e151899d5f19400a694666c0b24d482aef61ca +size 5242880 diff --git a/kernel/kernel/test_support/vfs/test_assets/ext2_4KB_fs.img b/kernel/kernel/test_support/vfs/test_assets/ext2_4KB_fs.img new file mode 100644 index 00000000..c3f6dafb --- /dev/null +++ b/kernel/kernel/test_support/vfs/test_assets/ext2_4KB_fs.img @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:026ca30269dbd80beb2dd74677c94676d1d4a7f6b5fe406c4ddb82836ba2dc00 +size 10485760 diff --git a/kernel/kernel/test_support/vfs/vfs.hpp b/kernel/kernel/test_support/vfs/vfs.hpp new file mode 100644 index 00000000..9d1e16d8 --- /dev/null +++ b/kernel/kernel/test_support/vfs/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::vfs +{ + //! Deinitialize the VFS singleton. + auto deinit() -> void; +} // namespace kernel::tests::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs.cpp b/kernel/kernel/vfs.cpp new file mode 100644 index 00000000..9dee6386 --- /dev/null +++ b/kernel/kernel/vfs.cpp @@ -0,0 +1,481 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace kstd::units_literals; + +namespace +{ + constinit auto static active_vfs = std::optional{}; +} // namespace + +namespace kernel::vfs +{ + auto vfs::init() -> void + { + if (active_vfs) + { + kapi::system::panic("[FILESYSTEM] vfs has already been initialized."); + } + + active_vfs.emplace(); + } + + vfs::vfs() + { + // mount rootfs at / + auto rootfs = driver_registry::get().find("rootfs"); + if (!rootfs) + { + kapi::system::panic("[OS:FS] rootfs driver is missing!"); + } + + auto root_mount_result = (*rootfs)->mount(nullptr); + if (!root_mount_result) + { + kapi::system::panic("[FILESYSTEM] failed to mount root FS", root_mount_result.error()); + } + + auto [root_inode, root_state] = *root_mount_result; + + auto root_mount = mount::attach(nullptr, *rootfs, root_inode, root_state, nullptr, nullptr); + m_mount_table.add_mount(root_mount); + + // mount devfs at /dev (inside rootfs, temporary, will be shadowed) + auto devfs = driver_registry::get().find("devfs"); + if (!devfs) + { + kapi::system::panic("[OS:FS] devfs driver is missing!"); + } + + auto device_fs_mount_result = (*devfs)->mount(nullptr); + if (!device_fs_mount_result) + { + kapi::system::panic("[FILESYSTEM] failed to mount device FS", device_fs_mount_result.error()); + } + + auto [device_fs_root, device_fs_data] = *device_fs_mount_result; + graft_persistent_device_fs(*devfs, device_fs_root, device_fs_data); + + // mount boot fs at / (shadows rootfs), re-graft devfs + auto [boot_device_dentry, boot_device_mount_context] = + resolve_path_internal("/dev/ram0").value_or(std::pair{nullptr, nullptr}); + if (boot_device_dentry && boot_device_mount_context) + { + auto driver = driver_registry::get().match(boot_device_dentry->inode()); + if (!driver) + { + kstd::println(kstd::print_sink::stderr, "[OS:FS] Missing driver for root disk!"); + return; + } + + auto root_dentry = resolve_path("/"); + if (!root_dentry) + { + kapi::system::panic("[OS:FS] No root directory found!"); + } + + auto mount = + mount::create(*root_dentry, *driver, root_mount, boot_device_mount_context, boot_device_dentry->inode()); + + if (!mount) + { + kapi::system::panic("[OS:FS] failed to mount boot FS", mount.error()); + } + m_mount_table.add_mount(*mount); + + graft_persistent_device_fs(*devfs, device_fs_root, device_fs_data); + } + } + + auto vfs::get() -> vfs & + { + if (!active_vfs) + { + kapi::system::panic("[FILESYSTEM] vfs has not been initialized."); + } + + return *active_vfs; + } + + auto vfs::open(std::string_view path) -> kstd::result + { + auto resolved_path = resolve_path_internal(path); + if (!resolved_path) + { + return kstd::failure(resolved_path.error()); + } + + auto [dentry, mount] = resolved_path.value(); + auto inode = dentry->inode(); + + if (inode->is_device()) + { + auto number = inode->raw_device(); + if (!number) + { + return kstd::failure(errc::no_such_device); + } + + auto type = inode->is_block_device() ? kapi::filesystem::file_type::block_device + : kapi::filesystem::file_type::character_device; + auto resolved_device = device_number_registry::get().resolve(*number, type); + if (!resolved_device) + { + return kstd::failure(resolved_device.error()); + } + + auto device_dentry = kstd::make_shared( + dentry->parent(), kstd::make_shared(*resolved_device, inode), dentry->name()); + + mount->increment_ref_count(); + return device_dentry; + } + + mount->increment_ref_count(); + return dentry; + } + + auto vfs::close(std::string_view path) -> kstd::result + { + if (auto mount = find_mount(path)) + { + mount.value()->decrement_ref_count(); + return kstd::success(); + } + return kstd::failure(errc::invalid_path); + } + + auto vfs::mount(std::string_view source, std::string_view target) -> kstd::result + { + if (!path::is_valid_path(source) || !path::is_valid_path(target)) + { + return kstd::failure(errc::invalid_path); + } + + auto resolved_target = resolve_path_internal(target); + if (!resolved_target) + { + return kstd::failure(resolved_target.error()); + } + auto [mount_point_dentry, mount_context] = *resolved_target; + + auto resolved_source = resolve_path_internal(source); + if (!resolved_source) + { + return kstd::failure(resolved_source.error()); + } + auto [source_dentry, source_mount_context] = *resolved_source; + + auto driver = driver_registry::get().match(source_dentry->inode()); + if (!driver) + { + return kstd::failure(driver.error()); + } + + auto mount = + mount::create(mount_point_dentry, *driver, mount_context, source_mount_context, source_dentry->inode()); + if (!mount) + { + return kstd::failure(mount.error()); + } + m_mount_table.add_mount(*mount); + + return kstd::success(); + } + + auto vfs::unmount(std::string_view path) -> kstd::result + { + if (!path::is_valid_path(path)) + { + return kstd::failure(errc::invalid_path); + } + + auto remove_result = m_mount_table.remove_mount(path); + if (remove_result == mount_table::operation_result::removed) + { + return kstd::success(); + } + else if (remove_result == mount_table::operation_result::mount_not_found) + { + return kstd::failure(errc::mount_point_not_found); + } + else if (remove_result == mount_table::operation_result::cannot_be_unmounted) + { + return kstd::failure(errc::mount_busy); + } + else if (remove_result == mount_table::operation_result::has_child_mounts) + { + return kstd::failure(errc::has_child_mounts); + } + + return kstd::failure(errc::unmount_failed); + } + + auto vfs::mkdir(std::string_view path) -> kstd::result + { + return create_inode(path, kapi::filesystem::file_type::directory); + } + + auto vfs::create(std::string_view path) -> kstd::result + { + return create_inode(path, kapi::filesystem::file_type::regular); + } + + auto vfs::status(std::string_view path) const -> kstd::result + { + auto resolved = resolve_path(path); + if (!resolved) + { + return kstd::failure(resolved.error()); + } + + return (*resolved)->inode()->status(); + } + + auto vfs::create_device_node(std::string_view path, std::uint32_t mode, kapi::filesystem::device_number device) + -> kstd::result + { + if (kapi::filesystem::is_block_device(mode)) + { + return create_inode(path, kapi::filesystem::file_type::block_device, device); + } + else if (kapi::filesystem::is_character_device(mode)) + { + return create_inode(path, kapi::filesystem::file_type::character_device, device); + } + + return kstd::failure(errc::invalid_argument); + } + + auto vfs::create_inode(std::string_view path, kapi::filesystem::file_type type, + std::optional raw_device) -> kstd::result + { + if (!path::is_valid_path(path)) + { + return kstd::failure(errc::invalid_path); + } + + if (resolve_path(path)) + { + return kstd::failure(errc::file_exists); + } + + auto [parent_path, name] = path::split_into_path_and_filename(path); + if (name.empty()) + { + return kstd::failure(errc::invalid_path); + } + + auto resolved_parent = resolve_path_internal(parent_path); + if (!resolved_parent) + { + return kstd::failure(resolved_parent.error()); + } + + auto [parent_dentry, mount_context] = *resolved_parent; + + if (auto fs = mount_context->filesystem()) + { + if (auto new_inode = + fs->create_inode(parent_dentry->inode(), name, type, mount_context->driver_data(), raw_device)) + { + (*new_inode)->set_owning_mount(mount_context); + + auto new_dentry = kstd::make_shared(parent_dentry, *new_inode, name); + parent_dentry->add_child(new_dentry); + + return kstd::success(); + } + } + + return kstd::failure(errc::no_such_file_or_directory); + } + + auto vfs::graft_persistent_device_fs(kstd::shared_ptr const & device_fs, + kstd::shared_ptr const & root_inode, kstd::shared_ptr driver_data) + -> void + { + auto [root_mount_point_dentry, root_mount] = resolve_path_internal("/").value_or(std::pair{nullptr, nullptr}); + if (root_mount_point_dentry && root_mount) + { + auto dev_dentry = root_mount_point_dentry->find_child("dev"); + if (!dev_dentry) + { + dev_dentry = kstd::make_shared(root_mount_point_dentry, root_inode, "dev"); + root_mount_point_dentry->add_child(dev_dentry); + } + + auto new_mount = mount::attach(dev_dentry, device_fs, root_inode, driver_data, root_mount, nullptr); + m_mount_table.add_mount(new_mount); + } + } + + auto vfs::resolve_path_internal(std::string_view path) const -> kstd::result> + { + if (!path::is_valid_absolute_path(path)) + { + return kstd::failure(errc::invalid_path); + } + + auto current_mount = m_mount_table.find_mount("/"); + if (!current_mount) + { + kapi::system::panic("[FILESYSTEM] no root mount found."); + } + + auto current_dentry = current_mount->root_dentry(); + + auto path_parts = path::split(path); + kstd::vector path_parts_vector(path_parts.begin(), path_parts.end()); + std::ranges::reverse(path_parts_vector); + + auto symlink_counter = 0uz; + + while (!path_parts_vector.empty()) + { + auto part = path_parts_vector.back(); + path_parts_vector.pop_back(); + + if (!current_dentry->inode()->is_directory()) + { + return kstd::failure(errc::not_a_directory); + } + + if (part == ".") + { + continue; + } + + if (part == "..") + { + auto parent_dentry = current_dentry->parent(); + + if (current_dentry == current_mount->root_dentry()) + { + if (current_mount->mount_path() == "/") + { + continue; + } + + if (auto parent_mount = current_mount->parent_mount()) + { + current_mount = parent_mount; + current_dentry = parent_dentry; + } + } + + current_dentry = parent_dentry; + continue; + } + + auto next_dentry = current_dentry->find_child(part); + if (!next_dentry) + { + auto current_fs = current_mount->filesystem(); + auto found_inode = current_fs->lookup(current_dentry->inode(), part, current_mount->driver_data()); + if (!found_inode) + { + return kstd::failure(found_inode.error()); + } + + (*found_inode)->set_owning_mount(current_mount); + + next_dentry = kstd::make_shared(current_dentry, *found_inode, part); + current_dentry->add_child(next_dentry); + } + else if (next_dentry->has_flag(dentry::dentry_flags::is_mount_point)) + { + current_mount = m_mount_table.find_mount(next_dentry->absolute_path()); + if (!current_mount) + { + kapi::system::panic("[FILESYSTEM] mount for dentry with mounted flag not found."); + } + + next_dentry = current_mount->root_dentry(); + } + + if (next_dentry->inode()->is_symbolic_link()) + { + if (symlink_counter++ > constants::symloop_max) + { + return kstd::failure(errc::too_many_symbolic_link_levels); + } + + kstd::vector buffer(constants::symlink_max_path_length); + auto const bytes_read = next_dentry->inode()->read(buffer, 0_B); + if (!bytes_read) + { + return kstd::failure(bytes_read.error()); + } + + auto const symbolic_link_path = + std::string_view{reinterpret_cast(buffer.data()), bytes_read->value}; + + auto symbolic_link_parts = path::split(symbolic_link_path); + kstd::vector symbolic_link_parts_vector(symbolic_link_parts.begin(), symbolic_link_parts.end()); + std::ranges::reverse(symbolic_link_parts_vector); + + path_parts_vector.insert_range(path_parts_vector.end(), symbolic_link_parts_vector); + + if (path::is_valid_absolute_path(symbolic_link_path)) + { + current_mount = m_mount_table.find_mount("/"); + current_dentry = current_mount->root_dentry(); + } + continue; + } + + current_dentry = next_dentry; + } + return std::pair{current_dentry, current_mount}; + } + + auto vfs::resolve_path(std::string_view path) const -> kstd::result + { + return resolve_path_internal(path).transform([](auto result) { return result.first; }); + } + + auto vfs::find_mount(std::string_view path) const -> kstd::result + { + return resolve_path_internal(path).transform([](auto result) { return result.second; }); + } + +} // namespace kernel::vfs + +namespace kernel::tests::vfs +{ + auto deinit() -> void + { + active_vfs.reset(); + } +} // namespace kernel::tests::vfs diff --git a/kernel/kernel/vfs.hpp b/kernel/kernel/vfs.hpp new file mode 100644 index 00000000..fed967d0 --- /dev/null +++ b/kernel/kernel/vfs.hpp @@ -0,0 +1,139 @@ +#ifndef TEACHOS_KERNEL_VFS_HPP +#define TEACHOS_KERNEL_VFS_HPP + +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export +#include // IWYU pragma: export + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace kernel::vfs +{ + //! The virtual filesystem interface. + //! + //! 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 involves + //! resolving the path to the appropriate mounted filesystem and delegating the file operation to that filesystem's + //! implementation. + struct vfs + { + using dentry_ptr = kstd::shared_ptr; + using mount_ptr = kstd::shared_ptr; + using fs_ptr = kstd::shared_ptr; + + vfs(); + + //! Initialize the virtual filesystem. + //! + //! @warning This function panics if the VFS has already been initialized. + auto static init() -> void; + + //! Get the singleton instance of the virtual filesystem. + //! + //! @warning This function panics if the VFS has not been initialized yet. + //! + //! @return A reference to the VFS instance. + auto static get() -> vfs &; + + //! Open a file by its path. + //! + //! @param path The path to the file to open. + //! @return A shared pointer to the dentry on success or an error code on failure. + auto open(std::string_view path) -> kstd::result; + + //! Close a file by its associated path. + //! + //! @param path The path to the file to close. + //! @return Nothing on success or an error code on failure. + auto close(std::string_view path) -> kstd::result; + + //! Mount a source path to a specific target path. + //! + //! @param source The source of the filesystem to mount. + //! @param target The path where the filesystem should be mounted. + //! @return Nothing on success or an error code on failure. + auto mount(std::string_view source, std::string_view target) -> kstd::result; + + //! Unmount the filesystem mounted at the specified path. + //! + //! @param path The path where the filesystem is mounted. + //! @return Nothing on success or an error code on failure. + auto unmount(std::string_view path) -> kstd::result; + + //! Create a new directory at the specified path. + //! + //! @param path The path where the new directory should be created. + //! @return Nothing on success, an error otherwise. + auto mkdir(std::string_view path) -> kstd::result; + + //! Create a new file at the specified path. + //! + //! @param path The path where the new file should be created. + //! @return Nothing on success, an error otherwise. + // TODO remove after the open method supports flags. + auto create(std::string_view path) -> kstd::result; + + //! Get the status of the file at a given path. + //! + //! @return A populated file status object on success, an error otherwise. + [[nodiscard]] auto status(std::string_view path) const -> kstd::result; + + //! Create a new device node at a given path. + //! + //! @return Nothing on success, an error otherwise. + auto create_device_node(std::string_view path, std::uint32_t mode, kapi::filesystem::device_number device) + -> kstd::result; + + private: + // Note: Resolving a dentry requires traversing mount points; since the associated 'mount' object is discovered as a + // byproduct of this traversal, we return it alongside the dentry to avoid redundant lookups in callers that require + // mount context. + // + // If only one component is needed, the convenience wrappers can be used: + // - resolve_path() for the dentry only. + // - find_mount() for the mount context only. + [[nodiscard]] auto resolve_path_internal(std::string_view path) const + -> kstd::result>; + + [[nodiscard]] auto resolve_path(std::string_view path) const -> kstd::result; + + [[nodiscard]] auto find_mount(std::string_view path) const -> kstd::result; + + auto graft_persistent_device_fs(kstd::shared_ptr const & device_fs, + kstd::shared_ptr const & root_inode, kstd::shared_ptr driver_data) + -> void; + + auto create_inode(std::string_view path, kapi::filesystem::file_type type, + std::optional raw_device = std::nullopt) -> kstd::result; + + mount_table m_mount_table{}; + }; +} // namespace kernel::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs.tests.cpp b/kernel/kernel/vfs.tests.cpp new file mode 100644 index 00000000..475daf2d --- /dev/null +++ b/kernel/kernel/vfs.tests.cpp @@ -0,0 +1,702 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace kstd::units_literals; + +SCENARIO_METHOD(kernel::tests::vfs::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::vfs::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::vfs::vfs::get(); + auto root_file = vfs.open("/"); + + REQUIRE(root_file != nullptr); + } + } +} + +SCENARIO_METHOD(kernel::tests::vfs::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::vfs::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("a real image file containing a /dev directory") + { + REQUIRE(std::filesystem::exists(image_path_2)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_2"}, {image_path_2})); + + THEN("vfs hides the image's /dev behind the devfs mount") + { + auto & vfs = kernel::vfs::vfs::get(); + + auto image_1 = vfs.open("/dev/image_1.txt"); + REQUIRE(image_1.error() == kstd::errc::no_such_file_or_directory); + + auto dev = vfs.open("/dev/ram0"); + REQUIRE(dev != 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::vfs::vfs::get(); + + THEN("vfs initializes first module as root") + { + auto & vfs = kernel::vfs::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.mount("/dev/ram1", "/information")); + + auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); + REQUIRE(mounted_monkey_1); + REQUIRE(vfs.close(mounted_monkey_1.value()->absolute_path())); + + REQUIRE(vfs.unmount("/information")); + auto unmounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); + REQUIRE(unmounted_monkey_1.error() == kstd::errc::no_such_file_or_directory); + + 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.mount("/dev/ram1", "/information")); + REQUIRE(vfs.mount("/dev/ram2", "/information/monkey_house/infrastructure")); + + 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); + REQUIRE(mounted_fish1); + + REQUIRE(vfs.close(mounted_monkey_1.value()->absolute_path())); + REQUIRE(vfs.close(mounted_fish1.value()->absolute_path())); + + REQUIRE(!vfs.unmount("/information")); + REQUIRE(vfs.unmount("/information/monkey_house/infrastructure")); + REQUIRE(vfs.unmount("/information")); + } + + THEN("image can be mounted, unmount only if no files are open") + { + REQUIRE(vfs.mount("/dev/ram1", "/information")); + + auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); + REQUIRE(mounted_monkey_1); + + REQUIRE(!vfs.unmount("/information")); + + REQUIRE(vfs.close(mounted_monkey_1.value()->absolute_path())); + + REQUIRE(vfs.unmount("/information")); + } + + THEN("file with invalid path or not opened file cannot be closed") + { + REQUIRE(!vfs.close("invalid_path")); + REQUIRE_THROWS_AS(vfs.close("/information/info_1.txt"), std::runtime_error); + } + + THEN("file cannot be closed twice") + { + auto info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1); + + REQUIRE(vfs.close(info_1.value()->absolute_path())); + REQUIRE_THROWS_AS(vfs.close(info_1.value()->absolute_path()), std::runtime_error); + } + + THEN("images can be stacked mounted and correct file system is unmounted again") + { + REQUIRE(vfs.mount("/dev/ram1", "/information")); + REQUIRE(vfs.mount("/dev/ram2", "/information")); + + auto mounted_tickets = vfs.open("/information/entrance/tickets.txt"); + REQUIRE(mounted_tickets); + + REQUIRE(vfs.close(mounted_tickets.value()->absolute_path())); + + REQUIRE(vfs.unmount("/information")); + mounted_tickets = vfs.open("/information/entrance/tickets.txt"); + REQUIRE(mounted_tickets.error() == kstd::errc::no_such_file_or_directory); + + auto mounted_monkey = vfs.open("/information/monkey_house/monkey_1.txt"); + REQUIRE(mounted_monkey); + } + + THEN("image can be mounted on / file opened and unmounted again") + { + auto info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 != nullptr); + + REQUIRE(vfs.mount("/dev/ram1", "/")); + + info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1.error() == kstd::errc::no_such_file_or_directory); + + auto water = vfs.open("/monkey_house/infrastructure/water.txt"); + REQUIRE(water != nullptr); + + REQUIRE(vfs.close(water.value()->absolute_path())); + + REQUIRE(vfs.unmount("/")); + + info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 != nullptr); + } + + THEN("image can be mounted on / just the boot root has /dev") + { + auto info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 != nullptr); + + REQUIRE(vfs.mount("/dev/ram1", "/")); + + info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1.error() == kstd::errc::no_such_file_or_directory); + + auto water = vfs.open("/monkey_house/infrastructure/water.txt"); + REQUIRE(water != nullptr); + + REQUIRE(vfs.close(water.value()->absolute_path())); + + auto dev_ram_16 = vfs.open("/dev/ram16"); + REQUIRE(dev_ram_16.error() == kstd::errc::no_such_file_or_directory); + + REQUIRE(vfs.mount("/dev/ram32", "/").error() == kstd::errc::no_such_file_or_directory); + + REQUIRE(vfs.unmount("/")); + + auto dev_ram_32 = vfs.open("/dev/ram32"); + REQUIRE(dev_ram_32 != nullptr); + } + + THEN("boot root can be unmounted and remounted again but /dev is not re-grafted") + { + auto info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 != nullptr); + + REQUIRE(vfs.close(info_1.value()->absolute_path())); + + REQUIRE(vfs.unmount("/dev")); + REQUIRE(vfs.unmount("/")); + + info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1.error() == kstd::errc::no_such_file_or_directory); + + REQUIRE(vfs.mount("/dev/ram0", "/")); + + info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 != nullptr); + + auto dev_ram_0 = vfs.open("/dev/ram0"); + REQUIRE(dev_ram_0.error() == kstd::errc::no_such_file_or_directory); + } + + THEN("mount with null file system fails") + { + REQUIRE(vfs.mount("/closed.txt", "/information").error() == kstd::errc::not_supported); + } + + THEN("mount with invalid path fails") + { + REQUIRE(vfs.mount("/dev/ram16", "").error() == kstd::errc::invalid_argument); + REQUIRE(vfs.mount("/dev/ram16", "information").error() == kstd::errc::invalid_argument); + } + + THEN("mount with non-existent source path fails") + { + REQUIRE(vfs.mount("/dev/nonexistent", "/information").error() == kstd::errc::no_such_file_or_directory); + } + + THEN("mount with non-existent mount point fails") + { + REQUIRE(vfs.mount("/dev/ram16", "/information/nonexistent").error() == kstd::errc::no_such_file_or_directory); + } + + THEN("unmount with invalid path fails") + { + REQUIRE(vfs.unmount("").error() == kstd::errc::invalid_argument); + REQUIRE(vfs.unmount("information").error() == kstd::errc::no_such_file_or_directory); + } + + THEN("unmounting non-existent mount point returns expected error code") + { + REQUIRE(vfs.unmount("/information/nonexistent").error() == kstd::errc::no_such_file_or_directory); + } + + THEN("a file can be access if . in the path") + { + auto info_1 = vfs.open("/information/./info_1.txt"); + REQUIRE(info_1 != nullptr); + } + + THEN("a file can be accessed within the same mount if path contains .. ") + { + auto info_1 = vfs.open("/archiv/../information/info_1.txt"); + REQUIRE(info_1 != nullptr); + + auto img = vfs.open("/archiv/../information/../archiv/2024.img"); + REQUIRE(img != nullptr); + } + + THEN("a file can be accessed over multiple mounts if path contains .. or . ") + { + REQUIRE(vfs.mount("/dev/ram1", "/information")); + + auto img = vfs.open("/information/monkey_house/caretaker/../../../../../../archiv/2024.img"); + REQUIRE(img != nullptr); + + auto dev_32 = vfs.open("/information/monkey_house/caretaker/../../../dev/ram32"); + REQUIRE(dev_32 != nullptr); + + auto water = vfs.open("/information/./monkey_house/infrastructure/water.txt"); + REQUIRE(water != nullptr); + } + + THEN("a file can be accessed over multiple mounts (device and file) if path contains .. ") + { + REQUIRE(vfs.mount("/dev/ram1", "/information")); + REQUIRE(vfs.mount("/archiv/2024.img", "/information/monkey_house/infrastructure")); + + auto pig_1 = vfs.open("/information/monkey_house/infrastructure/stable/pig_1.txt"); + REQUIRE(pig_1 != nullptr); + + auto isabelle = + vfs.open("/information/monkey_house/infrastructure/stable/../../../monkey_house/caretaker/isabelle.txt"); + REQUIRE(isabelle != nullptr); + + auto closed = vfs.open("/information/monkey_house/infrastructure/stable/../../../../closed.txt"); + REQUIRE(closed != nullptr); + } + } + + GIVEN("A real image file within which new files and directories can be created") + { + REQUIRE(std::filesystem::exists(image_path_1)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1})); + + THEN("a new file can be created and read again") + { + auto & vfs = kernel::vfs::vfs::get(); + REQUIRE(vfs.create("/information/new_file.txt")); + + auto new_file = vfs.open("/information/new_file.txt"); + REQUIRE(new_file != nullptr); + } + + THEN("a new directory can be created and read again") + { + auto & vfs = kernel::vfs::vfs::get(); + REQUIRE(vfs.mkdir("/information/new_directory")); + } + + THEN("a new file cannot be created if it already exists") + { + auto & vfs = kernel::vfs::vfs::get(); + REQUIRE(!vfs.create("/information/info_1.txt")); + } + + THEN("a new directory cannot be created if it already exists") + { + auto & vfs = kernel::vfs::vfs::get(); + REQUIRE(!vfs.mkdir("/information")); + } + + THEN("a new file cannot be created if path does not exist or is invalid") + { + auto & vfs = kernel::vfs::vfs::get(); + REQUIRE(!vfs.create("")); + REQUIRE(!vfs.create("invalid_path")); + } + + THEN("a new directory cannot be created if path does not exist or is invalid") + { + auto & vfs = kernel::vfs::vfs::get(); + REQUIRE(!vfs.mkdir("")); + REQUIRE(!vfs.mkdir("invalid_path")); + } + } + + GIVEN("A real image file containing as filesystem formatted files") + { + REQUIRE(std::filesystem::exists(image_path_1)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1})); + + THEN("the file-filesystem in the image can be mounted, files can be read and unmounted again") + { + auto & vfs = kernel::vfs::vfs::get(); + REQUIRE(vfs.mount("/archiv/2024.img", "/information")); + + auto info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1.error() == kstd::errc::no_such_file_or_directory); + + auto dentry = vfs.open("/information/sheep_1.txt"); + REQUIRE(dentry != nullptr); + auto sheep_1_ofd = kstd::make_shared(dentry.value()); + + kstd::vector buffer(7); + auto bytes_read = sheep_1_ofd->read(buffer); + std::string_view buffer_as_str{reinterpret_cast(buffer.data()), bytes_read->value}; + REQUIRE(buffer_as_str == "sheep_1"); + + REQUIRE(vfs.close(dentry.value()->absolute_path())); + + REQUIRE(vfs.unmount("/information")); + auto unmounted_sheep_1 = vfs.open("/information/sheep_1.txt"); + REQUIRE(unmounted_sheep_1.error() == kstd::errc::no_such_file_or_directory); + } + + THEN("the file-filesystem in the image can be mounted and in this filesystem can another file-filesystem be " + "mounted, files can be read and unmounted again") + { + auto & vfs = kernel::vfs::vfs::get(); + REQUIRE(vfs.mount("/archiv/2024.img", "/information")); + REQUIRE(vfs.mount("/archiv/2025.img", "/information/stable")); + + auto sheep_1 = vfs.open("/information/sheep_1.txt"); + auto goat_1 = vfs.open("/information/stable/petting_zoo/goat_1.txt"); + REQUIRE(sheep_1 != nullptr); + REQUIRE(goat_1 != nullptr); + + auto sheep_1_ofd = kstd::make_shared(sheep_1.value()); + auto goat_1_ofd = kstd::make_shared(goat_1.value()); + + kstd::vector sheep_buffer(7); + auto bytes_read = sheep_1_ofd->read(sheep_buffer); + std::string_view buffer_as_str{reinterpret_cast(sheep_buffer.data()), bytes_read->value}; + REQUIRE(buffer_as_str == "sheep_1"); + + kstd::vector goat_buffer(6); + bytes_read = goat_1_ofd->read(goat_buffer); + buffer_as_str = std::string_view{reinterpret_cast(goat_buffer.data()), bytes_read->value}; + REQUIRE(buffer_as_str == "goat_1"); + + REQUIRE(vfs.close(sheep_1.value()->absolute_path())); + REQUIRE(vfs.close(goat_1.value()->absolute_path())); + + REQUIRE(vfs.unmount("/information").error() == kstd::errc::device_or_resource_busy); + + REQUIRE(vfs.unmount("/information/stable")); + auto unmounted_goat_1 = vfs.open("/information/stable/petting_zoo/goat_1.txt"); + REQUIRE(unmounted_goat_1.error() == kstd::errc::no_such_file_or_directory); + + auto still_mounted_sheep_1 = vfs.open("/information/sheep_1.txt"); + REQUIRE(still_mounted_sheep_1 != nullptr); + + REQUIRE(vfs.close(still_mounted_sheep_1.value()->absolute_path())); + + REQUIRE(vfs.unmount("/information")); + auto unmounted_sheep_1 = vfs.open("/information/sheep_1.txt"); + REQUIRE(unmounted_sheep_1.error() == kstd::errc::no_such_file_or_directory); + } + } + + GIVEN("two real image files where the second contains as filesystem formatted files") + { + REQUIRE(std::filesystem::exists(image_path_1)); + REQUIRE(std::filesystem::exists(image_path_3)); + REQUIRE_NOTHROW( + setup_modules_from_img_and_init_vfs({"test_img_module_3", "test_img_module_1"}, {image_path_3, image_path_1})); + + auto & vfs = kernel::vfs::vfs::get(); + + THEN("cannot unmount a filesystem if files are mounted") + { + REQUIRE(vfs.mount("/dev/ram1", "/entrance")); + REQUIRE(vfs.mount("/entrance/archiv/2024.img", "/enclosures")); + + REQUIRE(vfs.unmount("/entrance").error() == kstd::errc::device_or_resource_busy); + REQUIRE(vfs.unmount("/enclosures")); + REQUIRE(vfs.unmount("/entrance")); + } + + THEN("can mount filesystem onto the directory that contains it") + { + REQUIRE(vfs.mount("/dev/ram1", "/entrance")); + REQUIRE(vfs.mount("/entrance/archiv/2024.img", "/entrance")); + + REQUIRE(vfs.unmount("/entrance")); + REQUIRE(vfs.unmount("/entrance")); + } + } + + GIVEN("A real image files, containing symbolic links") + { + REQUIRE(std::filesystem::exists(image_path_1)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1})); + + THEN("file can be opened through absolute symbolic link") + { + auto & vfs = kernel::vfs::vfs::get(); + auto info_1 = vfs.open("/symlinks/info_1_absolute"); + REQUIRE(info_1 != nullptr); + } + + THEN("file can be opened through relative symbolic link") + { + auto & vfs = kernel::vfs::vfs::get(); + auto info_1 = vfs.open("/symlinks/info_1_relative"); + REQUIRE(info_1 != nullptr); + } + + THEN("file can be opened through symbolic link pointing absolute to the directory containing the file") + { + auto & vfs = kernel::vfs::vfs::get(); + auto info_1 = vfs.open("/symlinks/information_directory_absolute/info_1.txt"); + REQUIRE(info_1 != nullptr); + } + + THEN("file can be opened through symbolic link pointing relative to the directory containing the file") + { + auto & vfs = kernel::vfs::vfs::get(); + auto info_1 = vfs.open("/symlinks/information_directory_relative/info_1.txt"); + REQUIRE(info_1 != nullptr); + } + + THEN("symbolic link with path traversing back to the root") + { + auto & vfs = kernel::vfs::vfs::get(); + auto info_1 = vfs.open("/symlinks/traverse_back_5_times/information/info_1.txt"); + REQUIRE(info_1 != nullptr); + } + + THEN("symbolic link containing an invalid absolute path is handled correctly") + { + auto & vfs = kernel::vfs::vfs::get(); + auto invalid_symlink = vfs.open("/symlinks/invalid_absolute"); + REQUIRE(invalid_symlink.error() == kstd::errc::no_such_file_or_directory); + } + + THEN("symbolic link containing an invalid relative path is handled correctly") + { + auto & vfs = kernel::vfs::vfs::get(); + auto invalid_symlink = vfs.open("/symlinks/invalid_relative"); + REQUIRE(invalid_symlink.error() == kstd::errc::no_such_file_or_directory); + } + + THEN("circular symbolic links are detected and handled correctly") + { + auto & vfs = kernel::vfs::vfs::get(); + auto circular_symlink = vfs.open("/symlinks/symloop_a"); + REQUIRE(circular_symlink.error() == kstd::errc::too_many_symbolic_link_levels); + } + } + + GIVEN("A real image file containing as filesystem formatted files and this filesystem contains a symbolic link") + { + REQUIRE(std::filesystem::exists(image_path_1)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_1"}, {image_path_1})); + + auto & vfs = kernel::vfs::vfs::get(); + REQUIRE(vfs.mount("/archiv/2024.img", "/information")); + + THEN("file can be opened through symbolic link pointing to the parent filesystem") + { + auto closed_file = vfs.open("/information/symlinks/traverse_back_twice/closed.txt"); + REQUIRE(closed_file != nullptr); + } + } + + GIVEN("Two real images, one containing a symbolic link leaving and reentering filesystem again") + { + REQUIRE(std::filesystem::exists(image_path_1)); + REQUIRE(std::filesystem::exists(image_path_2)); + REQUIRE_NOTHROW( + setup_modules_from_img_and_init_vfs({"test_img_module_1", "test_img_module_2"}, {image_path_1, image_path_2})); + + auto & vfs = kernel::vfs::vfs::get(); + REQUIRE(vfs.mount("/dev/ram1", "/information")); + + THEN("file can be opened through symbolic link pointing to the parent filesystem and back into the mounted " + "filesystem again") + { + auto monkey_1 = vfs.open("/information/symlinks/leave_and_reenter_mount/monkey_1.txt"); + REQUIRE(monkey_1 != nullptr); + } + } + + GIVEN("One real image containing a very long symbolic link") + { + REQUIRE(std::filesystem::exists(image_path_3)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_3"}, {image_path_3})); + + auto & vfs = kernel::vfs::vfs::get(); + + THEN("file can be opened through symbolic link with a long path") + { + auto fish_30 = vfs.open("/symlinks/very_long_symlink"); + REQUIRE(fish_30 != nullptr); + } + } + + GIVEN("One real image containing a valid symbolic link chain") + { + REQUIRE(std::filesystem::exists(image_path_3)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module_3"}, {image_path_3})); + + auto & vfs = kernel::vfs::vfs::get(); + + THEN("file can be opened through symbolic link chain") + { + auto map = vfs.open("/symlinks/symlink_chain_1/map.txt"); + REQUIRE(map != nullptr); + } + } +} + +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_vfs_fixture, "VFS status()", "[filesystem][vfs][img]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("a real image file") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module"}, {image_path})); + + auto & vfs = kernel::vfs::vfs::get(); + + THEN("status(/dev/ram0) reports it as the correct block device") + { + auto result = vfs.status("/dev/ram0"); + REQUIRE(result); + REQUIRE(kapi::filesystem::is_block_device(result->mode)); + REQUIRE(result->raw_device.major == kernel::vfs::block_major_numbers::ram_disk); + } + + THEN("status(/information/info_1.txt) reports it as a regular file") + { + auto result = vfs.status("/information/info_1.txt"); + REQUIRE(result); + REQUIRE(kapi::filesystem::is_regular(result->mode)); + REQUIRE(result->raw_device == kapi::filesystem::device_number{}); + } + + WHEN("creating a persistent device node on the ext2 filesystem, resolving to /dev/ram0") + { + auto ram0_status = vfs.status("/dev/ram0"); + REQUIRE(ram0_status); + + auto number = kapi::filesystem::device_number{ + ram0_status->raw_device.major, + ram0_status->raw_device.minor, + }; + + REQUIRE(vfs.create_device_node("/persistent_ram0", + std::to_underlying(kapi::filesystem::file_type::block_device) | 0660, number)); + + THEN("opening it reaches the same device as /dev/ram0") + { + auto persistent_node_status = vfs.status("/persistent_ram0"); + REQUIRE(persistent_node_status); + REQUIRE(persistent_node_status->raw_device == ram0_status->raw_device); + + auto via_devfs = vfs.open("/dev/ram0"); + REQUIRE(via_devfs != nullptr); + + auto via_persistent_fs = vfs.open("/persistent_ram0"); + REQUIRE(via_persistent_fs != nullptr); + + auto marker = kstd::vector{std::byte{'M'}, std::byte{'K'}, std::byte{'N'}, std::byte{'O'}, std::byte{'D'}}; + auto written = (*via_persistent_fs)->inode()->write(marker, 0_B); + REQUIRE(written == kstd::bytes{marker.size()}); + + auto read_back = kstd::vector(marker.size()); + auto read = (*via_devfs)->inode()->read(read_back, 0_B); + REQUIRE(read == kstd::bytes{marker.size()}); + + REQUIRE(read_back == marker); + } + } + + WHEN("create_device_node() is called with a number that matches no live device") + { + auto const number = kapi::filesystem::device_number{.major = 250, .minor = 250}; + REQUIRE(vfs.create_device_node("/nowhere", std::to_underlying(kapi::filesystem::file_type::block_device) | 0660, + number)); + + THEN("opening it fails with ENODEV, not a panic") + { + auto result = vfs.open("/nowhere"); + REQUIRE(!result); + REQUIRE(result.error() == kstd::errc::no_such_device); + } + } + + THEN("create_device_node() with a mode that isn't a device type fails with invalid_argument") + { + auto const number = kapi::filesystem::device_number{.major = 1, .minor = 0}; + auto result = vfs.create_device_node("/not_a_device", + std::to_underlying(kapi::filesystem::file_type::regular) | 0644, number); + REQUIRE(!result); + REQUIRE(result.error() == kstd::errc::invalid_argument); + } + } +} diff --git a/kernel/kernel/vfs/byte_offset_file_descriptor.cpp b/kernel/kernel/vfs/byte_offset_file_descriptor.cpp new file mode 100644 index 00000000..7e99a3fb --- /dev/null +++ b/kernel/kernel/vfs/byte_offset_file_descriptor.cpp @@ -0,0 +1,83 @@ +#include + +#include + +#include + +#include +#include +#include + +#include +#include + +namespace kernel::vfs +{ + + auto byte_offset_file_descriptor::read(std::span buffer) -> kstd::result + { + if (auto result = get_dentry()->inode()->read(buffer, m_offset); !result) + { + return kstd::failure(result.error()); + } + else + { + auto read_bytes = result.value(); + m_offset += read_bytes; + return read_bytes; + } + } + + auto byte_offset_file_descriptor::write(std::span buffer) -> kstd::result + { + if (auto result = get_dentry()->inode()->write(buffer, m_offset); !result) + { + return kstd::failure(result.error()); + } + else + { + auto written_bytes = result.value(); + m_offset += written_bytes; + return written_bytes; + } + } + + auto byte_offset_file_descriptor::seek(kstd::offset offset, kapi::filesystem::seek_origin origin) + -> kstd::result + { + auto const base = [&] { + switch (origin) + { + case kapi::filesystem::seek_origin::beginning: + { + return kstd::bytes{0}; + } + case kapi::filesystem::seek_origin::current_position: + { + return m_offset; + } + case kapi::filesystem::seek_origin::end: + { + return get_dentry()->inode()->status()->size; + } + default: + { + return kstd::bytes{0}; + } + } + }(); + + auto new_offset = base + offset; + if (new_offset) + { + m_offset = *new_offset; + } + return new_offset; + } + + auto byte_offset_file_descriptor::offset() const noexcept -> kstd::bytes + { + return m_offset; + } + +} // namespace kernel::vfs \ No newline at end of file diff --git a/kernel/kernel/vfs/byte_offset_file_descriptor.hpp b/kernel/kernel/vfs/byte_offset_file_descriptor.hpp new file mode 100644 index 00000000..4008d500 --- /dev/null +++ b/kernel/kernel/vfs/byte_offset_file_descriptor.hpp @@ -0,0 +1,41 @@ +#ifndef TEACHOS_KERNEL_VFS_BYTE_OFFSET_FILE_DESCRIPTOR_HPP +#define TEACHOS_KERNEL_VFS_BYTE_OFFSET_FILE_DESCRIPTOR_HPP + +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +namespace kernel::vfs +{ + //! An open file. + //! + //! This class encapsulates the state of an open file, including a reference to the associated directory and the + //! current byte offset within the file. + struct byte_offset_file_descriptor : open_file_descriptor + { + using open_file_descriptor::open_file_descriptor; + + auto read(std::span buffer) -> kstd::result override; + + auto write(std::span buffer) -> kstd::result override; + + auto seek(kstd::offset offset, kapi::filesystem::seek_origin origin) -> kstd::result override; + + [[nodiscard]] auto offset() const noexcept -> kstd::bytes; + + private: + kstd::bytes m_offset{}; + }; + +} // namespace kernel::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs/byte_offset_file_descriptor.tests.cpp b/kernel/kernel/vfs/byte_offset_file_descriptor.tests.cpp new file mode 100644 index 00000000..ed934a16 --- /dev/null +++ b/kernel/kernel/vfs/byte_offset_file_descriptor.tests.cpp @@ -0,0 +1,216 @@ +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace kstd::units_literals; + +// NOLINTBEGIN(readability-magic-numbers) + +SCENARIO("Open file descriptor construction", "[filesystem][byte_offset_file_descriptor]") +{ + GIVEN("a dentry and an open file descriptor for that dentry") + { + auto inode = kstd::make_shared(); + auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); + auto file_descriptor = kernel::vfs::byte_offset_file_descriptor{dentry}; + + THEN("the initial offset is zero") + { + REQUIRE(file_descriptor.offset() == 0_B); + } + } +} + +SCENARIO("Open file descriptor read/write offset management", "[filesystem][byte_offset_file_descriptor]") +{ + GIVEN("a dentry that tracks read/write calls and an open file descriptor for that dentry") + { + auto inode = kstd::make_shared(); + auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); + auto file_descriptor = kernel::vfs::byte_offset_file_descriptor{dentry}; + + THEN("the offset is updated correctly after reads") + { + auto buffer = std::vector{100}; + + REQUIRE(file_descriptor.read(buffer) == 100_B); + REQUIRE(file_descriptor.offset() == 100_B); + REQUIRE(file_descriptor.read(std::span{buffer}.subspan(0, 50)) == 50_B); + REQUIRE(file_descriptor.offset() == 150_B); + } + + THEN("the offset is updated correctly after writes") + { + auto buffer = std::vector{200}; + + REQUIRE(file_descriptor.write(buffer) == 200_B); + REQUIRE(file_descriptor.offset() == 200_B); + REQUIRE(file_descriptor.write(std::span{buffer}.subspan(0, 25)) == 25_B); + REQUIRE(file_descriptor.offset() == 225_B); + } + + THEN("reads and writes both update the same offset") + { + auto buffer = std::vector{20}; + + REQUIRE(file_descriptor.read(std::span{buffer}.subspan(0, 10)) == 10_B); + REQUIRE(file_descriptor.offset() == 10_B); + REQUIRE(file_descriptor.write(std::span{buffer}.subspan(0, 20)) == 20_B); + REQUIRE(file_descriptor.offset() == 30_B); + REQUIRE(file_descriptor.read(std::span{buffer}.subspan(0, 5)) == 5_B); + REQUIRE(file_descriptor.offset() == 35_B); + REQUIRE(file_descriptor.write(std::span{buffer}.subspan(0, 15)) == 15_B); + REQUIRE(file_descriptor.offset() == 50_B); + } + } +} + +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_vfs_fixture, "Open file descriptor read with real image", + "[filesystem][byte_offset_file_descriptor][img]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("an open file descriptor 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::vfs::vfs::get(); + auto dentry = vfs.open("/information/info_1.txt"); + REQUIRE(dentry); + auto ofd = kstd::make_shared(dentry.value()); + + THEN("the file can be read and the offset is updated") + { + kstd::vector buffer(32); + auto bytes_read = ofd->read(buffer); + REQUIRE(bytes_read == 7_B); + REQUIRE(ofd->offset() == 7_B); + + std::string_view buffer_as_str{reinterpret_cast(buffer.data()), bytes_read->value}; + REQUIRE(buffer_as_str == "info_1\n"); + } + + THEN("the file can be read multiple times and the offset is updated") + { + kstd::vector buffer(4); + auto bytes_read_1 = ofd->read(std::span{buffer}.first(buffer.size() / 2)); + REQUIRE(bytes_read_1 == kstd::bytes{buffer.size() / 2}); + REQUIRE(ofd->offset() == kstd::bytes{buffer.size() / 2}); + + auto bytes_read_2 = ofd->read(std::span{buffer}.last(buffer.size() / 2)); + REQUIRE(bytes_read_2 == kstd::bytes{buffer.size() / 2}); + REQUIRE(ofd->offset() == kstd::bytes{buffer.size()}); + + std::string_view buffer_as_str{reinterpret_cast(buffer.data()), + bytes_read_1->value + bytes_read_2->value}; + REQUIRE(buffer_as_str == "info"); + } + + THEN("the file can be written to and the offset is updated") + { + auto write_buffer = kstd::vector(12, std::byte{0xAA}); + auto const bytes_written = ofd->write(write_buffer); + REQUIRE(bytes_written == 12_B); + REQUIRE(ofd->offset() == 12_B); + } + + THEN("the file can be written to multiple times and the offset is updated") + { + auto write_buffer = kstd::vector(8, std::byte{0xAA}); + auto const bytes_written_1 = ofd->write(std::span{write_buffer}.first(write_buffer.size() / 2)); + REQUIRE(bytes_written_1 == kstd::bytes{write_buffer.size() / 2}); + REQUIRE(ofd->offset() == kstd::bytes{write_buffer.size() / 2}); + + auto const bytes_written_2 = ofd->write(std::span{write_buffer}.last(write_buffer.size() / 2)); + REQUIRE(bytes_written_2 == kstd::bytes{write_buffer.size() / 2}); + REQUIRE(ofd->offset() == kstd::bytes{write_buffer.size()}); + } + } +} + +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_fixture, "Open file descriptor handles device removal", + "[filesystem][byte_offset_file_descriptor]") +{ + GIVEN("A file descriptor open on a bound, published devices") + { + setup_modules(1); + + auto device = kernel::devices::storage::determine_boot_device(); + REQUIRE(device); + + auto weak_reference = kstd::weak_ptr{device}; + + auto device_inode = kstd::make_shared(device); + auto dentry = kstd::make_shared(nullptr, device_inode, "ram0"); + auto fd = kstd::make_shared(dentry); + + device.reset(); + REQUIRE_FALSE(weak_reference.expired()); + + WHEN("the device is removed while the descriptor is still open") + { + auto locked = weak_reference.lock(); + REQUIRE(locked); + REQUIRE(kapi::devices::remove_device(*locked)); + locked.reset(); + + THEN("the device is kept alive through the file descriptor") + { + REQUIRE_FALSE(weak_reference.expired()); + } + + THEN("reading through the file descriptor fails with 'no such device'") + { + auto buffer = kstd::vector(10); + auto bytes_read = fd->read(buffer); + + REQUIRE(bytes_read.error() == kstd::errc::no_such_device); + } + + THEN("writing through the file descriptor fails with 'no such device'") + { + auto buffer = kstd::vector(10); + auto bytes_written = fd->write(buffer); + + REQUIRE(bytes_written.error() == kstd::errc::no_such_device); + } + + THEN("resetting the chain destroys the device") + { + REQUIRE_FALSE(weak_reference.expired()); + + fd.reset(); + REQUIRE_FALSE(weak_reference.expired()); + + dentry.reset(); + REQUIRE_FALSE(weak_reference.expired()); + + device_inode.reset(); + REQUIRE(weak_reference.expired()); + } + } + } +} + +// NOLINTEND(readability-magic-numbers) \ No newline at end of file diff --git a/kernel/kernel/vfs/constants.hpp b/kernel/kernel/vfs/constants.hpp new file mode 100644 index 00000000..b5759d32 --- /dev/null +++ b/kernel/kernel/vfs/constants.hpp @@ -0,0 +1,18 @@ +#ifndef TEACHOS_KERNEL_VFS_CONSTANTS_HPP +#define TEACHOS_KERNEL_VFS_CONSTANTS_HPP + +#include + +namespace kernel::vfs::constants +{ + //! The maximum allowed length for any path in the system. + constexpr size_t inline max_path_length = 4096; + + //! The maximum allowed path length for the target of a symlink. + constexpr size_t inline symlink_max_path_length = 4096; + + //! The maximum number of loops allowed in a path, via symlinks. + constexpr size_t inline symloop_max = 40; +} // namespace kernel::vfs::constants + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs/dentry.cpp b/kernel/kernel/vfs/dentry.cpp new file mode 100644 index 00000000..a7bfc3cb --- /dev/null +++ b/kernel/kernel/vfs/dentry.cpp @@ -0,0 +1,96 @@ +#include + +#include + +#include + +#include +#include + +#include +#include +#include + +namespace kernel::vfs +{ + dentry::dentry(kstd::shared_ptr const & parent, kstd::shared_ptr const & inode, + std::string_view name) + : m_name(name) + , m_parent(parent) + , m_inode(inode) + , m_flags(0) + { + if (!m_inode) + { + kapi::system::panic("[FILESYSTEM] dentry constructed with null inode."); + } + if (m_name.empty()) + { + kapi::system::panic("[FILESYSTEM] dentry constructed with empty name."); + } + } + + auto dentry::inode() const -> kstd::shared_ptr const & + { + return m_inode; + } + + auto dentry::parent() const -> kstd::shared_ptr + { + return m_parent.lock(); + } + + auto dentry::name() const -> std::string_view + { + return m_name; + } + + auto dentry::absolute_path() const -> kstd::string + { + kstd::string path = m_name; + + auto parent = this->parent(); + while (parent) + { + auto parent_name = parent->m_name; + if (parent_name == "/") + { + path = "/" + path; + } + else + { + path = parent_name + "/" + path; + } + + parent = parent->parent(); + } + + return path; + } + + auto dentry::add_child(kstd::shared_ptr const & child) -> void + { + m_children.push_back(child); + } + + auto dentry::find_child(std::string_view name) const -> kstd::shared_ptr + { + auto it = std::ranges::find_if(m_children, [&](auto const & child) { return child->m_name == name; }); + return (it != m_children.end()) ? *it : nullptr; + } + + auto dentry::set_flag(dentry_flags flag) -> void + { + m_flags |= static_cast(flag); + } + + auto dentry::unset_flag(dentry_flags flag) -> void + { + m_flags &= ~static_cast(flag); + } + + auto dentry::has_flag(dentry_flags flag) const -> bool + { + return (m_flags & static_cast(flag)) != 0; + } +} // namespace kernel::vfs \ No newline at end of file diff --git a/kernel/kernel/vfs/dentry.hpp b/kernel/kernel/vfs/dentry.hpp new file mode 100644 index 00000000..b15a6f6c --- /dev/null +++ b/kernel/kernel/vfs/dentry.hpp @@ -0,0 +1,98 @@ +#ifndef TEACHOS_KERNEL_VFS_DENTRY_HPP +#define TEACHOS_KERNEL_VFS_DENTRY_HPP + +#include + +#include +#include +#include + +#include +#include + +namespace kernel::vfs +{ + //! A directory entry in the filesystem. + struct dentry + { + using dentry_ptr = kstd::shared_ptr; + using inode_ptr = kstd::shared_ptr; + + //! Flags for the dentry. + enum class dentry_flags : uint32_t + { + is_mount_point = 1 << 0 + }; + + //! Create a directory entry with the given parent, associated inode, and name. + //! + //! @warning This function will panic if invoked with a null pointer for the inode argument. + //! + //! @param parent The parent directory entry, if any. + //! @param inode The associated inode for this dentry. + //! @param name The name of the dentry. + dentry(dentry_ptr const & parent, inode_ptr const & inode, std::string_view name); + + //! Get this entrys associated inode. + //! + //! @return The inode associated with this directory entry. + [[nodiscard]] auto inode() const -> inode_ptr const &; + + //! Get this entrys parent directory entry. + //! + //! @return The parent directory entry, or @p nullptr if this entry has no parent. + [[nodiscard]] auto parent() const -> dentry_ptr; + + //! Get this entrys name. + //! + //! @return The name of this directory entry. + [[nodiscard]] auto name() const -> std::string_view; + + //! Get the full path of this entry by traversing up to the root. + //! + //! @note This function performs a full path traversal to the root. Even if all entries on that path a cached, this + //! may imply a non insignificant time penalty, as traversal complexity is linear in the depth of the path. + //! + //! @return The full path of this directory entry. + [[nodiscard]] auto absolute_path() const -> kstd::string; + + //! Add a child to this entry. + //! + //! @param child The child directory entry to add. + auto add_child(dentry_ptr const & child) -> void; + + //! Find a child entry with the given name. + //! + //! @note This function performs no path traversal. The name supplied to this function must name a direct child of + //! this directory entry in order for it to be found. + //! + //! @param name The name of the child directory entry to find. + //! @return A pointer to the child dentry if it exists, a null pointer otherwise. + [[nodiscard]] auto find_child(std::string_view name) const -> dentry_ptr; + + //! Set a flag for this entry. + //! + //! @param flag The flag to set. + auto set_flag(dentry_flags flag) -> void; + + //! Clear a flag from this entry. + //! + //! @param flag The flag to clear. + auto unset_flag(dentry_flags flag) -> void; + + //! Check if this entry has a specific flag set. + //! + //! @param flag The flag to check. + //! @return @c true iff. the flag is set on this entry, @c false otherwise. + [[nodiscard]] auto has_flag(dentry_flags flag) const -> bool; + + private: + kstd::string m_name; + kstd::weak_ptr m_parent; + kstd::vector m_children; + inode_ptr m_inode; + uint32_t m_flags; + }; +} // namespace kernel::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs/dentry.tests.cpp b/kernel/kernel/vfs/dentry.tests.cpp new file mode 100644 index 00000000..a504354c --- /dev/null +++ b/kernel/kernel/vfs/dentry.tests.cpp @@ -0,0 +1,150 @@ +#include + +#include +#include + +#include +#include + +#include + +SCENARIO("Dentry construction", "[filesystem][dentry]") +{ + GIVEN("A parent dentry and inode") + { + auto inode = kstd::make_shared(); + auto parent_dentry = kstd::make_shared(nullptr, inode, "parent"); + + WHEN("constructing a dentry") + { + auto child_dentry = kernel::vfs::dentry{parent_dentry, inode, "child"}; + + THEN("the dentry has the correct parent, inode, and name") + { + REQUIRE(child_dentry.parent() == parent_dentry); + REQUIRE(child_dentry.inode() == inode); + REQUIRE(child_dentry.name() == "child"); + } + + THEN("no flag is set") + { + REQUIRE_FALSE(child_dentry.has_flag(kernel::vfs::dentry::dentry_flags::is_mount_point)); + } + } + + WHEN("constructing a dentry with an empty name") + { + THEN("the dentry has the correct parent and inode, and an empty name") + { + REQUIRE_THROWS_AS((kernel::vfs::dentry{parent_dentry, inode, ""}), kernel::tests::cpu::halt); + } + } + + WHEN("constructing a dentry with a null parent") + { + auto child_dentry = kernel::vfs::dentry{nullptr, inode, "child"}; + + THEN("the dentry has a null parent, the correct inode, and the correct name") + { + REQUIRE(child_dentry.parent() == nullptr); + REQUIRE(child_dentry.inode() == inode); + REQUIRE(child_dentry.name() == "child"); + } + + THEN("no flag is set") + { + REQUIRE_FALSE(child_dentry.has_flag(kernel::vfs::dentry::dentry_flags::is_mount_point)); + } + } + + WHEN("constructing a dentry with a null inode") + { + THEN("the system panics") + { + REQUIRE_THROWS_AS((kernel::vfs::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(); + auto parent_dentry = kstd::make_shared(nullptr, inode, "parent"); + + WHEN("adding child dentries") + { + auto child1 = kstd::make_shared(parent_dentry, inode, "child1"); + auto child2 = kstd::make_shared(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(); + auto dentry = kernel::vfs::dentry{nullptr, inode, "test"}; + + WHEN("setting a flag") + { + dentry.set_flag(kernel::vfs::dentry::dentry_flags::is_mount_point); + + THEN("the flag is set") + { + REQUIRE(dentry.has_flag(kernel::vfs::dentry::dentry_flags::is_mount_point)); + } + } + + WHEN("unsetting a flag") + { + dentry.set_flag(kernel::vfs::dentry::dentry_flags::is_mount_point); + dentry.unset_flag(kernel::vfs::dentry::dentry_flags::is_mount_point); + + THEN("the flag is unset") + { + REQUIRE_FALSE(dentry.has_flag(kernel::vfs::dentry::dentry_flags::is_mount_point)); + } + } + } +} + +SCENARIO("Dentry path resolution", "[filesystem][dentry]") +{ + GIVEN("A dentry with a parent hierarchy") + { + auto root_inode = kstd::make_shared(); + auto root_dentry = kstd::make_shared(nullptr, root_inode, "/"); + + auto home_inode = kstd::make_shared(); + auto home_dentry = kstd::make_shared(root_dentry, home_inode, "home"); + root_dentry->add_child(home_dentry); + + auto user_inode = kstd::make_shared(); + auto user_dentry = kstd::make_shared(home_dentry, user_inode, "user"); + home_dentry->add_child(user_dentry); + + THEN("the full path is constructed correctly") + { + REQUIRE(root_dentry->absolute_path() == "/"); + REQUIRE(home_dentry->absolute_path() == "/home"); + REQUIRE(user_dentry->absolute_path() == "/home/user"); + } + } +} \ No newline at end of file diff --git a/kernel/kernel/vfs/device_inode.cpp b/kernel/kernel/vfs/device_inode.cpp new file mode 100644 index 00000000..ebb39541 --- /dev/null +++ b/kernel/kernel/vfs/device_inode.cpp @@ -0,0 +1,120 @@ +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace kernel::vfs +{ + device_inode::device_inode(kstd::shared_ptr const & device) + : device_inode{device, nullptr} + { + if (!device) + { + kapi::system::panic("[FILESYSTEM] device_inode constructed with null device."); + } + } + + device_inode::device_inode(kstd::shared_ptr const & device, + kstd::shared_ptr const & origin) + : m_device{device} + , m_origin{origin} + { + if (!device) + { + kapi::system::panic("[FILESYSTEM] device_inode constructed with null device."); + } + } + + auto device_inode::read(std::span buffer, kstd::bytes offset) const -> kstd::result + { + if (m_device->state() == kapi::devices::state::removed) + { + return kstd::failure(errc::no_such_device); + } + + if (auto block_device = + kapi::devices::facet_registry::get().resolve(*m_device)) + { + return devices::block_device_utils::read(*block_device, buffer, offset); + } + else if (auto character_device = + kapi::devices::facet_registry::get().resolve(*m_device)) + { + return character_device->read(buffer); + } + + return kstd::failure(errc::no_such_device); + } + + auto device_inode::write(std::span buffer, kstd::bytes offset) -> kstd::result + { + if (m_device->state() == kapi::devices::state::removed) + { + return kstd::failure(errc::no_such_device); + } + + if (auto block_device = + kapi::devices::facet_registry::get().resolve(*m_device)) + { + return devices::block_device_utils::write(*block_device, buffer, offset); + } + else if (auto character_device = + kapi::devices::facet_registry::get().resolve(*m_device)) + { + return character_device->write(buffer); + } + + return kstd::failure(errc::no_such_device); + } + + auto device_inode::device() const -> kstd::shared_ptr const & + { + return m_device; + } + + auto device_inode::is_block_device() const -> bool + { + return kapi::devices::facet_registry::get().resolve(*m_device); + } + + auto device_inode::is_character_device() const -> bool + { + return kapi::devices::facet_registry::get().resolve(*m_device); + } + + auto device_inode::status() const -> kstd::result + { + if (m_origin) + { + return m_origin->status(); + } + + return inode::status(); + } + + auto device_inode::raw_device() const -> std::optional + { + if (auto number = device_number_registry::get().number_of(*m_device)) + { + return *number; + } + + return std::nullopt; + } + +} // namespace kernel::vfs \ No newline at end of file diff --git a/kernel/kernel/vfs/device_inode.hpp b/kernel/kernel/vfs/device_inode.hpp new file mode 100644 index 00000000..dd89c7b3 --- /dev/null +++ b/kernel/kernel/vfs/device_inode.hpp @@ -0,0 +1,103 @@ +#ifndef TEACHOS_KERNEL_VFS_DEVICE_INODE_HPP +#define TEACHOS_KERNEL_VFS_DEVICE_INODE_HPP + +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace kernel::vfs +{ + + //! Inode implementation for device inodes in the filesystem. + //! + //! An object of this type 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 final : inode + { + //! Create an inode representing a given device. + //! + //! @param device The device to associate with the inode. + explicit device_inode(kstd::shared_ptr const & device); + + //! Create a device inode wrapping a device and carrying another inodes metadata. + //! + //! This constructor enables reconstituting a persistend device node from a filesystem. POSIX status information + //! will be queried by the origin inode. + //! + //! @param device The device that the new inode should represent. + //! @param origin The original inode being wrapped by the new inode. + device_inode(kstd::shared_ptr const & device, kstd::shared_ptr const & origin); + + //! @name I/O Operations + //! @{ + + //! Read data, starting at a given offset, from the device into a buffer; + //! + //! @param buffer The buffer to read data into. + //! @param offset The offset to read from. + //! @return The number of bytes read on success, an error on otherwise. + [[nodiscard]] auto read(std::span buffer, kstd::bytes offset) const + -> kstd::result override; + + //! Write data from a buffer to the device, at a given offset. + //! + //! @param buffer The buffer containing data to write. + //! @param offset The offset to write to. + //! @return The number of bytes written on success, an error otherwise. + auto write(std::span buffer, kstd::bytes offset) -> kstd::result override; + + //! @} + + //! @name Property Queries + //! @{ + + [[nodiscard]] auto is_block_device() const -> bool override; + + [[nodiscard]] auto is_character_device() const -> bool override; + + //! @} + + //! @name POSIX Information Access + //! @{ + + [[nodiscard]] auto status() const -> kstd::result override; + + [[nodiscard]] auto raw_device() const -> std::optional override; + + //! @} + + //! @name Device Access + //! @{ + + //! @brief Get the device, if any, associated with this inode. + //! + //! @return The associated device, of nullptr if no such device exists. + [[nodiscard]] auto device() const -> kstd::shared_ptr const &; + + //! @} + + private: + //! The handle to the device represented by this inode. + //! + //! Any I/O operations will be redirected to this device, allowing for filesystem based device I/O operations. + kstd::shared_ptr m_device; + + //! The original inode this inode was reconstituted from, if any. + //! + //! If this inode was reconstituted from a filesystem inode representing a device, e.g. from an ext2 block device + //! inode, Some queries must be forwarded to that origin inode. This data member will be used to forward those call + //! when applicable. + kstd::shared_ptr m_origin; + }; +} // namespace kernel::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs/device_inode.tests.cpp b/kernel/kernel/vfs/device_inode.tests.cpp new file mode 100644 index 00000000..0f9b5881 --- /dev/null +++ b/kernel/kernel/vfs/device_inode.tests.cpp @@ -0,0 +1,244 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include + +using namespace kstd::units_literals; + +namespace +{ + struct registry_reset_guard + { + ~registry_reset_guard() + { + kernel::tests::vfs::device_number_registry::deinit(); + } + }; + + struct mock_origin_inode : kernel::vfs::inode + { + auto read(std::span, kstd::bytes) const -> kstd::result override + { + return 0_B; + } + + auto write(std::span, kstd::bytes) -> kstd::result override + { + return 0_B; + } + + [[nodiscard]] auto status() const -> kstd::result override + { + return kapi::filesystem::file_status{ + .mode = std::to_underlying(kapi::filesystem::file_type::block_device), + .size = 4096_B, + .raw_device = kapi::filesystem::device_number{.major = 1, .minor = 0}, + .inode_number = 40, + .link_count = 1, + }; + } + }; +} // namespace + +SCENARIO("Device inode construction", "[filesystem][device_inode]") +{ + GIVEN("a block device") + { + auto device = kstd::make_shared("test_block_device", 512_B, 3 * 512_B); + + WHEN("constructing a device inode with the block device") + { + auto inode = kernel::vfs::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()); + REQUIRE_FALSE(inode.is_symbolic_link()); + } + } + + WHEN("constructing a device inode with a null device") + { + THEN("the constructor panics") + { + REQUIRE_THROWS_AS((kernel::vfs::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("test_block_device", 512_B, 3 * 512_B); + auto inode = kernel::vfs::device_inode{device}; + + WHEN("writing to the device inode") + { + kstd::vector write_buffer(1024); + for (size_t i = 0; i < write_buffer.size(); ++i) + { + write_buffer[i] = static_cast(i % 256); + } + + auto bytes_written = inode.write(write_buffer, 256_B); + + THEN("the correct number of bytes is written") + { + REQUIRE(bytes_written == 1024_B); + } + + THEN("the data written matches the data read back from the device inode") + { + kstd::vector read_buffer(1024); + auto bytes_read = inode.read(read_buffer, 256_B); + + REQUIRE(bytes_read.value().value == 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("test_character_device"); + auto inode = kernel::vfs::device_inode{device}; + + WHEN("reading from the device inode") + { + kstd::vector read_buffer(512); + + THEN("the result is an error") + { + auto result = inode.read(read_buffer, 0_B); + REQUIRE(!result); + REQUIRE(result.error() == kstd::errc::no_such_device); + } + } + + WHEN("writing to the device inode") + { + kstd::vector write_buffer(512); + + THEN("the system panics") + { + auto result = inode.write(write_buffer, 0_B); + REQUIRE(!result); + REQUIRE(result.error() == kstd::errc::no_such_device); + } + } + } +} + +SCENARIO("Device inode raw_device()", "[filesystem][device-inode]") +{ + auto resetter = registry_reset_guard{}; + + GIVEN("A block device that has been published and numbered") + { + auto device = kstd::make_shared("ram0", 512_B); + + CHECK(kernel::tests::devices::bind(*device, std::uint8_t{1})); + + REQUIRE(kapi::devices::facet_registry::get().publish(device, "ram0")); + + auto inode = kernel::vfs::device_inode{device}; + + THEN("raw_device() return the number assigned to the device") + { + auto expected = kernel::vfs::device_number_registry::get().number_of(*device); + REQUIRE(expected); + + auto actual = inode.raw_device(); + REQUIRE(actual == expected); + } + } + + GIVEN("a block device that has never been published") + { + auto device = kstd::make_shared("never_published", 512_B); + auto inode = kernel::vfs::device_inode{device}; + + THEN("raw_device() is nullopt, not a stale or default number") + { + REQUIRE_FALSE(inode.raw_device()); + } + } +} + +SCENARIO("Device inode status()", "[filesystem][device_inode]") +{ + registry_reset_guard const guard{}; + + GIVEN("a device inode with no origin") + { + auto device = kstd::make_shared("ram0", 512_B); + auto inode = kernel::vfs::device_inode{device}; + + THEN("status() falls back to the minimal default") + { + auto const result = inode.status(); + REQUIRE(result.has_value()); + REQUIRE(result->mode == std::to_underlying(kapi::filesystem::file_type::block_device)); + REQUIRE(result->size == 0_B); + REQUIRE(result->inode_number == 0); + } + } + + GIVEN("a device inode constructed with an origin inode") + { + auto device = kstd::make_shared("ram0", 512_B, 512_B); + auto origin = kstd::make_shared(); + auto inode = kernel::vfs::device_inode{device, origin}; + + THEN("status() forwards to the origin outright, not device_inode's own default") + { + auto const result = inode.status(); + REQUIRE(result.has_value()); + REQUIRE(result->size == 4096_B); + REQUIRE(result->inode_number == 40); + REQUIRE(result->link_count == 1); + REQUIRE(result->raw_device == kapi::filesystem::device_number{.major = 1, .minor = 0}); + } + + THEN("read/write still delegate to the device, never to the origin") + { + kstd::vector buffer(512); + auto const result = inode.read(buffer, 0_B); + REQUIRE(result == 512_B); + } + } +} \ No newline at end of file diff --git a/kernel/kernel/vfs/device_number_registry.cpp b/kernel/kernel/vfs/device_number_registry.cpp new file mode 100644 index 00000000..738ce043 --- /dev/null +++ b/kernel/kernel/vfs/device_number_registry.cpp @@ -0,0 +1,283 @@ +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace +{ + constinit auto static instance = std::optional{}; +} // namespace + +namespace kernel::vfs +{ + + auto device_number_registry::get() -> device_number_registry & + { + if (!instance) + { + instance.emplace(kapi::devices::facet_registry::get()); + } + return *instance; + } + + device_number_registry::device_number_registry(kapi::devices::facet_registry & facet_registry) + : m_facet_registry{&facet_registry} + { + for (auto const & binding : file_type_bindings) + { + for (auto const & published : facet_registry.all(binding.facet)) + { + try_number(binding.facet, published); + } + } + + m_facet_registry->subscribe(*this); + } + + device_number_registry::~device_number_registry() + { + m_facet_registry->unsubscribe(*this); + } + + auto device_number_registry::resolve(kapi::filesystem::device_number number, file_type type) const + -> kstd::result> + { + auto guard = kstd::lock_guard{m_lock}; + + erase_if(m_entries, [&](auto entry) { return entry.device.expired(); }); + + auto found = + std::ranges::find_if(m_entries, [&](auto entry) { return entry.number == number && entry.type == type; }); + + if (found == std::ranges::cend(m_entries)) + { + return kstd::failure(errc::no_such_device); + } + + if (auto device = found->device.lock()) + { + return device; + } + + return kstd::failure(errc::no_such_device); + } + + auto device_number_registry::number_of(kapi::devices::device const & device) const + -> kstd::result + { + auto guard = kstd::lock_guard{m_lock}; + + erase_if(m_entries, [&](auto entry) { return entry.device.expired(); }); + + auto found = std::ranges::find_if(m_entries, [&](auto entry) { + auto locked = entry.device.lock(); + return locked.get() == &device; + }); + + if (found == std::ranges::cend(m_entries)) + { + return kstd::failure(errc::no_such_device); + } + + return found->number; + } + + auto device_number_registry::all() const -> kstd::vector + { + auto guard = kstd::lock_guard{m_lock}; + + erase_if(m_entries, [&](auto entry) { return entry.device.expired(); }); + + return m_entries; + } + + auto device_number_registry::subscribe(kstd::weak_ptr observer) -> void + { + auto guard = kstd::lock_guard{m_lock}; + + erase_if(m_observers, [&](auto existing) { return existing.expired(); }); + + m_observers.push_back(std::move(observer)); + } + + auto device_number_registry::on_facet_published(kapi::capabilities::facet_id facet, + kapi::devices::facet_registry::entry const & published) -> void + { + try_number(facet, published); + } + + auto device_number_registry::on_facet_withdrawn(kapi::capabilities::facet_id facet, kapi::devices::device & device) + -> void + { + // Check if there even is a binding defined for the given facet. + auto binding = + std::ranges::find_if(file_type_bindings, [&](auto const & binding) { return binding.facet == facet; }); + if (binding == std::ranges::cend(file_type_bindings)) + { + return; + } + + auto unnumbered = entry{}; + + { + auto guard = kstd::lock_guard{m_lock}; + + auto found = std::ranges::find_if(m_entries, [&](auto entry) { + auto locked = entry.device.lock(); + return entry.type == binding->posix_type && locked.get() == &device; + }); + + if (found == std::ranges::cend(m_entries)) + { + return; + } + + unnumbered = *found; + m_entries.erase(found); + } + + auto observers = kstd::vector>{}; + { + auto guard = kstd::lock_guard{m_lock}; + observers = m_observers; + } + + std::ranges::for_each(observers, [&](auto const & weak_observer) { + auto observer = weak_observer.lock(); + if (observer) + { + observer->on_device_unnumbered(unnumbered); + } + }); + } + + auto device_number_registry::try_number(kapi::capabilities::facet_id facet, + kapi::devices::facet_registry::entry const & published) -> void + { + // Check if there even is a binding defined for the given facet. + auto binding = + std::ranges::find_if(file_type_bindings, [&](auto const & binding) { return binding.facet == facet; }); + if (binding == std::ranges::cend(file_type_bindings)) + { + return; + } + + // Check if the device is still alive (it might have died while we are being notified). + auto device = published.device(); + if (!device) + { + return; + } + + // Check if the device has a bound driver (otherwise there is no point in numbering it). + auto driver = device->bound_driver(); + if (!driver) + { + return; + } + + // Check if the driver has claimed a major number (otherwise we can't number it). + auto driver_major = driver->claimed_major(); + if (!driver_major) + { + return; + } + + auto numbered = entry{}; + { + auto guard = kstd::lock_guard{m_lock}; + + auto minor = std::uint8_t{}; + auto driver_minor = driver->claimed_minor(*device); + if (driver_minor) + { + minor = *driver_minor; + auto filtered = std::views::filter( + m_entries, [&](auto e) { return e.type == binding->posix_type && e.number.major == *driver_major; }); + auto found = std::ranges::find_if(filtered, [&](auto e) { return e.minor == minor; }, &entry::number); + if (found != std::ranges::cend(filtered)) + { + kstd::println(kstd::print_sink::stderr, "[OS:DEV] Driver '{}' tried to claim duplicate minor {}", + driver->name(), minor); + return; + } + } + else + { + minor = allocate_minor(binding->posix_type, *driver_major); + } + + numbered = entry{ + .number = {.major = *driver_major, .minor = minor}, + .type = binding->posix_type, + .name = published.name(), + .device = device, + }; + + m_entries.push_back(numbered); + } + + auto observers = kstd::vector>{}; + { + auto guard = kstd::lock_guard{m_lock}; + observers = m_observers; + } + + std::ranges::for_each(observers, [&](auto const & weak_observer) { + auto observer = weak_observer.lock(); + if (observer) + { + observer->on_device_numbered(numbered); + } + }); + } + + auto device_number_registry::allocate_minor(file_type type, std::uint8_t major) const -> std::uint8_t + { + auto used = kstd::vector{}; + for (auto const & entry : m_entries) + { + if (entry.type == type && entry.number.major == major) + { + used.push_back(entry.number.minor); + } + } + std::ranges::sort(used); + + auto candidate = std::uint8_t{}; + for (auto minor : used) + { + if (minor != candidate) + { + break; + } + ++candidate; + } + + return candidate; + } + +} // namespace kernel::vfs + +namespace kernel::tests::vfs::device_number_registry +{ + auto deinit() -> void + { + instance.reset(); + } +} // namespace kernel::tests::vfs::device_number_registry diff --git a/kernel/kernel/vfs/device_number_registry.hpp b/kernel/kernel/vfs/device_number_registry.hpp new file mode 100644 index 00000000..734da498 --- /dev/null +++ b/kernel/kernel/vfs/device_number_registry.hpp @@ -0,0 +1,117 @@ +#ifndef TEACHOS_KERNEL_VFS_DEVICE_NUMBER_REGISTRY_HPP +#define TEACHOS_KERNEL_VFS_DEVICE_NUMBER_REGISTRY_HPP + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include + +namespace kernel::vfs +{ + + struct device_number_registry_observer; + + //! A registry to assign stable device numbers to published devices. + struct device_number_registry final : kapi::devices::facet_registry_observer + { + using file_type = kapi::filesystem::file_type; + + //! A single numbered device. + //! + //! Theses entries are used, for example, in devfs to provide access to devices. + struct entry + { + kapi::filesystem::device_number number; + file_type type; + kstd::string name; + kstd::weak_ptr device; + }; + + //! Construct a new device number registry, subscribing to the given facet registry. + //! + //! @param facet_registry The facet registry to back this device number registry. + explicit device_number_registry(kapi::devices::facet_registry & facet_registry); + + //! Destroy this registry. + ~device_number_registry() override; + + device_number_registry(device_number_registry const &) = delete; + device_number_registry(device_number_registry &&) = delete; + auto operator=(device_number_registry const &) -> device_number_registry & = delete; + auto operator=(device_number_registry &&) -> device_number_registry & = delete; + + //! Get the system global instance of the registry. + [[nodiscard]] auto static get() -> device_number_registry &; + + //! Resolve a device number and file type to a device. + //! + //! @param number The device number. + //! @param type The expected file type of the device. + //! @return A device if a fitting entry is found, an error otherwise. + [[nodiscard]] auto resolve(kapi::filesystem::device_number number, file_type type) const + -> kstd::result>; + + //! Resolve a device to it's device number. + //! + //! @param device The device to find the number for. + //! @return The device number associated with the given device, or an error otherwise. + [[nodiscard]] auto number_of(kapi::devices::device const & device) const + -> kstd::result; + + //! Get all currently registered devices. + //! + //! @return A range containing all currently registered devices numbers mappings. + [[nodiscard]] auto all() const -> kstd::vector; + + //! Subscribe to device numbering events. + //! + //! Observers subscribed through this function are held through non-owning pointers. It is safe of an observer + //! subscribed through this function to be destroyed without being unsubscribed. + //! + //! @param observer The observer to notify. + auto subscribe(kstd::weak_ptr observer) -> void; + + private: + auto on_facet_published(kapi::capabilities::facet_id facet, kapi::devices::facet_registry::entry const & entry) + -> void override; + + auto on_facet_withdrawn(kapi::capabilities::facet_id facet, kapi::devices::device & device) -> void override; + + auto try_number(kapi::capabilities::facet_id facet, kapi::devices::facet_registry::entry const & published) -> void; + + auto allocate_minor(file_type type, std::uint8_t major) const -> std::uint8_t; + + mutable kapi::tracked_mutex m_lock{}; + + kstd::observer_ptr m_facet_registry; + mutable kstd::vector m_entries{}; + kstd::vector> m_observers{}; + }; + + //! The facet for types interested in observing numbering transactions on a device number registry. + struct device_number_registry_observer + { + //! Enable correct destruction through base pointers. + virtual ~device_number_registry_observer() = default; + + //! Called after a device has been assigned a device number. + //! + //! @param numbered The newly numbered entry. + virtual auto on_device_numbered(device_number_registry::entry const & numbered) -> void = 0; + + //! Called after a device's number has been withdrawn. + //! + //! @param unnumbered The entry that is no longer numbered. + virtual auto on_device_unnumbered(device_number_registry::entry const & unnumbered) -> void = 0; + }; + +} // namespace kernel::vfs + +#endif diff --git a/kernel/kernel/vfs/device_number_registry.tests.cpp b/kernel/kernel/vfs/device_number_registry.tests.cpp new file mode 100644 index 00000000..abaacd74 --- /dev/null +++ b/kernel/kernel/vfs/device_number_registry.tests.cpp @@ -0,0 +1,154 @@ +#include + +#include +#include + +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +using namespace kstd::units_literals; + +namespace +{ + auto make_bound_block_device(kapi::devices::facet_registry & facets, kstd::string const & name, + kstd::bytes block_size, std::optional major) + -> kstd::shared_ptr + { + auto device = kstd::make_shared(name, block_size); + [[maybe_unused]] auto driver = kernel::tests::devices::bind(*device, major); + if (!facets.publish(device, name)) + { + throw std::runtime_error{"failed to publish block device!"}; + } + return device; + } +} // namespace + +SCENARIO("Device number registry resolves and numbers published block devices", + "[kernel][filesystem][device_number_registry]") +{ + auto facets = kapi::devices::facet_registry{}; + auto registry = kernel::vfs::device_number_registry{facets}; + + GIVEN("No devices have been published") + { + THEN("all() returns an empty vector") + { + REQUIRE(registry.all().empty()); + } + } + + GIVEN("A single block device has been published, bound to a driver claiming major 1") + { + auto device = make_bound_block_device(facets, "ram0", 512_B, std::uint8_t{1}); + + THEN("all() returns a vector with one element") + { + auto all = registry.all(); + REQUIRE(all.size() == 1); + + AND_THEN("that entry has the published name") + { + REQUIRE(all[0].name == "ram0"); + } + + AND_THEN("resolve() finds the device by number") + { + REQUIRE(registry.resolve(all[0].number, kapi::filesystem::file_type::block_device)); + } + + AND_THEN("number_of() finds the device number") + { + REQUIRE(registry.number_of(*device)); + } + + AND_THEN("number_of() is the inverse of resolve()") + { + auto number = registry.number_of(*device); + CHECK(number); + + auto resolved = registry.resolve(*number, kapi::filesystem::file_type::block_device); + CHECK(resolved); + + REQUIRE(resolved == device); + } + } + + THEN("number_of() fails on a device that has never been published") + { + auto not_published = kstd::make_shared("ram1", 512_B); + + REQUIRE(!registry.number_of(*not_published)); + } + } + + GIVEN("A device was published") + { + auto device = make_bound_block_device(facets, "ram0", 512_B, std::uint8_t{1}); + auto number = registry.number_of(*device); + + WHEN("destroying it with no further owner") + { + device.reset(); + + THEN("resolve() does not report it anymore") + { + REQUIRE(!registry.resolve(*number, kapi::filesystem::file_type::block_device)); + } + } + } + + GIVEN("Two block-capable drivers claiming different majors") + { + [[maybe_unused]] auto device_a = make_bound_block_device(facets, "ramA0", 512_B, std::uint8_t{1}); + [[maybe_unused]] auto device_b = make_bound_block_device(facets, "ramB0", 512_B, std::uint8_t{2}); + + THEN("their minors are numbered independently, starting at 0 for each driver") + { + auto all = registry.all(); + REQUIRE(all.size() == 2); + + auto entry_a = std::ranges::find_if(all, [](auto const & entry) { return entry.name == "ramA0"; }); + auto entry_b = std::ranges::find_if(all, [](auto const & entry) { return entry.name == "ramB0"; }); + REQUIRE(entry_a != all.end()); + REQUIRE(entry_b != all.end()); + + CHECK(entry_a->number.major == 1); + CHECK(entry_a->number.minor == 0); + CHECK(entry_b->number.major == 2); + CHECK(entry_b->number.minor == 0); + } + } + + GIVEN("A driver that never overrides claimed_major()") + { + make_bound_block_device(facets, "unnumbered0", 512_B, std::nullopt); + + THEN("it is never numbered, regardless of what it publishes") + { + REQUIRE(registry.all().empty()); + } + } + + GIVEN("A published device with no bound driver at all") + { + auto device = kstd::make_shared("unbound0", 512_B); + CHECK(facets.publish(device, "unbound0")); + + THEN("it is never numbered") + { + REQUIRE(registry.all().empty()); + } + } +} diff --git a/kernel/kernel/vfs/directory_file_descriptor.cpp b/kernel/kernel/vfs/directory_file_descriptor.cpp new file mode 100644 index 00000000..fd60d1d5 --- /dev/null +++ b/kernel/kernel/vfs/directory_file_descriptor.cpp @@ -0,0 +1,34 @@ +#include + +#include +#include +#include + +#include +#include + +#include +#include + +namespace kernel::vfs +{ + + auto directory_file_descriptor::read_directory(std::span entries) + -> kstd::result + { + auto result = get_dentry()->inode()->read_directory(m_position, entries); + if (!result) + { + return kstd::failure(result.error()); + } + + m_position = result->second; + return result->first; + } + + auto directory_file_descriptor::position() const noexcept -> directory_listing_cursor + { + return m_position; + } + +} // namespace kernel::vfs \ No newline at end of file diff --git a/kernel/kernel/vfs/directory_file_descriptor.hpp b/kernel/kernel/vfs/directory_file_descriptor.hpp new file mode 100644 index 00000000..92649783 --- /dev/null +++ b/kernel/kernel/vfs/directory_file_descriptor.hpp @@ -0,0 +1,35 @@ +#ifndef TEACHOS_KERNEL_VFS_DIRECTORY_FILE_DESCRIPTOR_HPP +#define TEACHOS_KERNEL_VFS_DIRECTORY_FILE_DESCRIPTOR_HPP + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include + +namespace kernel::vfs +{ + + struct directory_file_descriptor : open_file_descriptor + { + using open_file_descriptor::open_file_descriptor; + + auto read_directory(std::span entries) -> kstd::result override; + + [[nodiscard]] auto position() const noexcept -> directory_listing_cursor; + + private: + directory_listing_cursor m_position{}; + }; + +} // namespace kernel::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs/directory_file_descriptor.tests.cpp b/kernel/kernel/vfs/directory_file_descriptor.tests.cpp new file mode 100644 index 00000000..dd7a5afc --- /dev/null +++ b/kernel/kernel/vfs/directory_file_descriptor.tests.cpp @@ -0,0 +1,81 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +#include +#include +#include +#include +#include + +SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_vfs_fixture, + "directory_file_descriptor owns its cursor across calls, not the caller", + "[filesystem][directory_file_descriptor][img]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("a directory_file_descriptor open on a real directory") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img_and_init_vfs({"test_img_module"}, {image_path})); + + auto & vfs = kernel::vfs::vfs::get(); + auto dentry = vfs.open("/information"); + REQUIRE(dentry); + + auto fd = kernel::vfs::directory_file_descriptor{dentry.value()}; + + THEN("position starts at a default-constructed cursor") + { + REQUIRE(fd.position() == kernel::vfs::directory_listing_cursor{}); + } + + WHEN("read_directory is called one entry at a time, with no cursor passed by the caller") + { + auto seen = std::vector{}; + + for (auto guard = 0; guard < 16; ++guard) + { + auto one = kstd::vector(1); + auto result = fd.read_directory(one); + REQUIRE(result); + if (*result == 0) + { + break; + } + seen.push_back(std::string{one[0].name}); + } + + THEN("every call advanced position on its own, and the known entries all appear exactly once") + { + REQUIRE(fd.position() != kernel::vfs::directory_listing_cursor{}); + REQUIRE(std::ranges::find(seen, ".") != seen.end()); + REQUIRE(std::ranges::find(seen, "info_1.txt") != seen.end()); + + AND_THEN("a repeated call after exhaustion returns zero without changing position again") + { + auto const position_at_exhaustion = fd.position(); + + auto one = kstd::vector(1); + auto result = fd.read_directory(one); + + REQUIRE(result); + REQUIRE(*result == 0); + REQUIRE(fd.position() == position_at_exhaustion); + } + } + } + } +} \ No newline at end of file diff --git a/kernel/kernel/vfs/directory_listing_cursor.hpp b/kernel/kernel/vfs/directory_listing_cursor.hpp new file mode 100644 index 00000000..544d1115 --- /dev/null +++ b/kernel/kernel/vfs/directory_listing_cursor.hpp @@ -0,0 +1,23 @@ +#ifndef TEACHOS_KERNEL_VFS_DIRECTORY_LISTING_CURSOR_HPP +#define TEACHOS_KERNEL_VFS_DIRECTORY_LISTING_CURSOR_HPP + +#include +#include + +namespace kernel::vfs +{ + + //! A cursor used to keep track of positions in directory listings. + struct directory_listing_cursor + { + constexpr auto friend operator<=>(directory_listing_cursor const &, directory_listing_cursor const &) noexcept + -> std::strong_ordering = default; + constexpr auto friend operator==(directory_listing_cursor const &, directory_listing_cursor const &) noexcept + -> bool = default; + + std::uint64_t value; + }; + +}; // namespace kernel::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs/directory_listing_entry.hpp b/kernel/kernel/vfs/directory_listing_entry.hpp new file mode 100644 index 00000000..f636a6bf --- /dev/null +++ b/kernel/kernel/vfs/directory_listing_entry.hpp @@ -0,0 +1,23 @@ +#ifndef TEACHOS_KERNEL_VFS_DIRECTORY_LISTING_ENTRY_HPP +#define TEACHOS_KERNEL_VFS_DIRECTORY_LISTING_ENTRY_HPP + +#include + +#include + +#include + +namespace kernel::vfs +{ + + //! An entry in a directory listing. + struct directory_listing_entry + { + kstd::string name; + kapi::filesystem::file_type type; + std::uint64_t inode_number; + }; + +}; // namespace kernel::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs/driver_registry.cpp b/kernel/kernel/vfs/driver_registry.cpp new file mode 100644 index 00000000..16d048bf --- /dev/null +++ b/kernel/kernel/vfs/driver_registry.cpp @@ -0,0 +1,130 @@ +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace kernel::vfs +{ + + extern "C" + { + // We need to suppress clang-tidy linting warnings here, since these symbols are generated by the linker and we + // cannot choose their names, unless we wanted to extend the linker script needlessly. + // NOLINTBEGIN(readability-identifier-naming) + extern kstd::observer_ptr const __start_filesystem_drivers; + extern kstd::observer_ptr const __stop_filesystem_drivers; + // NOLINTEND(readability-identifier-naming) + } + + namespace + { + auto constinit instance = std::optional{}; + } + + auto driver_registry::init() -> void + { + if (instance) + { + kapi::system::panic("[OS:FS] The filesystem driver registry was already initialized!"); + } + + instance.emplace(); + + auto instances = + std::span{&__start_filesystem_drivers, &__stop_filesystem_drivers} | + std::views::filter([](auto descriptor) { return descriptor != nullptr; }) | + std::views::transform([](auto descriptor) { return std::pair{descriptor, descriptor->make_instance()}; }) | + std::views::filter([](auto entry) { return entry.second != nullptr; }); + + std::ranges::for_each(instances, [](auto entry) { + auto [descriptor, driver] = entry; + kstd::println("[OS:FS] Registering filesystem driver '{}'", descriptor->name()); + instance->add(descriptor->name(), driver); + }); + } + + auto driver_registry::get() -> driver_registry & + { + if (!instance) + { + kapi::system::panic("[OS:FS] The filesystem driver registry has not been initialized!"); + } + + return *instance; + } + + auto driver_registry::find(std::string_view name) const -> std::optional> + { + if (auto it = m_drivers.find(name); it != m_drivers.end()) + { + return it->second; + } + return std::nullopt; + } + + auto driver_registry::add(std::string_view name, kstd::shared_ptr driver) -> bool + { + auto result = m_drivers.emplace(name, driver); + if (!result.second) + { + kstd::println(kstd::print_sink::stderr, "[OS:FS] Tried to register duplicate filesystem driver '{}'!", name); + } + + return result.second; + } + + auto driver_registry::match(kstd::shared_ptr const & inode) const -> kstd::result> + { + struct candidate + { + std::uint32_t priority; + kstd::shared_ptr driver; + }; + + auto candidates = kstd::vector{}; + + for (auto const & entry : m_drivers) + { + auto const & [name, driver] = entry; + auto priority = driver->probe(inode); + if (priority) + { + candidates.push_back({*priority, driver}); + } + } + + std::ranges::stable_sort(candidates, std::ranges::greater{}, &candidate::priority); + + if (candidates.empty()) + { + return kstd::failure(errc::unsupported_filesystem); + } + + return candidates.front().driver; + } +} // namespace kernel::vfs + +namespace kernel::tests::vfs::driver_registry +{ + auto deinit() -> void + { + kernel::vfs::instance.reset(); + } +} // namespace kernel::tests::vfs::driver_registry \ No newline at end of file diff --git a/kernel/kernel/vfs/driver_registry.hpp b/kernel/kernel/vfs/driver_registry.hpp new file mode 100644 index 00000000..b463e086 --- /dev/null +++ b/kernel/kernel/vfs/driver_registry.hpp @@ -0,0 +1,73 @@ +#ifndef TEACHOS_KERNEL_VFS_DRIVER_REGISTRY_HPP +#define TEACHOS_KERNEL_VFS_DRIVER_REGISTRY_HPP + +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace kernel::vfs +{ + + struct driver_descriptor + { + virtual ~driver_descriptor() = default; + + //! Get the name of the driver. + [[nodiscard]] virtual auto name() const noexcept -> std::string_view = 0; + + //! Create a new instance of the driver represented by this descriptor. + [[nodiscard]] virtual auto make_instance() const -> kstd::shared_ptr = 0; + }; + + template Type> + struct driver_module + { + constexpr auto static instance = Type{}; + [[using gnu: section("filesystem_drivers"), used, visibility("hidden")]] constexpr auto static pointer{ + kstd::make_observer(&instance), + }; + }; + + struct driver_registry + { + constexpr driver_registry() noexcept = default; + + auto static init() -> void; + + auto static get() -> driver_registry &; + + //! Register a new filesystem. + //! + //! @param name The name of the filesystem. + //! @param driver The filesystem driver to register. + //! @return @c true iff. the driver was successfully added, @c false if not. + auto add(std::string_view name, kstd::shared_ptr driver) -> bool; + + //! Find a registered filesystem driver + //! + //! @param name The name of the filesystem + [[nodiscard]] auto find(std::string_view name) const -> std::optional>; + + //! Match all currently registered filesystem drivers against a given inode. + //! + //! @param inode The inode to match on, if any. + //! @return a match priority if th + [[nodiscard]] auto match(kstd::shared_ptr const & inode) const -> kstd::result>; + + private: + //! A map from filesystem names to filesystem drivers. + kstd::flat_map> m_drivers{}; + }; + +} // namespace kernel::vfs + +#endif diff --git a/kernel/kernel/vfs/error.cpp b/kernel/kernel/vfs/error.cpp new file mode 100644 index 00000000..40f4cd79 --- /dev/null +++ b/kernel/kernel/vfs/error.cpp @@ -0,0 +1,113 @@ +#include + +#include + +#include + +namespace kernel::vfs +{ + + namespace + { + struct category_t final : kstd::error_category + { + [[nodiscard]] constexpr auto name() const noexcept -> std::string_view override + { + return "vfs"; + } + + [[nodiscard]] constexpr auto message(int value) const noexcept -> std::string_view override + { + switch (static_cast(value)) + { + case errc::invalid_path: + return "invalid path"; + case errc::no_such_file_or_directory: + return "no such file or directory"; + case errc::mount_point_not_found: + return "mount point not found"; + case errc::mount_busy: + return "mount point is busy"; + case errc::has_child_mounts: + return "mount point has child mounts"; + case errc::invalid_filesystem: + return "invalid filesystem"; + case errc::unmount_failed: + return "unmount failed"; + case errc::too_many_symbolic_link_levels: + return "too many symbolic link levels"; + case errc::unsupported_filesystem: + return "unsupported filesystem"; + case errc::invalid_file_descriptor: + return "invalid file descriptor"; + case errc::invalid_inode: + return "invalid inode"; + case errc::not_a_directory: + return "not a directory"; + case errc::is_a_directory: + return "is a directory"; + case errc::read_only_file_system: + return "readonly filesystem"; + case errc::file_exists: + return "file already exists"; + case errc::no_such_device: + return "no such device"; + case errc::invalid_argument: + return "invalid argument"; + case errc::not_mounted: + return "filesystem is not mounted"; + case errc::invalid_driver_data: + return "invalid driver data"; + default: + return "unknown VFS error"; + } + } + + [[nodiscard]] constexpr auto default_error_condition(int value) const noexcept -> kstd::error_condition override + { + switch (static_cast(value)) + { + case errc::invalid_path: + case errc::invalid_filesystem: + case errc::invalid_inode: + case errc::invalid_argument: + case errc::invalid_driver_data: + return make_error_condition(kstd::errc::invalid_argument); + case errc::no_such_file_or_directory: + case errc::mount_point_not_found: + return make_error_condition(kstd::errc::no_such_file_or_directory); + case kernel::vfs::errc::mount_busy: + case kernel::vfs::errc::has_child_mounts: + case errc::unmount_failed: + return make_error_condition(kstd::errc::device_or_resource_busy); + case errc::too_many_symbolic_link_levels: + return make_error_condition(kstd::errc::too_many_symbolic_link_levels); + case errc::unsupported_filesystem: + return make_error_condition(kstd::errc::not_supported); + case errc::invalid_file_descriptor: + return make_error_condition(kstd::errc::bad_file_descriptor); + case errc::not_a_directory: + return make_error_condition(kstd::errc::not_a_directory); + case errc::is_a_directory: + return make_error_condition(kstd::errc::is_a_directory); + case errc::read_only_file_system: + return make_error_condition(kstd::errc::read_only_file_system); + case errc::file_exists: + return make_error_condition(kstd::errc::file_exists); + case errc::no_such_device: + return make_error_condition(kstd::errc::no_such_device); + case errc::not_mounted: + return make_error_condition(kstd::errc::not_connected); + default: + return kstd::error_condition{value, *this}; + } + } + } constexpr vfs_category_instance{}; + } // namespace + + [[nodiscard]] auto category() noexcept -> kstd::error_category const & + { + return vfs_category_instance; + } + +} // namespace kernel::vfs diff --git a/kernel/kernel/vfs/error.hpp b/kernel/kernel/vfs/error.hpp new file mode 100644 index 00000000..ec1bd045 --- /dev/null +++ b/kernel/kernel/vfs/error.hpp @@ -0,0 +1,56 @@ +#ifndef TEACHOS_KERNEL_VFS_ERROR_HPP +#define TEACHOS_KERNEL_VFS_ERROR_HPP + +#include + +#include + +namespace kernel::vfs +{ + + enum struct errc : int + { + invalid_path = 1, + no_such_file_or_directory, + mount_point_not_found, + mount_busy, + has_child_mounts, + invalid_filesystem, + unmount_failed, + too_many_symbolic_link_levels, + unsupported_filesystem, + invalid_file_descriptor, + invalid_inode, + not_a_directory, + is_a_directory, + read_only_file_system, + file_exists, + no_such_device, + invalid_argument, + not_mounted, + invalid_driver_data, + }; + + [[nodiscard]] auto category() noexcept -> kstd::error_category const &; + + [[nodiscard]] constexpr auto inline make_error_code(errc error) noexcept -> kstd::error_code + { + return {static_cast(error), category()}; + } + + [[nodiscard]] constexpr auto inline make_error_condition(errc error) noexcept -> kstd::error_condition + { + return {static_cast(error), category()}; + } + +} // namespace kernel::vfs + +namespace kstd +{ + template<> + struct is_error_code_enum : std::true_type + { + }; +} // namespace kstd + +#endif diff --git a/kernel/kernel/vfs/file_type_bindings.hpp b/kernel/kernel/vfs/file_type_bindings.hpp new file mode 100644 index 00000000..d3fe84e3 --- /dev/null +++ b/kernel/kernel/vfs/file_type_bindings.hpp @@ -0,0 +1,30 @@ +#ifndef TEACHOS_KERNEL_VFS_FILE_TYPE_BINDINGS_HPP +#define TEACHOS_KERNEL_VFS_FILE_TYPE_BINDINGS_HPP + +#include + +#include + +#include + +namespace kernel::vfs +{ + + //! A binding between a facet, a posix special file type + struct file_type_binding // NOLINT(cppcoreguidelines-pro-type-member-init) + { + kapi::capabilities::facet_id facet; + kapi::filesystem::file_type posix_type; + }; + + //! Predefined bindings for facet + file type + //! + //! This array associates published facets with files types. + constexpr auto inline file_type_bindings = std::array{ + file_type_binding{kapi::filesystem::character_special_file::id, kapi::filesystem::file_type::character_device}, + file_type_binding{ kapi::filesystem::block_special_file::id, kapi::filesystem::file_type::block_device}, + }; + +} // namespace kernel::vfs + +#endif diff --git a/kernel/kernel/vfs/filesystem.hpp b/kernel/kernel/vfs/filesystem.hpp new file mode 100644 index 00000000..52abb437 --- /dev/null +++ b/kernel/kernel/vfs/filesystem.hpp @@ -0,0 +1,77 @@ +#ifndef TEACHOS_KERNEL_VFS_FILESYSTEM_HPP +#define TEACHOS_KERNEL_VFS_FILESYSTEM_HPP + +#include + +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +namespace kernel::vfs +{ + //! The base class for all filesystems. + //! + //! This class provides a common interface for managing files and directories within the virtual filesystem. + //! Filesystem implementations must derive from this class. + struct filesystem + { + using filesystem_ptr = kstd::shared_ptr; + using driver_data_ptr = kstd::shared_ptr; + using inode_ptr = kstd::shared_ptr; + using mount_result = std::pair; + + //! Virtual destructor enabling polymorphic destruction. + virtual ~filesystem() = default; + + //! Check if the data represented by the given inode is recognized as this filesystem. + //! + //! @param inode The inode to probe, if any. + //! @return the priority (higher is better) of the match on success, an error otherwise. + [[nodiscard]] virtual auto probe(inode_ptr const & inode) const -> kstd::result = 0; + + //! Initializes the filesystem with the given inode. + //! + //! Typically, the backing inode is the inode representing the block device or another inode which contains the + //! filesystem data (e.g. a file on an already mounted filesystem). + //! + //! @param backing_inode The inode to use as the backing inode for the filesystem. + //! @return The root inode of the mounted filesystem on success, and error otherwise. + virtual auto mount(inode_ptr const & backing_inode) -> kstd::result = 0; + + //! Find a child inode below the given parent inode with the specified 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. + //! @param driver_data The per-mount data used by the driver. + //! @return A pointer to the requested child inode on success, an error otherwise. + [[nodiscard]] virtual auto lookup(inode_ptr const & parent, std::string_view name, + driver_data_ptr driver_data) const -> kstd::result = 0; + + //! Create a new inode with the given name below a given parent inode. + //! + //! @param parent The parent inode. + //! @param name The name of the inode to create. + //! @param type The file type of inode to be created. + //! @param driver_data The per-mount data used by the driver. + //! @param raw_device The device number the new inode should represent, if any. + //! @return A pointer to the created inode on success, an error otherwise. + [[nodiscard]] virtual auto create_inode(inode_ptr const & parent, std::string_view name, + kapi::filesystem::file_type type, driver_data_ptr driver_data, + std::optional raw_device = std::nullopt) + -> kstd::result> = 0; + }; + +} // namespace kernel::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs/inode.cpp b/kernel/kernel/vfs/inode.cpp new file mode 100644 index 00000000..ee7aa1e9 --- /dev/null +++ b/kernel/kernel/vfs/inode.cpp @@ -0,0 +1,103 @@ +#include + +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include + +namespace kernel::vfs +{ + auto inode::is_directory() const -> bool + { + return false; + } + + auto inode::is_regular() const -> bool + { + return false; + } + + auto inode::is_block_device() const -> bool + { + return false; + } + + auto inode::is_character_device() const -> bool + { + return false; + } + + auto inode::is_device() const -> bool + { + return is_block_device() || is_character_device(); + } + + auto inode::is_symbolic_link() const -> bool + { + return false; + } + + auto inode::status() const -> kstd::result + { + auto result = kapi::filesystem::file_status{}; + + if (is_directory()) + { + result.mode = std::to_underlying(kapi::filesystem::file_type::directory); + } + else if (is_regular()) + { + result.mode = std::to_underlying(kapi::filesystem::file_type::regular); + } + else if (is_block_device()) + { + result.mode = std::to_underlying(kapi::filesystem::file_type::block_device); + } + else if (is_character_device()) + { + result.mode = std::to_underlying(kapi::filesystem::file_type::character_device); + } + else if (is_symbolic_link()) + { + result.mode = std::to_underlying(kapi::filesystem::file_type::symbolic_link); + } + + if (auto const number = raw_device()) + { + result.raw_device = *number; + } + + return result; + } + + auto inode::raw_device() const -> std::optional + { + return std::nullopt; + } + + auto inode::read_directory(directory_listing_cursor, std::span) const + -> kstd::result> + { + return kstd::failure(errc::not_a_directory); + } + + auto inode::owning_mount() const noexcept -> kstd::weak_ptr + { + return m_owning_mount; + } + + auto inode::set_owning_mount(kstd::shared_ptr const & source) noexcept -> void + { + m_owning_mount = source; + } + +} // namespace kernel::vfs \ No newline at end of file diff --git a/kernel/kernel/vfs/inode.hpp b/kernel/kernel/vfs/inode.hpp new file mode 100644 index 00000000..80657b92 --- /dev/null +++ b/kernel/kernel/vfs/inode.hpp @@ -0,0 +1,142 @@ +#ifndef TEACHOS_KERNEL_VFS_INODE_HPP +#define TEACHOS_KERNEL_VFS_INODE_HPP + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace kernel::vfs +{ + struct mount; + + //! An inode in the filesystem. + //! + //! Specific filesystems, or other subsystems, may provide their own implementation of this abstract base class. For + //! example, the ext2 filesystem provides its own inode implementation derived from this class, performing ext2 + //! specific operations to fulfill the contract of this class. + struct inode + { + //! Create an inode. + inode() = default; + + //! Virtual destructor for the inode. + virtual ~inode() = default; + + //! @name I/O Operations + //! @{ + + //! Read from this inode into a buffer + //! + //! This function reads data from this inode into a given buffer. Implementations must guarantee, that if buffer is + //! larger than the entity represented by this inode, the buffer bytes beyond the range of the read bytes are left + //! untouched. + //! + //! @param buffer Destination buffer. + //! @param offset Read offset in bytes. + //! @return The number of bytes read on success, an error otherwise. + [[nodiscard]] virtual auto read(std::span buffer, kstd::bytes offset) const + -> kstd::result = 0; + + //! Writes data from a buffer into this inode. + //! + //! This function writes data from a buffer into this inode. Implementations must handle buffer sizes larger than + //! inode entity sizes gracefully. Specifically, all bytes in the buffer that do not fit into the entity represented + //! by this inode must be ignored. + //! + //! @param buffer Source buffer. + //! @param offset Write offset in bytes. + //! @return The number of bytes written on success, an error otherwise. + virtual auto write(std::span buffer, kstd::bytes offset) -> kstd::result = 0; + + //! @} + + //! @name Property Checking + //! @{ + + //! Check if this inode describes a block device node. + //! + //! @return @c true iff. this inode describes a block device node, @c false otherwise. + [[nodiscard]] virtual auto is_block_device() const -> bool; + + //! Check if this inode describes a character device node. + //! + //! @return @c true iff. this inode describes a character device node, @c false otherwise. + [[nodiscard]] virtual auto is_character_device() const -> bool; + + //! Check if this inode describes a device. + //! + //! @return @c true iff. this inode is either a block or character device node, @c false otherwise. + [[nodiscard]] auto is_device() const -> bool; + + //! Check if this inode describes a directory. + //! + //! @return @c true iff. this inode describes a directory, @c false otherwise. + [[nodiscard]] virtual auto is_directory() const -> bool; + + //! Check if this inode describes a regular file. + //! + //! @return @c true iff. this inode describes a regular file, @c false otherwise. + [[nodiscard]] virtual auto is_regular() const -> bool; + + //! Check if this inode describes a symbolic link. + //! + //! @return @c true iff. this inode describes a symbolic link, @c false otherwise. + [[nodiscard]] virtual auto is_symbolic_link() const -> bool; + + //! @} + + //! @name POSIX Information Access + //! @{ + + //! Get POSIX status information for this inode. + //! + //! @return a populated status information object, or an error or failure. + [[nodiscard]] virtual auto status() const -> kstd::result; + + //! Get the raw device, if any, this inode refers to. + //! + //! @return a device number if this inode refers to a device, an empty optional otherwise. + [[nodiscard]] virtual auto raw_device() const -> std::optional; + + //! List the contents of a directory. + //! + //! @param position The current directory listing position. + //! @param entries An output span to store directory entries. + //! @return A pair of the number or read entries and the next listing position on success, an error otherwise. + [[nodiscard]] virtual auto read_directory(directory_listing_cursor position, + std::span entries) const + -> kstd::result>; + + //! @} + + //! @name Mount Resolution + //! @{ + + //! Get the mount this inode belongs to. + //! + //! @return The mount this inode belongs to. + [[nodiscard]] auto owning_mount() const noexcept -> kstd::weak_ptr; + + //! Define which mount this inode belongs to. + //! + //! @param source The mount this inode belongs to. + auto set_owning_mount(kstd::shared_ptr const & source) noexcept -> void; + + //! @} + + private: + kstd::weak_ptr m_owning_mount{}; + }; +} // namespace kernel::vfs + +#endif diff --git a/kernel/kernel/vfs/mount.cpp b/kernel/kernel/vfs/mount.cpp new file mode 100644 index 00000000..22496307 --- /dev/null +++ b/kernel/kernel/vfs/mount.cpp @@ -0,0 +1,135 @@ +#include + +#include +#include + +#include + +#include +#include +#include + +#include +#include + +namespace kernel::vfs +{ + mount::mount(dentry_ptr const & mount_dentry, dentry_ptr const & root_dentry, filesystem_ptr const & fs, + mount_ptr const & parent_mount, mount_ptr const & source_mount, kstd::shared_ptr driver_data) + : m_mount_dentry(mount_dentry) + , m_root_dentry(root_dentry) + , m_filesystem(fs) + , m_parent_mount(parent_mount) + , m_source_mount(source_mount) + , m_driver_data(driver_data) + , m_ref_count(0) + { + if (!m_filesystem) + { + kapi::system::panic("[FILESYSTEM] mount initialized with null filesystem."); + } + } + + auto mount::attach(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, + kstd::shared_ptr const & root_inode, kstd::shared_ptr driver_data, + mount_ptr const & parent_mount, mount_ptr const & source_mount) -> mount_ptr + { + auto root_dentry = mount_dentry + ? kstd::make_shared(mount_dentry->parent(), root_inode, mount_dentry->name()) + : kstd::make_shared(nullptr, root_inode, "/"); + + auto instance = mount_ptr{ + new mount{mount_dentry, root_dentry, fs, parent_mount, source_mount, driver_data} + }; + + root_inode->set_owning_mount(instance); + + return instance; + } + + auto mount::create(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, mount_ptr const & parent_mount, + mount_ptr const & source_mount, kstd::shared_ptr const & backing_inode) + -> kstd::result + { + if (!fs) + { + kapi::system::panic("[OS:FS] Tried to mount a null filesystem!"); + } + + auto mount_result = fs->mount(backing_inode); + if (!mount_result) + { + return kstd::failure(mount_result.error()); + } + + auto [root_inode, driver_data] = *mount_result; + + auto instance = attach(mount_dentry, fs, root_inode, driver_data, parent_mount, source_mount); + + return instance; + } + + auto mount::mount_dentry() const -> dentry_ptr const & + { + return m_mount_dentry; + } + + auto mount::filesystem() const -> filesystem_ptr const & + { + return m_filesystem; + } + + auto mount::root_dentry() const -> dentry_ptr const & + { + return m_root_dentry; + } + + auto mount::mount_path() const -> kstd::string + { + if (m_mount_dentry) + { + return m_mount_dentry->absolute_path(); + } + return "/"; + } + + auto mount::parent_mount() const -> mount_ptr const & + { + return m_parent_mount; + } + + auto mount::source_mount() const -> mount_ptr + { + return m_source_mount.lock(); + } + + auto mount::driver_data() const -> kstd::shared_ptr + { + return m_driver_data; + } + + auto mount::increment_ref_count() -> void + { + m_ref_count += 1; + } + + auto mount::decrement_ref_count() -> void + { + if (m_ref_count == 0) + { + kapi::system::panic("[FILESYSTEM] decrement_ref_count() was called but ref_count is 0"); + } + + m_ref_count -= 1; + } + + auto mount::is_ready_to_unmount() const -> bool + { + return m_ref_count == 0; + } + + auto mount::ref_count() const -> size_t + { + return m_ref_count; + } +} // namespace kernel::vfs \ No newline at end of file diff --git a/kernel/kernel/vfs/mount.hpp b/kernel/kernel/vfs/mount.hpp new file mode 100644 index 00000000..b59d3de8 --- /dev/null +++ b/kernel/kernel/vfs/mount.hpp @@ -0,0 +1,109 @@ +#ifndef TEACHOS_KERNEL_VFS_MOUNT_HPP +#define TEACHOS_KERNEL_VFS_MOUNT_HPP + +#include +#include + +#include +#include +#include + +#include +#include + +namespace kernel::vfs +{ + struct inode; + + //! A mounted filesystem + struct mount + { + using dentry_ptr = kstd::shared_ptr; + using filesystem_ptr = kstd::shared_ptr; + using mount_ptr = kstd::shared_ptr; + using inode_ptr = kstd::shared_ptr; + + //! Attach a mounted filesystem with a known root inode + //! + //! @param mount_dentry The directory entry where the filesystem is mounted. + //! @param fs The filesystem instance being mounted. + //! @param root_inode The backing inode for the filesystem, must not be null. + //! @param driver_data The filesystem specific driver data. + //! @param parent_mount The parent mount which contains the mount_dentry. + //! @param source_mount The mount that the filesystem originates from. + auto static attach(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, inode_ptr const & root_inode, + kstd::shared_ptr driver_data, mount_ptr const & parent_mount, + mount_ptr const & source_mount) -> mount_ptr; + + //! Create a new mount with the given parameters. + //! + //! @param mount_dentry The directory entry where the filesystem is mounted. + //! @param fs The filesystem instance being mounted. + //! @param parent_mount The parent mount which contains the mount_dentry. + //! @param source_mount The mount that the filesystem originates from. + //! @param backing_inode The backing inode for the filesystem, if any. + auto static create(dentry_ptr const & mount_dentry, filesystem_ptr const & fs, mount_ptr const & parent_mount, + mount_ptr const & source_mount, inode_ptr const & backing_inode) -> kstd::result; + + //! Get the directory entry where the filesystem is mounted. + [[nodiscard]] auto mount_dentry() const -> dentry_ptr const &; + + //! Get the root directory entry of the mounted filesystem. + [[nodiscard]] auto root_dentry() const -> dentry_ptr const &; + + //! Get the filesystem instance being mounted. + [[nodiscard]] auto filesystem() const -> filesystem_ptr const &; + + //! Get the path at which the filesystem is mounted. + [[nodiscard]] auto mount_path() const -> kstd::string; + + //! Get the parent mount that this mount was attached beneath. + [[nodiscard]] auto parent_mount() const -> mount_ptr const &; + + //! Get the source mount where this mount originates from. + [[nodiscard]] auto source_mount() const -> mount_ptr; + + //! Get the data of the attached filesystem driver. + [[nodiscard]] auto driver_data() const -> kstd::shared_ptr; + + //! Increment the reference count for this mount. + auto increment_ref_count() -> void; + + //! Decrement the reference count for this mount. + //! + //! @warning This function panics if the reference count is already zero. + auto decrement_ref_count() -> void; + + //! Check if the mount is ready to be unmounted. + //! + //! @return True if the mount is ready to be unmounted, false otherwise. + [[nodiscard]] auto is_ready_to_unmount() const -> bool; + + //! Get the current reference count for this mount. + //! + //! @return The current reference count. + [[nodiscard]] auto ref_count() const -> size_t; + + private: + //! Create a new mount with the given parameters. + //! + //! @param mount_dentry The directory entry where the filesystem is mounted. + //! @param root_dentry The root directory entry of the mounted filesystem. + //! @param fs The filesystem instance being mounted. + //! @param parent_mount The parent mount which contains the mount_dentry. + //! @param source_mount The mount that the filesystem originates from. + //! @param driver_data The driver specific state data. + mount(dentry_ptr const & mount_dentry, dentry_ptr const & root_dentry, filesystem_ptr const & fs, + mount_ptr const & parent_mount, mount_ptr const & source_mount, kstd::shared_ptr driver_data); + + dentry_ptr m_mount_dentry{}; + dentry_ptr m_root_dentry{}; + filesystem_ptr m_filesystem{}; + mount_ptr m_parent_mount{}; + kstd::weak_ptr m_source_mount{}; + kstd::shared_ptr m_driver_data{}; + std::atomic_size_t m_ref_count{0}; + }; +} // namespace kernel::vfs + +#endif diff --git a/kernel/kernel/vfs/mount.tests.cpp b/kernel/kernel/vfs/mount.tests.cpp new file mode 100644 index 00000000..bb56f3ef --- /dev/null +++ b/kernel/kernel/vfs/mount.tests.cpp @@ -0,0 +1,95 @@ +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +SCENARIO("Mount construction", "[filesystem][mount]") +{ + GIVEN("a filesystem and a root inode") + { + auto fs = kstd::make_shared(); + auto root_inode = kstd::make_shared(); + + WHEN("attaching a mount with no mount point") + { + auto mount = kernel::vfs::mount::attach(nullptr, fs, root_inode, nullptr, nullptr, nullptr); + + THEN("the mount has the correct filesystem, root dentry, mount dentry, and mount path") + { + REQUIRE(mount->filesystem() == fs); + REQUIRE(mount->root_dentry()->inode() == root_inode); + REQUIRE(mount->root_dentry()->name() == "/"); + REQUIRE(mount->mount_dentry() == nullptr); + REQUIRE(mount->mount_path() == "/"); + REQUIRE(mount->is_ready_to_unmount()); + } + + THEN("the mount has no parent mount and no source mount") + { + REQUIRE(mount->parent_mount() == nullptr); + REQUIRE(mount->source_mount() == nullptr); + } + } + + WHEN("constructing a mount with a null filesystem") + { + THEN("the constructor panics") + { + REQUIRE_THROWS_AS((kernel::vfs::mount::create(nullptr, nullptr, nullptr, nullptr, nullptr)), + kernel::tests::cpu::halt); + } + } + } +} + +SCENARIO("Mount reference counting", "[filesystem][mount]") +{ + GIVEN("a filesystem and a root dentry") + { + auto fs = kstd::make_shared(); + auto root_inode = kstd::make_shared(); + auto root_dentry = kstd::make_shared(nullptr, root_inode, "/"); + + THEN("reference count can be incremented and decremented, the mount is ready to unmount when the reference " + "count == 0") + { + auto mount = kernel::vfs::mount::create(root_dentry, fs, nullptr, nullptr, nullptr); + REQUIRE(mount); + + (*mount)->increment_ref_count(); + REQUIRE((*mount)->ref_count() == 1); + REQUIRE_FALSE((*mount)->is_ready_to_unmount()); + + (*mount)->increment_ref_count(); + REQUIRE((*mount)->ref_count() == 2); + REQUIRE_FALSE((*mount)->is_ready_to_unmount()); + + (*mount)->decrement_ref_count(); + REQUIRE((*mount)->ref_count() == 1); + REQUIRE_FALSE((*mount)->is_ready_to_unmount()); + + (*mount)->decrement_ref_count(); + REQUIRE((*mount)->ref_count() == 0); + REQUIRE((*mount)->is_ready_to_unmount()); + } + + THEN("decrementing reference count when it is already zero does not decrement it below zero") + { + auto mount = kernel::vfs::mount::create(root_dentry, fs, nullptr, nullptr, nullptr); + + REQUIRE_THROWS_AS((*mount)->decrement_ref_count(), std::runtime_error); + REQUIRE((*mount)->ref_count() == 0); + REQUIRE((*mount)->is_ready_to_unmount()); + } + } +} \ No newline at end of file diff --git a/kernel/kernel/vfs/mount_table.cpp b/kernel/kernel/vfs/mount_table.cpp new file mode 100644 index 00000000..2b494040 --- /dev/null +++ b/kernel/kernel/vfs/mount_table.cpp @@ -0,0 +1,79 @@ +#include + +#include +#include + +#include +#include + +#include +#include +#include + +namespace kernel::vfs +{ + auto mount_table::has_child_mounts(kstd::shared_ptr const & parent_mount) const -> bool + { + return std::ranges::any_of(m_mounts, + [&parent_mount](auto const & mount) { return mount->parent_mount() == parent_mount; }); + } + + auto mount_table::add_mount(kstd::shared_ptr const & mount) -> void + { + m_mounts.push_back(mount); + + if (auto source_mount = mount->source_mount()) + { + source_mount->increment_ref_count(); + } + + if (auto mount_dentry = mount->mount_dentry()) + { + mount_dentry->set_flag(dentry::dentry_flags::is_mount_point); + } + } + + auto mount_table::remove_mount(std::string_view path) -> operation_result + { + auto mount_it = find_mount_iterator(path); + if (mount_it == m_mounts.end()) + { + return operation_result::mount_not_found; + } + + auto const & mount = *mount_it; + if (!mount->is_ready_to_unmount()) + { + return operation_result::cannot_be_unmounted; + } + if (has_child_mounts(mount)) + { + return operation_result::has_child_mounts; + } + + if (auto source_mount = mount->source_mount()) + { + source_mount->decrement_ref_count(); + } + + if (auto mount_dentry = mount->mount_dentry()) + { + mount_dentry->unset_flag(dentry::dentry_flags::is_mount_point); + } + + m_mounts.erase(mount_it); + return operation_result::removed; + } + + auto mount_table::find_mount(std::string_view path) const -> kstd::shared_ptr + { + auto mount_it = find_mount_iterator(path); + return (mount_it != m_mounts.end()) ? *mount_it : nullptr; + } + + auto mount_table::find_mount_iterator(std::string_view path) const + -> kstd::vector>::const_iterator + { + return std::ranges::find_last_if(m_mounts, [&](auto const & mount) { return mount->mount_path() == path; }).begin(); + } +} // namespace kernel::vfs \ No newline at end of file diff --git a/kernel/kernel/vfs/mount_table.hpp b/kernel/kernel/vfs/mount_table.hpp new file mode 100644 index 00000000..86810a16 --- /dev/null +++ b/kernel/kernel/vfs/mount_table.hpp @@ -0,0 +1,52 @@ +#ifndef TEACHOS_KERNEL_VFS_MOUNT_TABLE_HPP +#define TEACHOS_KERNEL_VFS_MOUNT_TABLE_HPP + +#include + +#include +#include + +#include + +namespace kernel::vfs +{ + //! A table managing all mounted filesystems. + struct mount_table + { + //! Results for mount table operations. + enum class operation_result : int + { + removed = 0, + has_child_mounts = -1, + mount_not_found = -2, + cannot_be_unmounted = -3 + }; + + //! Add a mount to the table. + //! + //! @param mount The mount to add. + auto add_mount(kstd::shared_ptr const & mount) -> void; + + //! Remove 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; // TODO: replace return type with kstd::result + + //! Find the mount with the exact mount path matching the given path. + //! + //! @param path The path to match against the mount paths in the table. + //! @return A pointer to the mount with the exact matching path on success, nullpointer otherwise. + [[nodiscard]] auto find_mount(std::string_view path) const -> kstd::shared_ptr; + + private: + [[nodiscard]] auto has_child_mounts(kstd::shared_ptr const & parent_mount) const -> bool; + [[nodiscard]] auto find_mount_iterator(std::string_view path) const + -> kstd::vector>::const_iterator; + + kstd::vector> m_mounts; + }; +} // namespace kernel::vfs + +#endif diff --git a/kernel/kernel/vfs/mount_table.tests.cpp b/kernel/kernel/vfs/mount_table.tests.cpp new file mode 100644 index 00000000..e8f3ec4a --- /dev/null +++ b/kernel/kernel/vfs/mount_table.tests.cpp @@ -0,0 +1,184 @@ +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +SCENARIO("Mount table construction", "[filesystem][mount_table]") +{ + GIVEN("an empty mount table") + { + kernel::vfs::mount_table table; + + THEN("removing any mount returns mount_not_found") + { + REQUIRE(table.remove_mount("/") == kernel::vfs::mount_table::operation_result::mount_not_found); + REQUIRE(table.remove_mount("/any/path") == kernel::vfs::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::vfs::mount_table table; + + auto fs1 = kstd::make_shared(); + auto root_dentry1 = + kstd::make_shared(nullptr, kstd::make_shared(), "/"); + auto mount_dentry1 = + kstd::make_shared(nullptr, kstd::make_shared(), "/"); + auto mount1 = kernel::vfs::mount::create(mount_dentry1, fs1, nullptr, nullptr, nullptr); + + auto fs2 = kstd::make_shared(); + auto root_dentry2 = + kstd::make_shared(nullptr, kstd::make_shared(), "/"); + auto mount_dentry2 = + kstd::make_shared(nullptr, kstd::make_shared(), "/mnt"); + auto mount2 = kernel::vfs::mount::create(mount_dentry2, fs2, nullptr, nullptr, nullptr); + + table.add_mount(*mount1); + table.add_mount(*mount2); + + THEN("dentry flags are set correctly for mounted dentries") + { + REQUIRE(mount_dentry1->has_flag(kernel::vfs::dentry::dentry_flags::is_mount_point)); + REQUIRE(mount_dentry2->has_flag(kernel::vfs::dentry::dentry_flags::is_mount_point)); + } + + THEN("finding mounts by exact valid path returns the correct mount") + { + REQUIRE(table.find_mount("/") == mount1); + REQUIRE(table.find_mount("/mnt") == mount2); + } + + THEN("finding mounts by exact invalid path returns null") + { + REQUIRE(table.find_mount("/nonexistent") == nullptr); + REQUIRE(table.find_mount("/mnt/file") == nullptr); + } + + THEN("removing a mount that has no child mounts succeeds") + { + REQUIRE(table.remove_mount("/mnt") == kernel::vfs::mount_table::operation_result::removed); + REQUIRE_FALSE(root_dentry2->has_flag(kernel::vfs::dentry::dentry_flags::is_mount_point)); + } + + THEN("removing a mount that does not exist returns mount_not_found") + { + REQUIRE(table.remove_mount("/nonexistent") == kernel::vfs::mount_table::operation_result::mount_not_found); + } + } + + GIVEN("multiple mounts with the same path") + { + kernel::vfs::mount_table table; + + auto fs1 = kstd::make_shared(); + auto root_dentry1 = + kstd::make_shared(nullptr, kstd::make_shared(), "/"); + auto mount_dentry1 = + kstd::make_shared(nullptr, kstd::make_shared(), "/"); + auto mount1 = kernel::vfs::mount::create(mount_dentry1, fs1, nullptr, nullptr, nullptr); + + auto fs2 = kstd::make_shared(); + auto root_dentry2 = + kstd::make_shared(nullptr, kstd::make_shared(), "/"); + auto mount_dentry2 = + kstd::make_shared(nullptr, kstd::make_shared(), "/"); + auto mount2 = kernel::vfs::mount::create(mount_dentry2, fs2, nullptr, nullptr, nullptr); + + table.add_mount(*mount1); + table.add_mount(*mount2); + + THEN("finding mounts by exact valid path returns the correct mount") + { + REQUIRE(table.find_mount("/") == mount2); + } + + THEN("removing the topmost mount with the same path succeeds") + { + REQUIRE(table.remove_mount("/") == kernel::vfs::mount_table::operation_result::removed); + REQUIRE_FALSE(root_dentry2->has_flag(kernel::vfs::dentry::dentry_flags::is_mount_point)); + } + } + + GIVEN("a mount with child mounts") + { + kernel::vfs::mount_table table; + + auto fs1 = kstd::make_shared(); + auto root_dentry1 = + kstd::make_shared(nullptr, kstd::make_shared(), "/"); + auto mount_dentry1 = + kstd::make_shared(nullptr, kstd::make_shared(), "/"); + auto mount1 = kernel::vfs::mount::create(mount_dentry1, fs1, nullptr, nullptr, nullptr); + + auto fs2 = kstd::make_shared(); + auto root_dentry2 = + kstd::make_shared(nullptr, kstd::make_shared(), "/"); + auto mount_dentry2 = kstd::make_shared( + mount_dentry1, kstd::make_shared(), "mnt"); + auto mount2 = kernel::vfs::mount::create(mount_dentry2, fs2, *mount1, nullptr, nullptr); + + auto fs3 = kstd::make_shared(); + auto root_dentry3 = + kstd::make_shared(nullptr, kstd::make_shared(), "/"); + auto mount_dentry3 = kstd::make_shared( + mount_dentry2, kstd::make_shared(), "submnt"); + auto mount3 = kernel::vfs::mount::create(mount_dentry3, fs3, *mount2, nullptr, nullptr); + + table.add_mount(*mount1); + table.add_mount(*mount2); + table.add_mount(*mount3); + + THEN("removing a mount with child mounts returns has_child_mounts") + { + REQUIRE(table.remove_mount("/") == kernel::vfs::mount_table::operation_result::has_child_mounts); + REQUIRE(table.remove_mount("/mnt") == kernel::vfs::mount_table::operation_result::has_child_mounts); + } + + THEN("removing a leaf mount succeeds") + { + REQUIRE(table.remove_mount("/mnt/submnt") == kernel::vfs::mount_table::operation_result::removed); + REQUIRE_FALSE(root_dentry3->has_flag(kernel::vfs::dentry::dentry_flags::is_mount_point)); + } + } +} + +SCENARIO("Mount reference counting", "[filesystem][mount_table]") +{ + kernel::vfs::mount_table table; + + GIVEN("a filesystem and a root dentry") + { + auto fs = kstd::make_shared(); + auto root_inode = kstd::make_shared(); + auto root_dentry = kstd::make_shared(nullptr, root_inode, "/"); + + auto source_mount = kernel::vfs::mount::create(root_dentry, fs, nullptr, nullptr, nullptr); + auto mount = kernel::vfs::mount::create(root_dentry, fs, nullptr, *source_mount, nullptr); + + THEN("reference count of source mount is incremented when a mount is added to the mount table and decremented when " + "the mount is removed") + { + REQUIRE((*source_mount)->ref_count() == 0); + + table.add_mount(*mount); + REQUIRE((*source_mount)->ref_count() == 1); + + REQUIRE(table.remove_mount("/") == kernel::vfs::mount_table::operation_result::removed); + REQUIRE((*source_mount)->ref_count() == 0); + } + } +} diff --git a/kernel/kernel/vfs/open_file_descriptor.cpp b/kernel/kernel/vfs/open_file_descriptor.cpp new file mode 100644 index 00000000..44d55bb9 --- /dev/null +++ b/kernel/kernel/vfs/open_file_descriptor.cpp @@ -0,0 +1,53 @@ +#include + +#include +#include +#include + +#include + +#include +#include +#include +#include + +#include +#include + +namespace kernel::vfs +{ + open_file_descriptor::open_file_descriptor(kstd::shared_ptr const & dentry) + : m_dentry(dentry) + { + if (!dentry) + { + kstd::os::panic("[FILESYSTEM] open_file_descriptor constructed with null dentry."); + } + } + + auto open_file_descriptor::read(std::span) -> kstd::result + { + return kstd::failure(errc::is_a_directory); + } + + auto open_file_descriptor::write(std::span) -> kstd::result + { + return kstd::failure(errc::is_a_directory); + } + + auto open_file_descriptor::seek(kstd::offset, kapi::filesystem::seek_origin) -> kstd::result + { + return kstd::failure(errc::is_a_directory); + } + + auto open_file_descriptor::read_directory(std::span) -> kstd::result + { + return kstd::failure(errc::not_a_directory); + } + + auto open_file_descriptor::get_dentry() const -> kstd::shared_ptr const & + { + return m_dentry; + } + +} // namespace kernel::vfs \ No newline at end of file diff --git a/kernel/kernel/vfs/open_file_descriptor.hpp b/kernel/kernel/vfs/open_file_descriptor.hpp new file mode 100644 index 00000000..2fa27adc --- /dev/null +++ b/kernel/kernel/vfs/open_file_descriptor.hpp @@ -0,0 +1,68 @@ +#ifndef TEACHOS_KERNEL_VFS_OPEN_FILE_DESCRIPTOR_HPP +#define TEACHOS_KERNEL_VFS_OPEN_FILE_DESCRIPTOR_HPP + +#include +#include + +#include + +#include +#include +#include + +#include +#include + +namespace kernel::vfs +{ + //! An open file. + //! + //! This class encapsulates the state of an open file, including a reference to the associated directory and the + //! current byte offset within the file. + struct open_file_descriptor + { + //! Create an new open file descriptor for a given directory entry. + //! + //! @param dentry The directory entry to associate with the open file descriptor. + explicit open_file_descriptor(kstd::shared_ptr const & dentry); + + //! Virtual destructor to enable safe destruction through base pointers. + virtual ~open_file_descriptor() = default; + + //! Read data from the open file descriptor into a buffer. + //! + //! @param buffer The buffer to read data into. + //! @return The number of bytes read on success, an error otherwise. + virtual auto read(std::span buffer) -> kstd::result; + + //! Write data to the open file descriptor from a buffer. + //! + //! @param buffer The buffer to write data from. + //! @return The number of bytes written on success, an error otherwise. + virtual auto write(std::span buffer) -> kstd::result; + + //! Move the read/write offset of the file. + //! + //! @param offset The offset to apply relative to the given origin. + //! @param origin The origin of the offset. + //! @return the new offset on success, an error otherwise. + virtual auto seek(kstd::offset offset, kapi::filesystem::seek_origin origin) -> kstd::result; + + //! Read directory entries from the file. + //! + //! @param entries A buffer to read the entries into. + //! @return The number of read entries on success, an error otherwise. + virtual auto read_directory(std::span entries) -> kstd::result; + + //! Get a reference to the directory entry associated with this open file descriptor. + //! + //! @return A reference to the associated directory entry. + [[nodiscard]] auto get_dentry() const -> kstd::shared_ptr const &; + + private: + kstd::shared_ptr m_dentry; + }; + +} // namespace kernel::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs/open_file_descriptor.tests.cpp b/kernel/kernel/vfs/open_file_descriptor.tests.cpp new file mode 100644 index 00000000..861bddef --- /dev/null +++ b/kernel/kernel/vfs/open_file_descriptor.tests.cpp @@ -0,0 +1,110 @@ +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +#include +#include + +using namespace kstd::units_literals; + +SCENARIO("open_file_descriptor's base defaults fail every operation, unconditionally", + "[filesystem][open_file_descriptor]") +{ + GIVEN("a base open_file_descriptor constructed directly, not through a derived type") + { + auto inode = kstd::make_shared(); + auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); + auto fd = kernel::vfs::open_file_descriptor{dentry}; + + THEN("read fails as 'this is a directory', not silently succeeding with zero bytes") + { + auto buffer = kstd::vector(4); + auto result = fd.read(buffer); + + REQUIRE_FALSE(result); + REQUIRE(result.error() == kernel::vfs::errc::is_a_directory); + } + + THEN("write fails the same way") + { + auto buffer = kstd::vector{std::byte{'x'}}; + auto result = fd.write(buffer); + + REQUIRE_FALSE(result); + REQUIRE(result.error() == kernel::vfs::errc::is_a_directory); + } + + THEN("seek fails the same way") + { + auto result = fd.seek(kstd::offset{0}, kapi::filesystem::seek_origin::beginning); + + REQUIRE_FALSE(result); + REQUIRE(result.error() == kernel::vfs::errc::is_a_directory); + } + + THEN("read_directory fails as 'this is not a directory' — the opposite error, and the only one of " + "the four that's correct for it") + { + auto entries = kstd::vector(1); + auto result = fd.read_directory(entries); + + REQUIRE_FALSE(result); + REQUIRE(result.error() == kernel::vfs::errc::not_a_directory); + } + } +} + +SCENARIO("Cross-type calls through open_file_descriptor fall through to the correct base default, not " + "the other subclass's behavior", + "[filesystem][open_file_descriptor]") +{ + GIVEN("a byte_offset_file_descriptor, accessed through a base pointer") + { + auto inode = kstd::make_shared(); + auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); + kstd::shared_ptr fd = + kstd::make_shared(dentry); + + THEN("read_directory falls through to the base — byte_offset_file_descriptor never overrode it") + { + auto entries = kstd::vector(1); + auto result = fd->read_directory(entries); + + REQUIRE_FALSE(result); + REQUIRE(result.error() == kernel::vfs::errc::not_a_directory); + } + } + + GIVEN("a directory_file_descriptor, accessed through a base pointer") + { + auto inode = kstd::make_shared(); + auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); + kstd::shared_ptr fd = + kstd::make_shared(dentry); + + THEN("read/write/seek all fall through to the base — directory_file_descriptor never overrode any " + "of them") + { + auto buffer = kstd::vector(4); + REQUIRE_FALSE(fd->read(buffer)); + + auto write_buffer = kstd::vector{std::byte{'x'}}; + REQUIRE_FALSE(fd->write(write_buffer)); + + REQUIRE_FALSE(fd->seek(kstd::offset{0}, kapi::filesystem::seek_origin::beginning)); + } + } +} \ No newline at end of file diff --git a/kernel/kernel/vfs/open_file_table.cpp b/kernel/kernel/vfs/open_file_table.cpp new file mode 100644 index 00000000..286fa57b --- /dev/null +++ b/kernel/kernel/vfs/open_file_table.cpp @@ -0,0 +1,90 @@ +#include + +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include + +namespace +{ + constinit auto static global_open_file_table = std::optional{}; +} // namespace + +namespace kernel::vfs +{ + auto open_file_table::init() -> void + { + if (global_open_file_table) + { + kapi::system::panic("[FILESYSTEM] Open file table has already been initialized."); + } + + global_open_file_table.emplace(open_file_table{}); + } + + auto open_file_table::get() -> open_file_table & + { + if (!global_open_file_table) + { + kapi::system::panic("[FILESYSTEM] Open file table has not been initialized."); + } + + return *global_open_file_table; + } + + auto open_file_table::add_file(kstd::shared_ptr const & file_descriptor) + -> kstd::result + { + if (!file_descriptor) + { + return kstd::failure(errc::invalid_file_descriptor); + } + + auto it = std::ranges::find_if(m_open_files, [](auto const & open_file) { return open_file == nullptr; }); + if (it != m_open_files.end()) + { + *it = file_descriptor; + return it - m_open_files.begin(); + } + + m_open_files.push_back(file_descriptor); + return m_open_files.size() - 1; + } + + auto open_file_table::file(size_t fd) const -> kstd::result> + { + if (fd >= m_open_files.size() || m_open_files.at(fd) == nullptr) + { + return kstd::failure(errc::invalid_file_descriptor); + } + + return kstd::success(m_open_files.at(fd)); + } + + auto open_file_table::remove_file(size_t fd) -> kstd::result + { + if (fd >= m_open_files.size() || m_open_files.at(fd) == nullptr) + { + return kstd::failure(errc::invalid_file_descriptor); + } + + m_open_files.at(fd) = nullptr; + return kstd::success(); + } +} // namespace kernel::vfs + +namespace kernel::tests::vfs::open_file_table +{ + auto deinit() -> void + { + global_open_file_table.reset(); + } +} // namespace kernel::tests::vfs::open_file_table diff --git a/kernel/kernel/vfs/open_file_table.hpp b/kernel/kernel/vfs/open_file_table.hpp new file mode 100644 index 00000000..10674ba7 --- /dev/null +++ b/kernel/kernel/vfs/open_file_table.hpp @@ -0,0 +1,57 @@ +#ifndef TEACHOS_KERNEL_VFS_OPEN_FILE_TABLE_HPP +#define TEACHOS_KERNEL_VFS_OPEN_FILE_TABLE_HPP + +#include + +#include +#include +#include +#include + +#include + +namespace kernel::vfs +{ + //! @brief A table for managing file descriptors in the filesystem. + //! + //! This class provides methods for adding, retrieving, and removing open file descriptors. + struct open_file_table + { + //! Initialize the global open file table. + //! + //! @warning This function panics if called more than once. + auto static init() -> void; + + //! Get the global open file table instance. + //! + //! @warning Panics if the open file table has not been initialized. + //! + //! @return A reference to the global open file table. + auto static get() -> open_file_table &; + + //! Add a file to the open file table. + //! + //! @param fd The file descriptor to add. + //! @return The file descriptor index assigned to the file on success, an error otherwise. + auto add_file(kstd::shared_ptr const & fd) -> kstd::result; + + //! Get a file from the open file table. + //! + //! @param fd The file descriptor index to retrieve. + //! @return The requested file descriptor on success, an error otherwise. + [[nodiscard]] auto file(size_t fd) const -> kstd::result>; + + //! Remove a file from the open file table. + //! + //! @param fd The file descriptor index to remove. + //! @return Nothin on success, an error otherwise. + auto remove_file(size_t fd) -> kstd::result; + + private: + open_file_table() = default; + + kstd::vector> m_open_files{}; + }; +} // namespace kernel::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/vfs/open_file_table.tests.cpp b/kernel/kernel/vfs/open_file_table.tests.cpp new file mode 100644 index 00000000..cae4a079 --- /dev/null +++ b/kernel/kernel/vfs/open_file_table.tests.cpp @@ -0,0 +1,107 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +SCENARIO("Open file table add/get file", "[filesystem][open_file_table]") +{ + GIVEN("a open file table and an open file descriptor") + { + auto & table = kernel::vfs::open_file_table::get(); + auto inode = kstd::make_shared(); + auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); + + auto file_descriptor_1 = kstd::make_shared(dentry); + auto file_descriptor_2 = kstd::make_shared(dentry); + + WHEN("adding the open file descriptor to the open file table") + { + auto fd_1 = table.add_file(file_descriptor_1); + auto fd_2 = table.add_file(file_descriptor_2); + auto fd_3 = table.add_file(file_descriptor_2); + + THEN("a valid file descriptor is returned") + { + REQUIRE(fd_1 == 0); + REQUIRE(fd_2 == 1); + REQUIRE(fd_3 == 2); + } + + THEN("the file descriptor can be retrieved using the returned file descriptor") + { + auto retrieved_descriptor = table.file(*fd_1); + REQUIRE(retrieved_descriptor == file_descriptor_1); + } + } + } + + GIVEN("a invalid open file descriptor") + { + auto & table = kernel::vfs::open_file_table::get(); + + THEN("adding a null file descriptor returns an error") + { + auto fd = table.add_file(nullptr); + REQUIRE(fd.error() == kstd::errc::bad_file_descriptor); + } + + THEN("retrieving a file descriptor with an out-of-bounds file descriptor returns an error") + { + auto retrieved_descriptor = table.file(1000); + REQUIRE(retrieved_descriptor.error() == kstd::errc::bad_file_descriptor); + } + } +} + +SCENARIO("Open file table remove file", "[filesystem][open_file_table]") +{ + GIVEN("a open file table with an open file descriptor") + { + auto & table = kernel::vfs::open_file_table::get(); + auto inode = kstd::make_shared(); + auto dentry = kstd::make_shared(nullptr, inode, "test_dentry"); + auto file_descriptor = kstd::make_shared(dentry); + auto fd = table.add_file(file_descriptor); + + WHEN("removing the file descriptor using the file descriptor") + { + CHECK(table.remove_file(*fd)); + + THEN("the file descriptor can no longer be retrieved using the file descriptor") + { + auto retrieved_descriptor = table.file(*fd); + REQUIRE(retrieved_descriptor.error() == kstd::errc::bad_file_descriptor); + } + } + + WHEN("removing a file descriptor the other file descriptor keep the same index") + { + auto fd2 = table.add_file(file_descriptor); + CHECK(table.remove_file(*fd)); + + THEN("the second file descriptor can still be retrieved using its file descriptor") + { + auto retrieved_descriptor = table.file(*fd2); + REQUIRE(retrieved_descriptor == file_descriptor); + } + } + } + + GIVEN("an invalid file descriptor") + { + auto & table = kernel::vfs::open_file_table::get(); + + 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/kernel/vfs/path.hpp b/kernel/kernel/vfs/path.hpp new file mode 100644 index 00000000..e3dbe8d5 --- /dev/null +++ b/kernel/kernel/vfs/path.hpp @@ -0,0 +1,97 @@ +#ifndef TEACHOS_KERNEL_VFS_PATH_HPP +#define TEACHOS_KERNEL_VFS_PATH_HPP + +#include + +#include + +#include +#include +#include + +namespace kernel::vfs::path +{ + + //! Check if the given path is within the maximum allowed length. + //! + //! @param path The path to check. + //! @return @c true if the path length is valid, @c false otherwise. + auto inline is_valid_path_length(std::string_view path) -> bool + { + return path.length() < kernel::vfs::constants::max_path_length; + } + + //! Check if the given path is an absolute path. + //! + //! @param path The path to check. + //! @return @c true if the path is an absolute path, @c false otherwise. + auto inline is_valid_absolute_path(std::string_view path) -> bool + { + return !path.empty() && path.front() == '/' && is_valid_path_length(path); + } + + //! Check if the given path is a relative path. + //! + //! @param path The path to check. + //! @return @c true if the path is a relative path, @c false otherwise. + auto inline is_valid_relative_path(std::string_view path) -> bool + { + return !path.empty() && path.front() != '/' && is_valid_path_length(path); + } + + //! Check if the given path is a valid path. + //! + //! @param path The path to check. + //! @return @c true if the path is a valid path, @c false otherwise. + auto inline is_valid_path(std::string_view path) -> bool + { + return is_valid_absolute_path(path) || is_valid_relative_path(path); + } + + //! Split the given path into its components. + //! + //! @param path The path to split. + //! @return A range of strings representing the components of the path. + auto inline split(std::string_view path) + { + return std::views::split(path, '/') | std::views::filter([](auto const & part) { return !part.empty(); }) | + std::views::transform( + [](auto const & part) { return kstd::string(std::string_view(part.begin(), part.end())); }); + } + + //! Split the given path into its parent path and filename components. + //! + //! @param path The path to split. + //! @return A pair of string views representing the parent path and filename. + auto inline split_into_path_and_filename(std::string_view path) -> std::pair + { + if (path.empty()) + { + return {"", ""}; + } + + if (path.length() > 1 && path.back() == '/') + { + path = path.substr(0, path.length() - 1); + } + + auto last_separator_index = path.rfind('/'); + if (last_separator_index == std::string_view::npos) + { + return {"", path}; + } + else + { + auto parent_path = path.substr(0, last_separator_index); + if (parent_path.empty()) + { + parent_path = "/"; + } + auto filename = path.substr(last_separator_index + 1); + return {parent_path, filename}; + } + } + +} // namespace kernel::vfs::path + +#endif // TEACHOS_KERNEL_FILESYSTEM_PATH_HPP \ No newline at end of file diff --git a/kernel/kernel/vfs/path.tests.cpp b/kernel/kernel/vfs/path.tests.cpp new file mode 100644 index 00000000..5cdd7a81 --- /dev/null +++ b/kernel/kernel/vfs/path.tests.cpp @@ -0,0 +1,128 @@ +#include + +#include + +#include +#include +#include +#include + +SCENARIO("path utilities", "[filesystem][path]") +{ + GIVEN("valid and invalid paths") + { + THEN("valid absolute paths are recognized as valid") + { + REQUIRE(kernel::vfs::path::is_valid_path("/valid/absolute/path")); + REQUIRE(kernel::vfs::path::is_valid_path("/")); + } + + THEN("valid relative paths are recognized as valid") + { + REQUIRE(kernel::vfs::path::is_valid_path("valid/../relative/.././path")); + REQUIRE(kernel::vfs::path::is_valid_path("valid/relative/path")); + REQUIRE(kernel::vfs::path::is_valid_path("file.txt")); + } + + THEN("invalid paths are recognized as invalid") + { + REQUIRE_FALSE(kernel::vfs::path::is_valid_path("")); + REQUIRE_FALSE(kernel::vfs::path::is_valid_path(std::string(4096, 'a'))); + } + + THEN("valid absolute paths are recognized as absolute") + { + REQUIRE(kernel::vfs::path::is_valid_absolute_path("/valid/absolute/path")); + REQUIRE_FALSE(kernel::vfs::path::is_valid_absolute_path("valid/relative/path")); + } + + THEN("invalid paths are not recognized as absolute") + { + REQUIRE_FALSE(kernel::vfs::path::is_valid_absolute_path("")); + REQUIRE_FALSE(kernel::vfs::path::is_valid_absolute_path(std::string(4096, 'a'))); + REQUIRE_FALSE(kernel::vfs::path::is_valid_absolute_path("invalid/absolute/path")); + } + + THEN("valid relative paths are recognized as relative") + { + REQUIRE(kernel::vfs::path::is_valid_relative_path("valid/relative/path")); + REQUIRE_FALSE(kernel::vfs::path::is_valid_relative_path("/valid/absolute/path")); + } + + THEN("invalid paths are not recognized as relative") + { + REQUIRE_FALSE(kernel::vfs::path::is_valid_relative_path("")); + REQUIRE_FALSE(kernel::vfs::path::is_valid_relative_path(std::string(4096, 'a'))); + REQUIRE_FALSE(kernel::vfs::path::is_valid_relative_path("/invalid/absolute/path")); + } + } + + GIVEN("a valid path") + { + THEN("it can be split into components") + { + auto components = kernel::vfs::path::split("/a/b///c/d.txt"); + std::vector expected = {"a", "b", "c", "d.txt"}; + REQUIRE(std::ranges::equal(components, expected)); + } + } + + GIVEN("file paths") + { + THEN("path is split into parent path and filename") + { + auto [parent, filename] = kernel::vfs::path::split_into_path_and_filename("/a/b/c/d.txt"); + REQUIRE(parent == "/a/b/c"); + REQUIRE(filename == "d.txt"); + } + + THEN("root path is split into root and empty filename") + { + auto [parent, filename] = kernel::vfs::path::split_into_path_and_filename("/"); + REQUIRE(parent == "/"); + REQUIRE(filename == ""); + } + + THEN("file directly under root is split correctly") + { + auto [parent, filename] = kernel::vfs::path::split_into_path_and_filename("/file.txt"); + REQUIRE(parent == "/"); + REQUIRE(filename == "file.txt"); + } + + THEN("filename without parent path is split into empty parent and filename") + { + auto [parent, filename] = kernel::vfs::path::split_into_path_and_filename("file.txt"); + REQUIRE(parent == ""); + REQUIRE(filename == "file.txt"); + } + + THEN("path with trailing slash is split correctly") + { + auto [parent, filename] = kernel::vfs::path::split_into_path_and_filename("/a/b/c/"); + REQUIRE(parent == "/a/b"); + REQUIRE(filename == "c"); + } + + THEN("single component with trailing slash has root as parent") + { + auto [parent, filename] = kernel::vfs::path::split_into_path_and_filename("/a/"); + REQUIRE(parent == "/"); + REQUIRE(filename == "a"); + } + + THEN("relative path is split into relative parent and filename") + { + auto [parent, filename] = kernel::vfs::path::split_into_path_and_filename("a/b/c.txt"); + REQUIRE(parent == "a/b"); + REQUIRE(filename == "c.txt"); + } + + THEN("an empty path is split into empty parent and filename") + { + auto [parent, filename] = kernel::vfs::path::split_into_path_and_filename(""); + REQUIRE(parent == ""); + REQUIRE(filename == ""); + } + } +} \ No newline at end of file diff --git a/kernel/kernel/vfs/reserved_numbers.hpp b/kernel/kernel/vfs/reserved_numbers.hpp new file mode 100644 index 00000000..2632b4e6 --- /dev/null +++ b/kernel/kernel/vfs/reserved_numbers.hpp @@ -0,0 +1,36 @@ +#ifndef TEACHOS_KERNEL_VFS_RESERVED_NUMBERS_HPP +#define TEACHOS_KERNEL_VFS_RESERVED_NUMBERS_HPP + +#include + +//! Constants defining major numbers used in the VFS layer. +namespace kernel::vfs +{ + + namespace block_major_numbers + { + //! Reserved devices. + constexpr auto inline internal = std::uint8_t{0}; + + //! RAM disk devices. + constexpr auto inline ram_disk = std::uint8_t{1}; + } // namespace block_major_numbers + + namespace character_major_numbers + { + //! Memory devices. + constexpr auto inline memory = std::uint8_t{1}; + } // namespace character_major_numbers + + namespace character_minor_numbers + { + //! The 'null' device + constexpr auto inline null = std::uint8_t{3}; + + //! The 'zero' device + constexpr auto inline zero = std::uint8_t{5}; + } // namespace character_minor_numbers + +} // namespace kernel::vfs + +#endif -- cgit v1.2.3