From 3012fa5dfe5dfed5e83baf9b40934ed8e8317627 Mon Sep 17 00:00:00 2001 From: Felix Morgner Date: Fri, 3 Jul 2026 09:32:30 +0200 Subject: kernel: rearrange sources according to p1204 --- kernel/CMakeLists.txt | 138 ++--- kernel/include/kernel/acpi/manager.hpp | 32 -- kernel/include/kernel/devices/block_device.hpp | 97 ---- .../include/kernel/devices/block_device_utils.hpp | 44 -- kernel/include/kernel/devices/root_bus.hpp | 16 - .../include/kernel/devices/storage/controller.hpp | 71 --- .../include/kernel/devices/storage/management.hpp | 78 --- .../kernel/devices/storage/ram_disk/controller.hpp | 31 -- .../kernel/devices/storage/ram_disk/device.hpp | 58 --- kernel/include/kernel/filesystem/constants.hpp | 14 - kernel/include/kernel/filesystem/dentry.hpp | 104 ---- .../include/kernel/filesystem/devfs/filesystem.hpp | 46 -- kernel/include/kernel/filesystem/devfs/inode.hpp | 42 -- kernel/include/kernel/filesystem/device_inode.hpp | 64 --- .../filesystem/ext2/block_group_descriptor.hpp | 24 - .../include/kernel/filesystem/ext2/filesystem.hpp | 115 ----- kernel/include/kernel/filesystem/ext2/inode.hpp | 105 ---- .../filesystem/ext2/linked_directory_entry.hpp | 22 - .../include/kernel/filesystem/ext2/superblock.hpp | 74 --- kernel/include/kernel/filesystem/filesystem.hpp | 79 --- kernel/include/kernel/filesystem/inode.hpp | 69 --- kernel/include/kernel/filesystem/mount.hpp | 97 ---- kernel/include/kernel/filesystem/mount_table.hpp | 58 --- .../kernel/filesystem/open_file_descriptor.hpp | 68 --- .../include/kernel/filesystem/open_file_table.hpp | 66 --- kernel/include/kernel/filesystem/path.hpp | 71 --- .../kernel/filesystem/rootfs/filesystem.hpp | 41 -- kernel/include/kernel/filesystem/rootfs/inode.hpp | 45 -- kernel/include/kernel/filesystem/type.hpp | 42 -- kernel/include/kernel/filesystem/type_registry.hpp | 53 -- kernel/include/kernel/filesystem/vfs.hpp | 113 ---- kernel/include/kernel/memory.hpp | 19 - kernel/include/kernel/memory/bitmap_allocator.hpp | 56 -- .../include/kernel/memory/block_list_allocator.hpp | 90 ---- kernel/include/kernel/memory/heap_allocator.hpp | 29 -- kernel/include/kernel/memory/mmio_allocator.hpp | 41 -- .../include/kernel/test_support/boot_modules.hpp | 10 - .../kernel/test_support/bump_frame_allocator.hpp | 54 -- kernel/include/kernel/test_support/cio.hpp | 39 -- kernel/include/kernel/test_support/cpu.hpp | 23 - .../kernel/test_support/devices/block_device.hpp | 31 -- .../test_support/devices/character_device.hpp | 23 - .../test_support/devices/storage/management.hpp | 10 - .../kernel/test_support/filesystem/ext2.hpp | 21 - .../kernel/test_support/filesystem/filesystem.hpp | 22 - .../kernel/test_support/filesystem/inode.hpp | 19 - .../test_support/filesystem/open_file_table.hpp | 10 - .../filesystem/storage_boot_module_fixture.hpp | 48 -- .../filesystem/storage_boot_module_vfs_fixture.hpp | 23 - .../include/kernel/test_support/filesystem/vfs.hpp | 10 - kernel/include/kernel/test_support/log_buffer.hpp | 37 -- kernel/include/kernel/test_support/memory.hpp | 22 - kernel/include/kernel/test_support/page_mapper.hpp | 49 -- .../kernel/test_support/simulated_memory.hpp | 67 --- kernel/kernel/acpi/manager.cpp | 98 ++++ kernel/kernel/acpi/manager.hpp | 32 ++ kernel/kernel/devices/block_device.cpp | 42 ++ kernel/kernel/devices/block_device.hpp | 97 ++++ kernel/kernel/devices/block_device.tests.cpp | 46 ++ kernel/kernel/devices/block_device_utils.cpp | 106 ++++ kernel/kernel/devices/block_device_utils.hpp | 44 ++ kernel/kernel/devices/block_device_utils.tests.cpp | 219 ++++++++ kernel/kernel/devices/root_bus.cpp | 12 + kernel/kernel/devices/root_bus.hpp | 16 + kernel/kernel/devices/storage/controller.cpp | 44 ++ kernel/kernel/devices/storage/controller.hpp | 71 +++ kernel/kernel/devices/storage/management.cpp | 95 ++++ kernel/kernel/devices/storage/management.hpp | 78 +++ .../kernel/devices/storage/ram_disk/controller.cpp | 27 + .../kernel/devices/storage/ram_disk/controller.hpp | 31 ++ kernel/kernel/devices/storage/ram_disk/device.cpp | 71 +++ kernel/kernel/devices/storage/ram_disk/device.hpp | 58 +++ .../devices/storage/ram_disk/device.tests.cpp | 116 +++++ kernel/kernel/filesystem/constants.hpp | 14 + kernel/kernel/filesystem/dentry.cpp | 95 ++++ kernel/kernel/filesystem/dentry.hpp | 104 ++++ kernel/kernel/filesystem/dentry.tests.cpp | 150 ++++++ kernel/kernel/filesystem/devfs/filesystem.cpp | 81 +++ kernel/kernel/filesystem/devfs/filesystem.hpp | 46 ++ .../kernel/filesystem/devfs/filesystem.tests.cpp | 72 +++ kernel/kernel/filesystem/devfs/inode.cpp | 21 + kernel/kernel/filesystem/devfs/inode.hpp | 42 ++ kernel/kernel/filesystem/devfs/inode.tests.cpp | 55 ++ kernel/kernel/filesystem/device_inode.cpp | 57 +++ kernel/kernel/filesystem/device_inode.hpp | 64 +++ kernel/kernel/filesystem/device_inode.tests.cpp | 109 ++++ .../filesystem/ext2/block_group_descriptor.hpp | 24 + kernel/kernel/filesystem/ext2/filesystem.cpp | 247 +++++++++ kernel/kernel/filesystem/ext2/filesystem.hpp | 115 +++++ kernel/kernel/filesystem/ext2/filesystem.tests.cpp | 139 +++++ kernel/kernel/filesystem/ext2/inode.cpp | 111 ++++ kernel/kernel/filesystem/ext2/inode.hpp | 105 ++++ kernel/kernel/filesystem/ext2/inode.tests.cpp | 376 ++++++++++++++ .../filesystem/ext2/linked_directory_entry.hpp | 22 + kernel/kernel/filesystem/ext2/superblock.hpp | 74 +++ kernel/kernel/filesystem/filesystem.cpp | 60 +++ kernel/kernel/filesystem/filesystem.hpp | 79 +++ kernel/kernel/filesystem/inode.cpp | 24 + kernel/kernel/filesystem/inode.hpp | 69 +++ kernel/kernel/filesystem/mount.cpp | 90 ++++ kernel/kernel/filesystem/mount.hpp | 97 ++++ kernel/kernel/filesystem/mount.tests.cpp | 94 ++++ kernel/kernel/filesystem/mount_table.cpp | 79 +++ kernel/kernel/filesystem/mount_table.hpp | 58 +++ kernel/kernel/filesystem/mount_table.tests.cpp | 184 +++++++ kernel/kernel/filesystem/open_file_descriptor.cpp | 46 ++ kernel/kernel/filesystem/open_file_descriptor.hpp | 68 +++ .../filesystem/open_file_descriptor.tests.cpp | 113 ++++ kernel/kernel/filesystem/open_file_table.cpp | 87 ++++ kernel/kernel/filesystem/open_file_table.hpp | 66 +++ kernel/kernel/filesystem/open_file_table.tests.cpp | 106 ++++ kernel/kernel/filesystem/path.hpp | 71 +++ kernel/kernel/filesystem/path.tests.cpp | 69 +++ kernel/kernel/filesystem/rootfs/filesystem.cpp | 47 ++ kernel/kernel/filesystem/rootfs/filesystem.hpp | 41 ++ .../kernel/filesystem/rootfs/filesystem.tests.cpp | 38 ++ kernel/kernel/filesystem/rootfs/inode.cpp | 23 + kernel/kernel/filesystem/rootfs/inode.hpp | 45 ++ kernel/kernel/filesystem/rootfs/inode.tests.cpp | 37 ++ kernel/kernel/filesystem/type.hpp | 42 ++ kernel/kernel/filesystem/type_registry.cpp | 76 +++ kernel/kernel/filesystem/type_registry.hpp | 53 ++ kernel/kernel/filesystem/type_registry.tests.cpp | 77 +++ kernel/kernel/filesystem/vfs.cpp | 299 +++++++++++ kernel/kernel/filesystem/vfs.hpp | 113 ++++ kernel/kernel/filesystem/vfs.tests.cpp | 568 +++++++++++++++++++++ kernel/kernel/main.cpp | 152 ++++++ kernel/kernel/memory.cpp | 61 +++ kernel/kernel/memory.hpp | 19 + kernel/kernel/memory/bitmap_allocator.cpp | 106 ++++ kernel/kernel/memory/bitmap_allocator.hpp | 56 ++ kernel/kernel/memory/bitmap_allocator.tests.cpp | 288 +++++++++++ kernel/kernel/memory/block_list_allocator.cpp | 215 ++++++++ kernel/kernel/memory/block_list_allocator.hpp | 90 ++++ .../kernel/memory/block_list_allocator.tests.cpp | 85 +++ kernel/kernel/memory/heap_allocator.hpp | 29 ++ kernel/kernel/memory/mmio_allocator.cpp | 111 ++++ kernel/kernel/memory/mmio_allocator.hpp | 41 ++ kernel/kernel/memory/operators.cpp | 102 ++++ kernel/kernel/test_support/boot_modules.hpp | 10 + .../kernel/test_support/bump_frame_allocator.hpp | 54 ++ kernel/kernel/test_support/cio.hpp | 39 ++ kernel/kernel/test_support/cpu.hpp | 23 + .../kernel/test_support/devices/block_device.cpp | 61 +++ .../kernel/test_support/devices/block_device.hpp | 31 ++ .../test_support/devices/character_device.cpp | 19 + .../test_support/devices/character_device.hpp | 23 + .../test_support/devices/storage/management.hpp | 10 + kernel/kernel/test_support/filesystem/ext2.cpp | 68 +++ kernel/kernel/test_support/filesystem/ext2.hpp | 21 + .../kernel/test_support/filesystem/filesystem.cpp | 17 + .../kernel/test_support/filesystem/filesystem.hpp | 22 + kernel/kernel/test_support/filesystem/inode.cpp | 23 + kernel/kernel/test_support/filesystem/inode.hpp | 19 + .../test_support/filesystem/open_file_table.hpp | 10 + .../filesystem/storage_boot_module_fixture.cpp | 139 +++++ .../filesystem/storage_boot_module_fixture.hpp | 48 ++ .../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_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/kapi/cio.cpp | 54 ++ kernel/kernel/test_support/kapi/cpu.cpp | 52 ++ kernel/kernel/test_support/kapi/interrupts.cpp | 11 + kernel/kernel/test_support/kapi/memory.cpp | 74 +++ kernel/kernel/test_support/log_buffer.cpp | 33 ++ kernel/kernel/test_support/log_buffer.hpp | 37 ++ kernel/kernel/test_support/memory.hpp | 22 + kernel/kernel/test_support/output_device.cpp | 28 + kernel/kernel/test_support/page_mapper.cpp | 70 +++ kernel/kernel/test_support/page_mapper.hpp | 49 ++ kernel/kernel/test_support/simulated_memory.cpp | 106 ++++ kernel/kernel/test_support/simulated_memory.hpp | 67 +++ .../kernel/test_support/state_reset_listener.cpp | 48 ++ kernel/src/acpi/manager.cpp | 98 ---- kernel/src/devices/block_device.cpp | 42 -- kernel/src/devices/block_device.tests.cpp | 46 -- kernel/src/devices/block_device_utils.cpp | 106 ---- kernel/src/devices/block_device_utils.tests.cpp | 219 -------- kernel/src/devices/root_bus.cpp | 12 - kernel/src/devices/storage/controller.cpp | 44 -- kernel/src/devices/storage/management.cpp | 95 ---- kernel/src/devices/storage/ram_disk/controller.cpp | 27 - kernel/src/devices/storage/ram_disk/device.cpp | 71 --- .../src/devices/storage/ram_disk/device.tests.cpp | 116 ----- kernel/src/filesystem/dentry.cpp | 95 ---- kernel/src/filesystem/dentry.tests.cpp | 150 ------ kernel/src/filesystem/devfs/filesystem.cpp | 81 --- kernel/src/filesystem/devfs/filesystem.tests.cpp | 72 --- kernel/src/filesystem/devfs/inode.cpp | 21 - kernel/src/filesystem/devfs/inode.tests.cpp | 55 -- kernel/src/filesystem/device_inode.cpp | 57 --- kernel/src/filesystem/device_inode.tests.cpp | 109 ---- kernel/src/filesystem/ext2/filesystem.cpp | 247 --------- kernel/src/filesystem/ext2/filesystem.tests.cpp | 139 ----- kernel/src/filesystem/ext2/inode.cpp | 111 ---- kernel/src/filesystem/ext2/inode.tests.cpp | 376 -------------- kernel/src/filesystem/filesystem.cpp | 60 --- kernel/src/filesystem/inode.cpp | 24 - kernel/src/filesystem/mount.cpp | 90 ---- kernel/src/filesystem/mount.tests.cpp | 94 ---- kernel/src/filesystem/mount_table.cpp | 79 --- kernel/src/filesystem/mount_table.tests.cpp | 184 ------- kernel/src/filesystem/open_file_descriptor.cpp | 46 -- .../src/filesystem/open_file_descriptor.tests.cpp | 113 ---- kernel/src/filesystem/open_file_table.cpp | 87 ---- kernel/src/filesystem/open_file_table.tests.cpp | 106 ---- kernel/src/filesystem/path.tests.cpp | 69 --- kernel/src/filesystem/rootfs/filesystem.cpp | 47 -- kernel/src/filesystem/rootfs/filesystem.tests.cpp | 38 -- kernel/src/filesystem/rootfs/inode.cpp | 23 - kernel/src/filesystem/rootfs/inode.tests.cpp | 37 -- kernel/src/filesystem/type_registry.cpp | 76 --- kernel/src/filesystem/type_registry.tests.cpp | 77 --- kernel/src/filesystem/vfs.cpp | 299 ----------- kernel/src/filesystem/vfs.tests.cpp | 568 --------------------- kernel/src/main.cpp | 152 ------ kernel/src/memory.cpp | 61 --- kernel/src/memory/bitmap_allocator.cpp | 106 ---- kernel/src/memory/bitmap_allocator.tests.cpp | 288 ----------- kernel/src/memory/block_list_allocator.cpp | 215 -------- kernel/src/memory/block_list_allocator.tests.cpp | 85 --- kernel/src/memory/mmio_allocator.cpp | 111 ---- kernel/src/memory/operators.cpp | 102 ---- kernel/src/test_support/devices/block_device.cpp | 61 --- .../src/test_support/devices/character_device.cpp | 19 - kernel/src/test_support/filesystem/ext2.cpp | 68 --- kernel/src/test_support/filesystem/filesystem.cpp | 17 - kernel/src/test_support/filesystem/inode.cpp | 23 - .../filesystem/storage_boot_module_fixture.cpp | 139 ----- .../filesystem/storage_boot_module_vfs_fixture.cpp | 31 -- .../test_support/filesystem/test_assets/README.md | 1 - .../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/src/test_support/kapi/cio.cpp | 54 -- kernel/src/test_support/kapi/cpu.cpp | 52 -- kernel/src/test_support/kapi/interrupts.cpp | 11 - kernel/src/test_support/kapi/memory.cpp | 74 --- kernel/src/test_support/log_buffer.cpp | 33 -- kernel/src/test_support/output_device.cpp | 28 - kernel/src/test_support/page_mapper.cpp | 70 --- kernel/src/test_support/simulated_memory.cpp | 106 ---- kernel/src/test_support/state_reset_listener.cpp | 48 -- 247 files changed, 9131 insertions(+), 9131 deletions(-) delete mode 100644 kernel/include/kernel/acpi/manager.hpp delete mode 100644 kernel/include/kernel/devices/block_device.hpp delete mode 100644 kernel/include/kernel/devices/block_device_utils.hpp delete mode 100644 kernel/include/kernel/devices/root_bus.hpp delete mode 100644 kernel/include/kernel/devices/storage/controller.hpp delete mode 100644 kernel/include/kernel/devices/storage/management.hpp delete mode 100644 kernel/include/kernel/devices/storage/ram_disk/controller.hpp delete mode 100644 kernel/include/kernel/devices/storage/ram_disk/device.hpp delete mode 100644 kernel/include/kernel/filesystem/constants.hpp delete mode 100644 kernel/include/kernel/filesystem/dentry.hpp delete mode 100644 kernel/include/kernel/filesystem/devfs/filesystem.hpp delete mode 100644 kernel/include/kernel/filesystem/devfs/inode.hpp delete mode 100644 kernel/include/kernel/filesystem/device_inode.hpp delete mode 100644 kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp delete mode 100644 kernel/include/kernel/filesystem/ext2/filesystem.hpp delete mode 100644 kernel/include/kernel/filesystem/ext2/inode.hpp delete mode 100644 kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp delete mode 100644 kernel/include/kernel/filesystem/ext2/superblock.hpp delete mode 100644 kernel/include/kernel/filesystem/filesystem.hpp delete mode 100644 kernel/include/kernel/filesystem/inode.hpp delete mode 100644 kernel/include/kernel/filesystem/mount.hpp delete mode 100644 kernel/include/kernel/filesystem/mount_table.hpp delete mode 100644 kernel/include/kernel/filesystem/open_file_descriptor.hpp delete mode 100644 kernel/include/kernel/filesystem/open_file_table.hpp delete mode 100644 kernel/include/kernel/filesystem/path.hpp delete mode 100644 kernel/include/kernel/filesystem/rootfs/filesystem.hpp delete mode 100644 kernel/include/kernel/filesystem/rootfs/inode.hpp delete mode 100644 kernel/include/kernel/filesystem/type.hpp delete mode 100644 kernel/include/kernel/filesystem/type_registry.hpp delete mode 100644 kernel/include/kernel/filesystem/vfs.hpp delete mode 100644 kernel/include/kernel/memory.hpp delete mode 100644 kernel/include/kernel/memory/bitmap_allocator.hpp delete mode 100644 kernel/include/kernel/memory/block_list_allocator.hpp delete mode 100644 kernel/include/kernel/memory/heap_allocator.hpp delete mode 100644 kernel/include/kernel/memory/mmio_allocator.hpp delete mode 100644 kernel/include/kernel/test_support/boot_modules.hpp delete mode 100644 kernel/include/kernel/test_support/bump_frame_allocator.hpp delete mode 100644 kernel/include/kernel/test_support/cio.hpp delete mode 100644 kernel/include/kernel/test_support/cpu.hpp delete mode 100644 kernel/include/kernel/test_support/devices/block_device.hpp delete mode 100644 kernel/include/kernel/test_support/devices/character_device.hpp delete mode 100644 kernel/include/kernel/test_support/devices/storage/management.hpp delete mode 100644 kernel/include/kernel/test_support/filesystem/ext2.hpp delete mode 100644 kernel/include/kernel/test_support/filesystem/filesystem.hpp delete mode 100644 kernel/include/kernel/test_support/filesystem/inode.hpp delete mode 100644 kernel/include/kernel/test_support/filesystem/open_file_table.hpp delete mode 100644 kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp delete mode 100644 kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp delete mode 100644 kernel/include/kernel/test_support/filesystem/vfs.hpp delete mode 100644 kernel/include/kernel/test_support/log_buffer.hpp delete mode 100644 kernel/include/kernel/test_support/memory.hpp delete mode 100644 kernel/include/kernel/test_support/page_mapper.hpp delete mode 100644 kernel/include/kernel/test_support/simulated_memory.hpp create mode 100644 kernel/kernel/acpi/manager.cpp create mode 100644 kernel/kernel/acpi/manager.hpp create mode 100644 kernel/kernel/devices/block_device.cpp create mode 100644 kernel/kernel/devices/block_device.hpp create mode 100644 kernel/kernel/devices/block_device.tests.cpp create mode 100644 kernel/kernel/devices/block_device_utils.cpp create mode 100644 kernel/kernel/devices/block_device_utils.hpp create mode 100644 kernel/kernel/devices/block_device_utils.tests.cpp create mode 100644 kernel/kernel/devices/root_bus.cpp create mode 100644 kernel/kernel/devices/root_bus.hpp create mode 100644 kernel/kernel/devices/storage/controller.cpp create mode 100644 kernel/kernel/devices/storage/controller.hpp create mode 100644 kernel/kernel/devices/storage/management.cpp create mode 100644 kernel/kernel/devices/storage/management.hpp create mode 100644 kernel/kernel/devices/storage/ram_disk/controller.cpp create mode 100644 kernel/kernel/devices/storage/ram_disk/controller.hpp create mode 100644 kernel/kernel/devices/storage/ram_disk/device.cpp create mode 100644 kernel/kernel/devices/storage/ram_disk/device.hpp create mode 100644 kernel/kernel/devices/storage/ram_disk/device.tests.cpp create mode 100644 kernel/kernel/filesystem/constants.hpp create mode 100644 kernel/kernel/filesystem/dentry.cpp create mode 100644 kernel/kernel/filesystem/dentry.hpp create mode 100644 kernel/kernel/filesystem/dentry.tests.cpp create mode 100644 kernel/kernel/filesystem/devfs/filesystem.cpp create mode 100644 kernel/kernel/filesystem/devfs/filesystem.hpp create mode 100644 kernel/kernel/filesystem/devfs/filesystem.tests.cpp create mode 100644 kernel/kernel/filesystem/devfs/inode.cpp create mode 100644 kernel/kernel/filesystem/devfs/inode.hpp create mode 100644 kernel/kernel/filesystem/devfs/inode.tests.cpp create mode 100644 kernel/kernel/filesystem/device_inode.cpp create mode 100644 kernel/kernel/filesystem/device_inode.hpp create mode 100644 kernel/kernel/filesystem/device_inode.tests.cpp create mode 100644 kernel/kernel/filesystem/ext2/block_group_descriptor.hpp create mode 100644 kernel/kernel/filesystem/ext2/filesystem.cpp create mode 100644 kernel/kernel/filesystem/ext2/filesystem.hpp create mode 100644 kernel/kernel/filesystem/ext2/filesystem.tests.cpp create mode 100644 kernel/kernel/filesystem/ext2/inode.cpp create mode 100644 kernel/kernel/filesystem/ext2/inode.hpp create mode 100644 kernel/kernel/filesystem/ext2/inode.tests.cpp create mode 100644 kernel/kernel/filesystem/ext2/linked_directory_entry.hpp create mode 100644 kernel/kernel/filesystem/ext2/superblock.hpp create mode 100644 kernel/kernel/filesystem/filesystem.cpp create mode 100644 kernel/kernel/filesystem/filesystem.hpp create mode 100644 kernel/kernel/filesystem/inode.cpp create mode 100644 kernel/kernel/filesystem/inode.hpp create mode 100644 kernel/kernel/filesystem/mount.cpp create mode 100644 kernel/kernel/filesystem/mount.hpp create mode 100644 kernel/kernel/filesystem/mount.tests.cpp create mode 100644 kernel/kernel/filesystem/mount_table.cpp create mode 100644 kernel/kernel/filesystem/mount_table.hpp create mode 100644 kernel/kernel/filesystem/mount_table.tests.cpp create mode 100644 kernel/kernel/filesystem/open_file_descriptor.cpp create mode 100644 kernel/kernel/filesystem/open_file_descriptor.hpp create mode 100644 kernel/kernel/filesystem/open_file_descriptor.tests.cpp create mode 100644 kernel/kernel/filesystem/open_file_table.cpp create mode 100644 kernel/kernel/filesystem/open_file_table.hpp create mode 100644 kernel/kernel/filesystem/open_file_table.tests.cpp create mode 100644 kernel/kernel/filesystem/path.hpp create mode 100644 kernel/kernel/filesystem/path.tests.cpp create mode 100644 kernel/kernel/filesystem/rootfs/filesystem.cpp create mode 100644 kernel/kernel/filesystem/rootfs/filesystem.hpp create mode 100644 kernel/kernel/filesystem/rootfs/filesystem.tests.cpp create mode 100644 kernel/kernel/filesystem/rootfs/inode.cpp create mode 100644 kernel/kernel/filesystem/rootfs/inode.hpp create mode 100644 kernel/kernel/filesystem/rootfs/inode.tests.cpp create mode 100644 kernel/kernel/filesystem/type.hpp create mode 100644 kernel/kernel/filesystem/type_registry.cpp create mode 100644 kernel/kernel/filesystem/type_registry.hpp create mode 100644 kernel/kernel/filesystem/type_registry.tests.cpp create mode 100644 kernel/kernel/filesystem/vfs.cpp create mode 100644 kernel/kernel/filesystem/vfs.hpp create mode 100644 kernel/kernel/filesystem/vfs.tests.cpp create mode 100644 kernel/kernel/main.cpp create mode 100644 kernel/kernel/memory.cpp create mode 100644 kernel/kernel/memory.hpp create mode 100644 kernel/kernel/memory/bitmap_allocator.cpp create mode 100644 kernel/kernel/memory/bitmap_allocator.hpp create mode 100644 kernel/kernel/memory/bitmap_allocator.tests.cpp create mode 100644 kernel/kernel/memory/block_list_allocator.cpp create mode 100644 kernel/kernel/memory/block_list_allocator.hpp create mode 100644 kernel/kernel/memory/block_list_allocator.tests.cpp create mode 100644 kernel/kernel/memory/heap_allocator.hpp create mode 100644 kernel/kernel/memory/mmio_allocator.cpp create mode 100644 kernel/kernel/memory/mmio_allocator.hpp create mode 100644 kernel/kernel/memory/operators.cpp create mode 100644 kernel/kernel/test_support/boot_modules.hpp create mode 100644 kernel/kernel/test_support/bump_frame_allocator.hpp create mode 100644 kernel/kernel/test_support/cio.hpp create mode 100644 kernel/kernel/test_support/cpu.hpp create mode 100644 kernel/kernel/test_support/devices/block_device.cpp create mode 100644 kernel/kernel/test_support/devices/block_device.hpp create mode 100644 kernel/kernel/test_support/devices/character_device.cpp create mode 100644 kernel/kernel/test_support/devices/character_device.hpp create mode 100644 kernel/kernel/test_support/devices/storage/management.hpp create mode 100644 kernel/kernel/test_support/filesystem/ext2.cpp create mode 100644 kernel/kernel/test_support/filesystem/ext2.hpp create mode 100644 kernel/kernel/test_support/filesystem/filesystem.cpp create mode 100644 kernel/kernel/test_support/filesystem/filesystem.hpp create mode 100644 kernel/kernel/test_support/filesystem/inode.cpp create mode 100644 kernel/kernel/test_support/filesystem/inode.hpp create mode 100644 kernel/kernel/test_support/filesystem/open_file_table.hpp create mode 100644 kernel/kernel/test_support/filesystem/storage_boot_module_fixture.cpp create mode 100644 kernel/kernel/test_support/filesystem/storage_boot_module_fixture.hpp create mode 100644 kernel/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.cpp create mode 100644 kernel/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp create mode 120000 kernel/kernel/test_support/filesystem/test_assets/README.md create mode 100644 kernel/kernel/test_support/filesystem/test_assets/ext2_1KB_fs.img create mode 100644 kernel/kernel/test_support/filesystem/test_assets/ext2_2KB_fs.img create mode 100644 kernel/kernel/test_support/filesystem/test_assets/ext2_4KB_fs.img create mode 100644 kernel/kernel/test_support/filesystem/vfs.hpp create mode 100644 kernel/kernel/test_support/kapi/cio.cpp create mode 100644 kernel/kernel/test_support/kapi/cpu.cpp create mode 100644 kernel/kernel/test_support/kapi/interrupts.cpp create mode 100644 kernel/kernel/test_support/kapi/memory.cpp create mode 100644 kernel/kernel/test_support/log_buffer.cpp create mode 100644 kernel/kernel/test_support/log_buffer.hpp create mode 100644 kernel/kernel/test_support/memory.hpp create mode 100644 kernel/kernel/test_support/output_device.cpp create mode 100644 kernel/kernel/test_support/page_mapper.cpp create mode 100644 kernel/kernel/test_support/page_mapper.hpp create mode 100644 kernel/kernel/test_support/simulated_memory.cpp create mode 100644 kernel/kernel/test_support/simulated_memory.hpp create mode 100644 kernel/kernel/test_support/state_reset_listener.cpp delete mode 100644 kernel/src/acpi/manager.cpp delete mode 100644 kernel/src/devices/block_device.cpp delete mode 100644 kernel/src/devices/block_device.tests.cpp delete mode 100644 kernel/src/devices/block_device_utils.cpp delete mode 100644 kernel/src/devices/block_device_utils.tests.cpp delete mode 100644 kernel/src/devices/root_bus.cpp delete mode 100644 kernel/src/devices/storage/controller.cpp delete mode 100644 kernel/src/devices/storage/management.cpp delete mode 100644 kernel/src/devices/storage/ram_disk/controller.cpp delete mode 100644 kernel/src/devices/storage/ram_disk/device.cpp delete mode 100644 kernel/src/devices/storage/ram_disk/device.tests.cpp delete mode 100644 kernel/src/filesystem/dentry.cpp delete mode 100644 kernel/src/filesystem/dentry.tests.cpp delete mode 100644 kernel/src/filesystem/devfs/filesystem.cpp delete mode 100644 kernel/src/filesystem/devfs/filesystem.tests.cpp delete mode 100644 kernel/src/filesystem/devfs/inode.cpp delete mode 100644 kernel/src/filesystem/devfs/inode.tests.cpp delete mode 100644 kernel/src/filesystem/device_inode.cpp delete mode 100644 kernel/src/filesystem/device_inode.tests.cpp delete mode 100644 kernel/src/filesystem/ext2/filesystem.cpp delete mode 100644 kernel/src/filesystem/ext2/filesystem.tests.cpp delete mode 100644 kernel/src/filesystem/ext2/inode.cpp delete mode 100644 kernel/src/filesystem/ext2/inode.tests.cpp delete mode 100644 kernel/src/filesystem/filesystem.cpp delete mode 100644 kernel/src/filesystem/inode.cpp delete mode 100644 kernel/src/filesystem/mount.cpp delete mode 100644 kernel/src/filesystem/mount.tests.cpp delete mode 100644 kernel/src/filesystem/mount_table.cpp delete mode 100644 kernel/src/filesystem/mount_table.tests.cpp delete mode 100644 kernel/src/filesystem/open_file_descriptor.cpp delete mode 100644 kernel/src/filesystem/open_file_descriptor.tests.cpp delete mode 100644 kernel/src/filesystem/open_file_table.cpp delete mode 100644 kernel/src/filesystem/open_file_table.tests.cpp delete mode 100644 kernel/src/filesystem/path.tests.cpp delete mode 100644 kernel/src/filesystem/rootfs/filesystem.cpp delete mode 100644 kernel/src/filesystem/rootfs/filesystem.tests.cpp delete mode 100644 kernel/src/filesystem/rootfs/inode.cpp delete mode 100644 kernel/src/filesystem/rootfs/inode.tests.cpp delete mode 100644 kernel/src/filesystem/type_registry.cpp delete mode 100644 kernel/src/filesystem/type_registry.tests.cpp delete mode 100644 kernel/src/filesystem/vfs.cpp delete mode 100644 kernel/src/filesystem/vfs.tests.cpp delete mode 100644 kernel/src/main.cpp delete mode 100644 kernel/src/memory.cpp delete mode 100644 kernel/src/memory/bitmap_allocator.cpp delete mode 100644 kernel/src/memory/bitmap_allocator.tests.cpp delete mode 100644 kernel/src/memory/block_list_allocator.cpp delete mode 100644 kernel/src/memory/block_list_allocator.tests.cpp delete mode 100644 kernel/src/memory/mmio_allocator.cpp delete mode 100644 kernel/src/memory/operators.cpp delete mode 100644 kernel/src/test_support/devices/block_device.cpp delete mode 100644 kernel/src/test_support/devices/character_device.cpp delete mode 100644 kernel/src/test_support/filesystem/ext2.cpp delete mode 100644 kernel/src/test_support/filesystem/filesystem.cpp delete mode 100644 kernel/src/test_support/filesystem/inode.cpp delete mode 100644 kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp delete mode 100644 kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp delete mode 120000 kernel/src/test_support/filesystem/test_assets/README.md delete mode 100644 kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img delete mode 100644 kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img delete mode 100644 kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img delete mode 100644 kernel/src/test_support/kapi/cio.cpp delete mode 100644 kernel/src/test_support/kapi/cpu.cpp delete mode 100644 kernel/src/test_support/kapi/interrupts.cpp delete mode 100644 kernel/src/test_support/kapi/memory.cpp delete mode 100644 kernel/src/test_support/log_buffer.cpp delete mode 100644 kernel/src/test_support/output_device.cpp delete mode 100644 kernel/src/test_support/page_mapper.cpp delete mode 100644 kernel/src/test_support/simulated_memory.cpp delete mode 100644 kernel/src/test_support/state_reset_listener.cpp (limited to 'kernel') diff --git a/kernel/CMakeLists.txt b/kernel/CMakeLists.txt index b12f9f27..087ffcdf 100644 --- a/kernel/CMakeLists.txt +++ b/kernel/CMakeLists.txt @@ -25,65 +25,65 @@ target_sources("kernel_lib" PRIVATE "kstd/print.cpp" # ACPI Subsystem - "src/acpi/manager.cpp" + "kernel/acpi/manager.cpp" # Memory Subsystem - "src/memory/bitmap_allocator.cpp" - "src/memory/block_list_allocator.cpp" - "src/memory/mmio_allocator.cpp" - "src/memory.cpp" + "kernel/memory/bitmap_allocator.cpp" + "kernel/memory/block_list_allocator.cpp" + "kernel/memory/mmio_allocator.cpp" + "kernel/memory.cpp" # Device Subsystem - "src/devices/block_device.cpp" - "src/devices/block_device_utils.cpp" - "src/devices/root_bus.cpp" + "kernel/devices/block_device.cpp" + "kernel/devices/block_device_utils.cpp" + "kernel/devices/root_bus.cpp" # Storage Device Subsystem - "src/devices/storage/controller.cpp" - "src/devices/storage/management.cpp" - "src/devices/storage/ram_disk/controller.cpp" - "src/devices/storage/ram_disk/device.cpp" + "kernel/devices/storage/controller.cpp" + "kernel/devices/storage/management.cpp" + "kernel/devices/storage/ram_disk/controller.cpp" + "kernel/devices/storage/ram_disk/device.cpp" # Filesystem Subsystem - "src/filesystem/dentry.cpp" - "src/filesystem/device_inode.cpp" - "src/filesystem/filesystem.cpp" - "src/filesystem/inode.cpp" - "src/filesystem/mount_table.cpp" - "src/filesystem/mount.cpp" - "src/filesystem/open_file_descriptor.cpp" - "src/filesystem/open_file_table.cpp" - "src/filesystem/type_registry.cpp" - "src/filesystem/vfs.cpp" + "kernel/filesystem/dentry.cpp" + "kernel/filesystem/device_inode.cpp" + "kernel/filesystem/filesystem.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/type_registry.cpp" + "kernel/filesystem/vfs.cpp" # DevFS Filesystem - "src/filesystem/devfs/filesystem.cpp" - "src/filesystem/devfs/inode.cpp" + "kernel/filesystem/devfs/filesystem.cpp" + "kernel/filesystem/devfs/inode.cpp" # ext2 Filesystem - "src/filesystem/ext2/filesystem.cpp" - "src/filesystem/ext2/inode.cpp" + "kernel/filesystem/ext2/filesystem.cpp" + "kernel/filesystem/ext2/inode.cpp" # Rootfs Filesystem - "src/filesystem/rootfs/filesystem.cpp" - "src/filesystem/rootfs/inode.cpp" + "kernel/filesystem/rootfs/filesystem.cpp" + "kernel/filesystem/rootfs/inode.cpp" ) file(GLOB_RECURSE KERNEL_HEADERS RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} CONFIGURE_DEPENDS - "include/**.hpp" + "kernel/**.hpp" ) target_sources("kernel_lib" PUBLIC FILE_SET HEADERS - BASE_DIRS "include" + BASE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}" FILES ${KERNEL_HEADERS} ) target_include_directories("kernel_lib" PUBLIC - "include" + "${CMAKE_CURRENT_SOURCE_DIR}" ) target_link_libraries("kernel_lib" PUBLIC @@ -105,8 +105,8 @@ if(NOT BUILD_TESTING) add_executable(kernel::exe ALIAS "kernel") target_sources("kernel" PRIVATE - "src/main.cpp" - "src/memory/operators.cpp" + "kernel/main.cpp" + "kernel/memory/operators.cpp" ) target_link_libraries("kernel" PRIVATE @@ -148,32 +148,32 @@ if(BUILD_TESTING) target_sources("kernel_tests" PRIVATE # Platform-defined KAPI - "src/test_support/kapi/cpu.cpp" - "src/test_support/kapi/cio.cpp" - "src/test_support/kapi/interrupts.cpp" - "src/test_support/kapi/memory.cpp" + "kernel/test_support/kapi/cpu.cpp" + "kernel/test_support/kapi/cio.cpp" + "kernel/test_support/kapi/interrupts.cpp" + "kernel/test_support/kapi/memory.cpp" # Device Subsystem Support - "src/test_support/devices/block_device.cpp" - "src/test_support/devices/character_device.cpp" + "kernel/test_support/devices/block_device.cpp" + "kernel/test_support/devices/character_device.cpp" # Filesystem Subsystem Support - "src/test_support/filesystem/inode.cpp" - "src/test_support/filesystem/filesystem.cpp" - "src/test_support/filesystem/ext2.cpp" - "src/test_support/filesystem/storage_boot_module_fixture.cpp" - "src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp" + "kernel/test_support/filesystem/inode.cpp" + "kernel/test_support/filesystem/filesystem.cpp" + "kernel/test_support/filesystem/ext2.cpp" + "kernel/test_support/filesystem/storage_boot_module_fixture.cpp" + "kernel/test_support/filesystem/storage_boot_module_vfs_fixture.cpp" # I/O Support - "src/test_support/log_buffer.cpp" - "src/test_support/output_device.cpp" + "kernel/test_support/log_buffer.cpp" + "kernel/test_support/output_device.cpp" # Memory Support - "src/test_support/page_mapper.cpp" - "src/test_support/simulated_memory.cpp" + "kernel/test_support/page_mapper.cpp" + "kernel/test_support/simulated_memory.cpp" # Support System Listener - "src/test_support/state_reset_listener.cpp" + "kernel/test_support/state_reset_listener.cpp" # KAPI Shim Tests "kapi/cpu.tests.cpp" @@ -184,30 +184,30 @@ if(BUILD_TESTING) "kstd/print.tests.cpp" # Memory Subsystem Tests - "src/memory/bitmap_allocator.tests.cpp" - "src/memory/block_list_allocator.tests.cpp" + "kernel/memory/bitmap_allocator.tests.cpp" + "kernel/memory/block_list_allocator.tests.cpp" # Filesystem Subsystem Tests - "src/filesystem/devfs/filesystem.tests.cpp" - "src/filesystem/devfs/inode.tests.cpp" - "src/filesystem/ext2/filesystem.tests.cpp" - "src/filesystem/ext2/inode.tests.cpp" - "src/filesystem/path.tests.cpp" - "src/filesystem/rootfs/filesystem.tests.cpp" - "src/filesystem/rootfs/inode.tests.cpp" - "src/filesystem/dentry.tests.cpp" - "src/filesystem/device_inode.tests.cpp" - "src/filesystem/mount_table.tests.cpp" - "src/filesystem/mount.tests.cpp" - "src/filesystem/open_file_descriptor.tests.cpp" - "src/filesystem/open_file_table.tests.cpp" - "src/filesystem/type_registry.tests.cpp" - "src/filesystem/vfs.tests.cpp" + "kernel/filesystem/devfs/filesystem.tests.cpp" + "kernel/filesystem/devfs/inode.tests.cpp" + "kernel/filesystem/ext2/filesystem.tests.cpp" + "kernel/filesystem/ext2/inode.tests.cpp" + "kernel/filesystem/path.tests.cpp" + "kernel/filesystem/rootfs/filesystem.tests.cpp" + "kernel/filesystem/rootfs/inode.tests.cpp" + "kernel/filesystem/dentry.tests.cpp" + "kernel/filesystem/device_inode.tests.cpp" + "kernel/filesystem/mount_table.tests.cpp" + "kernel/filesystem/mount.tests.cpp" + "kernel/filesystem/open_file_descriptor.tests.cpp" + "kernel/filesystem/open_file_table.tests.cpp" + "kernel/filesystem/type_registry.tests.cpp" + "kernel/filesystem/vfs.tests.cpp" # Storage Subsystem Tests - "src/devices/block_device_utils.tests.cpp" - "src/devices/block_device.tests.cpp" - "src/devices/storage/ram_disk/device.tests.cpp" + "kernel/devices/block_device_utils.tests.cpp" + "kernel/devices/block_device.tests.cpp" + "kernel/devices/storage/ram_disk/device.tests.cpp" ) target_link_libraries("kernel_tests" PRIVATE diff --git a/kernel/include/kernel/acpi/manager.hpp b/kernel/include/kernel/acpi/manager.hpp deleted file mode 100644 index 1e8c1e85..00000000 --- a/kernel/include/kernel/acpi/manager.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef TEACHOS_KERNEL_ACPI_MANAGER_HPP -#define TEACHOS_KERNEL_ACPI_MANAGER_HPP - -#include - -#include -#include -#include - -#include - -namespace kernel::acpi -{ - - struct manager - { - explicit manager(::acpi::rsdp const & sdp); - - auto load_tables() -> bool; - - auto get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::table_header const>; - - private: - ::acpi::rsdp const * m_sdp{}; - ::acpi::table_header const * m_rsdt{}; - kstd::flat_map m_tables{}; - bool m_extended{}; - }; - -} // namespace kernel::acpi - -#endif diff --git a/kernel/include/kernel/devices/block_device.hpp b/kernel/include/kernel/devices/block_device.hpp deleted file mode 100644 index a6d68ee2..00000000 --- a/kernel/include/kernel/devices/block_device.hpp +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef TEACH_OS_KERNEL_DEVICES_BLOCK_DEVICE_HPP -#define TEACH_OS_KERNEL_DEVICES_BLOCK_DEVICE_HPP - -#include - -#include - -#include - -namespace kernel::devices -{ - /** - * @brief Base interface for block-addressable devices. - */ - struct block_device : kapi::devices::device - { - /** - * @brief Create a block device descriptor. - * @param major Device major number. - * @param minor Device minor number. - * @param name Device name. - * @param block_size Size of one logical block in bytes. - */ - block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size); - - /** - * @brief Read data from the block at @p block_index into @p buffer. - * @param block_index Zero-based block index. - * @param buffer Destination buffer. - * @warning Panics if @p buffer is null. - * @note Reads up to one logical block (see constructor @p block_size). Implementations may perform a partial - * transfer for the final block when fewer than @p block_size bytes remain. - */ - virtual auto read_block(size_t block_index, void * buffer) const -> void = 0; - - /** - * @brief Write data to the block at @p block_index. - * @param block_index Zero-based block index. - * @param buffer Source buffer, must not be null. - * @warning Panics if @p buffer is null. - * @note Writes up to one logical block (see constructor @p block_size). - * Implementations may perform a partial transfer for the final block when - * fewer than @p block_size bytes remain. - */ - virtual auto write_block(size_t block_index, void const * buffer) -> void = 0; - - /** - * @brief Return logical block size in bytes. - * @return One logical block size in bytes. - */ - [[nodiscard]] auto block_size() const -> size_t; - - /** - * @brief Return device capacity in bytes. - * @return Total number of addressable bytes. - */ - [[nodiscard]] auto capacity() const -> size_t; - - /** - * @brief Override to identify block devices. - * @return true if this device is a block device, false otherwise. - */ - - [[nodiscard]] auto is_block_device() const -> bool override - { - return true; - } - - protected: - /** - * @brief Information describing the transfer window for one block index. - */ - struct transfer_info - { - size_t offset; - size_t to_transfer; - size_t remainder; - }; - - /** - * @brief Return total device size in bytes. - * @return Total number of addressable bytes. - */ - [[nodiscard]] virtual auto size() const -> size_t = 0; - - /** - * @brief Compute transfer information for @p block_index. - * @param block_index Zero-based block index. - * @return Computed transfer information for one logical block access. - */ - [[nodiscard]] auto calculate_transfer(size_t block_index) const -> transfer_info; - - size_t m_block_size; - }; -} // namespace kernel::devices - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/devices/block_device_utils.hpp b/kernel/include/kernel/devices/block_device_utils.hpp deleted file mode 100644 index 8be75b6f..00000000 --- a/kernel/include/kernel/devices/block_device_utils.hpp +++ /dev/null @@ -1,44 +0,0 @@ -#ifndef TEACH_OS_KERNEL_DEVICES_BLOCK_DEVICE_UTILS_HPP -#define TEACH_OS_KERNEL_DEVICES_BLOCK_DEVICE_UTILS_HPP - -#include - -#include - -#include - -namespace kernel::devices::block_device_utils -{ - /** - @brief Utility functions for block devices, such as reading/writing data at specific offsets. These functions handle - the necessary logic to interact with block devices, such as calculating block boundaries and ensuring proper access - patterns. They abstract away the details of block device interactions, providing a simple interface for reading and - writing data to block devices. - */ - - /** - @brief Reads data from a @p device into a @p buffer, starting at a specific @p offset and for a given @p size. - @warning Panics if @p buffer or @p device is null. - @param device The block device to read from. - @param buffer The buffer to read data into. - @param offset The offset on the block device to start reading from. - @param size The number of bytes to read. - @return The number of bytes actually read, which may be less than the requested size. - */ - auto read(kstd::shared_ptr const & device, void * buffer, size_t offset, size_t size) - -> size_t; - - /** - @brief Writes data from a @p buffer to a @p device, starting at a specific @p offset and for a given @p size. - @warning Panics if @p buffer or @p device is null. - @param device The block device to write to. - @param buffer The buffer to write data from. - @param offset The offset on the block device to start writing to. - @param size The number of bytes to write. - @return The number of bytes actually written, which may be less than the requested size. - */ - auto write(kstd::shared_ptr const & device, void const * buffer, size_t offset, size_t size) - -> size_t; -} // namespace kernel::devices::block_device_utils - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/devices/root_bus.hpp b/kernel/include/kernel/devices/root_bus.hpp deleted file mode 100644 index c8fee520..00000000 --- a/kernel/include/kernel/devices/root_bus.hpp +++ /dev/null @@ -1,16 +0,0 @@ -#ifndef TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP -#define TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP - -#include - -namespace kernel::devices -{ - - struct root_bus final : kapi::devices::bus - { - root_bus(); - }; - -} // namespace kernel::devices - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/devices/storage/controller.hpp b/kernel/include/kernel/devices/storage/controller.hpp deleted file mode 100644 index bea18f38..00000000 --- a/kernel/include/kernel/devices/storage/controller.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef TEACH_OS_KERNEL_DEVICES_STORAGE_CONTROLLER_HPP -#define TEACH_OS_KERNEL_DEVICES_STORAGE_CONTROLLER_HPP - -#include - -#include -#include - -#include - -namespace kernel::devices::storage -{ - /** - * @brief Base interface for storage controllers. - * - * A storage controller probes for devices and resolves devices by major/minor - * numbers. - */ - struct controller - { - /** - * @brief Virtual destructor. - */ - virtual ~controller() = default; - - /** - * @brief Probe the controller and register discovered devices. - */ - virtual auto probe() -> void = 0; - - /** - * @brief Assign the major number and minor stride for this controller. - * @param major Major number assigned to this controller. - * @param minors_per_dev Minor number stride between devices. - */ - auto set_ids(size_t major, size_t minors_per_dev) -> void; - - /** - * @brief Return the assigned major number. - * @return Assigned major number. - */ - [[nodiscard]] auto major() const -> size_t; - - /** - * @brief Return the number of devices managed by this controller. - * @return Number of managed devices. - */ - [[nodiscard]] auto devices_count() const -> size_t; - - /** - * @brief Return all devices managed by this controller. - * @return Vector of all managed devices. - */ - [[nodiscard]] auto all_devices() const -> kstd::vector> const &; - - /** - * @brief Find a managed device by major/minor numbers. - * @param major Device major number. - * @param minor Device minor number. - * @return Matching block device, or nullptr if no device matches. - */ - [[nodiscard]] auto device_by_minor(size_t minor) const -> kstd::shared_ptr; - - protected: - size_t m_major{}; - size_t m_minors_per_device{}; - kstd::vector> m_devices{}; - }; -} // namespace kernel::devices::storage - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/devices/storage/management.hpp b/kernel/include/kernel/devices/storage/management.hpp deleted file mode 100644 index 9a840875..00000000 --- a/kernel/include/kernel/devices/storage/management.hpp +++ /dev/null @@ -1,78 +0,0 @@ -#ifndef TEACH_OS_KERNEL_DEVICES_STORAGE_MANAGEMENT_HPP -#define TEACH_OS_KERNEL_DEVICES_STORAGE_MANAGEMENT_HPP - -#include - -#include - -#include -#include - -#include - -namespace kernel::devices::storage -{ - /** - * @brief Global storage subsystem manager. - * - * Owns registered storage controllers and provides device lookup by - * major/minor numbers. - */ - struct management - { - /** - * @brief Initialize global storage management. - * - * Creates the singleton instance, registers controllers and probes - * them for devices. - * - * @warning Panics if called more than once. - */ - auto static init() -> void; - - /** - * @brief Return the active storage manager singleton. - * @return Reference to the active storage manager. - * @warning Panics if storage management has not been initialized. - */ - auto static get() -> management &; - - /** - * @brief Register a storage controller. - * @param controller Controller to register. - * - * Assigns controller IDs (major number range and minors per device). - */ - auto add_controller(kstd::shared_ptr const & controller) -> void; - - /** - * @brief Return all registered storage controllers. - * @return Vector of all registered storage controllers. - */ - [[nodiscard]] auto all_controllers() const -> kstd::vector> const &; - - /** - * @brief Find a device by major/minor numbers. - * @param major Device major number. - * @param minor Device minor number. - * @return Matching device, or nullptr if no device matches. - */ - auto device_by_major_minor(size_t major, size_t minor) -> kstd::shared_ptr; - - /** - * @brief Determine the boot device. - * @return Boot device, or nullptr if it cannot be determined. - */ - auto determine_boot_device() -> kstd::shared_ptr; - - private: - /** - * @brief Private default constructor for storage management singleton. - */ - management() = default; - - kstd::vector> m_controllers{}; - }; -} // namespace kernel::devices::storage - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/devices/storage/ram_disk/controller.hpp b/kernel/include/kernel/devices/storage/ram_disk/controller.hpp deleted file mode 100644 index 93cf30d8..00000000 --- a/kernel/include/kernel/devices/storage/ram_disk/controller.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef TEACH_OS_KERNEL_DEVICES_STORAGE_RAM_DISK_CONTROLLER_HPP -#define TEACH_OS_KERNEL_DEVICES_STORAGE_RAM_DISK_CONTROLLER_HPP - -#include - -#include - -namespace kernel::devices::storage::ram_disk -{ - /** - * @brief Storage controller that exposes boot modules as RAM-disk devices. - */ - struct controller : kernel::devices::storage::controller - { - /** - * @brief Create a RAM-disk controller. - * @param registry Boot module registry as device source. - */ - explicit controller(kapi::boot_modules::boot_module_registry const * registry); - - /** - * @brief Probe boot modules and create RAM-disk devices. - */ - auto probe() -> void override; - - private: - kapi::boot_modules::boot_module_registry const * m_boot_module_registry; - }; -} // namespace kernel::devices::storage::ram_disk - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/devices/storage/ram_disk/device.hpp b/kernel/include/kernel/devices/storage/ram_disk/device.hpp deleted file mode 100644 index 89789ea9..00000000 --- a/kernel/include/kernel/devices/storage/ram_disk/device.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef TEACH_OS_KERNEL_DEVICES_STORAGE_RAM_DISK_DEVICE_HPP -#define TEACH_OS_KERNEL_DEVICES_STORAGE_RAM_DISK_DEVICE_HPP - -#include - -#include - -#include - -namespace kernel::devices::storage::ram_disk -{ - /** - * @brief Block device for a boot module. - */ - struct device : block_device - { - /** - * @brief Create a RAM disk for the @p module. - * @param module Boot module providing the memory region. - * @param major Device major number. - * @param minor Device minor number. - */ - device(kapi::boot_modules::boot_module const & module, size_t major, size_t minor); - - /** - * @brief Initialize the RAM disk device. - * @return true if module backing memory is valid, false otherwise. - */ - auto init() -> bool override; - - /** - * @brief Read one logical block into @p buffer. - * @param block_index Zero-based block index. - * @param buffer Destination buffer, must not be null. - * @note If the request reaches the module end, only available bytes are copied and the rest of the - * logical block is filled with zeros. - */ - auto read_block(size_t block_index, void * buffer) const -> void override; - - /** - * @brief Write one logical block from @p buffer. - * @param block_index Zero-based block index. - * @param buffer Source buffer, must not be null. - * @note If the request reaches the module end, only the bytes in the module range are written. - */ - auto write_block(size_t block_index, void const * buffer) -> void override; - - private: - /** - * @brief Return module size in bytes. - */ - [[nodiscard]] auto size() const -> size_t override; - - kapi::boot_modules::boot_module m_boot_module{}; - }; -} // namespace kernel::devices::storage::ram_disk - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/constants.hpp b/kernel/include/kernel/filesystem/constants.hpp deleted file mode 100644 index 8388d058..00000000 --- a/kernel/include/kernel/filesystem/constants.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_CONSTANTS_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_CONSTANTS_HPP - -#include - -namespace kernel::filesystem::constants -{ - constexpr size_t inline max_path_length = 4096; - - constexpr size_t inline symlink_max_path_length = 4096; - constexpr size_t inline symloop_max = 40; -} // namespace kernel::filesystem::constants - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/dentry.hpp b/kernel/include/kernel/filesystem/dentry.hpp deleted file mode 100644 index 096a6bbb..00000000 --- a/kernel/include/kernel/filesystem/dentry.hpp +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_DENTRY_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_DENTRY_HPP - -#include - -#include -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - /** - @brief Represents a directory entry (dentry) in the filesystem. A dentry is a node in the directory tree that - represents a file or directory. It contains a reference to its parent dentry, a reference to the associated real - filesystem inode, and a list of child dentries. - */ - struct dentry - { - /** - @brief Flags for the dentry. - */ - enum class dentry_flags : uint32_t - { - is_mount_point = 1 << 0 - }; - - /** - @brief Create a dentry with the given @p parent, associated @p inode, and optional @p name. The dentry is - initialized with the provided parent and inode, and the name is stored for lookup purposes. - @param parent The parent dentry. - @param inode The associated inode for this dentry. - @param name The name of the dentry (optional). - */ - dentry(kstd::shared_ptr const & parent, kstd::shared_ptr const & inode, std::string_view name); - - /** - @brief Get the associated inode. - @return A reference to the associated inode. - */ - [[nodiscard]] auto get_inode() const -> kstd::shared_ptr const &; - - /** - @brief Get the parent dentry. - @return A reference to the parent dentry. - */ - [[nodiscard]] auto parent() const -> kstd::shared_ptr; - - /** - @brief Get the name of the dentry. - @return The name of the dentry. - */ - [[nodiscard]] auto name() const -> std::string_view; - - /** - @brief Get the full path of the dentry by traversing up to the root. - @return The full path of the dentry. - */ - [[nodiscard]] auto absolute_path() const -> kstd::string; - - /** - @brief Add a @p child dentry. - @param child The child dentry to add. - */ - auto add_child(kstd::shared_ptr const & child) -> void; - - /** - @brief Find a child dentry by @p name. - @param name The name of the child dentry to find. - @return A pointer to the found child dentry, or a null pointer if not found. - */ - [[nodiscard]] auto find_child(std::string_view name) const -> kstd::shared_ptr; - - /** - @brief Set a @p flag for the dentry. - @param flag The flag to set. - */ - auto set_flag(dentry_flags flag) -> void; - - /** - @brief Unset a @p flag for the dentry. - @param flag The flag to unset. - */ - auto unset_flag(dentry_flags flag) -> void; - - /** - @brief Check if the dentry has a specific @p flag. - @param flag The flag to check. - @return True if the dentry has the flag, false otherwise. - */ - [[nodiscard]] auto has_flag(dentry_flags flag) const -> bool; - - private: - kstd::string m_name; - kstd::weak_ptr m_parent; - kstd::vector> m_children; - kstd::shared_ptr m_inode; - uint32_t m_flags; - }; -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/devfs/filesystem.hpp b/kernel/include/kernel/filesystem/devfs/filesystem.hpp deleted file mode 100644 index dbaa387b..00000000 --- a/kernel/include/kernel/filesystem/devfs/filesystem.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_DEVFS_FILESYSTEM_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_DEVFS_FILESYSTEM_HPP - -#include -#include -#include - -#include -#include - -#include - -namespace kernel::filesystem::devfs -{ - /** - @brief A filesystem for managing device nodes in the virtual filesystem. This filesystem provides a way to represent - devices as files in the /dev directory, allowing user-space applications to interact with devices using standard file - operations. The devfs filesystem dynamically creates inodes for devices registered in the system, enabling seamless - access to device functionality through the filesystem interface. - */ - struct filesystem : kernel::filesystem::filesystem - { - /** - @brief Initializes the devfs instance and builds the device inode table. - @param backing_inode Backing inode passed by the vfs (not required by devfs). - @return The result of the mount operation. - */ - auto mount(kstd::shared_ptr const & backing_inode) -> operation_result override; - - /** - @brief Looks up an inode by @p name within a @p parent directory. - @param parent The parent directory inode. - @param name The name of the inode to look up. - @return A pointer to the found inode, or a null pointer if not found. - */ - [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const - -> kstd::shared_ptr override; - - private: - auto build_device_inode_table() -> void; - - kstd::vector> m_inodes{}; - }; -} // namespace kernel::filesystem::devfs - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/devfs/inode.hpp b/kernel/include/kernel/filesystem/devfs/inode.hpp deleted file mode 100644 index e4288918..00000000 --- a/kernel/include/kernel/filesystem/devfs/inode.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_DEVFS_INODE_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_DEVFS_INODE_HPP - -#include - -#include - -namespace kernel::filesystem::devfs -{ - /** - @brief Inode implementation for the devfs filesystem. - This inode represents root device node in the /dev directory. - */ - struct inode : kernel::filesystem::inode - { - /** - @brief Reads from the devfs directory inode. - @param buffer Destination buffer. - @param offset Read offset in bytes. - @param size Number of bytes requested. - @return Number of bytes read (always 0 because this inode does not expose file data). - */ - auto read(void * buffer, size_t offset, size_t size) const -> size_t override; - - /** - @brief Writes to the devfs directory inode. - @param buffer Source buffer. - @param offset Write offset in bytes. - @param size Number of bytes requested. - @return Number of bytes written (always 0 because writes are not supported for this inode). - */ - auto write(void const * buffer, size_t offset, size_t size) -> size_t override; - - /** - @brief Check if this inode represents a directory. - @return returns true, since this inode represents the /dev directory in the devfs filesystem. - */ - [[nodiscard]] auto is_directory() const -> bool override; - }; -} // namespace kernel::filesystem::devfs - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/device_inode.hpp b/kernel/include/kernel/filesystem/device_inode.hpp deleted file mode 100644 index f4aa2d1e..00000000 --- a/kernel/include/kernel/filesystem/device_inode.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_DEVICE_INODE_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_DEVICE_INODE_HPP - -#include - -#include - -#include - -#include - -namespace kernel::filesystem -{ - /** - @brief Inode implementation for device inodes in the filesystem. This inode represents a device file that provides - access to a device registered in the system. The device inode allows reading from and writing to the associated - device. - */ - struct device_inode : inode - { - /** - @brief Create a device inode with the given @p device. - @param device The device to associate with the inode. - */ - explicit device_inode(kstd::shared_ptr const & device); - - /** - @brief Read data from the device inode (and in the background from the associated device) into a @p buffer, starting - at @p offset and reading @p size bytes. - @param buffer The buffer to read data into. - @param offset The offset to read from. - @param size The number of bytes to read. - @return The number of bytes read. - */ - auto read(void * buffer, size_t offset, size_t size) const -> size_t override; - - /** - @brief Write data to the device inode (and in the background from the associated device) from a @p buffer, starting - at @p offset and writing @p size bytes. - @param buffer The buffer containing data to write. - @param offset The offset to write to. - @param size The number of bytes to write. - @return The number of bytes written. - */ - auto write(void const * buffer, size_t offset, size_t size) -> size_t override; - - /** - @brief Get the associated device. - @return A reference to the associated device. - */ - [[nodiscard]] auto device() const -> kstd::shared_ptr const &; - - /** - @brief Check if this inode represents a device. - @return returns true, since this indoe is a device inode and represents a device. - */ - [[nodiscard]] auto is_device() const -> bool override; - - private: - kstd::shared_ptr m_device; - }; -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp b/kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp deleted file mode 100644 index 7fbba3f3..00000000 --- a/kernel/include/kernel/filesystem/ext2/block_group_descriptor.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_EXT2_BLOCK_GROUP_DESCRIPTOR_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_EXT2_BLOCK_GROUP_DESCRIPTOR_HPP - -#include -#include - -namespace kernel::filesystem::ext2 -{ - /** - @brief Represents 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/include/kernel/filesystem/ext2/filesystem.hpp b/kernel/include/kernel/filesystem/ext2/filesystem.hpp deleted file mode 100644 index 2284d7bf..00000000 --- a/kernel/include/kernel/filesystem/ext2/filesystem.hpp +++ /dev/null @@ -1,115 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_EXT2_FILESYSTEM_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_EXT2_FILESYSTEM_HPP - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -namespace kernel::filesystem::ext2 -{ - /** - @brief Constants related to the ext2 filesystem. - */ - namespace constants - { - constexpr size_t inline base_block_size = 1024; - constexpr size_t inline superblock_offset = base_block_size; - constexpr uint16_t inline magic_number = 0xEF53; - - constexpr uint32_t inline 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; - } // namespace constants - - /** - @brief A filesystem implementation for the ext2 filesystem format. This class provides methods for mounting an ext2 - filesystem, and looking up inodes. - */ - struct filesystem : kernel::filesystem::filesystem - { - /** - @brief Initializes the ext2 filesystem with the given @p backing_inode. - @param backing_inode The backing inode to mount. - @return The result of the mount operation. - */ - auto mount(kstd::shared_ptr const & backing_inode) -> operation_result override; - - /** - @brief Looks up an inode by @p name within a @p parent directory. - @param parent The parent directory inode. - @param name The name of the inode to look up. - @return A pointer to the found inode, or a null pointer if not found. - */ - [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const - -> kstd::shared_ptr override; - - /** - @brief Gets the size of a block in the filesystem. - @return The size of a block in bytes. - */ - [[nodiscard]] auto block_size() const -> size_t; - - /** - @brief Gets the revision level of the filesystem. - @return The revision level. - */ - [[nodiscard]] auto revision_level() const -> uint32_t; - - /** - @brief Maps an inode block index to a global block number. - @param inode_block_index The index of the block within the inode. - @param data The inode data. - @return The global block number. - */ - [[nodiscard]] auto map_inode_block_index_to_global_block_number(size_t inode_block_index, inode_data data) const - -> kstd::ssize_t; - - private: - struct indirect_level - { - uint32_t slot_index; - size_t capacity; - }; - - [[nodiscard]] auto indirect_levels() const -> std::array; - - [[nodiscard]] auto read_inode(uint32_t inode_number) const -> kstd::shared_ptr; - [[nodiscard]] auto read_block_number_at_index(uint32_t block_number, size_t index) const -> uint32_t; - - [[nodiscard]] auto inode_size() const -> uint16_t; - [[nodiscard]] auto inode_block_count(inode_data const & data) const -> uint32_t; - [[nodiscard]] auto block_group_descriptor_table_offset() const -> size_t; - - [[nodiscard]] auto block_numbers_per_block() const -> size_t; - [[nodiscard]] auto block_numbers_per_singly_indirect_block() const -> size_t; - [[nodiscard]] auto block_numbers_per_doubly_indirect_block() const -> size_t; - [[nodiscard]] auto block_numbers_per_triply_indirect_block() const -> size_t; - - superblock m_superblock{}; - kstd::vector m_block_group_descriptors; - }; -} // namespace kernel::filesystem::ext2 - -#endif diff --git a/kernel/include/kernel/filesystem/ext2/inode.hpp b/kernel/include/kernel/filesystem/ext2/inode.hpp deleted file mode 100644 index f2496f0f..00000000 --- a/kernel/include/kernel/filesystem/ext2/inode.hpp +++ /dev/null @@ -1,105 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_EXT2_INODE_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_EXT2_INODE_HPP - -#include - -#include - -#include -#include -#include - -namespace kernel::filesystem::ext2 -{ - struct filesystem; - - /** - @brief Represents the data associated with an ext2 inode. - */ - struct [[gnu::packed]] inode_data - { - uint16_t mode; - uint16_t uid; - uint32_t size; - 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 - { - /** - @brief Create an ext2 inode associated with the given filesystem. - @param fs The ext2 filesystem that this inode belongs to. - @param data The data associated with this inode, read from the disk. - */ - explicit inode(filesystem const * fs, inode_data const & data); - - /** - @brief Reads from the ext2 inode into a @p buffer, starting at the specified @p offset and for a given @p size. - @param buffer Destination buffer. - @param offset Read offset in bytes. - @param size Number of bytes requested. - @return Number of bytes read. - */ - auto read(void * buffer, size_t offset, size_t size) const -> size_t override; - - /** - @brief Writes to the ext2 inode into a @p buffer, starting at the specified @p offset and for a given @p size. - @warning This method is not implemented yet and will panic if called. - @param buffer Source buffer. - @param offset Write offset in bytes. - @param size Number of bytes requested. - @return Number of bytes written. - */ - auto write(void const * buffer, size_t offset, size_t size) -> size_t override; - - /** - @brief Get the data associated with this inode. - @return A reference to the inode data. - */ - [[nodiscard]] auto data() const -> inode_data const &; - - /** - @brief Check if this inode represents a directory. - @return returns true if this inode represents a directory, false otherwise. - */ - [[nodiscard]] auto is_directory() const -> bool override; - - /** - @brief Check if this inode represents a regular file. - @return returns true if this inode represents a regular file, false otherwise. - */ - [[nodiscard]] auto is_regular() const -> bool override; - - /** - @brief Check if this inode represents a symbolic link. - @return returns true if this inode represents a symbolic link, false otherwise. - */ - [[nodiscard]] auto is_symbolic_link() const -> bool override; - - /** - @brief Get the size of the file represented by this inode. - @return The size of the file in bytes. - */ - [[nodiscard]] auto size() const -> uint64_t; - - private: - filesystem const * m_filesystem; - inode_data m_data{}; - }; -} // namespace kernel::filesystem::ext2 - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp b/kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp deleted file mode 100644 index 4097cbb8..00000000 --- a/kernel/include/kernel/filesystem/ext2/linked_directory_entry.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_EXT2_LINKED_DIRECTORY_ENTRY_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_EXT2_LINKED_DIRECTORY_ENTRY_HPP - -#include -#include - -namespace kernel::filesystem::ext2 -{ - /** - @brief Represents a linked directory entry in the ext2 filesystem. - */ - struct [[gnu::packed]] linked_directory_entry - { - uint32_t inode; - uint16_t rec_len; - uint8_t name_len; - uint8_t file_type; - std::array name; // NOLINT(readability-magic-numbers) - }; -} // namespace kernel::filesystem::ext2 - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/ext2/superblock.hpp b/kernel/include/kernel/filesystem/ext2/superblock.hpp deleted file mode 100644 index 41ad9358..00000000 --- a/kernel/include/kernel/filesystem/ext2/superblock.hpp +++ /dev/null @@ -1,74 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_EXT2_SUPERBLOCK_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_EXT2_SUPERBLOCK_HPP - -#include -#include - -namespace kernel::filesystem::ext2 -{ - /** - @brief Represents 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; - uint32_t feature_compat; - uint32_t feature_incompat; - uint32_t 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 -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/filesystem.hpp b/kernel/include/kernel/filesystem/filesystem.hpp deleted file mode 100644 index bec1b160..00000000 --- a/kernel/include/kernel/filesystem/filesystem.hpp +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_FILESYSTEM_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_FILESYSTEM_HPP - -#include - -#include -#include - -#include - -namespace kernel::filesystem -{ - /** - @brief A base class for implementing filesystems in the kernel. This class provides a common interface for managing - files and directories within the virtual filesystem. - */ - struct filesystem - { - enum class operation_result : int - { - success = 0, - invalid_magic_number = -1, - invalid_root_inode = -2, - unmount_failed = -3 - }; - - /** - @brief Virtual destructor for the filesystem. - */ - virtual ~filesystem() = default; - - /** - @brief Probes the given @p backing_inode to determine if it contains a recognizable filesystem, and if so, mounts it - and returns a pointer to the mounted filesystem instance. This method iterates through known filesystem types and - attempts to initialize it with the backing inode until the mount was successful or all types have been tried. - @param backing_inode The inode to probe and mount. - @return A pointer to the mounted filesystem instance if successful, or a null pointer if no recognizable filesystem - is found on the backing inode. - @warning Panics if @p backing_inode is null. - */ - auto static probe_and_mount(kstd::shared_ptr const & backing_inode) -> kstd::shared_ptr; - - /** - @brief Initializes the filesystem with the given @p backing_inode. - @param backing_inode The inode to use as the backing inode for the filesystem. (This is typically the inode - representing the block device or another inode which contains the filesystem data.) - @return The result of the mount operation. - */ - virtual auto mount(kstd::shared_ptr const & backing_inode) -> operation_result; - - /** - @brief Looks up a child inode within the given @p parent inode with the specified @p name. This method must be - implemented by concrete filesystem subclasses to provide the logic for traversing the filesystem structure and - finding the requested inode. - @param parent The parent inode. - @param name The name of the child inode to look up. - @return A pointer to the requested child inode, or a null pointer if not found. - */ - [[nodiscard]] virtual auto lookup(kstd::shared_ptr const & parent, std::string_view name) const - -> kstd::shared_ptr = 0; - - /** - @brief Returns a reference to the root inode of the filesystem. - */ - [[nodiscard]] auto root_inode() const -> kstd::shared_ptr const &; - - /** - @brief Returns a reference to the backing inode of the filesystem. - */ - [[nodiscard]] auto backing_inode() const -> kstd::shared_ptr const &; - - protected: - kstd::shared_ptr m_root_inode{}; - kstd::shared_ptr m_backing_inode{}; - }; - -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/inode.hpp b/kernel/include/kernel/filesystem/inode.hpp deleted file mode 100644 index b34b921e..00000000 --- a/kernel/include/kernel/filesystem/inode.hpp +++ /dev/null @@ -1,69 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_INODE_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_INODE_HPP - -#include - -namespace kernel::filesystem -{ - /** - @brief Represents an inode in the filesystem. - */ - struct inode - { - /** - @brief Create an inode. - */ - inode() = default; - - /** - @brief Virtual destructor for the inode. - */ - virtual ~inode() = default; - - /** - @brief Reads from the inode into a @p buffer, starting at the specified @p offset and for a given @p size. This - method must be implemented by concrete inode subclasses. - @param buffer Destination buffer. - @param offset Read offset in bytes. - @param size Number of bytes requested. - @return Number of bytes read. - */ - virtual auto read(void * buffer, size_t offset, size_t size) const -> size_t = 0; - - /** - @brief Writes to the inode into a @p buffer, starting at the specified @p offset and for a given @p size. This - method must be implemented by concrete inode subclasses. - @param buffer Source buffer. - @param offset Write offset in bytes. - @param size Number of bytes to write. - @return Number of bytes written. - */ - virtual auto write(void const * buffer, size_t offset, size_t size) -> size_t = 0; - - /** - @brief Returns whether the inode is a directory. - @return true if the inode is a directory, false otherwise. - */ - [[nodiscard]] virtual auto is_directory() const -> bool; - - /** - @brief Returns whether the inode is a regular file. - @return true if the inode is a regular file, false otherwise. - */ - [[nodiscard]] virtual auto is_regular() const -> bool; - - /** - @brief Returns whether the inode is a device. - @return true if the inode is a device, false otherwise. - */ - [[nodiscard]] virtual auto is_device() const -> bool; - - /** - @brief Returns whether the inode is a symbolic link. - @return true if the inode is a symbolic link, false otherwise. - */ - [[nodiscard]] virtual auto is_symbolic_link() const -> bool; - }; -} // namespace kernel::filesystem - -#endif diff --git a/kernel/include/kernel/filesystem/mount.hpp b/kernel/include/kernel/filesystem/mount.hpp deleted file mode 100644 index ced4f814..00000000 --- a/kernel/include/kernel/filesystem/mount.hpp +++ /dev/null @@ -1,97 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_MOUNT_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_MOUNT_HPP - -#include -#include - -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - /** - @brief Represents a mounted filesystem in the kernel. - */ - struct mount - { - /** - @brief Creates a mount for the given @p filesystem at the specified @p mount_path. The @p mount_dentry represents - the dentry where the filesystem is mounted, and the @p root_dentry represents the root dentry of the mounted - filesystem. - @param mount_dentry The dentry where the filesystem is mounted. - @param root_dentry The root dentry of the mounted filesystem. - @param fs The filesystem instance being mounted. - @param parent_mount The mount that contains the mount_dentry. - @param source_mount The mount that the filesystem originates from. - */ - mount(kstd::shared_ptr const & mount_dentry, kstd::shared_ptr const & root_dentry, - kstd::shared_ptr const & fs, kstd::shared_ptr const & parent_mount, - kstd::shared_ptr const & source_mount); - - /** - @brief Get the dentry where the filesystem is mounted. - */ - [[nodiscard]] auto mount_dentry() const -> kstd::shared_ptr const &; - - /** - @brief Get the root dentry of the mounted filesystem. - */ - [[nodiscard]] auto root_dentry() const -> kstd::shared_ptr const &; - - /** - @brief Get the filesystem instance being mounted. - */ - [[nodiscard]] auto get_filesystem() const -> kstd::shared_ptr const &; - - /** - @brief Get the path at which the filesystem is mounted. - */ - [[nodiscard]] auto mount_path() const -> kstd::string; - - /** - @brief Get the parent mount that this mount was attached beneath. - */ - [[nodiscard]] auto parent_mount() const -> kstd::shared_ptr const &; - - /** - @brief Get the source mount where this mount originates from. - */ - [[nodiscard]] auto source_mount() const -> kstd::shared_ptr; - - /** - @brief Increment the reference count for this mount. - */ - auto increment_ref_count() -> void; - - /** - @brief Decrement the reference count for this mount. - @warning Throws if ref_count is zero. - */ - auto decrement_ref_count() -> void; - - /** - @brief 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; - - /** - @brief Get the current reference count for this mount. - @return The current reference count. - */ - [[nodiscard]] auto ref_count() const -> size_t; - - private: - kstd::shared_ptr m_mount_dentry{}; - kstd::shared_ptr m_root_dentry{}; - kstd::shared_ptr m_filesystem{}; - kstd::shared_ptr m_parent_mount{}; - kstd::weak_ptr m_source_mount{}; - std::atomic_size_t m_ref_count{0}; - }; -} // namespace kernel::filesystem - -#endif diff --git a/kernel/include/kernel/filesystem/mount_table.hpp b/kernel/include/kernel/filesystem/mount_table.hpp deleted file mode 100644 index 4f2d1b7f..00000000 --- a/kernel/include/kernel/filesystem/mount_table.hpp +++ /dev/null @@ -1,58 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_MOUNT_TABLE_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_MOUNT_TABLE_HPP - -#include - -#include -#include - -#include - -namespace kernel::filesystem -{ - /** - @brief A table for managing mounted filesystems in the kernel. - */ - struct mount_table - { - /** - @brief Results for mount table operations. - */ - enum class operation_result : int - { - removed = 0, - has_child_mounts = -1, - mount_not_found = -2, - cannot_be_unmounted = -3 - }; - - /** - @brief Adds a mount to the table. - @param mount The mount to add. - */ - auto add_mount(kstd::shared_ptr const & mount) -> void; - - /** - @brief Removes the topmost mount at the given @p path. - @param path The mount path to remove. - @return The result of the removal operation. - */ - [[nodiscard]] auto remove_mount(std::string_view path) -> operation_result; - - /** - @brief Finds the mount with the exact mount path matching the given @p 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, or a null pointer if no mount matches the path. - */ - [[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/include/kernel/filesystem/open_file_descriptor.hpp b/kernel/include/kernel/filesystem/open_file_descriptor.hpp deleted file mode 100644 index fd10e646..00000000 --- a/kernel/include/kernel/filesystem/open_file_descriptor.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_OPEN_FILE_DESCRIPTOR_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_OPEN_FILE_DESCRIPTOR_HPP - -#include - -#include - -#include - -namespace kernel::filesystem -{ - /** - @brief Represents an open file descriptor in the filesystem. This class encapsulates the state of an open file, - including a reference to the associated dentry and the current file offset. - */ - struct open_file_descriptor - { - /** - @brief Constructs an open file descriptor for the given @p dentry. - @param dentry The dentry to associate with the open file descriptor. - */ - explicit open_file_descriptor(kstd::shared_ptr const & dentry); - - /** - @brief Destructor for the open file descriptor. - */ - ~open_file_descriptor() = default; - - /** - @brief Reads data from the open file descriptor into a @p buffer, starting at the current file offset and for a - given - @p size. The file offset is advanced by the number of bytes read. - @param buffer The buffer to read data into. - @param size The number of bytes to read. - @return The number of bytes read. - */ - auto read(void * buffer, size_t size) -> size_t; - - /** - @brief Writes data to the open file descriptor from a @p buffer, starting at the current file offset and for a - given - @p size. The file offset is advanced by the number of bytes written. - @param buffer The buffer to write data from. - @param size The number of bytes to write. - @return The number of bytes written. - */ - auto write(void const * buffer, size_t size) -> size_t; - - /** - @brief Returns the current file offset for this open file descriptor. - @return The current file offset in bytes. - */ - [[nodiscard]] auto offset() const -> size_t; - - /** - @brief Return a reference to the dentry associated with this open file descriptor. - @return A reference to the associated dentry. - */ - [[nodiscard]] auto get_dentry() const -> kstd::shared_ptr const &; - - private: - kstd::shared_ptr m_dentry; - size_t m_offset; - }; - -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/open_file_table.hpp b/kernel/include/kernel/filesystem/open_file_table.hpp deleted file mode 100644 index 7e754ac1..00000000 --- a/kernel/include/kernel/filesystem/open_file_table.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_OPEN_FILE_TABLE_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_OPEN_FILE_TABLE_HPP - -#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 - { - /** - @brief Initialize the global open file table. This method creates the singleton instance of the open file table. - @warning Panics if called more than once. - */ - auto static init() -> void; - - /** - @brief Get the global open file table instance. - @return A reference to the global open file table. - @warning Panics if the open file table has not been initialized. - */ - auto static get() -> open_file_table &; - - /** - @brief Destructor for the open file table. - */ - ~open_file_table() = default; - - /** - @brief Add a file to the open file table. - @param fd The file descriptor to add. - @return The file descriptor index assigned to the file, or -1 on failure. - */ - auto add_file(kstd::shared_ptr const & fd) -> kstd::ssize_t; - - /** - @brief Get a file from the open file table. - @param fd The file descriptor index to retrieve. - @return A pointer to the requested file descriptor, or a null pointer if not found. - */ - [[nodiscard]] auto file(size_t fd) const -> kstd::shared_ptr; - - /** - @brief Remove a file from the open file table. - @param fd The file descriptor index to remove. - @return 0 on success, or -1 on failure. - */ - auto remove_file(size_t fd) -> kstd::ssize_t; - - private: - open_file_table() = default; - - kstd::vector> m_open_files{}; - }; -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/path.hpp b/kernel/include/kernel/filesystem/path.hpp deleted file mode 100644 index 4845bf1a..00000000 --- a/kernel/include/kernel/filesystem/path.hpp +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP - -#include - -#include - -#include -#include - -namespace kernel::filesystem::path -{ - /** - @brief Provides utilities for handling filesystem paths, including validation and splitting into components. - */ - - /** - @brief Checks if the given path is within the maximum allowed length. - @param path The path to check. - @return true if the path length is valid, false otherwise. - */ - auto inline is_valid_path_length(std::string_view path) -> bool - { - return path.length() < kernel::filesystem::constants::max_path_length; - } - - /** - @brief Checks if the given path is a valid absolute path (starts with '/'). - @param path The path to check. - @return true if the path is a valid absolute path, false otherwise. - */ - auto inline is_valid_absolute_path(std::string_view path) -> bool - { - return !path.empty() && path.front() == '/' && is_valid_path_length(path); - } - - /** - @brief Checks if the given path is a valid relative path (doesn't start with '/'). - @param path The path to check. - @return true if the path is a valid relative path, false otherwise. - */ - auto inline is_valid_relative_path(std::string_view path) -> bool - { - return !path.empty() && path.front() != '/' && is_valid_path_length(path); - } - - /** - @brief Checks if the given path is a valid path (either absolute or relative). - @param path The path to check. - @return true if the path is a valid path, false otherwise. - */ - auto inline is_valid_path(std::string_view path) -> bool - { - return is_valid_absolute_path(path) || is_valid_relative_path(path); - } - - /** - @brief Splits 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())); }); - } - -} // namespace kernel::filesystem::path - -#endif // TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/rootfs/filesystem.hpp b/kernel/include/kernel/filesystem/rootfs/filesystem.hpp deleted file mode 100644 index 3c2dcb1c..00000000 --- a/kernel/include/kernel/filesystem/rootfs/filesystem.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_ROOTFS_FILESYSTEM_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_ROOTFS_FILESYSTEM_HPP - -#include -#include - -#include -#include -#include - -#include - -namespace kernel::filesystem::rootfs -{ - /** - @brief A filesystem for the root filesystem. This filesystem provides access to the root directory and its contents, - which are typically populated by the init process during system startup. The rootfs filesystem serves as the top-level - directory in the filesystem hierarchy. It is responsible for providing a stable and consistent interface to the root - directory. - */ - struct filesystem : kernel::filesystem::filesystem - { - /** - @brief Initializes the rootfs filesystem with the given @p backing_inode. - @param backing_inode The backing inode to mount (not required by rootfs). - @return The result of the mount operation. - */ - auto mount(kstd::shared_ptr const & backing_inode) -> operation_result override; - - /** - @brief Looks up an inode by @p name within a @p parent directory. - @param parent The parent directory inode. - @param name The name of the inode to look up. - @return Always returns nullptr. - */ - [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const - -> kstd::shared_ptr override; - }; -} // namespace kernel::filesystem::rootfs - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/filesystem/rootfs/inode.hpp b/kernel/include/kernel/filesystem/rootfs/inode.hpp deleted file mode 100644 index 0f21eaa0..00000000 --- a/kernel/include/kernel/filesystem/rootfs/inode.hpp +++ /dev/null @@ -1,45 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_ROOTFS_INODE_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_ROOTFS_INODE_HPP - -#include - -#include -#include -#include - -#include - -namespace kernel::filesystem::rootfs -{ - /** - @brief Represents an inode in the rootfs filesystem. - */ - struct inode : kernel::filesystem::inode - { - /** - @brief Reads from the rootfs directory inode. - @param buffer Destination buffer. - @param offset Read offset in bytes. - @param size Number of bytes requested. - @return Number of bytes read (always 0 because this inode does not expose file data). - */ - auto read(void * buffer, size_t offset, size_t size) const -> size_t override; - - /** - @brief Writes to the rootfs directory inode. - @param buffer Source buffer. - @param offset Write offset in bytes. - @param size Number of bytes requested. - @return Number of bytes written (always 0 because writes are not supported for this inode). - */ - auto write(void const * buffer, size_t offset, size_t size) -> size_t override; - - /** - @brief Check if this inode represents a directory. - @return returns true, since this inode represents the / directory in the rootfs filesystem. - */ - [[nodiscard]] auto is_directory() const -> bool override; - }; -} // namespace kernel::filesystem::rootfs - -#endif diff --git a/kernel/include/kernel/filesystem/type.hpp b/kernel/include/kernel/filesystem/type.hpp deleted file mode 100644 index 0948e549..00000000 --- a/kernel/include/kernel/filesystem/type.hpp +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_TYPE_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_TYPE_HPP - -#include - -#include - -#include - -namespace kernel::filesystem -{ - - //! A type descriptor for a filesystem driver. - //! - //! Each filesystem must expose an instance of a class derived from this type in order to be registered with the vfs - //! filesystem registry. - struct type - { - virtual ~type() = default; - - //! Get the name of the filesystem represented by this descriptor. - [[nodiscard]] virtual auto name() const noexcept -> std::string_view = 0; - - //! Check if filesystems of this type require a device to back them. - [[nodiscard]] virtual auto requires_device() const noexcept -> bool = 0; - - //! Create a new instance of the filesytem represented by this descriptor. - [[nodiscard]] virtual auto make_instance() const -> kstd::shared_ptr = 0; - }; - - template - struct type_registration - { - constexpr auto static instance = Type{}; - [[using gnu: section("fs_types"), used, visibility("hidden")]] constexpr auto static pointer{ - kstd::make_observer(&instance), - }; - }; - -} // namespace kernel::filesystem - -#endif diff --git a/kernel/include/kernel/filesystem/type_registry.hpp b/kernel/include/kernel/filesystem/type_registry.hpp deleted file mode 100644 index 3be72950..00000000 --- a/kernel/include/kernel/filesystem/type_registry.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef TEACH_OS_KERNEL_TYPE_REGISRY_HPP -#define TEACH_OS_KERNEL_TYPE_REGISRY_HPP - -#include - -#include -#include -#include - -#include -#include -#include - -namespace kernel::filesystem -{ - - struct type_registry - { - using value_type = type; - using pointer = kstd::observer_ptr; - - auto static init() -> void; - auto static get() -> type_registry &; - - constexpr type_registry() noexcept = default; - - //! Add a type descriptor to this registry. - //! - //! This function will register the given descriptor with this registry, given that no descriptor for a filesystem - //! with the same name exists in this registry already. - //! - //! @param descriptor The filesystem type descriptor to add to the registry. - //! @return @p true iff. the descriptor was successfully added, @p false if not. - auto add(pointer descriptor) -> bool; - - //! Get all currently registered type descriptors. - //! - //! @return A span containing all currently registered filesystem type descriptors. - [[nodiscard]] auto all() const noexcept -> std::span; - - //! Get the number of registered filesystem types. - //! - //! @return The number of filesystem descriptors currently registered with this registry. - [[nodiscard]] auto size() const noexcept -> std::size_t; - - private: - //! A map from filesystem names (identifiers) to filesystem type descriptors. - kstd::flat_map m_descriptors{}; - }; - -} // namespace kernel::filesystem - -#endif diff --git a/kernel/include/kernel/filesystem/vfs.hpp b/kernel/include/kernel/filesystem/vfs.hpp deleted file mode 100644 index ddc9a9bc..00000000 --- a/kernel/include/kernel/filesystem/vfs.hpp +++ /dev/null @@ -1,113 +0,0 @@ -#ifndef TEACH_OS_KERNEL_FILESYSTEM_VFS_HPP -#define TEACH_OS_KERNEL_FILESYSTEM_VFS_HPP - -#include -#include -#include -#include -#include - -#include - -#include -#include - -namespace kernel::filesystem -{ - /** - @brief The virtual filesystem (VFS) is responsible for managing mounted filesystems and providing a unified interface - for file operations across different filesystem types. The VFS maintains a mount table to keep track of mounted - filesystems and their associated mount points. It provides methods for opening files by path, which involvesresolving - the path to the appropriate mounted filesystem and delegating the file operation to that filesystem's implementation. - */ - struct vfs - { - /** - @brief Results for VFS operations. - */ - enum class operation_result : int - { - success = 0, - invalid_path = -1, - non_existent_path = -2, - mount_point_not_found = -3, - unmount_failed = -4, - invalid_filesystem = -5 - }; - - vfs(); - - /** - @brief Initialize the virtual filesystem. - @warning Panics if the VFS has already been initialized. - */ - auto static init() -> void; - - /** - @brief Get the singleton instance of the virtual filesystem. - @return A reference to the VFS instance. - @warning Panics if the VFS has not been initialized yet. - */ - auto static get() -> vfs &; - - /** - @brief Destructor for the VFS. - */ - ~vfs() = default; - - /** - @brief Open a file by its @p path. This method resolves the path and returns the corresponding dentry. - @param path The path to the file to open. - @return A shared pointer to the dentry or a null pointer if the file could not be opened. - */ - auto open(std::string_view path) -> kstd::shared_ptr; - - /** - @brief Close a file by its associated @p path. - @param path The path to the file to close. - @return The result of the close operation. - */ - auto close(std::string_view path) -> operation_result; - - /** - @brief Mount a @p source path to a specific @p target path. - @param source The source of the filesystem to mount. - @param target The path where the filesystem should be mounted. - @return The result of the mount operation. - */ - auto do_mount(std::string_view source, std::string_view target) -> operation_result; - - /** - @brief Unmount the filesystem mounted at the specified @p path. - @param path The path where the filesystem is mounted. - @return The result of the unmount operation. - */ - auto unmount(std::string_view path) -> operation_result; - - private: - /** - * 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 - -> std::pair, kstd::shared_ptr>; - [[nodiscard]] auto resolve_path(std::string_view path) const -> kstd::shared_ptr; - [[nodiscard]] auto find_mount(std::string_view path) const -> kstd::shared_ptr; - - auto do_mount_internal(kstd::shared_ptr const & mount_point_dentry, - kstd::shared_ptr const & parent_mount, kstd::shared_ptr const & fs, - kstd::shared_ptr const & source_mount = nullptr) -> void; - - auto graft_persistent_device_fs(kstd::shared_ptr const & device_fs) -> void; - - mount_table m_mount_table{}; - }; -} // namespace kernel::filesystem - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/memory.hpp b/kernel/include/kernel/memory.hpp deleted file mode 100644 index 17fb82b4..00000000 --- a/kernel/include/kernel/memory.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef TEACHOS_KERNEL_MEMORY_HPP -#define TEACHOS_KERNEL_MEMORY_HPP - -#include // IWYU pragma: export - -#include - -namespace kernel::memory -{ - - //! Get a handle to the global heap allocator. - auto get_heap_allocator() -> heap_allocator &; - - //! Initialize the kernel heap. - auto init_heap(kapi::memory::linear_address base) -> void; - -} // namespace kernel::memory - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/memory/bitmap_allocator.hpp b/kernel/include/kernel/memory/bitmap_allocator.hpp deleted file mode 100644 index 370ce64d..00000000 --- a/kernel/include/kernel/memory/bitmap_allocator.hpp +++ /dev/null @@ -1,56 +0,0 @@ -#ifndef TEACHOS_KERNEL_MEMORY_BITMAP_ALLOCATOR_HPP -#define TEACHOS_KERNEL_MEMORY_BITMAP_ALLOCATOR_HPP - -#include - -#include -#include -#include -#include -#include - -namespace kernel::memory -{ - - //! A generic, bitmap base frame allocator. - //! - //! This frame allocator manages the allocation state of each frame present in the system using a single bit per - //! frame. All state information is stored in a contiguous region of memory. - struct bitmap_frame_allocator final : kapi::memory::frame_allocator - { - //! Construct a new, empty bitmap allocator. - //! - //! @param storage A contiguous region of virtual memory for state storage. - //! @param total_frame The total number of frames in the system. - bitmap_frame_allocator(std::span storage, std::size_t frame_count) noexcept; - - bitmap_frame_allocator(bitmap_frame_allocator const &) = delete; - bitmap_frame_allocator(bitmap_frame_allocator &&) = delete; - auto operator=(bitmap_frame_allocator const &) -> bitmap_frame_allocator & = delete; - auto operator=(bitmap_frame_allocator &&) -> bitmap_frame_allocator & = delete; - - //! @copydoc kapi::memory::frame_allocator::allocate_many - [[nodiscard]] auto allocate_many(std::size_t count = 1) noexcept - -> std::optional> override; - - //! @copydoc kapi::memory::frame_allocator::release_many - auto release_many(std::pair frame_set) -> void override; - - //! Mark a given frame as being used. - //! - //! This function is used during bootstrap to hand the platform allocator state over to this allocator. - auto mark_used(kapi::memory::frame frame) -> void override; - - private: - [[nodiscard]] auto test(std::size_t index) const noexcept -> bool; - auto set(std::size_t index) noexcept -> void; - auto clear(std::size_t index) noexcept -> void; - - std::span m_bitmap; - std::uint64_t m_frame_count; - std::uint64_t m_last_index; - }; - -} // namespace kernel::memory - -#endif diff --git a/kernel/include/kernel/memory/block_list_allocator.hpp b/kernel/include/kernel/memory/block_list_allocator.hpp deleted file mode 100644 index 51b226ee..00000000 --- a/kernel/include/kernel/memory/block_list_allocator.hpp +++ /dev/null @@ -1,90 +0,0 @@ -#ifndef TEACHOS_KERNEL_MEMORY_BLOCK_LIST_ALLOCATOR_HPP -#define TEACHOS_KERNEL_MEMORY_BLOCK_LIST_ALLOCATOR_HPP - -#include - -#include - -#include -#include - -#include -#include - -namespace kernel::memory -{ - - //! A simple, dynamically sized heap allocator. - //! - //! This allocator is designed to perform first-fit allocation using an intrusive doubly-linked list. The heap is - //! expanded as needed. - struct block_list_allocator final : heap_allocator - { - //! Construct a new free list allocator with the given base address. - explicit block_list_allocator(kapi::memory::linear_address base) noexcept; - - block_list_allocator(block_list_allocator const &) = delete; - block_list_allocator(block_list_allocator &&) = delete; - auto operator=(block_list_allocator const &) -> block_list_allocator & = delete; - auto operator=(block_list_allocator &&) -> block_list_allocator & = delete; - - //! Allocate a block of memory with the given alignment. - //! - //! @param size The size of the block to allocate - //! @param alignment The desired alignment of the allocated block - //! @return A pointer to the beginning of the block on success, @p nullptr otherwise. - [[nodiscard]] auto allocate(kstd::units::bytes size, kstd::units::bytes alignment) noexcept -> void * override; - - //! Deallocate a block of memory previously allocated. - //! - //! @param block The pointer to the block to free. - auto deallocate(void * block) noexcept -> void override; - - private: - struct block_header final - { - kstd::units::bytes usable_size{}; - bool free{}; - block_header * next{}; - block_header * prev{}; - }; - - //! The size of the metadata required for each allocated block. - //! - //! Each allocated block carries a block header, like any unallocated one, but in addition also has a back-pointer - //! to the block header to support padding due to alignment. - constexpr auto static allocated_metadata_size = kstd::units::bytes{sizeof(block_header) + sizeof(block_header *)}; - - //! The minimum number of bytes for an allocation. - constexpr auto static minimum_allocation_size = kstd::units::bytes{16uz}; - - //! Try to expand the heap to accommodate the given size. - //! - //! @param delta The size to expand the heap by. - //! @return @p true if the heap was expanded, @p false otherwise. - auto expand(kstd::units::bytes delta) noexcept -> bool; - - //! Split a given free block to accommodate and allocation. - //! - //! The newly split free block is inserted into the free list. - //! - //! @param block The block to split. - //! @param size The size of the allocation. - //! @param padding The amount of padding to apply. - auto split(block_header * block, kstd::units::bytes size, kstd::units::bytes padding) noexcept -> void; - - //! Try to coalesce a given block with it's preceding and/or following block. - //! - //! @param block The block to coalesce. - auto coalesce(block_header * block) noexcept -> void; - - kapi::memory::linear_address m_base; - kapi::memory::linear_address m_frontier; - block_header * m_block_list; - - kstd::mutex m_lock; - }; - -} // namespace kernel::memory - -#endif diff --git a/kernel/include/kernel/memory/heap_allocator.hpp b/kernel/include/kernel/memory/heap_allocator.hpp deleted file mode 100644 index fd39bef8..00000000 --- a/kernel/include/kernel/memory/heap_allocator.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef TEACHOS_KERNEL_MEMORY_HEAP_ALLOCATOR_HPP -#define TEACHOS_KERNEL_MEMORY_HEAP_ALLOCATOR_HPP - -#include - -namespace kernel::memory -{ - - //! The interface for all kernel heap allocators. - struct heap_allocator - { - virtual ~heap_allocator() = default; - - //! Allocate a block of memory with the given alignment. - //! - //! @param size The size of the block to allocate - //! @param alignment The desired alignment of the allocated block - //! @return A pointer to the beginning of the block on success, @p nullptr otherwise. - [[nodiscard]] virtual auto allocate(kstd::units::bytes size, kstd::units::bytes alignment) noexcept -> void * = 0; - - //! Deallocate a block of memory previously allocated. - //! - //! @param block The pointer to the block to free. - virtual auto deallocate(void * block) noexcept -> void = 0; - }; - -} // namespace kernel::memory - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/memory/mmio_allocator.hpp b/kernel/include/kernel/memory/mmio_allocator.hpp deleted file mode 100644 index c7a8ed0c..00000000 --- a/kernel/include/kernel/memory/mmio_allocator.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef TEACHOS_KERNEL_MEMORY_MMIO_ALLOCATOR_HPP -#define TEACHOS_KERNEL_MEMORY_MMIO_ALLOCATOR_HPP - -#include - -#include -#include - -#include - -namespace kernel::memory -{ - - struct mmio_allocator - { - mmio_allocator(kapi::memory::linear_address base, std::size_t pages); - - [[nodiscard]] auto allocate(std::size_t page_count) -> kapi::memory::linear_address; - auto release(kapi::memory::linear_address base) -> void; - - private: - struct node - { - kapi::memory::linear_address base{}; - std::size_t page_count{}; - node * next{}; - node * previous{}; - bool is_free{}; - }; - - auto make_node(kapi::memory::linear_address base, std::size_t page_count, node * next, node * previous, - bool is_free) -> node *; - auto destroy_node(node *) -> void; - - [[no_unique_address]] kstd::allocator m_allocator{}; - node * m_head{}; - }; - -} // namespace kernel::memory - -#endif diff --git a/kernel/include/kernel/test_support/boot_modules.hpp b/kernel/include/kernel/test_support/boot_modules.hpp deleted file mode 100644 index 2343a104..00000000 --- a/kernel/include/kernel/test_support/boot_modules.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_BOOT_MODULES_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_BOOT_MODULES_HPP - -namespace kernel::tests::boot_modules -{ - //! Deinitialize the boot module registry. - auto deinit() -> void; -} // namespace kernel::tests::boot_modules - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/test_support/bump_frame_allocator.hpp b/kernel/include/kernel/test_support/bump_frame_allocator.hpp deleted file mode 100644 index a8ffd486..00000000 --- a/kernel/include/kernel/test_support/bump_frame_allocator.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_BUMP_FRAME_ALLOCATOR_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_BUMP_FRAME_ALLOCATOR_HPP - -#include - -#include -#include -#include - -namespace kernel::tests -{ - - //! A simple bump-based frame allocator used for initial test bootstrap. - //! - //! We emulate the expected behavior of a platform, in that there is a handover to the kernel PMM that needs to happen - //! during boot. The expectation of the PMM initializer is that there is an active (if simple) frame allocator it can - //! use to reserve space for it's own metadata. - //! - //! @see kapi::memory::init_pmm - struct bump_frame_allocator : kapi::memory::frame_allocator - { - //! @copydoc kapi::memory::frame_allocator::mark_used - //! - //! @note Due to the simple nature of this allocator, all frames up to the given frame will be marked as used as - //! well. - auto mark_used(kapi::memory::frame frame) -> void override - { - if (frame.number() >= next_free_frame) - { - next_free_frame = frame.number() + 1; - } - } - - //! @copydoc kapi::memory::frame_allocator::allocate_many - auto allocate_many(std::size_t count) noexcept - -> std::optional> override - { - auto start = next_free_frame; - next_free_frame += count; - return std::pair{kapi::memory::frame{start}, count}; - } - - //! @copydoc kapi::memory::frame_allocator::release_many - //! - //! @note Due to the simple nature of this allocator, frames are never actually released. - auto release_many(std::pair) -> void override {} - - //! The next free frame to be allocated. - std::size_t next_free_frame{}; - }; - -} // namespace kernel::tests - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/test_support/cio.hpp b/kernel/include/kernel/test_support/cio.hpp deleted file mode 100644 index afe27e05..00000000 --- a/kernel/include/kernel/test_support/cio.hpp +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_CIO_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_CIO_HPP - -#include - -#include - -#include - -namespace kernel::tests::cio -{ - - //! A simple output device that writes to one of the standard output streams and a log buffer. - struct output_device : kapi::cio::output_device - { - //! @copydoc kapi::cio::output_device::write - auto write(kapi::cio::output_stream stream, std::string_view text) -> void override; - - //! Get the log buffer associated with this device. - //! - //! @return The associated log buffer. - [[nodiscard]] auto log_buffer() noexcept -> kernel::tests::log_buffer &; - - private: - //! The log buffer of this device. - kernel::tests::log_buffer m_log_buffer{}; - }; - - //! Deinitialize the CIO subsystem. - auto deinit() -> void; - - //! Get the log buffer of the currently active output device. - //! - //! @throws std::runtime_error if no output device has been registered. - //! @return The currently active device's log buffer. - [[nodiscard]] auto log_buffer() -> kernel::tests::log_buffer &; -} // namespace kernel::tests::cio - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/test_support/cpu.hpp b/kernel/include/kernel/test_support/cpu.hpp deleted file mode 100644 index c99d1a72..00000000 --- a/kernel/include/kernel/test_support/cpu.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_CPU_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_CPU_HPP - -#include - -namespace kernel::tests::cpu -{ - - //! Exception thrown when the CPU is halted. - struct halt : std::runtime_error - { - //! Construct a new halt exception. - halt() - : std::runtime_error{"CPU halt requested!"} - {} - }; - - //! Deinitialize the CPU subsystem. - auto deinit() -> void; - -} // namespace kernel::tests::cpu - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/test_support/devices/block_device.hpp b/kernel/include/kernel/test_support/devices/block_device.hpp deleted file mode 100644 index 89a2bf15..00000000 --- a/kernel/include/kernel/test_support/devices/block_device.hpp +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_BLOCK_DEVICE_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_BLOCK_DEVICE_HPP - -#include - -#include -#include - -#include -#include - -namespace kernel::tests::devices -{ - - struct block_device : kernel::devices::block_device - { - block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size, size_t initial_size = 0); - - auto init() -> bool override; - - auto read_block(size_t block_index, void * buffer) const -> void override; - auto write_block(size_t block_index, void const * buffer) -> void override; - - [[nodiscard]] auto size() const -> size_t override; - - kstd::vector data{}; - }; - -} // namespace kernel::tests::devices - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/test_support/devices/character_device.hpp b/kernel/include/kernel/test_support/devices/character_device.hpp deleted file mode 100644 index aba183a1..00000000 --- a/kernel/include/kernel/test_support/devices/character_device.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_CHARACTER_DEVICE_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_CHARACTER_DEVICE_HPP - -#include - -#include -#include - -#include - -namespace kernel::tests::devices -{ - // TODO fix inheritance when character devices are implemented - struct character_device : kapi::devices::device - { - character_device(size_t major, size_t minor, kstd::string const & name); - - auto init() -> bool override; - }; - -} // namespace kernel::tests::devices - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/test_support/devices/storage/management.hpp b/kernel/include/kernel/test_support/devices/storage/management.hpp deleted file mode 100644 index 581aa914..00000000 --- a/kernel/include/kernel/test_support/devices/storage/management.hpp +++ /dev/null @@ -1,10 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_STORAGE_MANAGEMENT_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_STORAGE_MANAGEMENT_HPP - -namespace kernel::tests::devices::storage::management -{ - //! Deinitialize the storage management singleton. - auto deinit() -> void; -} // namespace kernel::tests::devices::storage::management - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/test_support/filesystem/ext2.hpp b/kernel/include/kernel/test_support/filesystem/ext2.hpp deleted file mode 100644 index 18cef1c2..00000000 --- a/kernel/include/kernel/test_support/filesystem/ext2.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP - -#include -#include - -#include -#include - -namespace kernel::tests::filesystem::ext2 -{ - auto write_bytes(kernel::tests::devices::block_device & device, size_t offset, void const * source, size_t size) - -> void; - auto write_u32(kernel::tests::devices::block_device & device, size_t offset, uint32_t value) -> void; - auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void; - auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, - kernel::filesystem::ext2::superblock const & superblock) -> void; - -} // namespace kernel::tests::filesystem::ext2 - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/test_support/filesystem/filesystem.hpp b/kernel/include/kernel/test_support/filesystem/filesystem.hpp deleted file mode 100644 index 5f260220..00000000 --- a/kernel/include/kernel/test_support/filesystem/filesystem.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILESYSTEM_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILESYSTEM_HPP - -#include -#include - -#include - -#include - -namespace kernel::tests::filesystem -{ - struct filesystem : kernel::filesystem::filesystem - { - filesystem() = default; - - [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const - -> kstd::shared_ptr override; - }; -} // namespace kernel::tests::filesystem - -#endif diff --git a/kernel/include/kernel/test_support/filesystem/inode.hpp b/kernel/include/kernel/test_support/filesystem/inode.hpp deleted file mode 100644 index 8a764375..00000000 --- a/kernel/include/kernel/test_support/filesystem/inode.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_INODE_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_INODE_HPP - -#include - -#include - -namespace kernel::tests::filesystem -{ - struct inode : kernel::filesystem::inode - { - auto read(void * buffer, size_t offset, size_t size) const -> size_t override; - auto write(void const * buffer, size_t offset, size_t size) -> size_t override; - - [[nodiscard]] auto is_regular() const -> bool override; - }; -} // namespace kernel::tests::filesystem - -#endif diff --git a/kernel/include/kernel/test_support/filesystem/open_file_table.hpp b/kernel/include/kernel/test_support/filesystem/open_file_table.hpp deleted file mode 100644 index 46b03341..00000000 --- a/kernel/include/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/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp b/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp deleted file mode 100644 index 94a66688..00000000 --- a/kernel/include/kernel/test_support/filesystem/storage_boot_module_fixture.hpp +++ /dev/null @@ -1,48 +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 - -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; - }; - - kapi::boot_modules::boot_module_registry m_registry{}; - std::vector m_module_names{}; - std::vector> m_module_data{}; - std::vector m_mapped_images{}; - - private: - auto setup_module_from_img(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/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp b/kernel/include/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp deleted file mode 100644 index 55f4b290..00000000 --- a/kernel/include/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/include/kernel/test_support/filesystem/vfs.hpp b/kernel/include/kernel/test_support/filesystem/vfs.hpp deleted file mode 100644 index 739e3530..00000000 --- a/kernel/include/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/include/kernel/test_support/log_buffer.hpp b/kernel/include/kernel/test_support/log_buffer.hpp deleted file mode 100644 index 41d9a762..00000000 --- a/kernel/include/kernel/test_support/log_buffer.hpp +++ /dev/null @@ -1,37 +0,0 @@ -#ifndef KERNEL_TEST_SUPPORT_LOG_BUFFER_HPP -#define KERNEL_TEST_SUPPORT_LOG_BUFFER_HPP - -#include -#include - -namespace kernel::tests -{ - - struct log_buffer - { - //! Append a message to this buffer. - //! @param message The message to append. - auto append(std::string const & message) -> void; - - //! Clear this buffer. - auto clear() -> void; - - //! Get all messages in this buffer as a single string. - //! - //! @return All messages in this buffer as a single string. - auto flat_messages() -> std::string; - - //! Get all messages in this buffer. - //! - //! @note Messages may be split across multiple entries if they are longer than the internal kernel print buffer - //! size. - //! - //! @return All messages in this buffer. - auto messages() -> std::vector const &; - - private: - std::vector m_messages{}; - }; -} // namespace kernel::tests - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/test_support/memory.hpp b/kernel/include/kernel/test_support/memory.hpp deleted file mode 100644 index 21030a41..00000000 --- a/kernel/include/kernel/test_support/memory.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_MEMORY_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_MEMORY_HPP - -#include - -namespace kernel::tests::memory -{ - - //! Deinitialize the memory subsystem. - auto deinit() -> void; - - //! Get the virtual base address of the simulated system. - //! - //! Since we do not have direct access to an actual MMU, we simulate the virtual address space by mapping the - //! physical address space starting at some process local virtual address. - //! - //! @return The virtual base address of the simulated system. - auto virtual_base() -> kapi::memory::linear_address; - -} // namespace kernel::tests::memory - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/test_support/page_mapper.hpp b/kernel/include/kernel/test_support/page_mapper.hpp deleted file mode 100644 index be4403b0..00000000 --- a/kernel/include/kernel/test_support/page_mapper.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_PAGE_MAPPER_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_PAGE_MAPPER_HPP - -#include - -#include - -#include - -#include -#include -#include - -namespace kernel::tests -{ - - struct page_mapper : kapi::memory::page_mapper - { - //! Construct a new page mapper. - //! - //! @param physical_size The size of the physical memory. - //! @param virtual_size The size of the virtual address space. - page_mapper(kstd::units::bytes physical_size, kstd::units::bytes virtual_size); - - //! @copydoc kapi::memory::page_mapper::map - //! - //! @throws std::invalid_argument if the page has already been mapped. - //! @throws std::runtime_error if the page cannot be mapped. - //! @throws std::runtime_error if the underlying simulated memory cannot map the page. - auto map(kapi::memory::page page, kapi::memory::frame frame, flags) -> std::byte * override; - - //! @copydoc kapi::memory::page_mapper::unmap - //! - //! @throws std::invalid_argument if the page has not been mapped. - auto unmap(kapi::memory::page page) -> void override; - - //! @copydoc kapi::memory::page_mapper::try_unmap - auto try_unmap(kapi::memory::page page) noexcept -> bool override; - - //! The simulated memory. - kernel::tests::simulated_memory memory; - - //! The simplified page table entries. - std::unordered_map page_mappings; - }; - -} // namespace kernel::tests - -#endif \ No newline at end of file diff --git a/kernel/include/kernel/test_support/simulated_memory.hpp b/kernel/include/kernel/test_support/simulated_memory.hpp deleted file mode 100644 index a201c3d7..00000000 --- a/kernel/include/kernel/test_support/simulated_memory.hpp +++ /dev/null @@ -1,67 +0,0 @@ -#ifndef TEACHOS_KERNEL_TEST_SUPPORT_SIMULATED_MEMORY_HPP -#define TEACHOS_KERNEL_TEST_SUPPORT_SIMULATED_MEMORY_HPP - -#include - -#include - -#include - -#include - -namespace kernel::tests -{ - - //! A simulated memory device that can be used in tests. - //! - //! The general idea is to provide a means to simulate the physical and virtual address spaces of the system. This - //! implementation provides for a single physical and a single virtual address space. - //! - //! @note This is not a full-featured MMU. It is a simple simulation that can be used for testing only. - struct simulated_memory - { - //! Construct a new simulated memory device. - //! @param physical_size The size of the physical memory. - //! @param virtual_size The size of the virtual address space. - simulated_memory(kstd::units::bytes physical_size, kstd::units::bytes virtual_size); - - //! Destroy this device - ~simulated_memory(); - - //! Clear the contents of the physical memory of this device. - auto clear() -> void; - - //! Get the base address of the physical memory of this device. - [[nodiscard]] auto physical_base() noexcept -> std::byte *; - - //! Get the base address of the physical memory of this device. - [[nodiscard]] auto physical_base() const noexcept -> std::byte const *; - - //! Get the size of the physical memory of this device. - [[nodiscard]] auto physical_size() const noexcept -> kstd::units::bytes; - - //! Get the base address of the virtual address space of this device. - [[nodiscard]] auto virtual_base() const noexcept -> kapi::memory::linear_address; - - //! Get the size of the virtual address space of this device. - [[nodiscard]] auto virtual_size() const noexcept -> kstd::units::bytes; - - //! Map a region of physical memory to a region of virtual memory. - //! - //! @param size The size of the region to map. - //! @param to The base address of the virtual region. - //! @param offset The offset into the physical memory to map. - //! @return A pointer to the first byte of the mapped region. - [[nodiscard]] auto map(kstd::units::bytes size, std::byte * to, off_t offset) -> std::byte *; - - private: - int m_descriptor{}; - kstd::units::bytes m_physical_size{0}; - kstd::units::bytes m_virtual_size{0}; - std::byte * m_physical_base{nullptr}; - std::byte * m_virtual_base{nullptr}; - }; - -} // namespace kernel::tests - -#endif \ No newline at end of file diff --git a/kernel/kernel/acpi/manager.cpp b/kernel/kernel/acpi/manager.cpp new file mode 100644 index 00000000..bb895fda --- /dev/null +++ b/kernel/kernel/acpi/manager.cpp @@ -0,0 +1,98 @@ +#include + +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace kernel::acpi +{ + + manager::manager(::acpi::rsdp const & sdp) + : m_sdp{&sdp} + { + if (!m_sdp->validate()) + { + kapi::system::panic("[OS:ACPI] Invalid RSDP signature!"); + } + + m_extended = m_sdp->revision() >= 2; + + if (m_extended) + { + if (!static_cast<::acpi::xsdp const *>(m_sdp)->validate()) + { + kapi::system::panic("[OS:ACPI] Invalid XSDP signature!"); + } + } + else + { + if (!m_sdp->validate()) + { + kapi::system::panic("[OS:ACPI] Invalid RSDP checksum!"); + } + } + + auto physical_address = kapi::memory::physical_address{m_sdp->table_address()}; + auto linear_address = kapi::memory::hhdm_to_linear(physical_address); + m_rsdt = static_cast<::acpi::table_header const *>(linear_address); + } + + auto manager::load_tables() -> bool + { + if (!::acpi::validate_checksum({reinterpret_cast(m_rsdt), m_rsdt->length().value})) + { + kapi::system::panic("[OS:ACPI] Invalid RSDT checksum!"); + } + + auto check_and_register_table = [&](auto table_address) -> void { + auto physical_table_address = kapi::memory::physical_address{reinterpret_cast(table_address)}; + auto mapped_table = kapi::memory::hhdm_to_linear(physical_table_address); + auto table = static_cast<::acpi::table_header const *>(mapped_table); + + if (!::acpi::validate_checksum({static_cast(mapped_table), table->length().value})) + { + kstd::println(kstd::print_sink::stderr, "[OS:ACPI] Invalid table checksum!"); + } + else + { + kstd::println("[OS:ACPI] Found '{}' ACPI table", table->signature()); + m_tables.emplace(table->signature(), table); + } + }; + + if (m_extended) + { + auto xsdt = static_cast<::acpi::xsdt const *>(m_rsdt); + std::ranges::for_each(*xsdt | std::views::transform([](auto const & entry) { return entry.address(); }), + check_and_register_table); + } + else + { + auto rsdt = static_cast<::acpi::rsdt const *>(m_rsdt); + std::ranges::for_each(*rsdt | std::views::transform([](auto const & entry) { return entry.address(); }), + check_and_register_table); + } + + return !m_tables.empty(); + } + + auto manager::get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::table_header const> + { + if (m_tables.contains(signature)) + { + return kstd::make_observer(m_tables.at(signature)); + } + return nullptr; + } + +} // namespace kernel::acpi diff --git a/kernel/kernel/acpi/manager.hpp b/kernel/kernel/acpi/manager.hpp new file mode 100644 index 00000000..1e8c1e85 --- /dev/null +++ b/kernel/kernel/acpi/manager.hpp @@ -0,0 +1,32 @@ +#ifndef TEACHOS_KERNEL_ACPI_MANAGER_HPP +#define TEACHOS_KERNEL_ACPI_MANAGER_HPP + +#include + +#include +#include +#include + +#include + +namespace kernel::acpi +{ + + struct manager + { + explicit manager(::acpi::rsdp const & sdp); + + auto load_tables() -> bool; + + auto get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::table_header const>; + + private: + ::acpi::rsdp const * m_sdp{}; + ::acpi::table_header const * m_rsdt{}; + kstd::flat_map m_tables{}; + bool m_extended{}; + }; + +} // namespace kernel::acpi + +#endif diff --git a/kernel/kernel/devices/block_device.cpp b/kernel/kernel/devices/block_device.cpp new file mode 100644 index 00000000..13d73acd --- /dev/null +++ b/kernel/kernel/devices/block_device.cpp @@ -0,0 +1,42 @@ +#include + +#include +#include + +#include + +#include + +namespace kernel::devices +{ + block_device::block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size) + : kapi::devices::device(major, minor, name) + , m_block_size(block_size) + { + if (m_block_size == 0) + { + kapi::system::panic("[DEVICES] block_device constructed with zero block size."); + } + } + + auto block_device::calculate_transfer(size_t block_index) const -> transfer_info + { + size_t const offset = block_index * m_block_size; + size_t const limit = size(); + + size_t const available = (offset < limit) ? (limit - offset) : 0; + size_t const to_transfer = (available < m_block_size) ? available : m_block_size; + + return {offset, to_transfer, m_block_size - to_transfer}; + } + + auto block_device::block_size() const -> size_t + { + return m_block_size; + } + + auto block_device::capacity() const -> size_t + { + return size(); + } +} // namespace kernel::devices \ No newline at end of file diff --git a/kernel/kernel/devices/block_device.hpp b/kernel/kernel/devices/block_device.hpp new file mode 100644 index 00000000..a6d68ee2 --- /dev/null +++ b/kernel/kernel/devices/block_device.hpp @@ -0,0 +1,97 @@ +#ifndef TEACH_OS_KERNEL_DEVICES_BLOCK_DEVICE_HPP +#define TEACH_OS_KERNEL_DEVICES_BLOCK_DEVICE_HPP + +#include + +#include + +#include + +namespace kernel::devices +{ + /** + * @brief Base interface for block-addressable devices. + */ + struct block_device : kapi::devices::device + { + /** + * @brief Create a block device descriptor. + * @param major Device major number. + * @param minor Device minor number. + * @param name Device name. + * @param block_size Size of one logical block in bytes. + */ + block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size); + + /** + * @brief Read data from the block at @p block_index into @p buffer. + * @param block_index Zero-based block index. + * @param buffer Destination buffer. + * @warning Panics if @p buffer is null. + * @note Reads up to one logical block (see constructor @p block_size). Implementations may perform a partial + * transfer for the final block when fewer than @p block_size bytes remain. + */ + virtual auto read_block(size_t block_index, void * buffer) const -> void = 0; + + /** + * @brief Write data to the block at @p block_index. + * @param block_index Zero-based block index. + * @param buffer Source buffer, must not be null. + * @warning Panics if @p buffer is null. + * @note Writes up to one logical block (see constructor @p block_size). + * Implementations may perform a partial transfer for the final block when + * fewer than @p block_size bytes remain. + */ + virtual auto write_block(size_t block_index, void const * buffer) -> void = 0; + + /** + * @brief Return logical block size in bytes. + * @return One logical block size in bytes. + */ + [[nodiscard]] auto block_size() const -> size_t; + + /** + * @brief Return device capacity in bytes. + * @return Total number of addressable bytes. + */ + [[nodiscard]] auto capacity() const -> size_t; + + /** + * @brief Override to identify block devices. + * @return true if this device is a block device, false otherwise. + */ + + [[nodiscard]] auto is_block_device() const -> bool override + { + return true; + } + + protected: + /** + * @brief Information describing the transfer window for one block index. + */ + struct transfer_info + { + size_t offset; + size_t to_transfer; + size_t remainder; + }; + + /** + * @brief Return total device size in bytes. + * @return Total number of addressable bytes. + */ + [[nodiscard]] virtual auto size() const -> size_t = 0; + + /** + * @brief Compute transfer information for @p block_index. + * @param block_index Zero-based block index. + * @return Computed transfer information for one logical block access. + */ + [[nodiscard]] auto calculate_transfer(size_t block_index) const -> transfer_info; + + size_t m_block_size; + }; +} // namespace kernel::devices + +#endif \ No newline at end of file diff --git a/kernel/kernel/devices/block_device.tests.cpp b/kernel/kernel/devices/block_device.tests.cpp new file mode 100644 index 00000000..a2ddd2bb --- /dev/null +++ b/kernel/kernel/devices/block_device.tests.cpp @@ -0,0 +1,46 @@ +#include + +#include + +#include +#include +#include +#include + +#include + +#include + +SCENARIO("Block device construction", "[devices][block_device]") +{ + GIVEN("parameters for a block device") + { + size_t major = 1; + size_t minor = 0; + kstd::string name = "test_block_device"; + size_t block_size = 512; + + WHEN("constructing a block device") + { + auto device = + kstd::make_shared(major, minor, name, block_size, 3 * block_size); + + THEN("the block device has the correct properties") + { + REQUIRE(device->major() == major); + REQUIRE(device->minor() == minor); + REQUIRE(device->name() == name); + REQUIRE(device->block_size() == block_size); + REQUIRE(device->capacity() == 3 * 512); + } + } + + WHEN("constructing a block device with zero block size") + { + THEN("the constructor panics") + { + REQUIRE_THROWS_AS((kernel::tests::devices::block_device(major, minor, name, 0)), kernel::tests::cpu::halt); + } + } + } +} diff --git a/kernel/kernel/devices/block_device_utils.cpp b/kernel/kernel/devices/block_device_utils.cpp new file mode 100644 index 00000000..18d1e9d7 --- /dev/null +++ b/kernel/kernel/devices/block_device_utils.cpp @@ -0,0 +1,106 @@ +#include + +#include + +#include +#include + +#include +#include +#include + +#include +#include + +namespace kernel::devices::block_device_utils +{ + + using block_op = void (*)(size_t idx, size_t off, size_t len, size_t done, devices::block_device * device, + std::byte * scratch, void * buffer); + + auto process_blocks(kstd::shared_ptr const & device, size_t offset, size_t size, void * buffer, + block_op op) -> size_t + { + if (buffer == nullptr) + { + kapi::system::panic("[FILESYSTEM] device_file::process_blocks called with null buffer."); + } + + if (size == 0) + { + return 0; + } + + if (!device->is_block_device()) + { + kapi::system::panic("[FILESYSTEM] device_file: expected block_device."); + } + + auto * block_dev = static_cast(device.get()); + + size_t const block_size = block_dev->block_size(); + size_t const capacity = block_dev->capacity(); + + if (offset >= capacity) + { + return 0; + } + size_t const total_to_process = std::min(size, capacity - offset); + + kstd::vector scratch_buffer{block_size}; + auto processed = 0uz; + + while (processed < total_to_process) + { + size_t const absolute_offset = offset + processed; + size_t const block_index = absolute_offset / block_size; + size_t const in_block_offset = absolute_offset % block_size; + size_t const chunk_size = std::min(total_to_process - processed, block_size - in_block_offset); + + op(block_index, in_block_offset, chunk_size, processed, block_dev, scratch_buffer.data(), buffer); + + processed += chunk_size; + } + + return processed; + } + + auto read(kstd::shared_ptr const & device, void * buffer, size_t offset, size_t size) -> size_t + { + return process_blocks(device, offset, size, buffer, + [](size_t idx, size_t off, size_t len, size_t done, devices::block_device * device, + std::byte * scratch, void * buffer) { + auto * out = static_cast(buffer); + if (off == 0 && len == device->block_size()) + { + device->read_block(idx, out + done); + } + else + { + device->read_block(idx, scratch); + kstd::libc::memcpy(out + done, scratch + off, len); + } + }); + } + + auto write(kstd::shared_ptr const & device, void const * buffer, size_t offset, size_t size) + -> size_t + { + return process_blocks(device, offset, size, const_cast(buffer), + [](size_t idx, size_t off, size_t len, size_t done, devices::block_device * device, + std::byte * scratch, void * buffer) { + auto const * in = static_cast(buffer); + if (off == 0 && len == device->block_size()) + { + device->write_block(idx, in + done); + } + else + { + device->read_block(idx, scratch); + kstd::libc::memcpy(scratch + off, in + done, len); + device->write_block(idx, scratch); + } + }); + } + +} // namespace kernel::devices::block_device_utils \ No newline at end of file diff --git a/kernel/kernel/devices/block_device_utils.hpp b/kernel/kernel/devices/block_device_utils.hpp new file mode 100644 index 00000000..8be75b6f --- /dev/null +++ b/kernel/kernel/devices/block_device_utils.hpp @@ -0,0 +1,44 @@ +#ifndef TEACH_OS_KERNEL_DEVICES_BLOCK_DEVICE_UTILS_HPP +#define TEACH_OS_KERNEL_DEVICES_BLOCK_DEVICE_UTILS_HPP + +#include + +#include + +#include + +namespace kernel::devices::block_device_utils +{ + /** + @brief Utility functions for block devices, such as reading/writing data at specific offsets. These functions handle + the necessary logic to interact with block devices, such as calculating block boundaries and ensuring proper access + patterns. They abstract away the details of block device interactions, providing a simple interface for reading and + writing data to block devices. + */ + + /** + @brief Reads data from a @p device into a @p buffer, starting at a specific @p offset and for a given @p size. + @warning Panics if @p buffer or @p device is null. + @param device The block device to read from. + @param buffer The buffer to read data into. + @param offset The offset on the block device to start reading from. + @param size The number of bytes to read. + @return The number of bytes actually read, which may be less than the requested size. + */ + auto read(kstd::shared_ptr const & device, void * buffer, size_t offset, size_t size) + -> size_t; + + /** + @brief Writes data from a @p buffer to a @p device, starting at a specific @p offset and for a given @p size. + @warning Panics if @p buffer or @p device is null. + @param device The block device to write to. + @param buffer The buffer to write data from. + @param offset The offset on the block device to start writing to. + @param size The number of bytes to write. + @return The number of bytes actually written, which may be less than the requested size. + */ + auto write(kstd::shared_ptr const & device, void const * buffer, size_t offset, size_t size) + -> size_t; +} // namespace kernel::devices::block_device_utils + +#endif \ No newline at end of file diff --git a/kernel/kernel/devices/block_device_utils.tests.cpp b/kernel/kernel/devices/block_device_utils.tests.cpp new file mode 100644 index 00000000..e2e1e65e --- /dev/null +++ b/kernel/kernel/devices/block_device_utils.tests.cpp @@ -0,0 +1,219 @@ +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +SCENARIO("reading from a block device with block_device_utils", "[devices][block_device_utils]") +{ + GIVEN("a block device with known data") + { + auto const block_size = 512; + auto device = kstd::make_shared(0, 0, "test_block_device", block_size); + kstd::vector block_data(block_size); + for (size_t i = 0; i < block_data.size(); ++i) + { + block_data[i] = static_cast(i % 256); + } + device->write_block(0, block_data.data()); + device->write_block(1, block_data.data()); + + WHEN("reading from the block device using block_device_utils") + { + kstd::vector read_buffer(block_size); + auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, read_buffer.size()); + + THEN("the correct number of bytes is read") + { + REQUIRE(bytes_read == read_buffer.size()); + } + + THEN("the data read matches the data written to the block device") + { + REQUIRE(read_buffer == block_data); + } + } + + WHEN("reading over block boundaries") + { + kstd::vector read_buffer(1024); + auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 256, read_buffer.size()); + + THEN("the correct number of bytes is read") + { + REQUIRE(bytes_read == 1.5 * block_size); + } + + THEN("the data read matches the expected data across block boundaries") + { + for (size_t i = 0; i < bytes_read; ++i) + { + uint8_t expected_value = static_cast((256 + i) % 256); + REQUIRE(read_buffer[i] == expected_value); + } + } + } + + WHEN("reading beyond the device capacity") + { + kstd::vector read_buffer(block_size); + auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 1024, read_buffer.size()); + + THEN("no bytes are read") + { + REQUIRE(bytes_read == 0); + } + } + + WHEN("reading nothing") + { + kstd::vector read_buffer(block_size); + auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, 0); + + THEN("no bytes are read") + { + REQUIRE(bytes_read == 0); + } + } + + WHEN("reading with a null buffer") + { + THEN("the system panics") + { + REQUIRE_THROWS_AS(kernel::devices::block_device_utils::read(device, nullptr, 0, 512), kernel::tests::cpu::halt); + } + } + } +} + +SCENARIO("writing to a block device using block_device_utils", "[devices][block_device_utils]") +{ + GIVEN("a block device") + { + auto const block_size = 512; + auto device = + kstd::make_shared(0, 0, "test_block_device", block_size, 2 * block_size); + + WHEN("writing to the block device using block_device_utils") + { + kstd::vector write_buffer(block_size); + for (size_t i = 0; i < write_buffer.size(); ++i) + { + write_buffer[i] = static_cast(i % 256); + } + + auto bytes_written = + kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, write_buffer.size()); + + THEN("the correct number of bytes is written") + { + REQUIRE(bytes_written == write_buffer.size()); + } + + THEN("the data written matches the data read back from the block device") + { + kstd::vector read_buffer(block_size); + device->read_block(0, read_buffer.data()); + REQUIRE(read_buffer == write_buffer); + } + } + + WHEN("writing over block boundaries") + { + kstd::vector write_buffer(2 * block_size); + for (size_t i = 0; i < write_buffer.size(); ++i) + { + write_buffer[i] = static_cast(i % 256); + } + + auto bytes_written = + kernel::devices::block_device_utils::write(device, write_buffer.data(), 256, write_buffer.size()); + + THEN("the correct number of bytes is written") + { + REQUIRE(bytes_written == 1.5 * block_size); + } + + THEN("the data written matches the data read back from the block device across block boundaries") + { + kstd::vector read_buffer(2 * block_size); + auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 256, 2 * block_size); + + for (size_t i = 0; i < bytes_read; ++i) + { + REQUIRE(read_buffer[i] == write_buffer[i]); + } + } + } + + WHEN("writing beyond the device capacity") + { + kstd::vector write_buffer(block_size); + auto bytes_written = + kernel::devices::block_device_utils::write(device, write_buffer.data(), 1024, write_buffer.size()); + + THEN("no bytes are written") + { + REQUIRE(bytes_written == 0); + } + } + + WHEN("writing nothing") + { + kstd::vector write_buffer(block_size); + auto bytes_written = kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, 0); + + THEN("no bytes are written") + { + REQUIRE(bytes_written == 0); + } + } + + WHEN("writing with a null buffer") + { + THEN("the system panics") + { + REQUIRE_THROWS_AS(kernel::devices::block_device_utils::write(device, nullptr, 0, block_size), + kernel::tests::cpu::halt); + } + } + } +} + +SCENARIO("block_device_utils with a non-block device", "[devices][block_device_utils]") +{ + GIVEN("a non-block device") + { + auto device = kstd::make_shared(0, 0, "test_character_device"); + + WHEN("attempting to read from the non-block device using block_device_utils") + { + kstd::vector read_buffer(512); + THEN("the system panics") + { + REQUIRE_THROWS_AS(kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, read_buffer.size()), + kernel::tests::cpu::halt); + } + } + + WHEN("attempting to write to the non-block device using block_device_utils") + { + kstd::vector write_buffer(512); + THEN("the system panics") + { + REQUIRE_THROWS_AS( + kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, write_buffer.size()), + kernel::tests::cpu::halt); + } + } + } +} diff --git a/kernel/kernel/devices/root_bus.cpp b/kernel/kernel/devices/root_bus.cpp new file mode 100644 index 00000000..1b754f23 --- /dev/null +++ b/kernel/kernel/devices/root_bus.cpp @@ -0,0 +1,12 @@ +#include + +#include + +namespace kernel::devices +{ + + root_bus::root_bus() + : kapi::devices::bus{0, 0, "system"} + {} + +} // namespace kernel::devices \ No newline at end of file diff --git a/kernel/kernel/devices/root_bus.hpp b/kernel/kernel/devices/root_bus.hpp new file mode 100644 index 00000000..c8fee520 --- /dev/null +++ b/kernel/kernel/devices/root_bus.hpp @@ -0,0 +1,16 @@ +#ifndef TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP +#define TEACHOS_KERNEL_DEVICES_ROOT_BUS_HPP + +#include + +namespace kernel::devices +{ + + struct root_bus final : kapi::devices::bus + { + root_bus(); + }; + +} // namespace kernel::devices + +#endif \ No newline at end of file diff --git a/kernel/kernel/devices/storage/controller.cpp b/kernel/kernel/devices/storage/controller.cpp new file mode 100644 index 00000000..171b918e --- /dev/null +++ b/kernel/kernel/devices/storage/controller.cpp @@ -0,0 +1,44 @@ +#include + +#include + +#include +#include + +#include +#include + +namespace kernel::devices::storage +{ + auto controller::set_ids(size_t major, size_t minors_per_dev) -> void + { + m_major = major; + m_minors_per_device = minors_per_dev; + } + + auto controller::major() const -> size_t + { + return m_major; + } + + auto controller::device_by_minor(size_t minor) const -> kstd::shared_ptr + { + auto it = std::ranges::find_if(m_devices, [minor](auto const & device) { return device->minor() == minor; }); + + if (it != m_devices.end()) + { + return *it; + } + return nullptr; + } + + auto controller::devices_count() const -> size_t + { + return m_devices.size(); + } + + auto controller::all_devices() const -> kstd::vector> const & + { + return m_devices; + } +} // namespace kernel::devices::storage \ No newline at end of file diff --git a/kernel/kernel/devices/storage/controller.hpp b/kernel/kernel/devices/storage/controller.hpp new file mode 100644 index 00000000..bea18f38 --- /dev/null +++ b/kernel/kernel/devices/storage/controller.hpp @@ -0,0 +1,71 @@ +#ifndef TEACH_OS_KERNEL_DEVICES_STORAGE_CONTROLLER_HPP +#define TEACH_OS_KERNEL_DEVICES_STORAGE_CONTROLLER_HPP + +#include + +#include +#include + +#include + +namespace kernel::devices::storage +{ + /** + * @brief Base interface for storage controllers. + * + * A storage controller probes for devices and resolves devices by major/minor + * numbers. + */ + struct controller + { + /** + * @brief Virtual destructor. + */ + virtual ~controller() = default; + + /** + * @brief Probe the controller and register discovered devices. + */ + virtual auto probe() -> void = 0; + + /** + * @brief Assign the major number and minor stride for this controller. + * @param major Major number assigned to this controller. + * @param minors_per_dev Minor number stride between devices. + */ + auto set_ids(size_t major, size_t minors_per_dev) -> void; + + /** + * @brief Return the assigned major number. + * @return Assigned major number. + */ + [[nodiscard]] auto major() const -> size_t; + + /** + * @brief Return the number of devices managed by this controller. + * @return Number of managed devices. + */ + [[nodiscard]] auto devices_count() const -> size_t; + + /** + * @brief Return all devices managed by this controller. + * @return Vector of all managed devices. + */ + [[nodiscard]] auto all_devices() const -> kstd::vector> const &; + + /** + * @brief Find a managed device by major/minor numbers. + * @param major Device major number. + * @param minor Device minor number. + * @return Matching block device, or nullptr if no device matches. + */ + [[nodiscard]] auto device_by_minor(size_t minor) const -> kstd::shared_ptr; + + protected: + size_t m_major{}; + size_t m_minors_per_device{}; + kstd::vector> m_devices{}; + }; +} // namespace kernel::devices::storage + +#endif \ No newline at end of file diff --git a/kernel/kernel/devices/storage/management.cpp b/kernel/kernel/devices/storage/management.cpp new file mode 100644 index 00000000..06efc27d --- /dev/null +++ b/kernel/kernel/devices/storage/management.cpp @@ -0,0 +1,95 @@ +#include + +#include +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include + +namespace +{ + constexpr size_t static minors_per_device = 16; + constexpr size_t static start_major = 1; + constinit size_t static next_free_major = start_major; + + constinit auto static storage_management = std::optional{}; +} // namespace + +namespace kernel::devices::storage +{ + auto management::init() -> void + { + if (storage_management) + { + kapi::system::panic("[DEVICES] Storage management has already been initialized."); + } + storage_management.emplace(management{}); + + auto ram_disk_controller = kstd::make_shared(&kapi::boot_modules::get_boot_module_registry()); + storage_management->add_controller(ram_disk_controller); + + std::ranges::for_each(storage_management->m_controllers, [](auto controller) { controller->probe(); }); + } + + auto management::get() -> management & + { + if (!storage_management) + { + kapi::system::panic("[DEVICES] Storage management has not been initialized."); + } + + return *storage_management; + } + + auto management::add_controller(kstd::shared_ptr const & controller) -> void + { + controller->set_ids(next_free_major++, minors_per_device); + m_controllers.push_back(controller); + } + + auto management::all_controllers() const -> kstd::vector> const & + { + return m_controllers; + } + + auto management::device_by_major_minor(size_t major, size_t minor) -> kstd::shared_ptr + { + auto found = std::ranges::find_if(m_controllers, [=](auto const & controller) { + if (controller != nullptr && controller->major() == major) + { + return controller->device_by_minor(minor) != nullptr; + } + return false; + }); + + if (found != std::ranges::cend(m_controllers)) + { + return found->get()->device_by_minor(minor); + } + + return nullptr; + } + + auto management::determine_boot_device() -> kstd::shared_ptr + { + return device_by_major_minor(start_major, 0); + } +} // namespace kernel::devices::storage + +namespace kernel::tests::devices::storage::management +{ + auto deinit() -> void + { + storage_management.reset(); + next_free_major = start_major; + } +} // namespace kernel::tests::devices::storage::management diff --git a/kernel/kernel/devices/storage/management.hpp b/kernel/kernel/devices/storage/management.hpp new file mode 100644 index 00000000..9a840875 --- /dev/null +++ b/kernel/kernel/devices/storage/management.hpp @@ -0,0 +1,78 @@ +#ifndef TEACH_OS_KERNEL_DEVICES_STORAGE_MANAGEMENT_HPP +#define TEACH_OS_KERNEL_DEVICES_STORAGE_MANAGEMENT_HPP + +#include + +#include + +#include +#include + +#include + +namespace kernel::devices::storage +{ + /** + * @brief Global storage subsystem manager. + * + * Owns registered storage controllers and provides device lookup by + * major/minor numbers. + */ + struct management + { + /** + * @brief Initialize global storage management. + * + * Creates the singleton instance, registers controllers and probes + * them for devices. + * + * @warning Panics if called more than once. + */ + auto static init() -> void; + + /** + * @brief Return the active storage manager singleton. + * @return Reference to the active storage manager. + * @warning Panics if storage management has not been initialized. + */ + auto static get() -> management &; + + /** + * @brief Register a storage controller. + * @param controller Controller to register. + * + * Assigns controller IDs (major number range and minors per device). + */ + auto add_controller(kstd::shared_ptr const & controller) -> void; + + /** + * @brief Return all registered storage controllers. + * @return Vector of all registered storage controllers. + */ + [[nodiscard]] auto all_controllers() const -> kstd::vector> const &; + + /** + * @brief Find a device by major/minor numbers. + * @param major Device major number. + * @param minor Device minor number. + * @return Matching device, or nullptr if no device matches. + */ + auto device_by_major_minor(size_t major, size_t minor) -> kstd::shared_ptr; + + /** + * @brief Determine the boot device. + * @return Boot device, or nullptr if it cannot be determined. + */ + auto determine_boot_device() -> kstd::shared_ptr; + + private: + /** + * @brief Private default constructor for storage management singleton. + */ + management() = default; + + kstd::vector> m_controllers{}; + }; +} // namespace kernel::devices::storage + +#endif \ No newline at end of file diff --git a/kernel/kernel/devices/storage/ram_disk/controller.cpp b/kernel/kernel/devices/storage/ram_disk/controller.cpp new file mode 100644 index 00000000..30441fa5 --- /dev/null +++ b/kernel/kernel/devices/storage/ram_disk/controller.cpp @@ -0,0 +1,27 @@ +#include + +#include + +#include + +#include + +#include +#include + +namespace kernel::devices::storage::ram_disk +{ + controller::controller(kapi::boot_modules::boot_module_registry const * registry) + : m_boot_module_registry(registry) + {} + + auto controller::probe() -> void + { + size_t current_device_index = 0; + + std::ranges::for_each(*m_boot_module_registry, [this, ¤t_device_index](auto const & module) { + auto const minor = current_device_index++ * m_minors_per_device; + m_devices.push_back(kstd::make_shared(module, m_major, minor)); + }); + } +} // namespace kernel::devices::storage::ram_disk \ No newline at end of file diff --git a/kernel/kernel/devices/storage/ram_disk/controller.hpp b/kernel/kernel/devices/storage/ram_disk/controller.hpp new file mode 100644 index 00000000..93cf30d8 --- /dev/null +++ b/kernel/kernel/devices/storage/ram_disk/controller.hpp @@ -0,0 +1,31 @@ +#ifndef TEACH_OS_KERNEL_DEVICES_STORAGE_RAM_DISK_CONTROLLER_HPP +#define TEACH_OS_KERNEL_DEVICES_STORAGE_RAM_DISK_CONTROLLER_HPP + +#include + +#include + +namespace kernel::devices::storage::ram_disk +{ + /** + * @brief Storage controller that exposes boot modules as RAM-disk devices. + */ + struct controller : kernel::devices::storage::controller + { + /** + * @brief Create a RAM-disk controller. + * @param registry Boot module registry as device source. + */ + explicit controller(kapi::boot_modules::boot_module_registry const * registry); + + /** + * @brief Probe boot modules and create RAM-disk devices. + */ + auto probe() -> void override; + + private: + kapi::boot_modules::boot_module_registry const * m_boot_module_registry; + }; +} // namespace kernel::devices::storage::ram_disk + +#endif \ No newline at end of file diff --git a/kernel/kernel/devices/storage/ram_disk/device.cpp b/kernel/kernel/devices/storage/ram_disk/device.cpp new file mode 100644 index 00000000..1557204b --- /dev/null +++ b/kernel/kernel/devices/storage/ram_disk/device.cpp @@ -0,0 +1,71 @@ +#include + +#include + +#include +#include + +#include +#include + +#include + +namespace kernel::devices::storage::ram_disk +{ + namespace + { + constexpr size_t ram_disk_block_size = 512uz; + } // namespace + + device::device(kapi::boot_modules::boot_module const & module, size_t major, size_t minor) + : block_device(major, minor, "ram" + kstd::to_string(minor), ram_disk_block_size) + , m_boot_module(module) + {} + + auto device::init() -> bool + { + return m_boot_module.start_address.raw() != 0 && m_boot_module.size > 0; + } + + auto device::read_block(size_t block_index, void * buffer) const -> void + { + if (buffer == nullptr) + { + kapi::system::panic("[RAM DISK DEVICE] read_block called with null buffer."); + } + + auto const info = calculate_transfer(block_index); + + if (info.to_transfer > 0) + { + auto const src = static_cast(m_boot_module.start_address) + info.offset; + kstd::libc::memcpy(buffer, src, info.to_transfer); + } + + if (info.remainder > 0) + { + kstd::libc::memset(static_cast(buffer) + info.to_transfer, 0, info.remainder); + } + } + + auto device::write_block(size_t block_index, void const * buffer) -> void + { + if (buffer == nullptr) + { + kapi::system::panic("[RAM DISK DEVICE] write_block called with null buffer."); + } + + auto const info = calculate_transfer(block_index); + + if (info.to_transfer > 0) + { + auto const dest = static_cast(m_boot_module.start_address) + info.offset; + kstd::libc::memcpy(dest, buffer, info.to_transfer); + } + } + + auto device::size() const -> size_t + { + return m_boot_module.size; + } +} // namespace kernel::devices::storage::ram_disk \ No newline at end of file diff --git a/kernel/kernel/devices/storage/ram_disk/device.hpp b/kernel/kernel/devices/storage/ram_disk/device.hpp new file mode 100644 index 00000000..89789ea9 --- /dev/null +++ b/kernel/kernel/devices/storage/ram_disk/device.hpp @@ -0,0 +1,58 @@ +#ifndef TEACH_OS_KERNEL_DEVICES_STORAGE_RAM_DISK_DEVICE_HPP +#define TEACH_OS_KERNEL_DEVICES_STORAGE_RAM_DISK_DEVICE_HPP + +#include + +#include + +#include + +namespace kernel::devices::storage::ram_disk +{ + /** + * @brief Block device for a boot module. + */ + struct device : block_device + { + /** + * @brief Create a RAM disk for the @p module. + * @param module Boot module providing the memory region. + * @param major Device major number. + * @param minor Device minor number. + */ + device(kapi::boot_modules::boot_module const & module, size_t major, size_t minor); + + /** + * @brief Initialize the RAM disk device. + * @return true if module backing memory is valid, false otherwise. + */ + auto init() -> bool override; + + /** + * @brief Read one logical block into @p buffer. + * @param block_index Zero-based block index. + * @param buffer Destination buffer, must not be null. + * @note If the request reaches the module end, only available bytes are copied and the rest of the + * logical block is filled with zeros. + */ + auto read_block(size_t block_index, void * buffer) const -> void override; + + /** + * @brief Write one logical block from @p buffer. + * @param block_index Zero-based block index. + * @param buffer Source buffer, must not be null. + * @note If the request reaches the module end, only the bytes in the module range are written. + */ + auto write_block(size_t block_index, void const * buffer) -> void override; + + private: + /** + * @brief Return module size in bytes. + */ + [[nodiscard]] auto size() const -> size_t override; + + kapi::boot_modules::boot_module m_boot_module{}; + }; +} // namespace kernel::devices::storage::ram_disk + +#endif \ No newline at end of file diff --git a/kernel/kernel/devices/storage/ram_disk/device.tests.cpp b/kernel/kernel/devices/storage/ram_disk/device.tests.cpp new file mode 100644 index 00000000..d0fab76b --- /dev/null +++ b/kernel/kernel/devices/storage/ram_disk/device.tests.cpp @@ -0,0 +1,116 @@ +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +SCENARIO("RAM Disk Device Construction and Initialization", "[ram_disk_device]") +{ + GIVEN("an empty boot module") + { + kapi::boot_modules::boot_module boot_module{}; + boot_module.start_address = kapi::memory::linear_address{nullptr}; + boot_module.size = 0; + + WHEN("constructing the device") + { + kernel::devices::storage::ram_disk::device device{boot_module, 0, 0}; + + THEN("init return false") + { + REQUIRE_FALSE(device.init()); + } + } + } + + GIVEN("a boot module with a valid start address and size") + { + kapi::boot_modules::boot_module boot_module{}; + boot_module.start_address = kapi::memory::linear_address{reinterpret_cast(0x1000)}; + boot_module.size = 512; + + WHEN("constructing the device") + { + kernel::devices::storage::ram_disk::device device{boot_module, 0, 0}; + + THEN("init return true") + { + REQUIRE(device.init()); + } + } + } +} + +SCENARIO("RAM Disk Device Read and Write", "[ram_disk_device]") +{ + GIVEN("a device initialized with a valid boot module") + { + auto storage = std::vector{4096, std::byte{0xff}}; + auto module = + kapi::boot_modules::boot_module{"test_module", kapi::memory::linear_address{storage.data()}, storage.size()}; + auto device = kernel::devices::storage::ram_disk::device{module, 0, 0}; + REQUIRE(device.init()); + + WHEN("reading a full block from the device") + { + auto buffer = std::vector(device.block_size()); + device.read_block(0, buffer.data()); + + THEN("the buffer is filled with the module data") + { + REQUIRE_THAT(buffer, Catch::Matchers::RangeEquals(std::views::take(storage, device.block_size()))); + } + } + + WHEN("reading from a block index beyond the module size") + { + auto buffer = std::vector(device.block_size()); + device.read_block(10, buffer.data()); + + THEN("the buffer is filled with zeros") + { + REQUIRE_THAT(buffer, Catch::Matchers::RangeEquals(std::vector(device.block_size(), std::byte{0}))); + } + } + + WHEN("reading into a null buffer") + { + REQUIRE_THROWS_AS(device.read_block(0, nullptr), std::runtime_error); + } + + WHEN("writing to a full block") + { + auto buffer = std::vector(device.block_size(), std::byte{0x01}); + device.write_block(0, buffer.data()); + + THEN("the module data is updated") + { + REQUIRE_THAT(std::views::take(storage, device.block_size()), Catch::Matchers::RangeEquals(buffer)); + } + } + + WHEN("writing to a block index beyond the module size") + { + auto buffer = std::vector(device.block_size(), std::byte{0x01}); + device.write_block(10, buffer.data()); + + THEN("the module data is not updated") + { + REQUIRE_THAT(storage, Catch::Matchers::RangeEquals(std::vector{4096, std::byte{0xff}})); + } + } + + WHEN("writing from a null buffer") + { + REQUIRE_THROWS_AS(device.write_block(0, nullptr), std::runtime_error); + } + } +} \ No newline at end of file diff --git a/kernel/kernel/filesystem/constants.hpp b/kernel/kernel/filesystem/constants.hpp new file mode 100644 index 00000000..8388d058 --- /dev/null +++ b/kernel/kernel/filesystem/constants.hpp @@ -0,0 +1,14 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_CONSTANTS_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_CONSTANTS_HPP + +#include + +namespace kernel::filesystem::constants +{ + constexpr size_t inline max_path_length = 4096; + + constexpr size_t inline symlink_max_path_length = 4096; + 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 new file mode 100644 index 00000000..01de276c --- /dev/null +++ b/kernel/kernel/filesystem/dentry.cpp @@ -0,0 +1,95 @@ +#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::get_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 new file mode 100644 index 00000000..096a6bbb --- /dev/null +++ b/kernel/kernel/filesystem/dentry.hpp @@ -0,0 +1,104 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_DENTRY_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_DENTRY_HPP + +#include + +#include +#include +#include + +#include +#include + +namespace kernel::filesystem +{ + /** + @brief Represents a directory entry (dentry) in the filesystem. A dentry is a node in the directory tree that + represents a file or directory. It contains a reference to its parent dentry, a reference to the associated real + filesystem inode, and a list of child dentries. + */ + struct dentry + { + /** + @brief Flags for the dentry. + */ + enum class dentry_flags : uint32_t + { + is_mount_point = 1 << 0 + }; + + /** + @brief Create a dentry with the given @p parent, associated @p inode, and optional @p name. The dentry is + initialized with the provided parent and inode, and the name is stored for lookup purposes. + @param parent The parent dentry. + @param inode The associated inode for this dentry. + @param name The name of the dentry (optional). + */ + dentry(kstd::shared_ptr const & parent, kstd::shared_ptr const & inode, std::string_view name); + + /** + @brief Get the associated inode. + @return A reference to the associated inode. + */ + [[nodiscard]] auto get_inode() const -> kstd::shared_ptr const &; + + /** + @brief Get the parent dentry. + @return A reference to the parent dentry. + */ + [[nodiscard]] auto parent() const -> kstd::shared_ptr; + + /** + @brief Get the name of the dentry. + @return The name of the dentry. + */ + [[nodiscard]] auto name() const -> std::string_view; + + /** + @brief Get the full path of the dentry by traversing up to the root. + @return The full path of the dentry. + */ + [[nodiscard]] auto absolute_path() const -> kstd::string; + + /** + @brief Add a @p child dentry. + @param child The child dentry to add. + */ + auto add_child(kstd::shared_ptr const & child) -> void; + + /** + @brief Find a child dentry by @p name. + @param name The name of the child dentry to find. + @return A pointer to the found child dentry, or a null pointer if not found. + */ + [[nodiscard]] auto find_child(std::string_view name) const -> kstd::shared_ptr; + + /** + @brief Set a @p flag for the dentry. + @param flag The flag to set. + */ + auto set_flag(dentry_flags flag) -> void; + + /** + @brief Unset a @p flag for the dentry. + @param flag The flag to unset. + */ + auto unset_flag(dentry_flags flag) -> void; + + /** + @brief Check if the dentry has a specific @p flag. + @param flag The flag to check. + @return True if the dentry has the flag, false otherwise. + */ + [[nodiscard]] auto has_flag(dentry_flags flag) const -> bool; + + private: + kstd::string m_name; + kstd::weak_ptr m_parent; + kstd::vector> m_children; + kstd::shared_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 new file mode 100644 index 00000000..b7690f57 --- /dev/null +++ b/kernel/kernel/filesystem/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::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.get_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.get_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 new file mode 100644 index 00000000..ce887ff3 --- /dev/null +++ b/kernel/kernel/filesystem/devfs/filesystem.cpp @@ -0,0 +1,81 @@ +#include + +#include "kernel/filesystem/filesystem.hpp" +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include + +namespace kernel::filesystem::devfs +{ + struct type final : kernel::filesystem::type + { + [[nodiscard]] auto name() const noexcept -> std::string_view override + { + return "devfs"; + } + + [[nodiscard]] auto requires_device() const noexcept -> bool override + { + return false; + } + + [[nodiscard]] auto make_instance() const -> kstd::shared_ptr override + { + return kstd::make_shared(); + } + }; + + [[gnu::used]] + constexpr auto registration = type_registration{}; + + auto filesystem::mount(kstd::shared_ptr const &) -> operation_result + { + m_root_inode = kstd::make_shared(); + build_device_inode_table(); + + return operation_result::success; + } + + auto filesystem::lookup(kstd::shared_ptr const & parent, std::string_view name) const + -> kstd::shared_ptr + { + if (!parent || !parent->is_directory()) + { + return nullptr; + } + + if (parent.get() != m_root_inode.get()) + { + return nullptr; + } + + auto it = std::ranges::find_if(m_inodes, [&](auto const & dev_node) { + if (auto device_inode_ptr = static_cast(dev_node.get())) + { + return device_inode_ptr->device()->name() == name; + } + return false; + }); + return (it != m_inodes.end()) ? *it : nullptr; + } + + auto filesystem::build_device_inode_table() -> void + { + m_inodes.clear(); + + auto storage_mgmt = devices::storage::management::get(); + std::ranges::for_each(storage_mgmt.all_controllers(), [&](auto const & controller) { + std::ranges::for_each(controller->all_devices(), + [&](auto const & device) { m_inodes.push_back(kstd::make_shared(device)); }); + }); + } +} // 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 new file mode 100644 index 00000000..dbaa387b --- /dev/null +++ b/kernel/kernel/filesystem/devfs/filesystem.hpp @@ -0,0 +1,46 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_DEVFS_FILESYSTEM_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_DEVFS_FILESYSTEM_HPP + +#include +#include +#include + +#include +#include + +#include + +namespace kernel::filesystem::devfs +{ + /** + @brief A filesystem for managing device nodes in the virtual filesystem. This filesystem provides a way to represent + devices as files in the /dev directory, allowing user-space applications to interact with devices using standard file + operations. The devfs filesystem dynamically creates inodes for devices registered in the system, enabling seamless + access to device functionality through the filesystem interface. + */ + struct filesystem : kernel::filesystem::filesystem + { + /** + @brief Initializes the devfs instance and builds the device inode table. + @param backing_inode Backing inode passed by the vfs (not required by devfs). + @return The result of the mount operation. + */ + auto mount(kstd::shared_ptr const & backing_inode) -> operation_result override; + + /** + @brief Looks up an inode by @p name within a @p parent directory. + @param parent The parent directory inode. + @param name The name of the inode to look up. + @return A pointer to the found inode, or a null pointer if not found. + */ + [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const + -> kstd::shared_ptr override; + + private: + auto build_device_inode_table() -> void; + + kstd::vector> m_inodes{}; + }; +} // 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 new file mode 100644 index 00000000..36cb4117 --- /dev/null +++ b/kernel/kernel/filesystem/devfs/filesystem.tests.cpp @@ -0,0 +1,72 @@ +#include + +#include +#include + +#include + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, + "Devfs filesystem lookup uses storage management devices", "[filesystem][devfs][filesystem]") +{ + GIVEN("a boot module registry with one module") + { + setup_modules(1); + + auto fs = kernel::filesystem::devfs::filesystem{}; + auto result = fs.mount(nullptr); + + THEN("mount succeeds") + { + REQUIRE(result == kernel::filesystem::filesystem::operation_result::success); + REQUIRE(fs.root_inode() != nullptr); + } + + THEN("lookup on root finds ram0 device inode") + { + auto inode = fs.lookup(fs.root_inode(), "ram0"); + REQUIRE(inode != nullptr); + REQUIRE(inode->is_device()); + } + + THEN("lookup of an unknown device returns null") + { + auto inode = fs.lookup(fs.root_inode(), "ram99"); + REQUIRE(inode == nullptr); + } + + THEN("lookup with wrong parent returns null") + { + auto other_fs = kernel::filesystem::devfs::filesystem{}; + other_fs.mount(nullptr); + + auto inode = fs.lookup(other_fs.root_inode(), "ram0"); + REQUIRE(inode == nullptr); + } + + THEN("lookup with a non-directory parent returns null") + { + auto non_directory_inode = fs.lookup(fs.root_inode(), "ram0"); + REQUIRE(non_directory_inode != nullptr); + REQUIRE_FALSE(non_directory_inode->is_directory()); + + auto result = fs.lookup(non_directory_inode, "anything"); + REQUIRE(result == nullptr); + } + } + + GIVEN("a boot module registry with three modules") + { + setup_modules(3, 2048); + + auto fs = kernel::filesystem::devfs::filesystem{}; + auto result = fs.mount(nullptr); + REQUIRE(result == kernel::filesystem::filesystem::operation_result::success); + + THEN("lookup finds all generated RAM devices") + { + REQUIRE(fs.lookup(fs.root_inode(), "ram0") != nullptr); + REQUIRE(fs.lookup(fs.root_inode(), "ram16") != nullptr); + REQUIRE(fs.lookup(fs.root_inode(), "ram32") != nullptr); + } + } +} diff --git a/kernel/kernel/filesystem/devfs/inode.cpp b/kernel/kernel/filesystem/devfs/inode.cpp new file mode 100644 index 00000000..7bbfbbed --- /dev/null +++ b/kernel/kernel/filesystem/devfs/inode.cpp @@ -0,0 +1,21 @@ +#include + +#include + +namespace kernel::filesystem::devfs +{ + auto inode::read(void *, size_t, size_t) const -> size_t + { + return 0; + } + + auto inode::write(void const *, size_t, size_t) -> size_t + { + return 0; + } + + auto inode::is_directory() const -> bool + { + return true; + } +} // 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 new file mode 100644 index 00000000..e4288918 --- /dev/null +++ b/kernel/kernel/filesystem/devfs/inode.hpp @@ -0,0 +1,42 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_DEVFS_INODE_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_DEVFS_INODE_HPP + +#include + +#include + +namespace kernel::filesystem::devfs +{ + /** + @brief Inode implementation for the devfs filesystem. + This inode represents root device node in the /dev directory. + */ + struct inode : kernel::filesystem::inode + { + /** + @brief Reads from the devfs directory inode. + @param buffer Destination buffer. + @param offset Read offset in bytes. + @param size Number of bytes requested. + @return Number of bytes read (always 0 because this inode does not expose file data). + */ + auto read(void * buffer, size_t offset, size_t size) const -> size_t override; + + /** + @brief Writes to the devfs directory inode. + @param buffer Source buffer. + @param offset Write offset in bytes. + @param size Number of bytes requested. + @return Number of bytes written (always 0 because writes are not supported for this inode). + */ + auto write(void const * buffer, size_t offset, size_t size) -> size_t override; + + /** + @brief Check if this inode represents a directory. + @return returns true, since this inode represents the /dev directory in the devfs filesystem. + */ + [[nodiscard]] auto is_directory() const -> bool 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 new file mode 100644 index 00000000..ae26e742 --- /dev/null +++ b/kernel/kernel/filesystem/devfs/inode.tests.cpp @@ -0,0 +1,55 @@ +#include + +#include +#include +#include + +#include + +#include + +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.data(), 0, buffer.size()); + + THEN("no bytes are read") + { + REQUIRE(bytes_read == 0); + } + } + + WHEN("attempting to write to the devfs inode") + { + kstd::vector buffer(512); + auto bytes_written = inode.write(buffer.data(), 0, buffer.size()); + + THEN("no bytes are written") + { + REQUIRE(bytes_written == 0); + } + } + } +} diff --git a/kernel/kernel/filesystem/device_inode.cpp b/kernel/kernel/filesystem/device_inode.cpp new file mode 100644 index 00000000..81a784cd --- /dev/null +++ b/kernel/kernel/filesystem/device_inode.cpp @@ -0,0 +1,57 @@ +#include + +#include + +#include +#include + +#include + +#include + +namespace kernel::filesystem +{ + device_inode::device_inode(kstd::shared_ptr const & device) + : m_device(device) + { + if (!device) + { + kapi::system::panic("[FILESYSTEM] device_inode constructed with null device."); + } + } + + auto device_inode::read(void * buffer, size_t offset, size_t size) const -> size_t + { + if (m_device->is_block_device()) + { + return devices::block_device_utils::read(m_device, buffer, offset, size); + } + else + { + kapi::system::panic("[FILESYSTEM] device_file::read called on non-block device."); + } + } + + auto device_inode::write(void const * buffer, size_t offset, size_t size) -> size_t + { + if (m_device->is_block_device()) + { + return devices::block_device_utils::write(m_device, buffer, offset, size); + } + else + { + kapi::system::panic("[FILESYSTEM] device_file::write called on non-block device."); + } + } + + auto device_inode::device() const -> kstd::shared_ptr const & + { + return m_device; + } + + auto device_inode::is_device() const -> bool + { + return true; + } + +} // 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 new file mode 100644 index 00000000..f4aa2d1e --- /dev/null +++ b/kernel/kernel/filesystem/device_inode.hpp @@ -0,0 +1,64 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_DEVICE_INODE_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_DEVICE_INODE_HPP + +#include + +#include + +#include + +#include + +namespace kernel::filesystem +{ + /** + @brief Inode implementation for device inodes in the filesystem. This inode represents a device file that provides + access to a device registered in the system. The device inode allows reading from and writing to the associated + device. + */ + struct device_inode : inode + { + /** + @brief Create a device inode with the given @p device. + @param device The device to associate with the inode. + */ + explicit device_inode(kstd::shared_ptr const & device); + + /** + @brief Read data from the device inode (and in the background from the associated device) into a @p buffer, starting + at @p offset and reading @p size bytes. + @param buffer The buffer to read data into. + @param offset The offset to read from. + @param size The number of bytes to read. + @return The number of bytes read. + */ + auto read(void * buffer, size_t offset, size_t size) const -> size_t override; + + /** + @brief Write data to the device inode (and in the background from the associated device) from a @p buffer, starting + at @p offset and writing @p size bytes. + @param buffer The buffer containing data to write. + @param offset The offset to write to. + @param size The number of bytes to write. + @return The number of bytes written. + */ + auto write(void const * buffer, size_t offset, size_t size) -> size_t override; + + /** + @brief Get the associated device. + @return A reference to the associated device. + */ + [[nodiscard]] auto device() const -> kstd::shared_ptr const &; + + /** + @brief Check if this inode represents a device. + @return returns true, since this indoe is a device inode and represents a device. + */ + [[nodiscard]] auto is_device() const -> bool override; + + private: + kstd::shared_ptr m_device; + }; +} // 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 new file mode 100644 index 00000000..025a22a2 --- /dev/null +++ b/kernel/kernel/filesystem/device_inode.tests.cpp @@ -0,0 +1,109 @@ +#include + +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include + +SCENARIO("Device inode construction", "[filesystem][device_inode]") +{ + GIVEN("a block device") + { + auto device = kstd::make_shared(0, 0, "test_block_device", 512, 3 * 512); + + WHEN("constructing a device inode with the block device") + { + auto inode = kernel::filesystem::device_inode{device}; + + THEN("the device inode has the correct device") + { + REQUIRE(inode.device() == device); + } + + THEN("the device inode has the correct kind") + { + REQUIRE(inode.is_device()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_regular()); + 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(0, 0, "test_block_device", 512, 3 * 512); + 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.data(), 256, write_buffer.size()); + + THEN("the correct number of bytes is written") + { + REQUIRE(bytes_written == 1024); + } + + THEN("the data written matches the data read back from the device inode") + { + kstd::vector read_buffer(1024); + auto bytes_read = inode.read(read_buffer.data(), 256, read_buffer.size()); + + REQUIRE(bytes_read == write_buffer.size()); + REQUIRE(read_buffer == write_buffer); + } + } + } +} + +SCENARIO("Device inode read/write with a non-block device", "[filesystem][device_inode]") +{ + GIVEN("a non-block device and a device inode for that device") + { + auto device = kstd::make_shared(0, 0, "test_character_device"); + auto inode = kernel::filesystem::device_inode{device}; + + WHEN("reading from the device inode") + { + kstd::vector read_buffer(512); + THEN("the system panics") + { + REQUIRE_THROWS_AS(inode.read(read_buffer.data(), 0, read_buffer.size()), kernel::tests::cpu::halt); + } + } + + WHEN("writing to the device inode") + { + kstd::vector write_buffer(512); + THEN("the system panics") + { + REQUIRE_THROWS_AS(inode.write(write_buffer.data(), 0, write_buffer.size()), kernel::tests::cpu::halt); + } + } + } +} diff --git a/kernel/kernel/filesystem/ext2/block_group_descriptor.hpp b/kernel/kernel/filesystem/ext2/block_group_descriptor.hpp new file mode 100644 index 00000000..7fbba3f3 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/block_group_descriptor.hpp @@ -0,0 +1,24 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_EXT2_BLOCK_GROUP_DESCRIPTOR_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_EXT2_BLOCK_GROUP_DESCRIPTOR_HPP + +#include +#include + +namespace kernel::filesystem::ext2 +{ + /** + @brief Represents 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/filesystem.cpp b/kernel/kernel/filesystem/ext2/filesystem.cpp new file mode 100644 index 00000000..3180a19a --- /dev/null +++ b/kernel/kernel/filesystem/ext2/filesystem.cpp @@ -0,0 +1,247 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace kernel::filesystem::ext2 +{ + + struct type final : kernel::filesystem::type + { + [[nodiscard]] auto name() const noexcept -> std::string_view override + { + return "ext2"; + } + + [[nodiscard]] auto requires_device() const noexcept -> bool override + { + return true; + } + + [[nodiscard]] auto make_instance() const -> kstd::shared_ptr override + { + return kstd::make_shared(); + } + }; + + [[gnu::used]] + constexpr auto registration = type_registration{}; + + auto filesystem::mount(kstd::shared_ptr const & backing_inode) -> operation_result + { + kernel::filesystem::filesystem::mount(backing_inode); + + m_backing_inode->read(&m_superblock, constants::superblock_offset, sizeof(m_superblock)); + + if (m_superblock.magic != constants::magic_number) + { + return operation_result::invalid_magic_number; + } + + auto const blocks_per_group = m_superblock.blocks_per_group; + auto const num_block_groups = (m_superblock.blocks_count + blocks_per_group - 1) / blocks_per_group; + + m_block_group_descriptors = kstd::vector(num_block_groups); + + m_backing_inode->read(m_block_group_descriptors.data(), block_group_descriptor_table_offset(), + num_block_groups * sizeof(block_group_descriptor)); + + m_root_inode = read_inode(constants::root_inode_number); + + if (!m_root_inode || !m_root_inode->is_directory()) + { + return operation_result::invalid_root_inode; + } + return operation_result::success; + } + + auto filesystem::lookup(kstd::shared_ptr const & parent, std::string_view name) const + -> kstd::shared_ptr + { + if (!parent || !parent->is_directory()) + { + return nullptr; + } + + auto * ext2_parent = static_cast(parent.get()); + if (!ext2_parent) + { + return nullptr; + } + + auto const & inode_data = ext2_parent->data(); + kstd::vector buffer(block_size()); + + for (uint32_t i = 0; i < inode_block_count(inode_data); ++i) + { + auto const global_block_number = map_inode_block_index_to_global_block_number(i, inode_data); + auto const block_offset = global_block_number * block_size(); + m_backing_inode->read(buffer.data(), block_offset, block_size()); + + auto const * entry = reinterpret_cast(buffer.data()); + auto bytes_read = 0uz; + + while (bytes_read < block_size() && entry->inode != 0) + { + auto const entry_name = std::string_view{entry->name.data(), entry->name_len}; + if (entry_name == name) + { + return read_inode(entry->inode); + } + + bytes_read += entry->rec_len; + entry = reinterpret_cast(buffer.data() + bytes_read); + } + } + + return nullptr; + } + + auto filesystem::read_inode(uint32_t inode_number) const -> kstd::shared_ptr + { + auto const inodes_per_group = m_superblock.inodes_per_group; + auto const block_group_index = (inode_number - 1) / inodes_per_group; + auto const inode_index_within_group = (inode_number - 1) % inodes_per_group; + + if (block_group_index >= m_block_group_descriptors.size()) + { + return nullptr; + } + + auto const & block_group_descriptor = m_block_group_descriptors.at(block_group_index); + auto const inode_table_start_block = block_group_descriptor.inode_table; + auto const inode_table_offset = static_cast(inode_table_start_block) * block_size(); + auto const inode_offset = inode_table_offset + inode_index_within_group * inode_size(); + + auto new_inode_data = inode_data{}; + m_backing_inode->read(&new_inode_data, inode_offset, sizeof(inode_data)); + + return kstd::make_shared(this, new_inode_data); + } + + auto filesystem::indirect_levels() const -> std::array + { + return { + {{constants::singly_indirect_block_index, block_numbers_per_singly_indirect_block()}, + {constants::doubly_indirect_block_index, block_numbers_per_doubly_indirect_block()}, + {constants::triply_indirect_block_index, block_numbers_per_triply_indirect_block()}} + }; + } + + auto filesystem::map_inode_block_index_to_global_block_number(size_t inode_block_index, inode_data data) const + -> kstd::ssize_t + { + if (inode_block_index < constants::direct_block_count) + { + return data.block.at(inode_block_index); + } + + inode_block_index -= constants::direct_block_count; + + for (auto const & level : indirect_levels()) + { + if (inode_block_index >= level.capacity) + { + inode_block_index -= level.capacity; + continue; + } + + auto block_number = data.block[level.slot_index]; + if (block_number == 0) + { + return 0; + } + + for (auto stride = level.capacity / block_numbers_per_block();; stride /= block_numbers_per_block()) + { + auto const idx = inode_block_index / stride; + inode_block_index %= stride; + + block_number = read_block_number_at_index(block_number, idx); + if (block_number == 0) + { + return 0; + } + + if (stride == 1) + { + break; + } + } + + return block_number; + } + + return -1; + } + + auto filesystem::read_block_number_at_index(uint32_t block_number, size_t index) const -> uint32_t + { + uint32_t block_number_buffer = 0; + + auto const block_start_offset = block_number * block_size(); + auto const number_start_address = block_start_offset + index * sizeof(uint32_t); + m_backing_inode->read(&block_number_buffer, number_start_address, sizeof(uint32_t)); + + return block_number_buffer; + } + + auto filesystem::block_numbers_per_block() const -> size_t + { + return block_size() / sizeof(uint32_t); + } + + auto filesystem::block_numbers_per_singly_indirect_block() const -> size_t + { + return block_numbers_per_block(); + } + + auto filesystem::block_numbers_per_doubly_indirect_block() const -> size_t + { + return block_numbers_per_singly_indirect_block() * block_numbers_per_block(); + } + + auto filesystem::block_numbers_per_triply_indirect_block() const -> size_t + { + return block_numbers_per_doubly_indirect_block() * block_numbers_per_block(); + } + + auto filesystem::block_size() const -> size_t + { + return constants::base_block_size << m_superblock.log_block_size; + } + + auto filesystem::revision_level() const -> uint32_t + { + return m_superblock.rev_level; + } + + auto filesystem::inode_size() const -> uint16_t + { + return revision_level() == constants::good_old_revision ? 128 : m_superblock.inode_size; + } + + auto filesystem::inode_block_count(inode_data const & data) const -> uint32_t + { + return data.blocks / (2 << m_superblock.log_block_size); + } + + auto filesystem::block_group_descriptor_table_offset() const -> size_t + { + return block_size() == 1024 ? 2 * block_size() : block_size(); + } +} // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/filesystem.hpp b/kernel/kernel/filesystem/ext2/filesystem.hpp new file mode 100644 index 00000000..2284d7bf --- /dev/null +++ b/kernel/kernel/filesystem/ext2/filesystem.hpp @@ -0,0 +1,115 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_EXT2_FILESYSTEM_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_EXT2_FILESYSTEM_HPP + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace kernel::filesystem::ext2 +{ + /** + @brief Constants related to the ext2 filesystem. + */ + namespace constants + { + constexpr size_t inline base_block_size = 1024; + constexpr size_t inline superblock_offset = base_block_size; + constexpr uint16_t inline magic_number = 0xEF53; + + constexpr uint32_t inline 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; + } // namespace constants + + /** + @brief A filesystem implementation for the ext2 filesystem format. This class provides methods for mounting an ext2 + filesystem, and looking up inodes. + */ + struct filesystem : kernel::filesystem::filesystem + { + /** + @brief Initializes the ext2 filesystem with the given @p backing_inode. + @param backing_inode The backing inode to mount. + @return The result of the mount operation. + */ + auto mount(kstd::shared_ptr const & backing_inode) -> operation_result override; + + /** + @brief Looks up an inode by @p name within a @p parent directory. + @param parent The parent directory inode. + @param name The name of the inode to look up. + @return A pointer to the found inode, or a null pointer if not found. + */ + [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const + -> kstd::shared_ptr override; + + /** + @brief Gets the size of a block in the filesystem. + @return The size of a block in bytes. + */ + [[nodiscard]] auto block_size() const -> size_t; + + /** + @brief Gets the revision level of the filesystem. + @return The revision level. + */ + [[nodiscard]] auto revision_level() const -> uint32_t; + + /** + @brief Maps an inode block index to a global block number. + @param inode_block_index The index of the block within the inode. + @param data The inode data. + @return The global block number. + */ + [[nodiscard]] auto map_inode_block_index_to_global_block_number(size_t inode_block_index, inode_data data) const + -> kstd::ssize_t; + + private: + struct indirect_level + { + uint32_t slot_index; + size_t capacity; + }; + + [[nodiscard]] auto indirect_levels() const -> std::array; + + [[nodiscard]] auto read_inode(uint32_t inode_number) const -> kstd::shared_ptr; + [[nodiscard]] auto read_block_number_at_index(uint32_t block_number, size_t index) const -> uint32_t; + + [[nodiscard]] auto inode_size() const -> uint16_t; + [[nodiscard]] auto inode_block_count(inode_data const & data) const -> uint32_t; + [[nodiscard]] auto block_group_descriptor_table_offset() const -> size_t; + + [[nodiscard]] auto block_numbers_per_block() const -> size_t; + [[nodiscard]] auto block_numbers_per_singly_indirect_block() const -> size_t; + [[nodiscard]] auto block_numbers_per_doubly_indirect_block() const -> size_t; + [[nodiscard]] auto block_numbers_per_triply_indirect_block() const -> size_t; + + superblock m_superblock{}; + kstd::vector m_block_group_descriptors; + }; +} // namespace kernel::filesystem::ext2 + +#endif diff --git a/kernel/kernel/filesystem/ext2/filesystem.tests.cpp b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp new file mode 100644 index 00000000..83410702 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/filesystem.tests.cpp @@ -0,0 +1,139 @@ +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, + "Ext2 filesystem mount and lookup with real image", "[filesystem][ext2][filesystem][img]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("a mounted ext2 filesystem from a real image") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); + + auto boot_device = kernel::devices::storage::management::get().determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto dev_inode = kstd::make_shared(boot_device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + THEN("the root inode is available and is a directory") + { + REQUIRE(fs.root_inode() != nullptr); + REQUIRE(fs.root_inode()->is_directory()); + } + + THEN("lookup resolves known entries from the image") + { + auto information = fs.lookup(fs.root_inode(), "information"); + REQUIRE(information != nullptr); + REQUIRE(information->is_directory()); + + auto info_1 = fs.lookup(information, "info_1.txt"); + REQUIRE(info_1 != nullptr); + REQUIRE(info_1->is_regular()); + } + + THEN("lookup returns null for invalid inputs") + { + REQUIRE(fs.lookup(nullptr, "information") == nullptr); + + auto information = fs.lookup(fs.root_inode(), "information"); + REQUIRE(information != nullptr); + auto info_1 = fs.lookup(information, "info_1.txt"); + REQUIRE(info_1 != nullptr); + + REQUIRE(fs.lookup(info_1, "anything") == nullptr); + REQUIRE(fs.lookup(fs.root_inode(), "does_not_exist") == nullptr); + } + } +} + +SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem]") +{ + auto const block_size = 1024; + GIVEN("a block device that does not contain an ext2 superblock") + { + auto device = kstd::make_shared(0, 0, "mock", block_size, 2 * block_size); + REQUIRE(device != nullptr); + + auto dev_inode = kstd::make_shared(device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + + THEN("mount fails with invalid_magic_number") + { + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::invalid_magic_number); + } + } +} + +SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesystem][ext2][filesystem]") +{ + auto const block_size = 1024; + + GIVEN("a minimally valid ext2 layout with configured indirect block tables") + { + auto device = kstd::make_shared(0, 0, "mock", block_size, 128 * block_size); + REQUIRE(device != nullptr); + + kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); + + auto dev_inode = kstd::make_shared(device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto inode_data = kernel::filesystem::ext2::inode_data{}; + inode_data.block[0] = 7; + + inode_data.block[12] = 30; + kernel::tests::filesystem::ext2::write_u32(*device, 30 * block_size, 31); + + inode_data.block[13] = 40; + kernel::tests::filesystem::ext2::write_u32(*device, 40 * block_size, 41); + kernel::tests::filesystem::ext2::write_u32(*device, 41 * block_size, 42); + + inode_data.block[14] = 50; + kernel::tests::filesystem::ext2::write_u32(*device, 50 * block_size, 51); + kernel::tests::filesystem::ext2::write_u32(*device, 51 * block_size, 52); + kernel::tests::filesystem::ext2::write_u32(*device, 52 * block_size, 53); + + auto const numbers_per_block = static_cast(block_size / sizeof(uint32_t)); + 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("mapping resolves direct, singly, doubly and triply indirect indexes") + { + REQUIRE(fs.map_inode_block_index_to_global_block_number(0, inode_data) == 7); + REQUIRE(fs.map_inode_block_index_to_global_block_number(singly_start, inode_data) == 31); + REQUIRE(fs.map_inode_block_index_to_global_block_number(doubly_start, inode_data) == 42); + REQUIRE(fs.map_inode_block_index_to_global_block_number(triply_start, inode_data) == 53); + } + + THEN("mapping returns zero for out-of-range indexes") + { + auto const beyond_triply = triply_start + numbers_per_block * numbers_per_block * numbers_per_block; + REQUIRE(fs.map_inode_block_index_to_global_block_number(beyond_triply, inode_data) == -1); + } + } +} diff --git a/kernel/kernel/filesystem/ext2/inode.cpp b/kernel/kernel/filesystem/ext2/inode.cpp new file mode 100644 index 00000000..35a32ee4 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/inode.cpp @@ -0,0 +1,111 @@ +#include + +#include +#include + +#include + +#include + +#include +#include +#include + +namespace kernel::filesystem::ext2 +{ + inode::inode(filesystem const * fs, inode_data const & data) + : m_filesystem(fs) + , m_data(data) + { + if (!m_filesystem) + { + kapi::system::panic("[EXT2] ext2::inode constructed with filesystem null pointer"); + } + } + + auto inode::read(void * buffer, size_t offset, size_t size) const -> size_t + { + auto const max_readable = this->size() - offset; + auto const requested_size = std::min(size, max_readable); + + if (is_symbolic_link() && this->size() <= sizeof(m_data.block)) + { + auto inline_target = reinterpret_cast(m_data.block.data()); + kstd::libc::memcpy(static_cast(buffer), inline_target + offset, requested_size); + return requested_size; + } + + auto block_index = offset / m_filesystem->block_size(); + auto in_block_offset = offset % m_filesystem->block_size(); + + auto bytes_read = 0uz; + + while (bytes_read < requested_size) + { + auto const block_number = m_filesystem->map_inode_block_index_to_global_block_number(block_index, m_data); + if (block_number == -1) + { + break; + } + + auto const bytes_to_read = std::min(requested_size - bytes_read, m_filesystem->block_size() - in_block_offset); + if (block_number == 0) + { + kstd::libc::memset(static_cast(buffer) + bytes_read, 0, bytes_to_read); + bytes_read += bytes_to_read; + } + else + { + auto const block_start_offset = block_number * m_filesystem->block_size(); + auto const read_offset = block_start_offset + in_block_offset; + + bytes_read += m_filesystem->backing_inode()->read(static_cast(buffer) + bytes_read, read_offset, + bytes_to_read); + } + + block_index++; + in_block_offset = 0; // After the first block, we always start at the beginning of the block + } + + return bytes_read; + } + + auto inode::write(void const *, size_t, size_t) -> size_t + { + kapi::system::panic("[EXT2] inode::write is not implemented yet"); + return 0; + } + + [[nodiscard]] auto inode::data() const -> inode_data const & + { + return m_data; + } + + auto inode::is_regular() const -> bool + { + return (m_data.mode & constants::mode_mask) == constants::mode_regular; + } + + auto inode::is_directory() const -> bool + { + return (m_data.mode & constants::mode_mask) == constants::mode_directory; + } + + auto inode::is_symbolic_link() const -> bool + { + return (m_data.mode & constants::mode_mask) == constants::mode_symbolic_link; + } + + auto inode::size() const -> uint64_t + { + uint64_t size = m_data.size; + + if (m_filesystem->revision_level() > constants::good_old_revision && is_regular()) + { + size |= static_cast(m_data.dir_acl) << 32; + } + + return size; + } + +} // namespace kernel::filesystem::ext2 diff --git a/kernel/kernel/filesystem/ext2/inode.hpp b/kernel/kernel/filesystem/ext2/inode.hpp new file mode 100644 index 00000000..f2496f0f --- /dev/null +++ b/kernel/kernel/filesystem/ext2/inode.hpp @@ -0,0 +1,105 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_EXT2_INODE_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_EXT2_INODE_HPP + +#include + +#include + +#include +#include +#include + +namespace kernel::filesystem::ext2 +{ + struct filesystem; + + /** + @brief Represents the data associated with an ext2 inode. + */ + struct [[gnu::packed]] inode_data + { + uint16_t mode; + uint16_t uid; + uint32_t size; + 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 + { + /** + @brief Create an ext2 inode associated with the given filesystem. + @param fs The ext2 filesystem that this inode belongs to. + @param data The data associated with this inode, read from the disk. + */ + explicit inode(filesystem const * fs, inode_data const & data); + + /** + @brief Reads from the ext2 inode into a @p buffer, starting at the specified @p offset and for a given @p size. + @param buffer Destination buffer. + @param offset Read offset in bytes. + @param size Number of bytes requested. + @return Number of bytes read. + */ + auto read(void * buffer, size_t offset, size_t size) const -> size_t override; + + /** + @brief Writes to the ext2 inode into a @p buffer, starting at the specified @p offset and for a given @p size. + @warning This method is not implemented yet and will panic if called. + @param buffer Source buffer. + @param offset Write offset in bytes. + @param size Number of bytes requested. + @return Number of bytes written. + */ + auto write(void const * buffer, size_t offset, size_t size) -> size_t override; + + /** + @brief Get the data associated with this inode. + @return A reference to the inode data. + */ + [[nodiscard]] auto data() const -> inode_data const &; + + /** + @brief Check if this inode represents a directory. + @return returns true if this inode represents a directory, false otherwise. + */ + [[nodiscard]] auto is_directory() const -> bool override; + + /** + @brief Check if this inode represents a regular file. + @return returns true if this inode represents a regular file, false otherwise. + */ + [[nodiscard]] auto is_regular() const -> bool override; + + /** + @brief Check if this inode represents a symbolic link. + @return returns true if this inode represents a symbolic link, false otherwise. + */ + [[nodiscard]] auto is_symbolic_link() const -> bool override; + + /** + @brief Get the size of the file represented by this inode. + @return The size of the file in bytes. + */ + [[nodiscard]] auto size() const -> uint64_t; + + private: + filesystem const * m_filesystem; + 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 new file mode 100644 index 00000000..4aecc04a --- /dev/null +++ b/kernel/kernel/filesystem/ext2/inode.tests.cpp @@ -0,0 +1,376 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +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(&fs, 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(&fs, 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(&fs, 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(&fs, data); + + REQUIRE_FALSE(inode.is_regular()); + REQUIRE_FALSE(inode.is_directory()); + REQUIRE_FALSE(inode.is_device()); + REQUIRE_FALSE(inode.is_symbolic_link()); + } + } + + GIVEN("no filesystem (null pointer)") + { + THEN("constructing an inode with a null filesystem pointer panics") + { + REQUIRE_THROWS_AS(kernel::filesystem::ext2::inode(nullptr, {}), kernel::tests::cpu::halt); + } + } +} + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 inode reads from real image", + "[filesystem][ext2][inode][img]") +{ + auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; + + GIVEN("a mounted ext2 filesystem and a regular file inode") + { + REQUIRE(std::filesystem::exists(image_path)); + REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); + + auto boot_device = kernel::devices::storage::management::get().determine_boot_device(); + REQUIRE(boot_device != nullptr); + + auto dev_inode = kstd::make_shared(boot_device); + + auto fs = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto information = fs.lookup(fs.root_inode(), "information"); + REQUIRE(information != nullptr); + auto file = fs.lookup(information, "info_1.txt"); + REQUIRE(file != nullptr); + REQUIRE(file->is_regular()); + + THEN("reading from offset zero returns expected file prefix") + { + auto buffer = kstd::vector(6); + auto const bytes_read = file->read(buffer.data(), 0, buffer.size()); + + REQUIRE(bytes_read == 6); + + auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; + REQUIRE(text == "info_1"); + } + + THEN("reading with an offset returns the expected byte") + { + auto buffer = kstd::vector(1); + auto const bytes_read = file->read(buffer.data(), 5, buffer.size()); + + REQUIRE(bytes_read == 1); + REQUIRE(static_cast(buffer[0]) == '1'); + } + } +} + +SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesystem][ext2][inode]") +{ + auto const block_size = 1024uz; + GIVEN("an ext2 inode with only direct mapped data blocks") + { + auto device = kstd::make_shared(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto data = kernel::filesystem::ext2::inode_data{}; + data.block[0] = 30; + data.block[1] = 0; + data.block[2] = 31; + data.size = block_size * 3; + + kernel::tests::filesystem::ext2::write_bytes(*device, 30 * block_size, "Hello", 5); + kernel::tests::filesystem::ext2::write_bytes(*device, 31 * block_size, "World!", 6); + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + 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.data(), 0, buffer.size()); + REQUIRE(bytes_read == data.size); + + auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; + REQUIRE(text.substr(0, 5) == "Hello"); + REQUIRE(std::ranges::all_of(text.substr(5, block_size - 5), [](char c) { return c == '\0'; })); + REQUIRE(text.substr(2 * block_size, 6) == "World!"); + REQUIRE(std::ranges::all_of(text.substr(2 * block_size + 6, 3 * block_size), [](char c) { return c == '\0'; })); + } + } + + GIVEN("an ext2 indode with file holes in singly indirect blocks") + { + auto device = kstd::make_shared(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto data = kernel::filesystem::ext2::inode_data{}; + data.block[0] = 30; + data.block[12] = 31; + data.size = block_size * 15; + + kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size, 50); + kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size + 4, 0); + kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size + 8, 51); + + kernel::tests::filesystem::ext2::write_bytes(*device, 30 * block_size, "Hello", 5); + kernel::tests::filesystem::ext2::write_bytes(*device, 50 * block_size, "Blub", 4); + kernel::tests::filesystem::ext2::write_bytes(*device, 51 * block_size, "World!", 6); + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + 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.data(), 0, buffer.size()); + REQUIRE(bytes_read == data.size); + + auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; + REQUIRE(text.substr(0, 5) == "Hello"); + REQUIRE(std::ranges::all_of(text.substr(5, 12 * block_size - 5), [](char c) { return c == '\0'; })); + REQUIRE(text.substr(12 * block_size, 4) == "Blub"); + REQUIRE( + std::ranges::all_of(text.substr(12 * block_size + 4, 2 * block_size - 4), [](char c) { return c == '\0'; })); + REQUIRE(text.substr(14 * block_size, 6) == "World!"); + REQUIRE( + std::ranges::all_of(text.substr(14 * block_size + 6, 1 * block_size - 6), [](char c) { return c == '\0'; })); + } + } + + GIVEN("an ext2 inode with zero singly indirect block pointer") + { + auto device = kstd::make_shared(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto data = kernel::filesystem::ext2::inode_data{}; + data.block[12] = 0; + data.size = block_size * 15; + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + auto buffer = kstd::vector(block_size * 15, std::byte{0xAB}); + + THEN("all direct blocks are zero when singly indirect block pointer is zero") + { + auto const bytes_read = inode.read(buffer.data(), 0, buffer.size()); + REQUIRE(bytes_read == 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 = 1024uz; + GIVEN("an ext2 inode with two direct blocks and a block size of 1024 bytes") + { + auto device = kstd::make_shared(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto inode_data = kernel::filesystem::ext2::inode_data{}; + inode_data.size = block_size * 2; + inode_data.block[0] = 20; + kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size - 6, "Hello ", 6); + inode_data.block[1] = 21; + kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size, "World!", 6); + auto inode = kernel::filesystem::ext2::inode{&fs, inode_data}; + + 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.data(), block_size - 6, buffer.size()); + REQUIRE(bytes_read == 12); + + auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; + REQUIRE(text == "Hello World!"); + } + } +} + +SCENARIO("Ext2 inode write is not implemented", "[filesystem][ext2][inode]") +{ + GIVEN("an ext2 inode") + { + auto fs = kernel::filesystem::ext2::filesystem{}; + auto inode = kernel::filesystem::ext2::inode{&fs, kernel::filesystem::ext2::inode_data{}}; + + THEN("writing to the inode panics") + { + auto buffer = kstd::vector(32, std::byte{0x00}); + REQUIRE_THROWS_AS(inode.write(buffer.data(), 0, buffer.size()), kernel::tests::cpu::halt); + } + } +} + +SCENARIO("Ext2 inode get_size() correctly returns size depending on revision level", "[filesystem][ext2][inode]") +{ + auto const block_size = 1024uz; + + 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(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto data = kernel::filesystem::ext2::inode_data{}; + data.size = 256; + data.dir_acl = 32; + + THEN("the inode size is 256 if mode = regular") + { + data.mode = kernel::filesystem::ext2::constants::mode_regular; + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + REQUIRE(inode.size() == 256); + } + + THEN("the inode size is 256 if mode = directory") + { + data.mode = kernel::filesystem::ext2::constants::mode_directory; + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + REQUIRE(inode.size() == 256); + } + } + + 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(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; + REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); + + auto data = kernel::filesystem::ext2::inode_data{}; + data.size = 256; + data.dir_acl = 32; + + THEN("the inode size is 256 if mode = regular") + { + data.mode = kernel::filesystem::ext2::constants::mode_regular; + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + REQUIRE(inode.size() == 0x0000'0020'0000'0100); + } + + THEN("the inode size is 256 if mode = directory") + { + data.mode = kernel::filesystem::ext2::constants::mode_directory; + + auto inode = kernel::filesystem::ext2::inode{&fs, data}; + + REQUIRE(inode.size() == 256); + } + } +} \ 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 new file mode 100644 index 00000000..4097cbb8 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/linked_directory_entry.hpp @@ -0,0 +1,22 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_EXT2_LINKED_DIRECTORY_ENTRY_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_EXT2_LINKED_DIRECTORY_ENTRY_HPP + +#include +#include + +namespace kernel::filesystem::ext2 +{ + /** + @brief Represents a linked directory entry in the ext2 filesystem. + */ + struct [[gnu::packed]] linked_directory_entry + { + uint32_t inode; + uint16_t rec_len; + uint8_t name_len; + uint8_t file_type; + std::array name; // NOLINT(readability-magic-numbers) + }; +} // 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 new file mode 100644 index 00000000..41ad9358 --- /dev/null +++ b/kernel/kernel/filesystem/ext2/superblock.hpp @@ -0,0 +1,74 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_EXT2_SUPERBLOCK_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_EXT2_SUPERBLOCK_HPP + +#include +#include + +namespace kernel::filesystem::ext2 +{ + /** + @brief Represents 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; + uint32_t feature_compat; + uint32_t feature_incompat; + uint32_t 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 +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/filesystem.cpp b/kernel/kernel/filesystem/filesystem.cpp new file mode 100644 index 00000000..24d0e229 --- /dev/null +++ b/kernel/kernel/filesystem/filesystem.cpp @@ -0,0 +1,60 @@ +#include + +#include +#include + +#include + +#include + +#include + +namespace kernel::filesystem +{ + namespace + { + constexpr auto static filesystem_factories = std::array{ + []() { return kstd::make_shared(); }, + }; + } // namespace + + auto filesystem::probe_and_mount(kstd::shared_ptr const & backing_inode) -> kstd::shared_ptr + { + if (!backing_inode) + { + kapi::system::panic("[FILESYSTEM] cannot mount filesystem: backing inode is null."); + } + + for (auto & factory : filesystem_factories) + { + auto fs = factory(); + if (fs->mount(backing_inode) == operation_result::success) + { + return fs; + } + } + + return nullptr; + } + + auto filesystem::mount(kstd::shared_ptr const & backing_inode) -> operation_result + { + if (!backing_inode) + { + kapi::system::panic("[FILESYSTEM] cannot mount filesystem: backing inode is null."); + } + + m_backing_inode = backing_inode; + return operation_result::success; + } + + auto filesystem::root_inode() const -> kstd::shared_ptr const & + { + return m_root_inode; + } + + auto filesystem::backing_inode() const -> kstd::shared_ptr const & + { + return m_backing_inode; + } +} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/kernel/filesystem/filesystem.hpp b/kernel/kernel/filesystem/filesystem.hpp new file mode 100644 index 00000000..bec1b160 --- /dev/null +++ b/kernel/kernel/filesystem/filesystem.hpp @@ -0,0 +1,79 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_FILESYSTEM_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_FILESYSTEM_HPP + +#include + +#include +#include + +#include + +namespace kernel::filesystem +{ + /** + @brief A base class for implementing filesystems in the kernel. This class provides a common interface for managing + files and directories within the virtual filesystem. + */ + struct filesystem + { + enum class operation_result : int + { + success = 0, + invalid_magic_number = -1, + invalid_root_inode = -2, + unmount_failed = -3 + }; + + /** + @brief Virtual destructor for the filesystem. + */ + virtual ~filesystem() = default; + + /** + @brief Probes the given @p backing_inode to determine if it contains a recognizable filesystem, and if so, mounts it + and returns a pointer to the mounted filesystem instance. This method iterates through known filesystem types and + attempts to initialize it with the backing inode until the mount was successful or all types have been tried. + @param backing_inode The inode to probe and mount. + @return A pointer to the mounted filesystem instance if successful, or a null pointer if no recognizable filesystem + is found on the backing inode. + @warning Panics if @p backing_inode is null. + */ + auto static probe_and_mount(kstd::shared_ptr const & backing_inode) -> kstd::shared_ptr; + + /** + @brief Initializes the filesystem with the given @p backing_inode. + @param backing_inode The inode to use as the backing inode for the filesystem. (This is typically the inode + representing the block device or another inode which contains the filesystem data.) + @return The result of the mount operation. + */ + virtual auto mount(kstd::shared_ptr const & backing_inode) -> operation_result; + + /** + @brief Looks up a child inode within the given @p parent inode with the specified @p name. This method must be + implemented by concrete filesystem subclasses to provide the logic for traversing the filesystem structure and + finding the requested inode. + @param parent The parent inode. + @param name The name of the child inode to look up. + @return A pointer to the requested child inode, or a null pointer if not found. + */ + [[nodiscard]] virtual auto lookup(kstd::shared_ptr const & parent, std::string_view name) const + -> kstd::shared_ptr = 0; + + /** + @brief Returns a reference to the root inode of the filesystem. + */ + [[nodiscard]] auto root_inode() const -> kstd::shared_ptr const &; + + /** + @brief Returns a reference to the backing inode of the filesystem. + */ + [[nodiscard]] auto backing_inode() const -> kstd::shared_ptr const &; + + protected: + kstd::shared_ptr m_root_inode{}; + kstd::shared_ptr m_backing_inode{}; + }; + +} // namespace kernel::filesystem + +#endif \ No newline at end of file diff --git a/kernel/kernel/filesystem/inode.cpp b/kernel/kernel/filesystem/inode.cpp new file mode 100644 index 00000000..c1889179 --- /dev/null +++ b/kernel/kernel/filesystem/inode.cpp @@ -0,0 +1,24 @@ +#include + +namespace kernel::filesystem +{ + auto inode::is_directory() const -> bool + { + return false; + } + + auto inode::is_regular() const -> bool + { + return false; + } + + auto inode::is_device() const -> bool + { + return false; + } + + auto inode::is_symbolic_link() const -> bool + { + return false; + } +} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/kernel/filesystem/inode.hpp b/kernel/kernel/filesystem/inode.hpp new file mode 100644 index 00000000..b34b921e --- /dev/null +++ b/kernel/kernel/filesystem/inode.hpp @@ -0,0 +1,69 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_INODE_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_INODE_HPP + +#include + +namespace kernel::filesystem +{ + /** + @brief Represents an inode in the filesystem. + */ + struct inode + { + /** + @brief Create an inode. + */ + inode() = default; + + /** + @brief Virtual destructor for the inode. + */ + virtual ~inode() = default; + + /** + @brief Reads from the inode into a @p buffer, starting at the specified @p offset and for a given @p size. This + method must be implemented by concrete inode subclasses. + @param buffer Destination buffer. + @param offset Read offset in bytes. + @param size Number of bytes requested. + @return Number of bytes read. + */ + virtual auto read(void * buffer, size_t offset, size_t size) const -> size_t = 0; + + /** + @brief Writes to the inode into a @p buffer, starting at the specified @p offset and for a given @p size. This + method must be implemented by concrete inode subclasses. + @param buffer Source buffer. + @param offset Write offset in bytes. + @param size Number of bytes to write. + @return Number of bytes written. + */ + virtual auto write(void const * buffer, size_t offset, size_t size) -> size_t = 0; + + /** + @brief Returns whether the inode is a directory. + @return true if the inode is a directory, false otherwise. + */ + [[nodiscard]] virtual auto is_directory() const -> bool; + + /** + @brief Returns whether the inode is a regular file. + @return true if the inode is a regular file, false otherwise. + */ + [[nodiscard]] virtual auto is_regular() const -> bool; + + /** + @brief Returns whether the inode is a device. + @return true if the inode is a device, false otherwise. + */ + [[nodiscard]] virtual auto is_device() const -> bool; + + /** + @brief Returns whether the inode is a symbolic link. + @return true if the inode is a symbolic link, false otherwise. + */ + [[nodiscard]] virtual auto is_symbolic_link() const -> bool; + }; +} // namespace kernel::filesystem + +#endif diff --git a/kernel/kernel/filesystem/mount.cpp b/kernel/kernel/filesystem/mount.cpp new file mode 100644 index 00000000..ead7479d --- /dev/null +++ b/kernel/kernel/filesystem/mount.cpp @@ -0,0 +1,90 @@ +#include + +#include +#include + +#include + +#include +#include + +#include +#include + +namespace kernel::filesystem +{ + mount::mount(kstd::shared_ptr const & mount_dentry, kstd::shared_ptr const & root_dentry, + kstd::shared_ptr const & fs, kstd::shared_ptr const & parent_mount, + kstd::shared_ptr const & source_mount) + : m_mount_dentry(mount_dentry) + , m_root_dentry(root_dentry) + , m_filesystem(fs) + , m_parent_mount(parent_mount) + , m_source_mount(source_mount) + , m_ref_count(0) + { + if (!m_filesystem) + { + kapi::system::panic("[FILESYSTEM] mount initialized with null filesystem."); + } + } + + auto mount::mount_dentry() const -> kstd::shared_ptr const & + { + return m_mount_dentry; + } + + auto mount::get_filesystem() const -> kstd::shared_ptr const & + { + return m_filesystem; + } + + auto mount::root_dentry() const -> kstd::shared_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 -> kstd::shared_ptr const & + { + return m_parent_mount; + } + + auto mount::source_mount() const -> kstd::shared_ptr + { + return m_source_mount.lock(); + } + + 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 new file mode 100644 index 00000000..ced4f814 --- /dev/null +++ b/kernel/kernel/filesystem/mount.hpp @@ -0,0 +1,97 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_MOUNT_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_MOUNT_HPP + +#include +#include + +#include +#include + +#include +#include + +namespace kernel::filesystem +{ + /** + @brief Represents a mounted filesystem in the kernel. + */ + struct mount + { + /** + @brief Creates a mount for the given @p filesystem at the specified @p mount_path. The @p mount_dentry represents + the dentry where the filesystem is mounted, and the @p root_dentry represents the root dentry of the mounted + filesystem. + @param mount_dentry The dentry where the filesystem is mounted. + @param root_dentry The root dentry of the mounted filesystem. + @param fs The filesystem instance being mounted. + @param parent_mount The mount that contains the mount_dentry. + @param source_mount The mount that the filesystem originates from. + */ + mount(kstd::shared_ptr const & mount_dentry, kstd::shared_ptr const & root_dentry, + kstd::shared_ptr const & fs, kstd::shared_ptr const & parent_mount, + kstd::shared_ptr const & source_mount); + + /** + @brief Get the dentry where the filesystem is mounted. + */ + [[nodiscard]] auto mount_dentry() const -> kstd::shared_ptr const &; + + /** + @brief Get the root dentry of the mounted filesystem. + */ + [[nodiscard]] auto root_dentry() const -> kstd::shared_ptr const &; + + /** + @brief Get the filesystem instance being mounted. + */ + [[nodiscard]] auto get_filesystem() const -> kstd::shared_ptr const &; + + /** + @brief Get the path at which the filesystem is mounted. + */ + [[nodiscard]] auto mount_path() const -> kstd::string; + + /** + @brief Get the parent mount that this mount was attached beneath. + */ + [[nodiscard]] auto parent_mount() const -> kstd::shared_ptr const &; + + /** + @brief Get the source mount where this mount originates from. + */ + [[nodiscard]] auto source_mount() const -> kstd::shared_ptr; + + /** + @brief Increment the reference count for this mount. + */ + auto increment_ref_count() -> void; + + /** + @brief Decrement the reference count for this mount. + @warning Throws if ref_count is zero. + */ + auto decrement_ref_count() -> void; + + /** + @brief 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; + + /** + @brief Get the current reference count for this mount. + @return The current reference count. + */ + [[nodiscard]] auto ref_count() const -> size_t; + + private: + kstd::shared_ptr m_mount_dentry{}; + kstd::shared_ptr m_root_dentry{}; + kstd::shared_ptr m_filesystem{}; + kstd::shared_ptr m_parent_mount{}; + kstd::weak_ptr m_source_mount{}; + 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 new file mode 100644 index 00000000..c9ff82e9 --- /dev/null +++ b/kernel/kernel/filesystem/mount.tests.cpp @@ -0,0 +1,94 @@ +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include + +SCENARIO("Mount construction", "[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, "/"); + + WHEN("constructing a mount with the filesystem and root dentry") + { + auto mount = kernel::filesystem::mount{root_dentry, root_dentry, fs, nullptr, nullptr}; + + THEN("the mount has the correct filesystem, root dentry, mount dentry, and mount path") + { + REQUIRE(mount.get_filesystem() == fs); + REQUIRE(mount.root_dentry() == root_dentry); + REQUIRE(mount.mount_dentry() == root_dentry); + 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{root_dentry, root_dentry, 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{root_dentry, root_dentry, fs, nullptr, nullptr}; + + 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{root_dentry, root_dentry, fs, 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 new file mode 100644 index 00000000..e4baac7a --- /dev/null +++ b/kernel/kernel/filesystem/mount_table.cpp @@ -0,0 +1,79 @@ +#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 new file mode 100644 index 00000000..4f2d1b7f --- /dev/null +++ b/kernel/kernel/filesystem/mount_table.hpp @@ -0,0 +1,58 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_MOUNT_TABLE_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_MOUNT_TABLE_HPP + +#include + +#include +#include + +#include + +namespace kernel::filesystem +{ + /** + @brief A table for managing mounted filesystems in the kernel. + */ + struct mount_table + { + /** + @brief Results for mount table operations. + */ + enum class operation_result : int + { + removed = 0, + has_child_mounts = -1, + mount_not_found = -2, + cannot_be_unmounted = -3 + }; + + /** + @brief Adds a mount to the table. + @param mount The mount to add. + */ + auto add_mount(kstd::shared_ptr const & mount) -> void; + + /** + @brief Removes the topmost mount at the given @p path. + @param path The mount path to remove. + @return The result of the removal operation. + */ + [[nodiscard]] auto remove_mount(std::string_view path) -> operation_result; + + /** + @brief Finds the mount with the exact mount path matching the given @p 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, or a null pointer if no mount matches the path. + */ + [[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 new file mode 100644 index 00000000..19b47b23 --- /dev/null +++ b/kernel/kernel/filesystem/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::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 = kstd::make_shared(mount_dentry1, root_dentry1, fs1, 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 = kstd::make_shared(mount_dentry2, root_dentry2, fs2, 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 = kstd::make_shared(mount_dentry1, root_dentry1, fs1, 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 = kstd::make_shared(mount_dentry2, root_dentry2, fs2, 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 = kstd::make_shared(mount_dentry1, root_dentry1, fs1, 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 = kstd::make_shared(mount_dentry2, root_dentry2, fs2, mount1, 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 = kstd::make_shared(mount_dentry3, root_dentry3, fs3, mount2, 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 = kstd::make_shared(root_dentry, root_dentry, fs, nullptr, nullptr); + auto mount = kstd::make_shared(root_dentry, root_dentry, fs, nullptr, source_mount); + + 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 new file mode 100644 index 00000000..a5567bf4 --- /dev/null +++ b/kernel/kernel/filesystem/open_file_descriptor.cpp @@ -0,0 +1,46 @@ +#include + +#include + +#include +#include + +#include + +namespace kernel::filesystem +{ + open_file_descriptor::open_file_descriptor(kstd::shared_ptr const & dentry) + : m_dentry(dentry) + , m_offset(0) + { + if (!dentry) + { + kstd::os::panic("[FILESYSTEM] open_file_descriptor constructed with null dentry."); + } + } + + auto open_file_descriptor::read(void * buffer, size_t size) -> size_t + { + auto read_bytes = m_dentry->get_inode()->read(buffer, m_offset, size); + m_offset += read_bytes; + return read_bytes; + } + + auto open_file_descriptor::write(void const * buffer, size_t size) -> size_t + { + auto written_bytes = m_dentry->get_inode()->write(buffer, m_offset, size); + m_offset += written_bytes; + return written_bytes; + } + + auto open_file_descriptor::offset() const -> size_t + { + return m_offset; + } + + 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 new file mode 100644 index 00000000..fd10e646 --- /dev/null +++ b/kernel/kernel/filesystem/open_file_descriptor.hpp @@ -0,0 +1,68 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_OPEN_FILE_DESCRIPTOR_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_OPEN_FILE_DESCRIPTOR_HPP + +#include + +#include + +#include + +namespace kernel::filesystem +{ + /** + @brief Represents an open file descriptor in the filesystem. This class encapsulates the state of an open file, + including a reference to the associated dentry and the current file offset. + */ + struct open_file_descriptor + { + /** + @brief Constructs an open file descriptor for the given @p dentry. + @param dentry The dentry to associate with the open file descriptor. + */ + explicit open_file_descriptor(kstd::shared_ptr const & dentry); + + /** + @brief Destructor for the open file descriptor. + */ + ~open_file_descriptor() = default; + + /** + @brief Reads data from the open file descriptor into a @p buffer, starting at the current file offset and for a + given + @p size. The file offset is advanced by the number of bytes read. + @param buffer The buffer to read data into. + @param size The number of bytes to read. + @return The number of bytes read. + */ + auto read(void * buffer, size_t size) -> size_t; + + /** + @brief Writes data to the open file descriptor from a @p buffer, starting at the current file offset and for a + given + @p size. The file offset is advanced by the number of bytes written. + @param buffer The buffer to write data from. + @param size The number of bytes to write. + @return The number of bytes written. + */ + auto write(void const * buffer, size_t size) -> size_t; + + /** + @brief Returns the current file offset for this open file descriptor. + @return The current file offset in bytes. + */ + [[nodiscard]] auto offset() const -> size_t; + + /** + @brief Return a reference to the dentry associated with this open file descriptor. + @return A reference to the associated dentry. + */ + [[nodiscard]] auto get_dentry() const -> kstd::shared_ptr const &; + + private: + kstd::shared_ptr m_dentry; + size_t m_offset; + }; + +} // 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 new file mode 100644 index 00000000..8c24cf0e --- /dev/null +++ b/kernel/kernel/filesystem/open_file_descriptor.tests.cpp @@ -0,0 +1,113 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include +#include +#include + +SCENARIO("Open file descriptor construction", "[filesystem][open_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::open_file_descriptor{dentry}; + + THEN("the initial offset is zero") + { + REQUIRE(file_descriptor.offset() == 0); + } + } +} + +SCENARIO("Open file descriptor read/write offset management", "[filesystem][open_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::open_file_descriptor{dentry}; + + THEN("the offset is updated correctly after reads") + { + REQUIRE(file_descriptor.read(nullptr, 100) == 100); + REQUIRE(file_descriptor.offset() == 100); + REQUIRE(file_descriptor.read(nullptr, 50) == 50); + REQUIRE(file_descriptor.offset() == 150); + } + + THEN("the offset is updated correctly after writes") + { + REQUIRE(file_descriptor.write(nullptr, 200) == 200); + REQUIRE(file_descriptor.offset() == 200); + REQUIRE(file_descriptor.write(nullptr, 25) == 25); + REQUIRE(file_descriptor.offset() == 225); + } + + THEN("reads and writes both update the same offset") + { + REQUIRE(file_descriptor.read(nullptr, 10) == 10); + REQUIRE(file_descriptor.offset() == 10); + REQUIRE(file_descriptor.write(nullptr, 20) == 20); + REQUIRE(file_descriptor.offset() == 30); + REQUIRE(file_descriptor.read(nullptr, 5) == 5); + REQUIRE(file_descriptor.offset() == 35); + REQUIRE(file_descriptor.write(nullptr, 15) == 15); + REQUIRE(file_descriptor.offset() == 50); + } + } +} + +SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Open file descriptor read with real image", + "[filesystem][open_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 != nullptr); + auto ofd = kstd::make_shared(dentry); + + THEN("the file can be read and the offset is updated") + { + kstd::vector buffer(32); + auto bytes_read = ofd->read(buffer.data(), buffer.size()); + REQUIRE(bytes_read == 7); + REQUIRE(ofd->offset() == 7); + + std::string_view buffer_as_str{reinterpret_cast(buffer.data()), static_cast(bytes_read)}; + REQUIRE(buffer_as_str == "info_1\n"); + } + + THEN("the file can be read multiple times") + { + kstd::vector buffer(4); + auto bytes_read_1 = ofd->read(buffer.data(), buffer.size() / 2); + REQUIRE(bytes_read_1 == buffer.size() / 2); + REQUIRE(ofd->offset() == buffer.size() / 2); + + auto bytes_read_2 = ofd->read(buffer.data() + buffer.size() / 2, buffer.size() / 2); + REQUIRE(bytes_read_2 == buffer.size() / 2); + REQUIRE(ofd->offset() == buffer.size()); + + std::string_view buffer_as_str{reinterpret_cast(buffer.data()), bytes_read_1 + bytes_read_2}; + REQUIRE(buffer_as_str == "info"); + } + } +} diff --git a/kernel/kernel/filesystem/open_file_table.cpp b/kernel/kernel/filesystem/open_file_table.cpp new file mode 100644 index 00000000..2afe3aa4 --- /dev/null +++ b/kernel/kernel/filesystem/open_file_table.cpp @@ -0,0 +1,87 @@ +#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::ssize_t + { + if (!file_descriptor) + { + return -1; + } + + 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::shared_ptr + { + if (fd >= m_open_files.size()) + { + return nullptr; + } + + return m_open_files.at(fd); + } + + auto open_file_table::remove_file(size_t fd) -> kstd::ssize_t + { + if (fd >= m_open_files.size()) + { + return -1; + } + + m_open_files.at(fd) = nullptr; + return 0; + } +} // 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 new file mode 100644 index 00000000..7e754ac1 --- /dev/null +++ b/kernel/kernel/filesystem/open_file_table.hpp @@ -0,0 +1,66 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_OPEN_FILE_TABLE_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_OPEN_FILE_TABLE_HPP + +#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 + { + /** + @brief Initialize the global open file table. This method creates the singleton instance of the open file table. + @warning Panics if called more than once. + */ + auto static init() -> void; + + /** + @brief Get the global open file table instance. + @return A reference to the global open file table. + @warning Panics if the open file table has not been initialized. + */ + auto static get() -> open_file_table &; + + /** + @brief Destructor for the open file table. + */ + ~open_file_table() = default; + + /** + @brief Add a file to the open file table. + @param fd The file descriptor to add. + @return The file descriptor index assigned to the file, or -1 on failure. + */ + auto add_file(kstd::shared_ptr const & fd) -> kstd::ssize_t; + + /** + @brief Get a file from the open file table. + @param fd The file descriptor index to retrieve. + @return A pointer to the requested file descriptor, or a null pointer if not found. + */ + [[nodiscard]] auto file(size_t fd) const -> kstd::shared_ptr; + + /** + @brief Remove a file from the open file table. + @param fd The file descriptor index to remove. + @return 0 on success, or -1 on failure. + */ + auto remove_file(size_t fd) -> kstd::ssize_t; + + 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 new file mode 100644 index 00000000..3e91111d --- /dev/null +++ b/kernel/kernel/filesystem/open_file_table.tests.cpp @@ -0,0 +1,106 @@ +#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 code") + { + auto fd = table.add_file(nullptr); + REQUIRE(fd == -1); + } + + THEN("retrieving a file descriptor with an out-of-bounds file descriptor returns a null pointer") + { + auto retrieved_descriptor = table.file(1000); + REQUIRE(retrieved_descriptor == nullptr); + } + } +} + +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") + { + 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 == nullptr); + } + } + + WHEN("removing a file descriptor the other file descriptor keep the same index") + { + auto fd2 = table.add_file(file_descriptor); + 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 new file mode 100644 index 00000000..4845bf1a --- /dev/null +++ b/kernel/kernel/filesystem/path.hpp @@ -0,0 +1,71 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_PATH_HPP + +#include + +#include + +#include +#include + +namespace kernel::filesystem::path +{ + /** + @brief Provides utilities for handling filesystem paths, including validation and splitting into components. + */ + + /** + @brief Checks if the given path is within the maximum allowed length. + @param path The path to check. + @return true if the path length is valid, false otherwise. + */ + auto inline is_valid_path_length(std::string_view path) -> bool + { + return path.length() < kernel::filesystem::constants::max_path_length; + } + + /** + @brief Checks if the given path is a valid absolute path (starts with '/'). + @param path The path to check. + @return true if the path is a valid absolute path, false otherwise. + */ + auto inline is_valid_absolute_path(std::string_view path) -> bool + { + return !path.empty() && path.front() == '/' && is_valid_path_length(path); + } + + /** + @brief Checks if the given path is a valid relative path (doesn't start with '/'). + @param path The path to check. + @return true if the path is a valid relative path, false otherwise. + */ + auto inline is_valid_relative_path(std::string_view path) -> bool + { + return !path.empty() && path.front() != '/' && is_valid_path_length(path); + } + + /** + @brief Checks if the given path is a valid path (either absolute or relative). + @param path The path to check. + @return true if the path is a valid path, false otherwise. + */ + auto inline is_valid_path(std::string_view path) -> bool + { + return is_valid_absolute_path(path) || is_valid_relative_path(path); + } + + /** + @brief Splits 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())); }); + } + +} // namespace kernel::filesystem::path + +#endif // TEACH_OS_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 new file mode 100644 index 00000000..3c18b5c2 --- /dev/null +++ b/kernel/kernel/filesystem/path.tests.cpp @@ -0,0 +1,69 @@ +#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)); + } + } +} \ No newline at end of file diff --git a/kernel/kernel/filesystem/rootfs/filesystem.cpp b/kernel/kernel/filesystem/rootfs/filesystem.cpp new file mode 100644 index 00000000..7fe5c1ea --- /dev/null +++ b/kernel/kernel/filesystem/rootfs/filesystem.cpp @@ -0,0 +1,47 @@ +#include + +#include +#include +#include +#include + +#include + +#include + +namespace kernel::filesystem::rootfs +{ + + struct type final : kernel::filesystem::type + { + [[nodiscard]] auto name() const noexcept -> std::string_view override + { + return "rootfs"; + } + + [[nodiscard]] auto requires_device() const noexcept -> bool override + { + return true; + } + + [[nodiscard]] auto make_instance() const -> kstd::shared_ptr override + { + return kstd::make_shared(); + } + }; + + [[gnu::used]] + constexpr auto registration = type_registration{}; + + auto filesystem::mount(kstd::shared_ptr const &) -> operation_result + { + m_root_inode = kstd::make_shared(); + return operation_result::success; + } + + auto filesystem::lookup(kstd::shared_ptr const &, std::string_view) const + -> kstd::shared_ptr + { + return nullptr; + } +} // namespace kernel::filesystem::rootfs diff --git a/kernel/kernel/filesystem/rootfs/filesystem.hpp b/kernel/kernel/filesystem/rootfs/filesystem.hpp new file mode 100644 index 00000000..3c2dcb1c --- /dev/null +++ b/kernel/kernel/filesystem/rootfs/filesystem.hpp @@ -0,0 +1,41 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_ROOTFS_FILESYSTEM_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_ROOTFS_FILESYSTEM_HPP + +#include +#include + +#include +#include +#include + +#include + +namespace kernel::filesystem::rootfs +{ + /** + @brief A filesystem for the root filesystem. This filesystem provides access to the root directory and its contents, + which are typically populated by the init process during system startup. The rootfs filesystem serves as the top-level + directory in the filesystem hierarchy. It is responsible for providing a stable and consistent interface to the root + directory. + */ + struct filesystem : kernel::filesystem::filesystem + { + /** + @brief Initializes the rootfs filesystem with the given @p backing_inode. + @param backing_inode The backing inode to mount (not required by rootfs). + @return The result of the mount operation. + */ + auto mount(kstd::shared_ptr const & backing_inode) -> operation_result override; + + /** + @brief Looks up an inode by @p name within a @p parent directory. + @param parent The parent directory inode. + @param name The name of the inode to look up. + @return Always returns nullptr. + */ + [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const + -> kstd::shared_ptr 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 new file mode 100644 index 00000000..ae320e95 --- /dev/null +++ b/kernel/kernel/filesystem/rootfs/filesystem.tests.cpp @@ -0,0 +1,38 @@ +#include + +#include + +#include +#include +#include + +#include + +SCENARIO("Rootfs filesystem mount and lookup", "[filesystem][rootfs][filesystem]") +{ + GIVEN("a mounted rootfs filesystem") + { + auto fs = kernel::filesystem::rootfs::filesystem{}; + auto result = fs.mount(nullptr); + + THEN("the filesystem can be mounted successfully") + { + REQUIRE(result == kernel::filesystem::filesystem::operation_result::success); + REQUIRE(fs.root_inode() != nullptr); + } + + THEN("looking up a non-existent directory returns null") + { + auto non_existent_inode_1 = fs.lookup(fs.root_inode(), ""); + REQUIRE(non_existent_inode_1 == nullptr); + auto non_existent_inode_2 = fs.lookup(fs.root_inode(), "nonexistent"); + REQUIRE(non_existent_inode_2 == nullptr); + } + + THEN("looking up with a null parent inode returns null") + { + auto result = fs.lookup(nullptr, "dev"); + REQUIRE(result == nullptr); + } + } +} diff --git a/kernel/kernel/filesystem/rootfs/inode.cpp b/kernel/kernel/filesystem/rootfs/inode.cpp new file mode 100644 index 00000000..f64fb87c --- /dev/null +++ b/kernel/kernel/filesystem/rootfs/inode.cpp @@ -0,0 +1,23 @@ +#include + +#include + +#include + +namespace kernel::filesystem::rootfs +{ + auto inode::read(void *, size_t, size_t) const -> size_t + { + return 0; + } + + auto inode::write(void const *, size_t, size_t) -> size_t + { + return 0; + } + + auto inode::is_directory() const -> bool + { + return true; + } +} // namespace kernel::filesystem::rootfs diff --git a/kernel/kernel/filesystem/rootfs/inode.hpp b/kernel/kernel/filesystem/rootfs/inode.hpp new file mode 100644 index 00000000..0f21eaa0 --- /dev/null +++ b/kernel/kernel/filesystem/rootfs/inode.hpp @@ -0,0 +1,45 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_ROOTFS_INODE_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_ROOTFS_INODE_HPP + +#include + +#include +#include +#include + +#include + +namespace kernel::filesystem::rootfs +{ + /** + @brief Represents an inode in the rootfs filesystem. + */ + struct inode : kernel::filesystem::inode + { + /** + @brief Reads from the rootfs directory inode. + @param buffer Destination buffer. + @param offset Read offset in bytes. + @param size Number of bytes requested. + @return Number of bytes read (always 0 because this inode does not expose file data). + */ + auto read(void * buffer, size_t offset, size_t size) const -> size_t override; + + /** + @brief Writes to the rootfs directory inode. + @param buffer Source buffer. + @param offset Write offset in bytes. + @param size Number of bytes requested. + @return Number of bytes written (always 0 because writes are not supported for this inode). + */ + auto write(void const * buffer, size_t offset, size_t size) -> size_t override; + + /** + @brief Check if this inode represents a directory. + @return returns true, since this inode represents the / directory in the rootfs filesystem. + */ + [[nodiscard]] auto is_directory() const -> bool override; + }; +} // namespace kernel::filesystem::rootfs + +#endif diff --git a/kernel/kernel/filesystem/rootfs/inode.tests.cpp b/kernel/kernel/filesystem/rootfs/inode.tests.cpp new file mode 100644 index 00000000..f4b634fa --- /dev/null +++ b/kernel/kernel/filesystem/rootfs/inode.tests.cpp @@ -0,0 +1,37 @@ +#include + +#include +#include +#include + +#include + +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.data(), 0, buffer.size()); + + THEN("no bytes are read") + { + REQUIRE(bytes_read == 0); + } + } + + WHEN("writing to the inode") + { + kstd::vector buffer(10, 'x'); + auto bytes_written = inode.write(buffer.data(), 0, buffer.size()); + + THEN("no bytes are written") + { + REQUIRE(bytes_written == 0); + } + } + } +} diff --git a/kernel/kernel/filesystem/type.hpp b/kernel/kernel/filesystem/type.hpp new file mode 100644 index 00000000..0948e549 --- /dev/null +++ b/kernel/kernel/filesystem/type.hpp @@ -0,0 +1,42 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_TYPE_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_TYPE_HPP + +#include + +#include + +#include + +namespace kernel::filesystem +{ + + //! A type descriptor for a filesystem driver. + //! + //! Each filesystem must expose an instance of a class derived from this type in order to be registered with the vfs + //! filesystem registry. + struct type + { + virtual ~type() = default; + + //! Get the name of the filesystem represented by this descriptor. + [[nodiscard]] virtual auto name() const noexcept -> std::string_view = 0; + + //! Check if filesystems of this type require a device to back them. + [[nodiscard]] virtual auto requires_device() const noexcept -> bool = 0; + + //! Create a new instance of the filesytem represented by this descriptor. + [[nodiscard]] virtual auto make_instance() const -> kstd::shared_ptr = 0; + }; + + template + struct type_registration + { + constexpr auto static instance = Type{}; + [[using gnu: section("fs_types"), used, visibility("hidden")]] constexpr auto static pointer{ + kstd::make_observer(&instance), + }; + }; + +} // namespace kernel::filesystem + +#endif diff --git a/kernel/kernel/filesystem/type_registry.cpp b/kernel/kernel/filesystem/type_registry.cpp new file mode 100644 index 00000000..74b12090 --- /dev/null +++ b/kernel/kernel/filesystem/type_registry.cpp @@ -0,0 +1,76 @@ +#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 type_registry::pointer const __start_fs_types; + extern type_registry::pointer const __stop_fs_types; + // NOLINTEND(readability-identifier-naming) + } + + namespace + { + auto constinit instance = std::optional{}; + } + + auto type_registry::init() -> void + { + if (instance) + { + kapi::system::panic("[FILESYSTEM] tried to initialize type registry more than once!"); + } + + instance.emplace(); + + auto type_descriptors = std::span{&__start_fs_types, &__stop_fs_types} | // + std::views::filter([](auto p) { return p != nullptr; }); + + std::ranges::for_each(type_descriptors, [](auto descriptor) { + kstd::println("[FILESYSTEM] registering '{}'", descriptor->name()); + instance->add(descriptor); + }); + } + + auto type_registry::get() -> type_registry & + { + if (!instance) + { + kapi::system::panic("[FILESYSTEM] type registry has not been initialized!"); + } + + return *instance; + } + + auto type_registry::add(pointer descriptor) -> bool + { + auto result = m_descriptors.emplace(descriptor->name(), descriptor); + return result.second; + } + + auto type_registry::all() const noexcept -> std::span + { + return {m_descriptors.values().begin(), m_descriptors.values().end()}; + } + + auto type_registry::size() const noexcept -> std::size_t + { + return m_descriptors.size(); + } + +} // namespace kernel::filesystem diff --git a/kernel/kernel/filesystem/type_registry.hpp b/kernel/kernel/filesystem/type_registry.hpp new file mode 100644 index 00000000..3be72950 --- /dev/null +++ b/kernel/kernel/filesystem/type_registry.hpp @@ -0,0 +1,53 @@ +#ifndef TEACH_OS_KERNEL_TYPE_REGISRY_HPP +#define TEACH_OS_KERNEL_TYPE_REGISRY_HPP + +#include + +#include +#include +#include + +#include +#include +#include + +namespace kernel::filesystem +{ + + struct type_registry + { + using value_type = type; + using pointer = kstd::observer_ptr; + + auto static init() -> void; + auto static get() -> type_registry &; + + constexpr type_registry() noexcept = default; + + //! Add a type descriptor to this registry. + //! + //! This function will register the given descriptor with this registry, given that no descriptor for a filesystem + //! with the same name exists in this registry already. + //! + //! @param descriptor The filesystem type descriptor to add to the registry. + //! @return @p true iff. the descriptor was successfully added, @p false if not. + auto add(pointer descriptor) -> bool; + + //! Get all currently registered type descriptors. + //! + //! @return A span containing all currently registered filesystem type descriptors. + [[nodiscard]] auto all() const noexcept -> std::span; + + //! Get the number of registered filesystem types. + //! + //! @return The number of filesystem descriptors currently registered with this registry. + [[nodiscard]] auto size() const noexcept -> std::size_t; + + private: + //! A map from filesystem names (identifiers) to filesystem type descriptors. + kstd::flat_map m_descriptors{}; + }; + +} // namespace kernel::filesystem + +#endif diff --git a/kernel/kernel/filesystem/type_registry.tests.cpp b/kernel/kernel/filesystem/type_registry.tests.cpp new file mode 100644 index 00000000..8382579f --- /dev/null +++ b/kernel/kernel/filesystem/type_registry.tests.cpp @@ -0,0 +1,77 @@ +#include + +#include +#include + +#include + +#include + +#include +#include + +struct test_type final : kernel::filesystem::type +{ + [[nodiscard]] auto name() const noexcept -> std::string_view override + { + return "bht_testfs"; + } + + [[nodiscard]] auto requires_device() const noexcept -> bool override + { + return false; + } + + [[nodiscard]] auto make_instance() const -> kstd::shared_ptr override + { + return nullptr; + } +}; + +SCENARIO("Filesystem type registry initialization and construction", "[filesystem]") +{ + GIVEN("A default constructed type_registry") + { + auto instance = kernel::filesystem::type_registry{}; + + WHEN("getting the span of filesystem descriptors") + { + auto descriptors = instance.all(); + + THEN("the span is empty") + { + REQUIRE(descriptors.empty()); + } + } + } +} + +SCENARIO("Filesystem type registry modifiers", "[filesystem]") +{ + GIVEN("A default constructed type_registry") + { + auto instance = kernel::filesystem::type_registry{}; + + WHEN("adding a type descriptor") + { + auto descriptor = test_type{}; + + instance.add(kstd::make_observer(&descriptor)); + + THEN("the size of the registry is one") + { + REQUIRE(instance.size() == 1); + } + + THEN("the span is not empty") + { + REQUIRE_FALSE(instance.all().empty()); + } + + THEN("the span's size is equal to the registry size") + { + REQUIRE(std::size(instance.all()) == std::size(instance)); + } + } + } +} diff --git a/kernel/kernel/filesystem/vfs.cpp b/kernel/kernel/filesystem/vfs.cpp new file mode 100644 index 00000000..e5dff8c4 --- /dev/null +++ b/kernel/kernel/filesystem/vfs.cpp @@ -0,0 +1,299 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +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 root_fs = kstd::make_shared(); + root_fs->mount(nullptr); + + auto root_fs_root_dentry = kstd::make_shared(nullptr, root_fs->root_inode(), "/"); + auto root_mount = kstd::make_shared(nullptr, root_fs_root_dentry, root_fs, nullptr, nullptr); + m_mount_table.add_mount(root_mount); + + // mount devfs at /dev (inside rootfs, temporary, will be shadowed) + auto device_fs = kstd::make_shared(); + device_fs->mount(nullptr); + graft_persistent_device_fs(device_fs); + + // mount boot fs at / (shadows rootfs), re-graft devfs + auto [boot_device_dentry, boot_device_mount_context] = resolve_path_internal("/dev/ram0"); + if (boot_device_dentry && boot_device_mount_context) + { + if (auto boot_root_fs = kernel::filesystem::filesystem::probe_and_mount(boot_device_dentry->get_inode())) + { + if (auto root_dentry = resolve_path("/")) + { + do_mount_internal(root_dentry, root_mount, boot_root_fs, boot_device_mount_context); + graft_persistent_device_fs(device_fs); + } + } + } + } + + 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::shared_ptr + { + auto [dentry, mount] = resolve_path_internal(path); + if (!dentry || !mount) + { + return nullptr; + } + mount->increment_ref_count(); + return dentry; + } + + auto vfs::close(std::string_view path) -> operation_result + { + if (auto mount = find_mount(path)) + { + mount->decrement_ref_count(); + return operation_result::success; + } + return operation_result::invalid_path; + } + + auto vfs::do_mount(std::string_view source, std::string_view target) -> operation_result + { + if (!path::is_valid_path(source) || !path::is_valid_path(target)) + { + return operation_result::invalid_path; + } + + auto [mount_point_dentry, mount_context] = resolve_path_internal(target); + if (mount_point_dentry && mount_context) + { + auto [source_dentry, source_mount_context] = resolve_path_internal(source); + if (source_dentry && source_mount_context) + { + if (auto fs = kernel::filesystem::filesystem::probe_and_mount(source_dentry->get_inode())) + { + do_mount_internal(mount_point_dentry, mount_context, fs, source_mount_context); + return operation_result::success; + } + return operation_result::invalid_filesystem; + } + return operation_result::non_existent_path; + } + return operation_result::mount_point_not_found; + } + + auto vfs::unmount(std::string_view path) -> operation_result + { + if (!path::is_valid_path(path)) + { + return operation_result::invalid_path; + } + + auto remove_result = m_mount_table.remove_mount(path); + if (remove_result == mount_table::operation_result::removed) + { + return operation_result::success; + } + else if (remove_result == mount_table::operation_result::mount_not_found) + { + return operation_result::mount_point_not_found; + } + + return operation_result::unmount_failed; + } + + auto vfs::do_mount_internal(kstd::shared_ptr const & mount_point_dentry, + kstd::shared_ptr const & parent_mount, kstd::shared_ptr const & fs, + kstd::shared_ptr const & source_mount) -> void + { + auto new_fs_root = + kstd::make_shared(mount_point_dentry->parent(), fs->root_inode(), mount_point_dentry->name()); + auto new_mount = kstd::make_shared(mount_point_dentry, new_fs_root, fs, parent_mount, source_mount); + m_mount_table.add_mount(new_mount); + } + + auto vfs::graft_persistent_device_fs(kstd::shared_ptr const & device_fs) -> void + { + auto [root_mount_point_dentry, root_mount] = resolve_path_internal("/"); + 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, device_fs->root_inode(), "dev"); + root_mount_point_dentry->add_child(dev_dentry); + } + + do_mount_internal(dev_dentry, root_mount, device_fs); + } + } + + auto vfs::resolve_path_internal(std::string_view path) const + -> std::pair, kstd::shared_ptr> + { + if (!path::is_valid_absolute_path(path)) + { + return {nullptr, nullptr}; + } + + 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 (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->get_filesystem(); + auto found_inode = current_fs->lookup(current_dentry->get_inode(), part); + if (!found_inode) + { + return {nullptr, nullptr}; + } + + 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->get_inode()->is_symbolic_link()) + { + if (symlink_counter++ > constants::symloop_max) + { + return {nullptr, nullptr}; + } + + kstd::vector buffer(constants::symlink_max_path_length); + auto const bytes_read = next_dentry->get_inode()->read(buffer.data(), 0, buffer.size()); + auto const symbolic_link_path = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; + + 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 {current_dentry, current_mount}; + } + + auto vfs::resolve_path(std::string_view path) const -> kstd::shared_ptr + { + return resolve_path_internal(path).first; + } + + auto vfs::find_mount(std::string_view path) const -> kstd::shared_ptr + { + return resolve_path_internal(path).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 new file mode 100644 index 00000000..ddc9a9bc --- /dev/null +++ b/kernel/kernel/filesystem/vfs.hpp @@ -0,0 +1,113 @@ +#ifndef TEACH_OS_KERNEL_FILESYSTEM_VFS_HPP +#define TEACH_OS_KERNEL_FILESYSTEM_VFS_HPP + +#include +#include +#include +#include +#include + +#include + +#include +#include + +namespace kernel::filesystem +{ + /** + @brief The virtual filesystem (VFS) is responsible for managing mounted filesystems and providing a unified interface + for file operations across different filesystem types. The VFS maintains a mount table to keep track of mounted + filesystems and their associated mount points. It provides methods for opening files by path, which involvesresolving + the path to the appropriate mounted filesystem and delegating the file operation to that filesystem's implementation. + */ + struct vfs + { + /** + @brief Results for VFS operations. + */ + enum class operation_result : int + { + success = 0, + invalid_path = -1, + non_existent_path = -2, + mount_point_not_found = -3, + unmount_failed = -4, + invalid_filesystem = -5 + }; + + vfs(); + + /** + @brief Initialize the virtual filesystem. + @warning Panics if the VFS has already been initialized. + */ + auto static init() -> void; + + /** + @brief Get the singleton instance of the virtual filesystem. + @return A reference to the VFS instance. + @warning Panics if the VFS has not been initialized yet. + */ + auto static get() -> vfs &; + + /** + @brief Destructor for the VFS. + */ + ~vfs() = default; + + /** + @brief Open a file by its @p path. This method resolves the path and returns the corresponding dentry. + @param path The path to the file to open. + @return A shared pointer to the dentry or a null pointer if the file could not be opened. + */ + auto open(std::string_view path) -> kstd::shared_ptr; + + /** + @brief Close a file by its associated @p path. + @param path The path to the file to close. + @return The result of the close operation. + */ + auto close(std::string_view path) -> operation_result; + + /** + @brief Mount a @p source path to a specific @p target path. + @param source The source of the filesystem to mount. + @param target The path where the filesystem should be mounted. + @return The result of the mount operation. + */ + auto do_mount(std::string_view source, std::string_view target) -> operation_result; + + /** + @brief Unmount the filesystem mounted at the specified @p path. + @param path The path where the filesystem is mounted. + @return The result of the unmount operation. + */ + auto unmount(std::string_view path) -> operation_result; + + private: + /** + * 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 + -> std::pair, kstd::shared_ptr>; + [[nodiscard]] auto resolve_path(std::string_view path) const -> kstd::shared_ptr; + [[nodiscard]] auto find_mount(std::string_view path) const -> kstd::shared_ptr; + + auto do_mount_internal(kstd::shared_ptr const & mount_point_dentry, + kstd::shared_ptr const & parent_mount, kstd::shared_ptr const & fs, + kstd::shared_ptr const & source_mount = nullptr) -> void; + + auto graft_persistent_device_fs(kstd::shared_ptr const & device_fs) -> void; + + 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 new file mode 100644 index 00000000..f1d0df0d --- /dev/null +++ b/kernel/kernel/filesystem/vfs.tests.cpp @@ -0,0 +1,568 @@ +#include + +#include +#include + +#include +#include + +#include + +#include +#include +#include +#include + +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 == nullptr); + + 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.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); + + auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); + REQUIRE(mounted_monkey_1 != nullptr); + REQUIRE(vfs.close(mounted_monkey_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); + auto unmounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); + REQUIRE(unmounted_monkey_1 == nullptr); + + auto info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 != nullptr); + } + + THEN("third image can be mounted in a mounted file system, unmount only if no child mount exists") + { + REQUIRE(vfs.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.do_mount("/dev/ram32", "/information/monkey_house/infrastructure") == + kernel::filesystem::vfs::operation_result::success); + + auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); + auto mounted_fish1 = vfs.open("/information/monkey_house/infrastructure/enclosures/aquarium/tank_1/fish_1.txt"); + + REQUIRE(mounted_monkey_1 != nullptr); + REQUIRE(mounted_fish1 != nullptr); + + REQUIRE(vfs.close(mounted_monkey_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.close(mounted_fish1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::unmount_failed); + REQUIRE(vfs.unmount("/information/monkey_house/infrastructure") == + kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); + } + + THEN("image can be mounted, unmount only if no files are open") + { + REQUIRE(vfs.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); + + auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); + REQUIRE(mounted_monkey_1 != nullptr); + + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::unmount_failed); + + REQUIRE(vfs.close(mounted_monkey_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); + } + + THEN("file with invalid path or not opened file cannot be closed") + { + REQUIRE(vfs.close("invalid_path") == kernel::filesystem::vfs::operation_result::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 != nullptr); + + REQUIRE(vfs.close(info_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + REQUIRE_THROWS_AS(vfs.close(info_1->absolute_path()), std::runtime_error); + } + + THEN("images can be stacked mounted and correct file system is unmounted again") + { + REQUIRE(vfs.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.do_mount("/dev/ram32", "/information") == kernel::filesystem::vfs::operation_result::success); + + auto mounted_tickets = vfs.open("/information/entrance/tickets.txt"); + REQUIRE(mounted_tickets != nullptr); + + REQUIRE(vfs.close(mounted_tickets->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); + mounted_tickets = vfs.open("/information/entrance/tickets.txt"); + REQUIRE(mounted_tickets == nullptr); + + auto mounted_monkey = vfs.open("/information/monkey_house/monkey_1.txt"); + REQUIRE(mounted_monkey != nullptr); + } + + THEN("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.do_mount("/dev/ram16", "/") == kernel::filesystem::vfs::operation_result::success); + + info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 == nullptr); + + auto water = vfs.open("/monkey_house/infrastructure/water.txt"); + REQUIRE(water != nullptr); + + REQUIRE(vfs.close(water->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + + REQUIRE(vfs.unmount("/") == kernel::filesystem::vfs::operation_result::success); + + 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.do_mount("/dev/ram16", "/") == kernel::filesystem::vfs::operation_result::success); + + info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 == nullptr); + + auto water = vfs.open("/monkey_house/infrastructure/water.txt"); + REQUIRE(water != nullptr); + + REQUIRE(vfs.close(water->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + + auto dev_ram_16 = vfs.open("/dev/ram16"); + REQUIRE(dev_ram_16 == nullptr); + + REQUIRE(vfs.do_mount("/dev/ram32", "/") == kernel::filesystem::vfs::operation_result::non_existent_path); + + REQUIRE(vfs.unmount("/") == kernel::filesystem::vfs::operation_result::success); + + 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->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + + REQUIRE(vfs.unmount("/dev") == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.unmount("/") == kernel::filesystem::vfs::operation_result::success); + + info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 == nullptr); + + REQUIRE(vfs.do_mount("/dev/ram0", "/") == kernel::filesystem::vfs::operation_result::success); + + info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 != nullptr); + + auto dev_ram_0 = vfs.open("/dev/ram0"); + REQUIRE(dev_ram_0 == nullptr); + } + + THEN("mount with null file system fails") + { + REQUIRE(vfs.do_mount("/closed.txt", "/information") == + kernel::filesystem::vfs::operation_result::invalid_filesystem); + } + + THEN("mount with invalid path fails") + { + REQUIRE(vfs.do_mount("/dev/ram16", "") == kernel::filesystem::vfs::operation_result::invalid_path); + REQUIRE(vfs.do_mount("/dev/ram16", "information") == + kernel::filesystem::vfs::operation_result::mount_point_not_found); + } + + THEN("mount with non-existent source path fails") + { + REQUIRE(vfs.do_mount("/dev/nonexistent", "/information") == + kernel::filesystem::vfs::operation_result::non_existent_path); + } + + THEN("mount with non-existent mount point fails") + { + REQUIRE(vfs.do_mount("/dev/ram16", "/information/nonexistent") == + kernel::filesystem::vfs::operation_result::mount_point_not_found); + } + + THEN("unmount with invalid path fails") + { + REQUIRE(vfs.unmount("") == kernel::filesystem::vfs::operation_result::invalid_path); + REQUIRE(vfs.unmount("information") == kernel::filesystem::vfs::operation_result::mount_point_not_found); + } + + THEN("unmounting non-existent mount point returns expected error code") + { + REQUIRE(vfs.unmount("/information/nonexistent") == + kernel::filesystem::vfs::operation_result::mount_point_not_found); + } + + 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.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); + + 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.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.do_mount("/archiv/2024.img", "/information/monkey_house/infrastructure") == + kernel::filesystem::vfs::operation_result::success); + + 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 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.do_mount("/archiv/2024.img", "/information") == kernel::filesystem::vfs::operation_result::success); + + auto info_1 = vfs.open("/information/info_1.txt"); + REQUIRE(info_1 == nullptr); + + auto dentry = vfs.open("/information/sheep_1.txt"); + REQUIRE(dentry != nullptr); + auto sheep_1_ofd = kstd::make_shared(dentry); + + kstd::vector buffer(7); + auto bytes_read = sheep_1_ofd->read(buffer.data(), buffer.size()); + std::string_view buffer_as_str{reinterpret_cast(buffer.data()), bytes_read}; + REQUIRE(buffer_as_str == "sheep_1"); + + REQUIRE(vfs.close(dentry->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); + auto unmounted_sheep_1 = vfs.open("/information/sheep_1.txt"); + REQUIRE(unmounted_sheep_1 == nullptr); + } + + 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.do_mount("/archiv/2024.img", "/information") == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.do_mount("/archiv/2025.img", "/information/stable") == + kernel::filesystem::vfs::operation_result::success); + + 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); + auto goat_1_ofd = kstd::make_shared(goat_1); + + kstd::vector sheep_buffer(7); + auto bytes_read = sheep_1_ofd->read(sheep_buffer.data(), sheep_buffer.size()); + std::string_view buffer_as_str{reinterpret_cast(sheep_buffer.data()), bytes_read}; + REQUIRE(buffer_as_str == "sheep_1"); + + kstd::vector goat_buffer(6); + bytes_read = goat_1_ofd->read(goat_buffer.data(), goat_buffer.size()); + buffer_as_str = std::string_view{reinterpret_cast(goat_buffer.data()), bytes_read}; + REQUIRE(buffer_as_str == "goat_1"); + + REQUIRE(vfs.close(sheep_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.close(goat_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::unmount_failed); + + REQUIRE(vfs.unmount("/information/stable") == kernel::filesystem::vfs::operation_result::success); + auto unmounted_goat_1 = vfs.open("/information/stable/petting_zoo/goat_1.txt"); + REQUIRE(unmounted_goat_1 == nullptr); + + auto still_mounted_sheep_1 = vfs.open("/information/sheep_1.txt"); + REQUIRE(still_mounted_sheep_1 != nullptr); + + REQUIRE(vfs.close(still_mounted_sheep_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); + + REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); + auto unmounted_sheep_1 = vfs.open("/information/sheep_1.txt"); + REQUIRE(unmounted_sheep_1 == nullptr); + } + } + + 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.do_mount("/dev/ram16", "/entrance") == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.do_mount("/entrance/archiv/2024.img", "/enclosures") == + kernel::filesystem::vfs::operation_result::success); + + REQUIRE(vfs.unmount("/entrance") == kernel::filesystem::vfs::operation_result::unmount_failed); + REQUIRE(vfs.unmount("/enclosures") == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.unmount("/entrance") == kernel::filesystem::vfs::operation_result::success); + } + + THEN("can mount filesystem onto the directory that contains it") + { + REQUIRE(vfs.do_mount("/dev/ram16", "/entrance") == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.do_mount("/entrance/archiv/2024.img", "/entrance") == + kernel::filesystem::vfs::operation_result::success); + + REQUIRE(vfs.unmount("/entrance") == kernel::filesystem::vfs::operation_result::success); + REQUIRE(vfs.unmount("/entrance") == kernel::filesystem::vfs::operation_result::success); + } + } + + 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 == nullptr); + } + + 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 == nullptr); + } + + 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 == nullptr); + } + } + + 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(); + vfs.do_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(); + vfs.do_mount("/dev/ram16", "/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); + } + } +} diff --git a/kernel/kernel/main.cpp b/kernel/kernel/main.cpp new file mode 100644 index 00000000..8dc13490 --- /dev/null +++ b/kernel/kernel/main.cpp @@ -0,0 +1,152 @@ +#include "kernel/filesystem/type_registry.hpp" +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include + +using namespace kstd::units_literals; + +auto run_demo() -> void +{ + // 1) open a file + kstd::println("attempting to open /entrance/tickets.txt"); + auto fd_1 = kapi::filesystem::open("/entrance/tickets.txt"); + if (fd_1 == -1) + { + kapi::system::panic("demo failed"); + } + else + { + kstd::println("--> successfully opened /entrance/tickets.txt with file descriptor {}", fd_1); + } + + // 2) read from the file + kstd::vector buffer_1{10}; + auto bytes_read = kapi::filesystem::read(fd_1, buffer_1.data(), buffer_1.size()); + auto buffer_as_str = std::string_view{reinterpret_cast(buffer_1.data()), static_cast(bytes_read)}; + kstd::println("--> read {} bytes from /entrance/tickets.txt: {}", bytes_read, buffer_as_str); + kstd::println(""); + + // 3) show that /entrance/information/info_1.txt is not accessible before mounting + kstd::println("attempting to open /entrance/information/info_1.txt before mounting"); + auto fd_before_mount = kapi::filesystem::open("/entrance/information/info_1.txt"); + if (fd_before_mount == -1) + { + kstd::println("--> as expected the file could not be opened before mounting"); + } + + // 4) mount a new filesystem on top of /entrance + kstd::println("mount /dev/ram16 to /entrance"); + if (kapi::filesystem::mount("/dev/ram16", "/entrance") == 0) + { + kstd::println("--> successfully mounted /dev/ram16 to /entrance"); + } + else + { + kapi::system::panic("demo failed"); + } + kstd::println(""); + + // 5) open a file from the new filesystem + kstd::println("attempting to open /entrance/information/info_1.txt"); + auto fd_2 = kapi::filesystem::open("/entrance/information/info_1.txt"); + if (fd_2 != -1) + { + kstd::println("--> successfully opened /entrance/information/info_1.txt with file descriptor {}", fd_2); + } + else + { + kapi::system::panic("demo failed"); + } + + // 6) read from the new file + kstd::vector buffer_2{10}; + bytes_read = kapi::filesystem::read(fd_2, buffer_2.data(), buffer_2.size()); + buffer_as_str = std::string_view{reinterpret_cast(buffer_2.data()), static_cast(bytes_read)}; + kstd::println("--> read {} bytes from /entrance/information/info_1.txt: {} ", bytes_read, buffer_as_str); + + // 7) open device as file + kstd::println("attempting to open /dev/ram32 as a file"); + auto fd_3 = kapi::filesystem::open("/dev/ram32"); + if (fd_3 != -1) + { + kstd::println("--> successfully opened /dev/ram32 as a file with file descriptor {}", fd_3); + } + else + { + kapi::system::panic("demo failed"); + } + + // 8) read from the device file + kstd::vector buffer_3{2}; + bytes_read = kapi::filesystem::read(fd_3, buffer_3.data(), buffer_3.size()); + kstd::println("--> read {} bytes from /dev/ram32: {::#04x} ", bytes_read, buffer_3); + + // 9) write to the device file + auto const default_buffer_value = std::byte{0xAA}; + kstd::vector write_buffer{default_buffer_value, default_buffer_value}; + auto bytes_written = kapi::filesystem::write(fd_3, write_buffer.data(), write_buffer.size()); + kstd::println("--> written {} bytes to /dev/ram32: {::#04x}", bytes_written, write_buffer); + + // 10) do memory dump to show that the write to the device file had an effect +} + +auto main() -> int +{ + kapi::cio::init(); + kstd::println("[OS] IO subsystem initialized."); + + kapi::cpu::init(); + + kapi::memory::init(); + kernel::memory::init_heap(kapi::memory::heap_base); + kapi::system::memory_initialized(); + kapi::memory::init_mmio(kapi::memory::mmio_base, 1_GiB / kapi::memory::page::size); + kstd::println("[OS] Memory subsystem initialized."); + + kapi::devices::init(); + kstd::println("[OS] System root bus initialized."); + + kapi::devices::init_platform_devices(); + kstd::println("[OS] Platform devices initialized."); + + kapi::interrupts::enable(); + kstd::println("[OS] Interrupts enabled."); + + kapi::boot_modules::init(); + kstd::println("[OS] Boot module registry initialized."); + + kernel::devices::storage::management::init(); + kstd::println("[OS] Storage management initialized."); + + kernel::filesystem::open_file_table::init(); + kstd::println("[OS] Global open file table initialized."); + + kernel::filesystem::type_registry::init(); + kstd::println("[OS] Builtin filesystems registered."); + + kernel::filesystem::vfs::init(); + kstd::println("[OS] Virtual filesystem initialized."); + + // TODO BA-FS26 remove demo code? + // run_demo(); + + kapi::system::panic("Returning from kernel main!"); +} diff --git a/kernel/kernel/memory.cpp b/kernel/kernel/memory.cpp new file mode 100644 index 00000000..6a85c0e7 --- /dev/null +++ b/kernel/kernel/memory.cpp @@ -0,0 +1,61 @@ +#include + +#include +#include + +#include +#include + +#include +#include + +#include +#include + +namespace kernel::memory +{ + + namespace + { + struct null_allocator : heap_allocator + { + null_allocator static instance; + + [[nodiscard]] auto allocate(kstd::units::bytes, kstd::units::bytes) noexcept -> void * override + { + kstd::print(kstd::print_sink::stderr, "Tried to allocate memory without an active heap!"); + return nullptr; + } + + auto deallocate(void *) noexcept -> void override + { + kstd::print(kstd::print_sink::stderr, "Tried to deallocate memory without an active heap!"); + } + }; + + constinit null_allocator null_allocator::instance{}; + + auto constinit active_heap_allocator = std::optional{&null_allocator::instance}; + auto constinit basic_allocator = std::optional{}; + } // namespace + + auto get_heap_allocator() -> heap_allocator & + { + return *active_heap_allocator.value(); + } + + auto init_heap(kapi::memory::linear_address base) -> void + { + auto static constinit is_initialized = std::atomic_flag{}; + + if (is_initialized.test_and_set()) + { + kapi::system::panic("[OS:MEM] The heap has already been initialized."); + } + + auto & instance = basic_allocator.emplace(base); + active_heap_allocator = &instance; + kstd::println("[OS:MEM] Heap initialized. Dynamic memory available."); + } + +} // namespace kernel::memory \ No newline at end of file diff --git a/kernel/kernel/memory.hpp b/kernel/kernel/memory.hpp new file mode 100644 index 00000000..17fb82b4 --- /dev/null +++ b/kernel/kernel/memory.hpp @@ -0,0 +1,19 @@ +#ifndef TEACHOS_KERNEL_MEMORY_HPP +#define TEACHOS_KERNEL_MEMORY_HPP + +#include // IWYU pragma: export + +#include + +namespace kernel::memory +{ + + //! Get a handle to the global heap allocator. + auto get_heap_allocator() -> heap_allocator &; + + //! Initialize the kernel heap. + auto init_heap(kapi::memory::linear_address base) -> void; + +} // namespace kernel::memory + +#endif \ No newline at end of file diff --git a/kernel/kernel/memory/bitmap_allocator.cpp b/kernel/kernel/memory/bitmap_allocator.cpp new file mode 100644 index 00000000..240e2afe --- /dev/null +++ b/kernel/kernel/memory/bitmap_allocator.cpp @@ -0,0 +1,106 @@ +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace kernel::memory +{ + + bitmap_frame_allocator::bitmap_frame_allocator(std::span storage, std::size_t frame_count) noexcept + : m_bitmap{storage} + , m_frame_count{frame_count} + , m_last_index{} + { + constexpr auto bits_per_word = 64uz; + auto to_fill = (frame_count + bits_per_word - 1) / bits_per_word; + std::ranges::fill(std::views::take(m_bitmap, to_fill), ~0uz); + } + + auto bitmap_frame_allocator::allocate_many(std::size_t count) noexcept + -> std::optional> + { + if (count == 0) + { + return std::nullopt; + } + + auto free_count = 0uz; + auto first_free = 0uz; + + for (auto i = 0uz; i < m_frame_count; ++i) + { + auto const current = (m_last_index + i) % m_frame_count; + if (!test(current)) + { + if (free_count == 0) + { + first_free = current; + } + + ++free_count; + + if (free_count == count) + { + for (auto j = 0uz; j < count; ++j) + { + set(first_free + j); + } + m_last_index = first_free + count; + return std::make_pair(kapi::memory::frame{first_free}, count); + } + } + else + { + free_count = 0; + } + } + + return std::nullopt; + } + + auto bitmap_frame_allocator::release_many(std::pair frame_set) -> void + { + auto const [start, count] = frame_set; + for (auto i = 0uz; i < count; ++i) + { + clear(start.number() + i); + } + } + + auto bitmap_frame_allocator::mark_used(kapi::memory::frame frame) -> void + { + set(frame.number()); + } + + auto bitmap_frame_allocator::test(std::size_t index) const noexcept -> bool + { + constexpr auto bits_per_word = 64uz; + auto entry_entry = index / bits_per_word; + auto entry_offset = index % bits_per_word; + return (m_bitmap[entry_entry] & (1uz << entry_offset)); + } + + auto bitmap_frame_allocator::set(std::size_t index) noexcept -> void + { + constexpr auto bits_per_word = 64uz; + auto entry_entry = index / bits_per_word; + auto entry_offset = index % bits_per_word; + m_bitmap[entry_entry] |= (1uz << entry_offset); + } + + auto bitmap_frame_allocator::clear(std::size_t index) noexcept -> void + { + constexpr auto bits_per_word = 64uz; + auto entry_entry = index / bits_per_word; + auto entry_offset = index % bits_per_word; + m_bitmap[entry_entry] &= ~(1uz << entry_offset); + } + +} // namespace kernel::memory \ No newline at end of file diff --git a/kernel/kernel/memory/bitmap_allocator.hpp b/kernel/kernel/memory/bitmap_allocator.hpp new file mode 100644 index 00000000..370ce64d --- /dev/null +++ b/kernel/kernel/memory/bitmap_allocator.hpp @@ -0,0 +1,56 @@ +#ifndef TEACHOS_KERNEL_MEMORY_BITMAP_ALLOCATOR_HPP +#define TEACHOS_KERNEL_MEMORY_BITMAP_ALLOCATOR_HPP + +#include + +#include +#include +#include +#include +#include + +namespace kernel::memory +{ + + //! A generic, bitmap base frame allocator. + //! + //! This frame allocator manages the allocation state of each frame present in the system using a single bit per + //! frame. All state information is stored in a contiguous region of memory. + struct bitmap_frame_allocator final : kapi::memory::frame_allocator + { + //! Construct a new, empty bitmap allocator. + //! + //! @param storage A contiguous region of virtual memory for state storage. + //! @param total_frame The total number of frames in the system. + bitmap_frame_allocator(std::span storage, std::size_t frame_count) noexcept; + + bitmap_frame_allocator(bitmap_frame_allocator const &) = delete; + bitmap_frame_allocator(bitmap_frame_allocator &&) = delete; + auto operator=(bitmap_frame_allocator const &) -> bitmap_frame_allocator & = delete; + auto operator=(bitmap_frame_allocator &&) -> bitmap_frame_allocator & = delete; + + //! @copydoc kapi::memory::frame_allocator::allocate_many + [[nodiscard]] auto allocate_many(std::size_t count = 1) noexcept + -> std::optional> override; + + //! @copydoc kapi::memory::frame_allocator::release_many + auto release_many(std::pair frame_set) -> void override; + + //! Mark a given frame as being used. + //! + //! This function is used during bootstrap to hand the platform allocator state over to this allocator. + auto mark_used(kapi::memory::frame frame) -> void override; + + private: + [[nodiscard]] auto test(std::size_t index) const noexcept -> bool; + auto set(std::size_t index) noexcept -> void; + auto clear(std::size_t index) noexcept -> void; + + std::span m_bitmap; + std::uint64_t m_frame_count; + std::uint64_t m_last_index; + }; + +} // namespace kernel::memory + +#endif diff --git a/kernel/kernel/memory/bitmap_allocator.tests.cpp b/kernel/kernel/memory/bitmap_allocator.tests.cpp new file mode 100644 index 00000000..05d11a33 --- /dev/null +++ b/kernel/kernel/memory/bitmap_allocator.tests.cpp @@ -0,0 +1,288 @@ +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +constexpr auto all_bits_set = std::numeric_limits::max(); +constexpr auto available_frames = 1024uz; + +SCENARIO("Bitmap allocator construction and initialization", "[memory][bitmap_allocator]") +{ + GIVEN("A storage region") + { + auto storage = std::vector(available_frames / 64, 0uz); + + WHEN("constructing the allocator with 0 frames") + { + auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), 0}; + + THEN("the storage region is not modified") + { + REQUIRE_THAT(storage, Catch::Matchers::RangeEquals(std::vector(16, 0uz))); + } + } + + WHEN("constructing with 1 frame") + { + auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), 1}; + + THEN("the first word of the storage region is set to all ones") + { + REQUIRE_THAT(std::views::take(storage, 1), Catch::Matchers::RangeEquals(std::vector(1, all_bits_set))); + } + + THEN("the rest of the storage region is not modified") + { + REQUIRE_THAT(std::views::drop(storage, 1), Catch::Matchers::RangeEquals(std::vector(15, 0uz))); + } + } + + WHEN("constructing with 64 frames") + { + auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), 64}; + + THEN("the first word of the storage region is set to all ones") + { + REQUIRE_THAT(std::views::take(storage, 1), Catch::Matchers::RangeEquals(std::vector(1, all_bits_set))); + } + + THEN("the rest of the storage region is not modified") + { + REQUIRE_THAT(std::views::drop(storage, 1), Catch::Matchers::RangeEquals(std::vector(15, 0uz))); + } + } + + WHEN("constructing with all available frames") + { + auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; + + THEN("the storage region is filled with all ones") + { + REQUIRE_THAT(storage, Catch::Matchers::RangeEquals(std::vector(16, all_bits_set))); + } + } + + WHEN("constructing with half the available frames") + { + auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames / 2}; + + THEN("the first half of the storage region is filled with all ones") + { + REQUIRE_THAT(std::views::take(storage, (available_frames / 2) / 64), + Catch::Matchers::RangeEquals(std::vector((available_frames / 2) / 64, all_bits_set))); + } + + THEN("the second half of the storage region is filled with all zeros") + { + REQUIRE_THAT(std::views::drop(storage, (available_frames / 2) / 64), + Catch::Matchers::RangeEquals(std::vector((available_frames / 2) / 64, 0uz))); + } + } + } +} + +SCENARIO("Bitmap allocator frame allocation", "[memory][bitmap_allocator]") +{ + GIVEN("A storage region") + { + auto storage = std::vector(available_frames / 64, 0uz); + + AND_GIVEN("an allocator constructed with all available frames but no free ones") + { + auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; + + WHEN("allocating 1 frame") + { + auto result = allocator.allocate_many(1); + + THEN("the result is empty") + { + REQUIRE_FALSE(result.has_value()); + } + } + } + + AND_GIVEN("an allocator constructed with all available frames but only one free one") + { + auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; + allocator.release_many({kapi::memory::frame{0}, 1}); + + WHEN("allocating 1 frame") + { + auto result = allocator.allocate_many(1); + + THEN("the result is not empty") + { + REQUIRE(result.has_value()); + } + + THEN("the result contains 1 frame") + { + REQUIRE(result->second == 1); + } + } + + WHEN("allocating more frames than are free") + { + auto result = allocator.allocate_many(2); + + THEN("the result is empty") + { + REQUIRE_FALSE(result.has_value()); + } + + AND_WHEN("allocating a single frame") + { + auto result = allocator.allocate_many(1); + + THEN("the result is not empty") + { + REQUIRE(result.has_value()); + } + } + + WHEN("allocating 0 frames") + { + auto result = allocator.allocate_many(0); + + THEN("the result is empty") + { + REQUIRE_FALSE(result.has_value()); + } + } + } + } + + AND_GIVEN("an allocator with many single frame holes") + { + auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; + for (auto i = 0uz; i < available_frames; i += 2) + { + allocator.release_many({kapi::memory::frame{i}, 1}); + } + + WHEN("allocating 1 frame") + { + auto result = allocator.allocate_many(1); + + THEN("the result is not empty") + { + REQUIRE(result.has_value()); + } + + THEN("the result contains 1 frame") + { + REQUIRE(result->second == 1); + } + } + + WHEN("allocating 2 frames") + { + auto result = allocator.allocate_many(2); + + THEN("the result is empty") + { + REQUIRE_FALSE(result.has_value()); + } + } + } + + AND_GIVEN("and allocator with all frames marked as free") + { + auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; + allocator.release_many({kapi::memory::frame{0}, available_frames}); + + WHEN("allocating 1 frame") + { + auto result = allocator.allocate_many(1); + + THEN("the result is not empty") + { + REQUIRE(result.has_value()); + } + + THEN("the result contains 1 frame") + { + REQUIRE(result->second == 1); + } + } + + WHEN("allocating multiple frames") + { + auto result = allocator.allocate_many(20); + + THEN("the result is not empty") + { + REQUIRE(result.has_value()); + } + + THEN("the result contains 20 frames") + { + REQUIRE(result->second == 20); + } + } + + WHEN("marking all frames as used") + { + for (auto i = 0uz; i < available_frames; i++) + { + allocator.mark_used(kapi::memory::frame{i}); + } + + THEN("the allocator has no free frames") + { + REQUIRE_FALSE(allocator.allocate_many(1).has_value()); + } + } + } + + AND_GIVEN("an allocator with a contiguous block of free frames") + { + auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; + allocator.release_many({kapi::memory::frame{0}, available_frames}); + for (auto i = 0uz; i < available_frames / 2; i++) + { + allocator.mark_used(kapi::memory::frame{i}); + } + + WHEN("allocating a single frame") + { + auto result = allocator.allocate_many(1); + + THEN("the result is not empty") + { + REQUIRE(result.has_value()); + } + + THEN("the result contains 1 frame") + { + REQUIRE(result->second == 1); + } + } + + WHEN("allocating multiple frames") + { + auto result = allocator.allocate_many(20); + + THEN("the result is not empty") + { + REQUIRE(result.has_value()); + } + + THEN("the result contains 20 frames") + { + REQUIRE(result->second == 20); + } + } + } + } +} \ No newline at end of file diff --git a/kernel/kernel/memory/block_list_allocator.cpp b/kernel/kernel/memory/block_list_allocator.cpp new file mode 100644 index 00000000..6e68ada7 --- /dev/null +++ b/kernel/kernel/memory/block_list_allocator.cpp @@ -0,0 +1,215 @@ +#include + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +using namespace kstd::units_literals; + +namespace kernel::memory +{ + + namespace + { + [[nodiscard]] constexpr auto align_up(std::byte * pointer, kstd::units::bytes alignment) noexcept -> std::byte * + { + auto const remainder = std::bit_cast(pointer) % static_cast(alignment); + return remainder == 0 ? pointer : pointer + static_cast(alignment) - remainder; + } + } // namespace + + block_list_allocator::block_list_allocator(kapi::memory::linear_address base) noexcept + : heap_allocator{} + , m_base{base} + , m_frontier{base} + , m_block_list{} + , m_lock{} + {} + + auto block_list_allocator::allocate(kstd::units::bytes size, kstd::units::bytes alignment) noexcept -> void * + { + kstd::lock_guard guard{m_lock}; + + for (auto attempt = 0uz; attempt < 2uz; ++attempt) + { + auto current = m_block_list; + while (current != nullptr) + { + if (current->free) + { + auto const raw_block = reinterpret_cast(current); + auto const unaligned_payload = raw_block + allocated_metadata_size; + auto const aligned_payload = align_up(unaligned_payload, alignment); + auto const required_padding = static_cast(aligned_payload - unaligned_payload); + auto const total_required_size = required_padding + allocated_metadata_size + size; + + if (current->usable_size >= total_required_size) + { + auto const payload_header = aligned_payload - sizeof(block_header *) - sizeof(block_header); + auto const front_padding = static_cast(payload_header - raw_block); + + auto payload_block = current; + + if (front_padding >= allocated_metadata_size + minimum_allocation_size) + { + payload_block = reinterpret_cast(payload_header); + std::construct_at(payload_block, current->usable_size - front_padding, true, current->next); + + if (payload_block->next) + { + payload_block->next->prev = payload_block; + } + + current->usable_size = front_padding - allocated_metadata_size; + current->next = payload_block; + payload_block->prev = current; + } + + auto const header_size = + static_cast(aligned_payload - reinterpret_cast(payload_block)); + auto const payload_size = header_size - allocated_metadata_size + size; + split(payload_block, payload_size, 0_B); + + payload_block->free = false; + + auto back_pointer = reinterpret_cast(aligned_payload - sizeof(block_header *)); + *back_pointer = payload_block; + + return reinterpret_cast(aligned_payload); + } + } + current = current->next; + } + + auto const search_size = size + alignment; + if (attempt == 0uz && !expand(search_size)) + { + return nullptr; + } + } + + return nullptr; + } + + auto block_list_allocator::deallocate(void * ptr) noexcept -> void + { + if (!ptr) + { + return; + } + + kstd::lock_guard guard{m_lock}; + + auto const aligned_payload = reinterpret_cast(ptr); + auto back_pointer = reinterpret_cast(aligned_payload - sizeof(block_header *)); + auto block = *back_pointer; + + block->free = true; + coalesce(block); + } + + auto block_list_allocator::expand(kstd::units::bytes size) noexcept -> bool + { + auto const total_required_size = size + allocated_metadata_size; + auto const frames_needed = (total_required_size + kapi::memory::frame::size - 1_B) / kapi::memory::frame::size; + + auto const flags = kapi::memory::page_mapper::flags::writable | kapi::memory::page_mapper::flags::supervisor_only | + kapi::memory::page_mapper::flags::global; + + for (auto i = 0uz; i < frames_needed; ++i) + { + auto frame = kapi::memory::allocate_frame(); + if (!frame) + { + kapi::system::panic("[OS:Heap] OOM when expanding heap."); + return false; + } + + auto page = kapi::memory::page::containing(m_frontier + i * kapi::memory::page::size); + kapi::memory::map(page, *frame, flags); + } + + auto block = static_cast(m_frontier); + std::construct_at(block, frames_needed * kapi::memory::frame::size - allocated_metadata_size, true, nullptr, + nullptr); + + m_frontier += frames_needed * kapi::memory::frame::size; + + if (!m_block_list) + { + m_block_list = block; + } + else + { + auto current = m_block_list; + while (current->next) + { + current = current->next; + } + current->next = block; + block->prev = current; + } + + coalesce(block); + return true; + } + + auto block_list_allocator::coalesce(block_header * block) noexcept -> void + { + if (block->next && block->next->free) + { + auto follower = block->next; + block->usable_size += follower->usable_size + allocated_metadata_size; + block->next = follower->next; + if (block->next) + { + block->next->prev = block; + } + std::destroy_at(follower); + } + + if (block->prev && block->prev->free) + { + auto leader = block->prev; + leader->usable_size += block->usable_size + allocated_metadata_size; + leader->next = block->next; + if (block->next) + { + block->next->prev = leader; + } + std::destroy_at(block); + } + } + + auto block_list_allocator::split(block_header * block, kstd::units::bytes size, kstd::units::bytes padding) noexcept + -> void + { + auto const new_block_size = size + padding; + + if (block->usable_size > new_block_size + allocated_metadata_size + minimum_allocation_size) + { + auto raw_block = reinterpret_cast(block); + auto new_block = reinterpret_cast(raw_block + allocated_metadata_size + new_block_size); + std::construct_at(new_block, block->usable_size - new_block_size - allocated_metadata_size, true, block->next, + block); + + if (new_block->next) + { + new_block->next->prev = new_block; + } + + block->usable_size = new_block_size; + block->next = new_block; + } + } + +} // namespace kernel::memory \ No newline at end of file diff --git a/kernel/kernel/memory/block_list_allocator.hpp b/kernel/kernel/memory/block_list_allocator.hpp new file mode 100644 index 00000000..51b226ee --- /dev/null +++ b/kernel/kernel/memory/block_list_allocator.hpp @@ -0,0 +1,90 @@ +#ifndef TEACHOS_KERNEL_MEMORY_BLOCK_LIST_ALLOCATOR_HPP +#define TEACHOS_KERNEL_MEMORY_BLOCK_LIST_ALLOCATOR_HPP + +#include + +#include + +#include +#include + +#include +#include + +namespace kernel::memory +{ + + //! A simple, dynamically sized heap allocator. + //! + //! This allocator is designed to perform first-fit allocation using an intrusive doubly-linked list. The heap is + //! expanded as needed. + struct block_list_allocator final : heap_allocator + { + //! Construct a new free list allocator with the given base address. + explicit block_list_allocator(kapi::memory::linear_address base) noexcept; + + block_list_allocator(block_list_allocator const &) = delete; + block_list_allocator(block_list_allocator &&) = delete; + auto operator=(block_list_allocator const &) -> block_list_allocator & = delete; + auto operator=(block_list_allocator &&) -> block_list_allocator & = delete; + + //! Allocate a block of memory with the given alignment. + //! + //! @param size The size of the block to allocate + //! @param alignment The desired alignment of the allocated block + //! @return A pointer to the beginning of the block on success, @p nullptr otherwise. + [[nodiscard]] auto allocate(kstd::units::bytes size, kstd::units::bytes alignment) noexcept -> void * override; + + //! Deallocate a block of memory previously allocated. + //! + //! @param block The pointer to the block to free. + auto deallocate(void * block) noexcept -> void override; + + private: + struct block_header final + { + kstd::units::bytes usable_size{}; + bool free{}; + block_header * next{}; + block_header * prev{}; + }; + + //! The size of the metadata required for each allocated block. + //! + //! Each allocated block carries a block header, like any unallocated one, but in addition also has a back-pointer + //! to the block header to support padding due to alignment. + constexpr auto static allocated_metadata_size = kstd::units::bytes{sizeof(block_header) + sizeof(block_header *)}; + + //! The minimum number of bytes for an allocation. + constexpr auto static minimum_allocation_size = kstd::units::bytes{16uz}; + + //! Try to expand the heap to accommodate the given size. + //! + //! @param delta The size to expand the heap by. + //! @return @p true if the heap was expanded, @p false otherwise. + auto expand(kstd::units::bytes delta) noexcept -> bool; + + //! Split a given free block to accommodate and allocation. + //! + //! The newly split free block is inserted into the free list. + //! + //! @param block The block to split. + //! @param size The size of the allocation. + //! @param padding The amount of padding to apply. + auto split(block_header * block, kstd::units::bytes size, kstd::units::bytes padding) noexcept -> void; + + //! Try to coalesce a given block with it's preceding and/or following block. + //! + //! @param block The block to coalesce. + auto coalesce(block_header * block) noexcept -> void; + + kapi::memory::linear_address m_base; + kapi::memory::linear_address m_frontier; + block_header * m_block_list; + + kstd::mutex m_lock; + }; + +} // namespace kernel::memory + +#endif diff --git a/kernel/kernel/memory/block_list_allocator.tests.cpp b/kernel/kernel/memory/block_list_allocator.tests.cpp new file mode 100644 index 00000000..c5f84c52 --- /dev/null +++ b/kernel/kernel/memory/block_list_allocator.tests.cpp @@ -0,0 +1,85 @@ +#include + +#include + +#include + +#include + +#include + +#include + +using namespace kstd::units_literals; + +SCENARIO("Block List Allocator Operations", "[memory][allocator]") +{ + GIVEN("A newly initialized block list allocator mapped via the test sandbox") + { + kernel::tests::memory::deinit(); + kapi::memory::init(); + + auto sandbox_base = kernel::tests::memory::virtual_base(); + kernel::memory::block_list_allocator allocator{sandbox_base}; + + WHEN("a basic allocation request is made") + { + void * ptr = allocator.allocate(128_B, 8_B); + + THEN("a valid, non-null pointer is returned") + { + REQUIRE(ptr != nullptr); + } + + AND_THEN("the returned memory is writeable without causing segmentation faults") + { + auto byte_ptr = static_cast(ptr); + byte_ptr[0] = std::byte{0xDE}; + byte_ptr[127] = std::byte{0xAD}; + REQUIRE(byte_ptr[0] == std::byte{0xDE}); + REQUIRE(byte_ptr[127] == std::byte{0xAD}); + } + + allocator.deallocate(ptr); + } + + WHEN("multiple allocations are made sequentially") + { + void * ptr1 = allocator.allocate(64_B, 8_B); + void * ptr2 = allocator.allocate(64_B, 8_B); + void * ptr3 = allocator.allocate(1_KiB, 16_B); + + THEN("they return distinct, non-overlapping memory blocks") + { + REQUIRE(ptr1 != nullptr); + REQUIRE(ptr2 != nullptr); + REQUIRE(ptr3 != nullptr); + REQUIRE(ptr1 != ptr2); + REQUIRE(ptr2 != ptr3); + REQUIRE(ptr1 != ptr3); + } + + allocator.deallocate(ptr1); + allocator.deallocate(ptr2); + allocator.deallocate(ptr3); + } + + WHEN("a block is allocated and then completely freed") + { + void * original_ptr = allocator.allocate(512_B, 16_B); + allocator.deallocate(original_ptr); + + AND_WHEN("a new allocation of equal or smaller size is requested") + { + void * new_ptr = allocator.allocate(128_B, 16_B); + + THEN("the allocator actively reuses the coalesced space") + { + REQUIRE(new_ptr == original_ptr); + } + + allocator.deallocate(new_ptr); + } + } + } +} diff --git a/kernel/kernel/memory/heap_allocator.hpp b/kernel/kernel/memory/heap_allocator.hpp new file mode 100644 index 00000000..fd39bef8 --- /dev/null +++ b/kernel/kernel/memory/heap_allocator.hpp @@ -0,0 +1,29 @@ +#ifndef TEACHOS_KERNEL_MEMORY_HEAP_ALLOCATOR_HPP +#define TEACHOS_KERNEL_MEMORY_HEAP_ALLOCATOR_HPP + +#include + +namespace kernel::memory +{ + + //! The interface for all kernel heap allocators. + struct heap_allocator + { + virtual ~heap_allocator() = default; + + //! Allocate a block of memory with the given alignment. + //! + //! @param size The size of the block to allocate + //! @param alignment The desired alignment of the allocated block + //! @return A pointer to the beginning of the block on success, @p nullptr otherwise. + [[nodiscard]] virtual auto allocate(kstd::units::bytes size, kstd::units::bytes alignment) noexcept -> void * = 0; + + //! Deallocate a block of memory previously allocated. + //! + //! @param block The pointer to the block to free. + virtual auto deallocate(void * block) noexcept -> void = 0; + }; + +} // namespace kernel::memory + +#endif \ No newline at end of file diff --git a/kernel/kernel/memory/mmio_allocator.cpp b/kernel/kernel/memory/mmio_allocator.cpp new file mode 100644 index 00000000..ba23dbde --- /dev/null +++ b/kernel/kernel/memory/mmio_allocator.cpp @@ -0,0 +1,111 @@ +#include + +#include +#include + +#include + +#include +#include +#include + +namespace kernel::memory +{ + + mmio_allocator::mmio_allocator(kapi::memory::linear_address base, std::size_t pages) + : m_head{make_node(base, pages, nullptr, nullptr, true)} + {} + + auto mmio_allocator::allocate(std::size_t count) -> kapi::memory::linear_address + { + if (count == 0 || !m_head) + { + return {}; + } + + auto current = m_head; + while (current) + { + if (current->is_free && current->page_count >= count) + { + if (current->page_count > count) + { + auto new_base = current->base + (count * kapi::memory::page::size); + auto split_node = make_node(new_base, current->page_count - count, std::move(current->next), current, true); + + if (current->next) + { + current->next->previous = split_node; + } + current->next = split_node; + current->page_count = count; + } + + current->is_free = false; + return current->base; + } + current = current->next; + } + + kapi::system::panic("[OS:MEM] MMIO alloctor out of memory!"); + return {}; + } + + auto mmio_allocator::release(kapi::memory::linear_address base) -> void + { + auto current = m_head; + + while (current) + { + if (current->base == base && !current->is_free) + { + current->is_free = true; + + if (current->next && current->next->is_free) + { + auto removed = current->next; + current->page_count += removed->page_count; + current->next = removed->next; + if (current->next) + { + current->next->previous = current; + } + destroy_node(removed); + } + + if (current->previous && current->previous->is_free) + { + auto removed = current; + removed->previous->page_count += removed->page_count; + removed->previous->next = removed->next; + if (removed->next) + { + removed->next->previous = removed->previous; + } + destroy_node(removed); + } + return; + } + current = current->next; + } + } + + auto mmio_allocator::make_node(kapi::memory::linear_address base, std::size_t page_count, node * next, + node * previous, bool is_free) -> node * + { + using traits = std::allocator_traits>; + + auto new_node = traits::allocate(m_allocator, 1); + traits::construct(m_allocator, new_node, base, page_count, next, previous, is_free); + return new_node; + } + + auto mmio_allocator::destroy_node(node * instance) -> void + { + using traits = std::allocator_traits>; + + traits::destroy(m_allocator, instance); + traits::deallocate(m_allocator, instance, 1); + } + +} // namespace kernel::memory diff --git a/kernel/kernel/memory/mmio_allocator.hpp b/kernel/kernel/memory/mmio_allocator.hpp new file mode 100644 index 00000000..c7a8ed0c --- /dev/null +++ b/kernel/kernel/memory/mmio_allocator.hpp @@ -0,0 +1,41 @@ +#ifndef TEACHOS_KERNEL_MEMORY_MMIO_ALLOCATOR_HPP +#define TEACHOS_KERNEL_MEMORY_MMIO_ALLOCATOR_HPP + +#include + +#include +#include + +#include + +namespace kernel::memory +{ + + struct mmio_allocator + { + mmio_allocator(kapi::memory::linear_address base, std::size_t pages); + + [[nodiscard]] auto allocate(std::size_t page_count) -> kapi::memory::linear_address; + auto release(kapi::memory::linear_address base) -> void; + + private: + struct node + { + kapi::memory::linear_address base{}; + std::size_t page_count{}; + node * next{}; + node * previous{}; + bool is_free{}; + }; + + auto make_node(kapi::memory::linear_address base, std::size_t page_count, node * next, node * previous, + bool is_free) -> node *; + auto destroy_node(node *) -> void; + + [[no_unique_address]] kstd::allocator m_allocator{}; + node * m_head{}; + }; + +} // namespace kernel::memory + +#endif diff --git a/kernel/kernel/memory/operators.cpp b/kernel/kernel/memory/operators.cpp new file mode 100644 index 00000000..5673d680 --- /dev/null +++ b/kernel/kernel/memory/operators.cpp @@ -0,0 +1,102 @@ +#include + +#include + +#include + +#include +#include + +[[nodiscard]] auto operator new(std::size_t size, std::align_val_t alignment, std::nothrow_t const &) noexcept -> void * +{ + auto & allocator = kernel::memory::get_heap_allocator(); + return allocator.allocate(static_cast(size), + static_cast(static_cast(alignment))); +} + +[[nodiscard]] auto operator new(std::size_t size, std::align_val_t alignment) -> void * +{ + auto pointer = operator new(size, alignment, std::nothrow); + + if (pointer == nullptr) + { + kapi::system::panic("[OS:Heap] Out of memory!"); + } + + return pointer; +} + +[[nodiscard]] auto operator new(std::size_t size, std::nothrow_t const &) noexcept -> void * +{ + return operator new(size, std::align_val_t{__STDCPP_DEFAULT_NEW_ALIGNMENT__}, std::nothrow); +} + +[[nodiscard]] auto operator new(std::size_t size) -> void * +{ + return operator new(size, std::align_val_t{__STDCPP_DEFAULT_NEW_ALIGNMENT__}); +} + +[[nodiscard]] auto operator new[](std::size_t size, std::align_val_t alignment, std::nothrow_t const &) noexcept + -> void * +{ + return operator new(size, alignment, std::nothrow); +} + +[[nodiscard]] auto operator new[](std::size_t size, std::align_val_t alignment) -> void * +{ + return operator new(size, alignment); +} + +[[nodiscard]] auto operator new[](std::size_t size, std::nothrow_t const &) noexcept -> void * +{ + return ::operator new(size); +} + +[[nodiscard]] auto operator new[](std::size_t size) -> void * +{ + return ::operator new(size); +} + +auto operator delete(void * pointer) noexcept -> void +{ + if (pointer != nullptr) + { + auto & allocator = kernel::memory::get_heap_allocator(); + allocator.deallocate(pointer); + } +} + +auto operator delete(void * pointer, std::size_t) noexcept -> void +{ + ::operator delete(pointer); +} + +auto operator delete(void * pointer, std::align_val_t) noexcept -> void +{ + ::operator delete(pointer); +} + +auto operator delete(void * pointer, std::size_t size, std::align_val_t) noexcept -> void +{ + ::operator delete(pointer, size); +} + +auto operator delete[](void * pointer) noexcept -> void +{ + ::operator delete(pointer); +} + +auto operator delete[](void * pointer, std::size_t size) noexcept -> void +{ + ::operator delete(pointer, size); +} + +auto operator delete[](void * pointer, std::align_val_t) noexcept -> void +{ + ::operator delete(pointer); +} + +auto operator delete[](void * pointer, std::size_t size, std::align_val_t) noexcept -> void +{ + ::operator delete(pointer, size); +} diff --git a/kernel/kernel/test_support/boot_modules.hpp b/kernel/kernel/test_support/boot_modules.hpp new file mode 100644 index 00000000..2343a104 --- /dev/null +++ b/kernel/kernel/test_support/boot_modules.hpp @@ -0,0 +1,10 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_BOOT_MODULES_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_BOOT_MODULES_HPP + +namespace kernel::tests::boot_modules +{ + //! Deinitialize the boot module registry. + auto deinit() -> void; +} // namespace kernel::tests::boot_modules + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/bump_frame_allocator.hpp b/kernel/kernel/test_support/bump_frame_allocator.hpp new file mode 100644 index 00000000..a8ffd486 --- /dev/null +++ b/kernel/kernel/test_support/bump_frame_allocator.hpp @@ -0,0 +1,54 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_BUMP_FRAME_ALLOCATOR_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_BUMP_FRAME_ALLOCATOR_HPP + +#include + +#include +#include +#include + +namespace kernel::tests +{ + + //! A simple bump-based frame allocator used for initial test bootstrap. + //! + //! We emulate the expected behavior of a platform, in that there is a handover to the kernel PMM that needs to happen + //! during boot. The expectation of the PMM initializer is that there is an active (if simple) frame allocator it can + //! use to reserve space for it's own metadata. + //! + //! @see kapi::memory::init_pmm + struct bump_frame_allocator : kapi::memory::frame_allocator + { + //! @copydoc kapi::memory::frame_allocator::mark_used + //! + //! @note Due to the simple nature of this allocator, all frames up to the given frame will be marked as used as + //! well. + auto mark_used(kapi::memory::frame frame) -> void override + { + if (frame.number() >= next_free_frame) + { + next_free_frame = frame.number() + 1; + } + } + + //! @copydoc kapi::memory::frame_allocator::allocate_many + auto allocate_many(std::size_t count) noexcept + -> std::optional> override + { + auto start = next_free_frame; + next_free_frame += count; + return std::pair{kapi::memory::frame{start}, count}; + } + + //! @copydoc kapi::memory::frame_allocator::release_many + //! + //! @note Due to the simple nature of this allocator, frames are never actually released. + auto release_many(std::pair) -> void override {} + + //! The next free frame to be allocated. + std::size_t next_free_frame{}; + }; + +} // namespace kernel::tests + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/cio.hpp b/kernel/kernel/test_support/cio.hpp new file mode 100644 index 00000000..afe27e05 --- /dev/null +++ b/kernel/kernel/test_support/cio.hpp @@ -0,0 +1,39 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_CIO_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_CIO_HPP + +#include + +#include + +#include + +namespace kernel::tests::cio +{ + + //! A simple output device that writes to one of the standard output streams and a log buffer. + struct output_device : kapi::cio::output_device + { + //! @copydoc kapi::cio::output_device::write + auto write(kapi::cio::output_stream stream, std::string_view text) -> void override; + + //! Get the log buffer associated with this device. + //! + //! @return The associated log buffer. + [[nodiscard]] auto log_buffer() noexcept -> kernel::tests::log_buffer &; + + private: + //! The log buffer of this device. + kernel::tests::log_buffer m_log_buffer{}; + }; + + //! Deinitialize the CIO subsystem. + auto deinit() -> void; + + //! Get the log buffer of the currently active output device. + //! + //! @throws std::runtime_error if no output device has been registered. + //! @return The currently active device's log buffer. + [[nodiscard]] auto log_buffer() -> kernel::tests::log_buffer &; +} // namespace kernel::tests::cio + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/cpu.hpp b/kernel/kernel/test_support/cpu.hpp new file mode 100644 index 00000000..c99d1a72 --- /dev/null +++ b/kernel/kernel/test_support/cpu.hpp @@ -0,0 +1,23 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_CPU_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_CPU_HPP + +#include + +namespace kernel::tests::cpu +{ + + //! Exception thrown when the CPU is halted. + struct halt : std::runtime_error + { + //! Construct a new halt exception. + halt() + : std::runtime_error{"CPU halt requested!"} + {} + }; + + //! Deinitialize the CPU subsystem. + auto deinit() -> void; + +} // namespace kernel::tests::cpu + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/devices/block_device.cpp b/kernel/kernel/test_support/devices/block_device.cpp new file mode 100644 index 00000000..9a9e544d --- /dev/null +++ b/kernel/kernel/test_support/devices/block_device.cpp @@ -0,0 +1,61 @@ +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include + +namespace kernel::tests::devices +{ + block_device::block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size, + size_t initial_size) + : kernel::devices::block_device(major, minor, name, block_size) + { + data.resize(initial_size, 0); + } + + auto block_device::init() -> bool + { + return true; + } + + auto block_device::read_block(size_t block_index, void * buffer) const -> void + { + auto const offset = block_index * block_size(); + if (offset >= data.size()) + { + kstd::libc::memset(buffer, 0, block_size()); + return; + } + + auto const bytes_to_read = std::min(block_size(), data.size() - offset); + kstd::libc::memcpy(buffer, data.data() + offset, bytes_to_read); + if (bytes_to_read < block_size()) + { + kstd::libc::memset(static_cast(buffer) + bytes_to_read, 0, block_size() - bytes_to_read); + } + } + + auto block_device::write_block(size_t block_index, void const * buffer) -> void + { + auto const offset = block_index * block_size(); + auto const write_end = offset + block_size(); + if (write_end > data.size()) + { + data.resize(write_end, 0); + } + + kstd::libc::memcpy(data.data() + offset, static_cast(buffer), block_size()); + } + + auto block_device::size() const -> size_t + { + return data.size(); + } +} // namespace kernel::tests::devices \ No newline at end of file diff --git a/kernel/kernel/test_support/devices/block_device.hpp b/kernel/kernel/test_support/devices/block_device.hpp new file mode 100644 index 00000000..89a2bf15 --- /dev/null +++ b/kernel/kernel/test_support/devices/block_device.hpp @@ -0,0 +1,31 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_BLOCK_DEVICE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_BLOCK_DEVICE_HPP + +#include + +#include +#include + +#include +#include + +namespace kernel::tests::devices +{ + + struct block_device : kernel::devices::block_device + { + block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size, size_t initial_size = 0); + + auto init() -> bool override; + + auto read_block(size_t block_index, void * buffer) const -> void override; + auto write_block(size_t block_index, void const * buffer) -> void override; + + [[nodiscard]] auto size() const -> size_t override; + + kstd::vector data{}; + }; + +} // namespace kernel::tests::devices + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/devices/character_device.cpp b/kernel/kernel/test_support/devices/character_device.cpp new file mode 100644 index 00000000..3806654f --- /dev/null +++ b/kernel/kernel/test_support/devices/character_device.cpp @@ -0,0 +1,19 @@ +#include + +#include + +#include + +#include + +namespace kernel::tests::devices +{ + character_device::character_device(size_t major, size_t minor, kstd::string const & name) + : kapi::devices::device(major, minor, name) + {} + + auto character_device::init() -> bool + { + return true; + } +} // namespace kernel::tests::devices \ No newline at end of file diff --git a/kernel/kernel/test_support/devices/character_device.hpp b/kernel/kernel/test_support/devices/character_device.hpp new file mode 100644 index 00000000..aba183a1 --- /dev/null +++ b/kernel/kernel/test_support/devices/character_device.hpp @@ -0,0 +1,23 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_CHARACTER_DEVICE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_CHARACTER_DEVICE_HPP + +#include + +#include +#include + +#include + +namespace kernel::tests::devices +{ + // TODO fix inheritance when character devices are implemented + struct character_device : kapi::devices::device + { + character_device(size_t major, size_t minor, kstd::string const & name); + + auto init() -> bool override; + }; + +} // namespace kernel::tests::devices + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/devices/storage/management.hpp b/kernel/kernel/test_support/devices/storage/management.hpp new file mode 100644 index 00000000..581aa914 --- /dev/null +++ b/kernel/kernel/test_support/devices/storage/management.hpp @@ -0,0 +1,10 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_STORAGE_MANAGEMENT_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_DEVICES_STORAGE_MANAGEMENT_HPP + +namespace kernel::tests::devices::storage::management +{ + //! Deinitialize the storage management singleton. + auto deinit() -> void; +} // namespace kernel::tests::devices::storage::management + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/ext2.cpp b/kernel/kernel/test_support/filesystem/ext2.cpp new file mode 100644 index 00000000..52b6efe7 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/ext2.cpp @@ -0,0 +1,68 @@ +#include + +#include +#include +#include +#include +#include + +#include +#include + +namespace kernel::tests::filesystem::ext2 +{ + namespace + { + constexpr uint32_t root_directory_data_block = 20; + } // namespace + + auto write_bytes(kernel::tests::devices::block_device & device, size_t offset, void const * source, size_t size) + -> void + { + auto const required_size = offset + size; + if (device.data.size() < required_size) + { + device.data.resize(required_size, 0); + } + + std::memcpy(device.data.data() + offset, source, size); + } + + auto write_u32(kernel::tests::devices::block_device & device, size_t offset, uint32_t value) -> void + { + write_bytes(device, offset, &value, sizeof(value)); + } + + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void + { + auto superblock = kernel::filesystem::ext2::superblock{}; + superblock.magic = kernel::filesystem::ext2::constants::magic_number; + superblock.log_block_size = 0; + superblock.blocks_count = 64; + superblock.blocks_per_group = 64; + superblock.inodes_per_group = 32; + superblock.rev_level = 1; + superblock.inode_size = 128; + setup_mock_ext2_layout(device, superblock); + } + + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, + kernel::filesystem::ext2::superblock const & superblock) -> void + { + write_bytes(device, kernel::filesystem::ext2::constants::superblock_offset, &superblock, sizeof(superblock)); + + auto group_descriptor = kernel::filesystem::ext2::block_group_descriptor{}; + group_descriptor.inode_table = 5; + write_bytes(device, 2048, &group_descriptor, sizeof(group_descriptor)); + + auto root_inode_data = kernel::filesystem::ext2::inode_data{}; + root_inode_data.mode = kernel::filesystem::ext2::constants::mode_directory; + root_inode_data.blocks = 2; + root_inode_data.block[0] = root_directory_data_block; + + auto const root_inode_offset = + static_cast(group_descriptor.inode_table) * kernel::filesystem::ext2::constants::base_block_size + + (kernel::filesystem::ext2::constants::root_inode_number - 1) * superblock.inode_size; + write_bytes(device, root_inode_offset, &root_inode_data, sizeof(root_inode_data)); + } +} // namespace kernel::tests::filesystem::ext2 \ No newline at end of file diff --git a/kernel/kernel/test_support/filesystem/ext2.hpp b/kernel/kernel/test_support/filesystem/ext2.hpp new file mode 100644 index 00000000..18cef1c2 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/ext2.hpp @@ -0,0 +1,21 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_EXT2_HPP + +#include +#include + +#include +#include + +namespace kernel::tests::filesystem::ext2 +{ + auto write_bytes(kernel::tests::devices::block_device & device, size_t offset, void const * source, size_t size) + -> void; + auto write_u32(kernel::tests::devices::block_device & device, size_t offset, uint32_t value) -> void; + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void; + auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, + kernel::filesystem::ext2::superblock const & superblock) -> 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 new file mode 100644 index 00000000..ec706077 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/filesystem.cpp @@ -0,0 +1,17 @@ +#include + +#include +#include + +#include + +#include + +namespace kernel::tests::filesystem +{ + auto filesystem::lookup(kstd::shared_ptr const &, std::string_view) const + -> kstd::shared_ptr + { + 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 new file mode 100644 index 00000000..5f260220 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/filesystem.hpp @@ -0,0 +1,22 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILESYSTEM_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_FILESYSTEM_HPP + +#include +#include + +#include + +#include + +namespace kernel::tests::filesystem +{ + struct filesystem : kernel::filesystem::filesystem + { + filesystem() = default; + + [[nodiscard]] auto lookup(kstd::shared_ptr const & parent, std::string_view name) const + -> kstd::shared_ptr override; + }; +} // namespace kernel::tests::filesystem + +#endif diff --git a/kernel/kernel/test_support/filesystem/inode.cpp b/kernel/kernel/test_support/filesystem/inode.cpp new file mode 100644 index 00000000..0c8d9563 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/inode.cpp @@ -0,0 +1,23 @@ +#include + +#include + +#include + +namespace kernel::tests::filesystem +{ + auto inode::read(void *, size_t, size_t size) const -> size_t + { + return size; + } + + auto inode::write(void const *, size_t, size_t size) -> size_t + { + return size; + } + + 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 new file mode 100644 index 00000000..8a764375 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/inode.hpp @@ -0,0 +1,19 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_INODE_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_INODE_HPP + +#include + +#include + +namespace kernel::tests::filesystem +{ + struct inode : kernel::filesystem::inode + { + auto read(void * buffer, size_t offset, size_t size) const -> size_t override; + auto write(void const * buffer, size_t offset, size_t size) -> size_t override; + + [[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 new file mode 100644 index 00000000..46b03341 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/open_file_table.hpp @@ -0,0 +1,10 @@ +#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 new file mode 100644 index 00000000..aabaaceb --- /dev/null +++ b/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.cpp @@ -0,0 +1,139 @@ +#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() + { + kernel::tests::devices::storage::management::deinit(); + kernel::tests::boot_modules::deinit(); + } + + auto storage_boot_module_fixture::setup_modules(std::size_t module_count, std::size_t module_size) -> void + { + m_module_names.clear(); + m_module_data.clear(); + m_registry = {}; + + m_module_names.reserve(module_count); + m_module_data.reserve(module_count); + + for (std::size_t i = 0; i < module_count; ++i) + { + m_module_names.push_back(std::format("test_mod{}", i)); + m_module_data.emplace_back(module_size, std::byte{static_cast(0x40 + (i % 16))}); + } + + for (std::size_t i = 0; i < module_count; ++i) + { + m_registry.add_boot_module(kapi::boot_modules::boot_module{ + m_module_names[i].c_str(), kapi::memory::linear_address{m_module_data[i].data()}, m_module_data[i].size()}); + } + + kapi::boot_modules::set_boot_module_registry(m_registry); + kernel::devices::storage::management::init(); + } + + auto storage_boot_module_fixture::setup_modules_from_img(std::vector const & module_names, + std::vector const & img_paths) -> void + { + m_module_names.clear(); + m_module_data.clear(); + m_registry = {}; + + if (module_names.size() != img_paths.size()) + { + throw std::invalid_argument{"Module names and image paths vectors must have the same size."}; + } + + for (size_t i = 0; i < module_names.size(); ++i) + { + setup_module_from_img(module_names[i], img_paths[i]); + } + + kapi::boot_modules::set_boot_module_registry(m_registry); + kernel::devices::storage::management::init(); + } + + auto storage_boot_module_fixture::setup_module_from_img(std::string const & module_name, + std::filesystem::path const & img_path) -> void + { + m_module_names.push_back(module_name); + auto & mapped_image = m_mapped_images.emplace_back(img_path); + + m_registry.add_boot_module(kapi::boot_modules::boot_module{ + m_module_names.back().c_str(), kapi::memory::linear_address{mapped_image.mapping}, mapped_image.size}); + } +} // 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 new file mode 100644 index 00000000..94a66688 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/storage_boot_module_fixture.hpp @@ -0,0 +1,48 @@ +#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 + +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; + }; + + kapi::boot_modules::boot_module_registry m_registry{}; + std::vector m_module_names{}; + std::vector> m_module_data{}; + std::vector m_mapped_images{}; + + private: + auto setup_module_from_img(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 new file mode 100644 index 00000000..02ccfec3 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.cpp @@ -0,0 +1,31 @@ +#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 new file mode 100644 index 00000000..55f4b290 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/storage_boot_module_vfs_fixture.hpp @@ -0,0 +1,23 @@ +#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 new file mode 120000 index 00000000..718a2275 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/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/filesystem/test_assets/ext2_1KB_fs.img b/kernel/kernel/test_support/filesystem/test_assets/ext2_1KB_fs.img new file mode 100644 index 00000000..a5202ca3 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/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/filesystem/test_assets/ext2_2KB_fs.img b/kernel/kernel/test_support/filesystem/test_assets/ext2_2KB_fs.img new file mode 100644 index 00000000..7f297f07 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/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/filesystem/test_assets/ext2_4KB_fs.img b/kernel/kernel/test_support/filesystem/test_assets/ext2_4KB_fs.img new file mode 100644 index 00000000..c3f6dafb --- /dev/null +++ b/kernel/kernel/test_support/filesystem/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/filesystem/vfs.hpp b/kernel/kernel/test_support/filesystem/vfs.hpp new file mode 100644 index 00000000..739e3530 --- /dev/null +++ b/kernel/kernel/test_support/filesystem/vfs.hpp @@ -0,0 +1,10 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_VFS_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_FILESYSTEM_VFS_HPP + +namespace kernel::tests::filesystem::vfs +{ + //! Deinitialize the VFS singleton. + auto deinit() -> void; +} // namespace kernel::tests::filesystem::vfs + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/kapi/cio.cpp b/kernel/kernel/test_support/kapi/cio.cpp new file mode 100644 index 00000000..98bc99d7 --- /dev/null +++ b/kernel/kernel/test_support/kapi/cio.cpp @@ -0,0 +1,54 @@ +#include + +#include + +#include + +#include +#include +#include + +namespace +{ + + auto constinit is_initialized = std::atomic_flag{}; + auto constinit device = std::optional{}; + +} // namespace + +namespace kapi::cio +{ + + auto init() -> void + { + if (is_initialized.test_and_set()) + { + throw std::logic_error("kapi::cio::init() called more than once"); + } + + device.emplace(); + set_output_device(*device); + } + +} // namespace kapi::cio + +namespace kernel::tests::cio +{ + + auto deinit() -> void + { + if (!is_initialized.test()) + { + throw std::logic_error("kapi::cio::deinit() called before kapi::cio::init()"); + } + + device.reset(); + is_initialized.clear(); + } + + auto log_buffer() -> kernel::tests::log_buffer & + { + return device->log_buffer(); + } + +} // namespace kernel::tests::cio \ No newline at end of file diff --git a/kernel/kernel/test_support/kapi/cpu.cpp b/kernel/kernel/test_support/kapi/cpu.cpp new file mode 100644 index 00000000..5d956331 --- /dev/null +++ b/kernel/kernel/test_support/kapi/cpu.cpp @@ -0,0 +1,52 @@ +#include + +#include + +#include +#include + +namespace +{ + auto static initialized = std::atomic_flag{}; +} + +namespace kapi::cpu +{ + + auto init() -> void + { + if (initialized.test_and_set()) + { + throw std::logic_error("kapi::cpu::init() called more than once"); + } + + // TODO: make sure that simulated interrupt can run. + } + + auto halt() -> void + { + throw kernel::tests::cpu::halt{}; + } + + auto discover_topology() -> bool + { + // TODO: implement more meaningful simulated CPU topology discovery + return true; + } + +} // namespace kapi::cpu + +namespace kernel::tests::cpu +{ + + auto deinit() -> void + { + if (!initialized.test()) + { + throw std::logic_error{"kapi::cpu::reset() called before kapi::cpu::init()"}; + } + + initialized.clear(); + } + +} // namespace kernel::tests::cpu \ No newline at end of file diff --git a/kernel/kernel/test_support/kapi/interrupts.cpp b/kernel/kernel/test_support/kapi/interrupts.cpp new file mode 100644 index 00000000..00772669 --- /dev/null +++ b/kernel/kernel/test_support/kapi/interrupts.cpp @@ -0,0 +1,11 @@ +#include + +namespace kapi::interrupts +{ + + auto enable() -> void + { + // TODO: enable simulated interrupts. + } + +} // namespace kapi::interrupts \ No newline at end of file diff --git a/kernel/kernel/test_support/kapi/memory.cpp b/kernel/kernel/test_support/kapi/memory.cpp new file mode 100644 index 00000000..7fc95cb0 --- /dev/null +++ b/kernel/kernel/test_support/kapi/memory.cpp @@ -0,0 +1,74 @@ +#include + +#include +#include + +#include + +#include + +#include + +namespace +{ + //! The size of the simulated RAM. + constexpr auto physical_size = kstd::units::MiB(32); + constexpr auto virtual_size = kstd::units::GiB(1); + + constexpr auto number_of_frames = physical_size / kapi::memory::frame::size; + + auto constinit bump_allocator = std::optional{}; + auto constinit test_mapper = std::optional{}; + + auto constinit old_allocator = std::optional{}; + auto constinit old_mapper = std::optional{}; + + auto handoff_to_kernel_pmm(kapi::memory::frame_allocator & new_allocator) -> void + { + auto first_free_frame = bump_allocator->next_free_frame; + auto number_of_free_frames = number_of_frames - first_free_frame; + new_allocator.release_many({kapi::memory::frame{first_free_frame}, number_of_free_frames}); + } + +} // namespace + +namespace kapi::memory +{ + + auto init() -> void + { + bump_allocator.emplace(); + test_mapper.emplace(physical_size, virtual_size); + + old_allocator = set_frame_allocator(*bump_allocator); + old_mapper = set_page_mapper(*test_mapper); + + init_pmm(physical_size / frame::size, handoff_to_kernel_pmm); + } +} // namespace kapi::memory + +namespace kernel::tests::memory +{ + + auto deinit() -> void + { + if (old_allocator && *old_allocator) + { + set_frame_allocator(**old_allocator); + } + + if (old_mapper && *old_mapper) + { + set_page_mapper(**old_mapper); + } + + bump_allocator.reset(); + test_mapper.reset(); + } + + auto virtual_base() -> kapi::memory::linear_address + { + return test_mapper->memory.virtual_base(); + } + +} // namespace kernel::tests::memory \ No newline at end of file diff --git a/kernel/kernel/test_support/log_buffer.cpp b/kernel/kernel/test_support/log_buffer.cpp new file mode 100644 index 00000000..04d875bd --- /dev/null +++ b/kernel/kernel/test_support/log_buffer.cpp @@ -0,0 +1,33 @@ +#include + +#include +#include +#include + +namespace kernel::tests +{ + + auto log_buffer::append(std::string const & message) -> void + { + m_messages.push_back(message); + } + + auto log_buffer::clear() -> void + { + m_messages.clear(); + } + + auto log_buffer::flat_messages() -> std::string + { + return std::ranges::fold_left(m_messages, std::string{}, [](std::string accumulator, std::string const & message) { + accumulator += message; + return accumulator; + }); + } + + auto log_buffer::messages() -> std::vector const & + { + return m_messages; + } + +} // namespace kernel::tests diff --git a/kernel/kernel/test_support/log_buffer.hpp b/kernel/kernel/test_support/log_buffer.hpp new file mode 100644 index 00000000..41d9a762 --- /dev/null +++ b/kernel/kernel/test_support/log_buffer.hpp @@ -0,0 +1,37 @@ +#ifndef KERNEL_TEST_SUPPORT_LOG_BUFFER_HPP +#define KERNEL_TEST_SUPPORT_LOG_BUFFER_HPP + +#include +#include + +namespace kernel::tests +{ + + struct log_buffer + { + //! Append a message to this buffer. + //! @param message The message to append. + auto append(std::string const & message) -> void; + + //! Clear this buffer. + auto clear() -> void; + + //! Get all messages in this buffer as a single string. + //! + //! @return All messages in this buffer as a single string. + auto flat_messages() -> std::string; + + //! Get all messages in this buffer. + //! + //! @note Messages may be split across multiple entries if they are longer than the internal kernel print buffer + //! size. + //! + //! @return All messages in this buffer. + auto messages() -> std::vector const &; + + private: + std::vector m_messages{}; + }; +} // namespace kernel::tests + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/memory.hpp b/kernel/kernel/test_support/memory.hpp new file mode 100644 index 00000000..21030a41 --- /dev/null +++ b/kernel/kernel/test_support/memory.hpp @@ -0,0 +1,22 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_MEMORY_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_MEMORY_HPP + +#include + +namespace kernel::tests::memory +{ + + //! Deinitialize the memory subsystem. + auto deinit() -> void; + + //! Get the virtual base address of the simulated system. + //! + //! Since we do not have direct access to an actual MMU, we simulate the virtual address space by mapping the + //! physical address space starting at some process local virtual address. + //! + //! @return The virtual base address of the simulated system. + auto virtual_base() -> kapi::memory::linear_address; + +} // namespace kernel::tests::memory + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/output_device.cpp b/kernel/kernel/test_support/output_device.cpp new file mode 100644 index 00000000..45fb4bc8 --- /dev/null +++ b/kernel/kernel/test_support/output_device.cpp @@ -0,0 +1,28 @@ +#include +#include + +#include + +#include +#include +#include + +namespace kernel::tests::cio +{ + + auto output_device::write(kapi::cio::output_stream stream, std::string_view text) -> void + { + auto & standard_stream = stream == kapi::cio::output_stream::stdout ? std::cout : std::cerr; + standard_stream << text; + if (text != "\n") + { + m_log_buffer.append(std::string{text}); + } + } + + auto output_device::log_buffer() noexcept -> kernel::tests::log_buffer & + { + return m_log_buffer; + } + +} // namespace kernel::tests::cio \ No newline at end of file diff --git a/kernel/kernel/test_support/page_mapper.cpp b/kernel/kernel/test_support/page_mapper.cpp new file mode 100644 index 00000000..3d50ff1a --- /dev/null +++ b/kernel/kernel/test_support/page_mapper.cpp @@ -0,0 +1,70 @@ +#include + +#include + +#include + +#include +#include +#include + +namespace kernel::tests +{ + + page_mapper::page_mapper(kstd::units::bytes physical_size, kstd::units::bytes virtual_size) + : memory{physical_size, virtual_size} + {} + + auto page_mapper::map(kapi::memory::page page, kapi::memory::frame frame, flags) -> std::byte * + { + auto result = page_mappings.insert({page.number(), frame}); + if (!result.second) + { + auto error = std::format("Page {} was already mapped!", page.number()); + throw std::invalid_argument{error}; + } + + auto page_address = page.start_address(); + auto virtual_base = memory.virtual_base(); + auto virtual_end = virtual_base + memory.virtual_size(); + + if (page_address >= virtual_base && page_address < virtual_end) + { + auto virtual_target = static_cast(page_address); + auto physical_offset = frame.number() * kapi::memory::frame::size; + return memory.map(kapi::memory::page::size, virtual_target, physical_offset.value); + } + else if (page_address >= kapi::memory::mmio_base) + { + throw std::runtime_error("MMIO mapping not yet supported in testing!"); + } + else if (page_address >= kapi::memory::higher_half_direct_map_base) + { + auto offset = frame.number() * kapi::memory::frame::size; + return memory.physical_base() + offset; + } + + return nullptr; + } + + auto page_mapper::unmap(kapi::memory::page page) -> void + { + if (!try_unmap(page)) + { + auto error = std::format("Page {} was never mapped!", page.number()); + throw std::invalid_argument{error}; + } + } + + auto page_mapper::try_unmap(kapi::memory::page page) noexcept -> bool + { + if (page_mappings.contains(page.number())) + { + page_mappings.erase(page.number()); + return true; + } + + return false; + } + +} // namespace kernel::tests \ No newline at end of file diff --git a/kernel/kernel/test_support/page_mapper.hpp b/kernel/kernel/test_support/page_mapper.hpp new file mode 100644 index 00000000..be4403b0 --- /dev/null +++ b/kernel/kernel/test_support/page_mapper.hpp @@ -0,0 +1,49 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_PAGE_MAPPER_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_PAGE_MAPPER_HPP + +#include + +#include + +#include + +#include +#include +#include + +namespace kernel::tests +{ + + struct page_mapper : kapi::memory::page_mapper + { + //! Construct a new page mapper. + //! + //! @param physical_size The size of the physical memory. + //! @param virtual_size The size of the virtual address space. + page_mapper(kstd::units::bytes physical_size, kstd::units::bytes virtual_size); + + //! @copydoc kapi::memory::page_mapper::map + //! + //! @throws std::invalid_argument if the page has already been mapped. + //! @throws std::runtime_error if the page cannot be mapped. + //! @throws std::runtime_error if the underlying simulated memory cannot map the page. + auto map(kapi::memory::page page, kapi::memory::frame frame, flags) -> std::byte * override; + + //! @copydoc kapi::memory::page_mapper::unmap + //! + //! @throws std::invalid_argument if the page has not been mapped. + auto unmap(kapi::memory::page page) -> void override; + + //! @copydoc kapi::memory::page_mapper::try_unmap + auto try_unmap(kapi::memory::page page) noexcept -> bool override; + + //! The simulated memory. + kernel::tests::simulated_memory memory; + + //! The simplified page table entries. + std::unordered_map page_mappings; + }; + +} // namespace kernel::tests + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/simulated_memory.cpp b/kernel/kernel/test_support/simulated_memory.cpp new file mode 100644 index 00000000..074e6b10 --- /dev/null +++ b/kernel/kernel/test_support/simulated_memory.cpp @@ -0,0 +1,106 @@ +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace kernel::tests +{ + + simulated_memory::simulated_memory(kstd::units::bytes physical_size, kstd::units::bytes virtual_size) + : m_descriptor{memfd_create("teachos_simulated_memory", 0)} + , m_physical_size{physical_size} + , m_virtual_size{virtual_size} + { + if (m_descriptor < 0) + { + auto error = std::format("Failed to allocate backing memory: {}", strerror(errno)); + throw std::runtime_error(error); + } + + if (ftruncate(m_descriptor, static_cast(m_physical_size.value)) < 0) + { + auto error = std::format("Failed to reserve backing memory: {}", strerror(errno)); + throw std::runtime_error(error); + } + + auto physical_storage = mmap(nullptr, m_physical_size.value, PROT_READ | PROT_WRITE, MAP_SHARED, m_descriptor, 0); + if (physical_storage == MAP_FAILED) + { + auto error = std::format("Failed to map backing memory: {}", strerror(errno)); + throw std::runtime_error(error); + } + + auto virtual_pointer = mmap(nullptr, virtual_size.value, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + if (virtual_pointer == MAP_FAILED) + { + auto error = std::format("Failed to reserve virtual memory: {}", strerror(errno)); + throw std::runtime_error(error); + } + + m_physical_base = static_cast(physical_storage); + m_virtual_base = static_cast(virtual_pointer); + + clear(); + } + + simulated_memory::~simulated_memory() + { + munmap(m_virtual_base, m_virtual_size.value); + munmap(m_physical_base, m_physical_size.value); + close(m_descriptor); + } + + auto simulated_memory::clear() -> void + { + std::memset(m_physical_base, 0, m_physical_size.value); + } + + auto simulated_memory::physical_base() noexcept -> std::byte * + { + return m_physical_base; + } + + auto simulated_memory::physical_base() const noexcept -> std::byte const * + { + return m_physical_base; + } + + auto simulated_memory::physical_size() const noexcept -> kstd::units::bytes + { + return m_physical_size; + } + + auto simulated_memory::virtual_base() const noexcept -> kapi::memory::linear_address + { + return kapi::memory::linear_address{m_virtual_base}; + } + + auto simulated_memory::virtual_size() const noexcept -> kstd::units::bytes + { + return m_virtual_size; + } + + auto simulated_memory::map(kstd::units::bytes size, std::byte * to, off_t offset) -> std::byte * + { + auto mapped_ptr = mmap(to, size.value, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, m_descriptor, offset); + if (mapped_ptr == MAP_FAILED) + { + auto error = std::format("Failed to map page: {}", strerror(errno)); + throw std::runtime_error(error); + } + + return static_cast(mapped_ptr); + } + +} // namespace kernel::tests \ No newline at end of file diff --git a/kernel/kernel/test_support/simulated_memory.hpp b/kernel/kernel/test_support/simulated_memory.hpp new file mode 100644 index 00000000..a201c3d7 --- /dev/null +++ b/kernel/kernel/test_support/simulated_memory.hpp @@ -0,0 +1,67 @@ +#ifndef TEACHOS_KERNEL_TEST_SUPPORT_SIMULATED_MEMORY_HPP +#define TEACHOS_KERNEL_TEST_SUPPORT_SIMULATED_MEMORY_HPP + +#include + +#include + +#include + +#include + +namespace kernel::tests +{ + + //! A simulated memory device that can be used in tests. + //! + //! The general idea is to provide a means to simulate the physical and virtual address spaces of the system. This + //! implementation provides for a single physical and a single virtual address space. + //! + //! @note This is not a full-featured MMU. It is a simple simulation that can be used for testing only. + struct simulated_memory + { + //! Construct a new simulated memory device. + //! @param physical_size The size of the physical memory. + //! @param virtual_size The size of the virtual address space. + simulated_memory(kstd::units::bytes physical_size, kstd::units::bytes virtual_size); + + //! Destroy this device + ~simulated_memory(); + + //! Clear the contents of the physical memory of this device. + auto clear() -> void; + + //! Get the base address of the physical memory of this device. + [[nodiscard]] auto physical_base() noexcept -> std::byte *; + + //! Get the base address of the physical memory of this device. + [[nodiscard]] auto physical_base() const noexcept -> std::byte const *; + + //! Get the size of the physical memory of this device. + [[nodiscard]] auto physical_size() const noexcept -> kstd::units::bytes; + + //! Get the base address of the virtual address space of this device. + [[nodiscard]] auto virtual_base() const noexcept -> kapi::memory::linear_address; + + //! Get the size of the virtual address space of this device. + [[nodiscard]] auto virtual_size() const noexcept -> kstd::units::bytes; + + //! Map a region of physical memory to a region of virtual memory. + //! + //! @param size The size of the region to map. + //! @param to The base address of the virtual region. + //! @param offset The offset into the physical memory to map. + //! @return A pointer to the first byte of the mapped region. + [[nodiscard]] auto map(kstd::units::bytes size, std::byte * to, off_t offset) -> std::byte *; + + private: + int m_descriptor{}; + kstd::units::bytes m_physical_size{0}; + kstd::units::bytes m_virtual_size{0}; + std::byte * m_physical_base{nullptr}; + std::byte * m_virtual_base{nullptr}; + }; + +} // namespace kernel::tests + +#endif \ No newline at end of file diff --git a/kernel/kernel/test_support/state_reset_listener.cpp b/kernel/kernel/test_support/state_reset_listener.cpp new file mode 100644 index 00000000..6bb7537c --- /dev/null +++ b/kernel/kernel/test_support/state_reset_listener.cpp @@ -0,0 +1,48 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +struct state_reset_listener : Catch::EventListenerBase +{ + using EventListenerBase::EventListenerBase; + + void testCaseStarting(Catch::TestCaseInfo const &) override + { + kernel::filesystem::open_file_table::init(); + + kapi::cio::init(); + kapi::cpu::init(); + kapi::memory::init(); + } + + void testCaseEnded(Catch::TestCaseStats const &) override + { + kernel::tests::filesystem::open_file_table::deinit(); + kernel::tests::filesystem::vfs::deinit(); + kernel::tests::boot_modules::deinit(); + kernel::tests::devices::storage::management::deinit(); + + kernel::tests::memory::deinit(); + kernel::tests::cpu::deinit(); + kernel::tests::cio::deinit(); + } +}; + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" +CATCH_REGISTER_LISTENER(state_reset_listener); +#pragma GCC diagnostic pop \ No newline at end of file diff --git a/kernel/src/acpi/manager.cpp b/kernel/src/acpi/manager.cpp deleted file mode 100644 index bb895fda..00000000 --- a/kernel/src/acpi/manager.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include - -namespace kernel::acpi -{ - - manager::manager(::acpi::rsdp const & sdp) - : m_sdp{&sdp} - { - if (!m_sdp->validate()) - { - kapi::system::panic("[OS:ACPI] Invalid RSDP signature!"); - } - - m_extended = m_sdp->revision() >= 2; - - if (m_extended) - { - if (!static_cast<::acpi::xsdp const *>(m_sdp)->validate()) - { - kapi::system::panic("[OS:ACPI] Invalid XSDP signature!"); - } - } - else - { - if (!m_sdp->validate()) - { - kapi::system::panic("[OS:ACPI] Invalid RSDP checksum!"); - } - } - - auto physical_address = kapi::memory::physical_address{m_sdp->table_address()}; - auto linear_address = kapi::memory::hhdm_to_linear(physical_address); - m_rsdt = static_cast<::acpi::table_header const *>(linear_address); - } - - auto manager::load_tables() -> bool - { - if (!::acpi::validate_checksum({reinterpret_cast(m_rsdt), m_rsdt->length().value})) - { - kapi::system::panic("[OS:ACPI] Invalid RSDT checksum!"); - } - - auto check_and_register_table = [&](auto table_address) -> void { - auto physical_table_address = kapi::memory::physical_address{reinterpret_cast(table_address)}; - auto mapped_table = kapi::memory::hhdm_to_linear(physical_table_address); - auto table = static_cast<::acpi::table_header const *>(mapped_table); - - if (!::acpi::validate_checksum({static_cast(mapped_table), table->length().value})) - { - kstd::println(kstd::print_sink::stderr, "[OS:ACPI] Invalid table checksum!"); - } - else - { - kstd::println("[OS:ACPI] Found '{}' ACPI table", table->signature()); - m_tables.emplace(table->signature(), table); - } - }; - - if (m_extended) - { - auto xsdt = static_cast<::acpi::xsdt const *>(m_rsdt); - std::ranges::for_each(*xsdt | std::views::transform([](auto const & entry) { return entry.address(); }), - check_and_register_table); - } - else - { - auto rsdt = static_cast<::acpi::rsdt const *>(m_rsdt); - std::ranges::for_each(*rsdt | std::views::transform([](auto const & entry) { return entry.address(); }), - check_and_register_table); - } - - return !m_tables.empty(); - } - - auto manager::get_table(std::string_view signature) -> kstd::observer_ptr<::acpi::table_header const> - { - if (m_tables.contains(signature)) - { - return kstd::make_observer(m_tables.at(signature)); - } - return nullptr; - } - -} // namespace kernel::acpi diff --git a/kernel/src/devices/block_device.cpp b/kernel/src/devices/block_device.cpp deleted file mode 100644 index 13d73acd..00000000 --- a/kernel/src/devices/block_device.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include - -#include -#include - -#include - -#include - -namespace kernel::devices -{ - block_device::block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size) - : kapi::devices::device(major, minor, name) - , m_block_size(block_size) - { - if (m_block_size == 0) - { - kapi::system::panic("[DEVICES] block_device constructed with zero block size."); - } - } - - auto block_device::calculate_transfer(size_t block_index) const -> transfer_info - { - size_t const offset = block_index * m_block_size; - size_t const limit = size(); - - size_t const available = (offset < limit) ? (limit - offset) : 0; - size_t const to_transfer = (available < m_block_size) ? available : m_block_size; - - return {offset, to_transfer, m_block_size - to_transfer}; - } - - auto block_device::block_size() const -> size_t - { - return m_block_size; - } - - auto block_device::capacity() const -> size_t - { - return size(); - } -} // namespace kernel::devices \ No newline at end of file diff --git a/kernel/src/devices/block_device.tests.cpp b/kernel/src/devices/block_device.tests.cpp deleted file mode 100644 index a2ddd2bb..00000000 --- a/kernel/src/devices/block_device.tests.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include - -#include - -#include -#include -#include -#include - -#include - -#include - -SCENARIO("Block device construction", "[devices][block_device]") -{ - GIVEN("parameters for a block device") - { - size_t major = 1; - size_t minor = 0; - kstd::string name = "test_block_device"; - size_t block_size = 512; - - WHEN("constructing a block device") - { - auto device = - kstd::make_shared(major, minor, name, block_size, 3 * block_size); - - THEN("the block device has the correct properties") - { - REQUIRE(device->major() == major); - REQUIRE(device->minor() == minor); - REQUIRE(device->name() == name); - REQUIRE(device->block_size() == block_size); - REQUIRE(device->capacity() == 3 * 512); - } - } - - WHEN("constructing a block device with zero block size") - { - THEN("the constructor panics") - { - REQUIRE_THROWS_AS((kernel::tests::devices::block_device(major, minor, name, 0)), kernel::tests::cpu::halt); - } - } - } -} diff --git a/kernel/src/devices/block_device_utils.cpp b/kernel/src/devices/block_device_utils.cpp deleted file mode 100644 index 18d1e9d7..00000000 --- a/kernel/src/devices/block_device_utils.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include - -#include - -#include -#include - -#include -#include -#include - -#include -#include - -namespace kernel::devices::block_device_utils -{ - - using block_op = void (*)(size_t idx, size_t off, size_t len, size_t done, devices::block_device * device, - std::byte * scratch, void * buffer); - - auto process_blocks(kstd::shared_ptr const & device, size_t offset, size_t size, void * buffer, - block_op op) -> size_t - { - if (buffer == nullptr) - { - kapi::system::panic("[FILESYSTEM] device_file::process_blocks called with null buffer."); - } - - if (size == 0) - { - return 0; - } - - if (!device->is_block_device()) - { - kapi::system::panic("[FILESYSTEM] device_file: expected block_device."); - } - - auto * block_dev = static_cast(device.get()); - - size_t const block_size = block_dev->block_size(); - size_t const capacity = block_dev->capacity(); - - if (offset >= capacity) - { - return 0; - } - size_t const total_to_process = std::min(size, capacity - offset); - - kstd::vector scratch_buffer{block_size}; - auto processed = 0uz; - - while (processed < total_to_process) - { - size_t const absolute_offset = offset + processed; - size_t const block_index = absolute_offset / block_size; - size_t const in_block_offset = absolute_offset % block_size; - size_t const chunk_size = std::min(total_to_process - processed, block_size - in_block_offset); - - op(block_index, in_block_offset, chunk_size, processed, block_dev, scratch_buffer.data(), buffer); - - processed += chunk_size; - } - - return processed; - } - - auto read(kstd::shared_ptr const & device, void * buffer, size_t offset, size_t size) -> size_t - { - return process_blocks(device, offset, size, buffer, - [](size_t idx, size_t off, size_t len, size_t done, devices::block_device * device, - std::byte * scratch, void * buffer) { - auto * out = static_cast(buffer); - if (off == 0 && len == device->block_size()) - { - device->read_block(idx, out + done); - } - else - { - device->read_block(idx, scratch); - kstd::libc::memcpy(out + done, scratch + off, len); - } - }); - } - - auto write(kstd::shared_ptr const & device, void const * buffer, size_t offset, size_t size) - -> size_t - { - return process_blocks(device, offset, size, const_cast(buffer), - [](size_t idx, size_t off, size_t len, size_t done, devices::block_device * device, - std::byte * scratch, void * buffer) { - auto const * in = static_cast(buffer); - if (off == 0 && len == device->block_size()) - { - device->write_block(idx, in + done); - } - else - { - device->read_block(idx, scratch); - kstd::libc::memcpy(scratch + off, in + done, len); - device->write_block(idx, scratch); - } - }); - } - -} // namespace kernel::devices::block_device_utils \ No newline at end of file diff --git a/kernel/src/devices/block_device_utils.tests.cpp b/kernel/src/devices/block_device_utils.tests.cpp deleted file mode 100644 index e2e1e65e..00000000 --- a/kernel/src/devices/block_device_utils.tests.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include - -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include - -SCENARIO("reading from a block device with block_device_utils", "[devices][block_device_utils]") -{ - GIVEN("a block device with known data") - { - auto const block_size = 512; - auto device = kstd::make_shared(0, 0, "test_block_device", block_size); - kstd::vector block_data(block_size); - for (size_t i = 0; i < block_data.size(); ++i) - { - block_data[i] = static_cast(i % 256); - } - device->write_block(0, block_data.data()); - device->write_block(1, block_data.data()); - - WHEN("reading from the block device using block_device_utils") - { - kstd::vector read_buffer(block_size); - auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, read_buffer.size()); - - THEN("the correct number of bytes is read") - { - REQUIRE(bytes_read == read_buffer.size()); - } - - THEN("the data read matches the data written to the block device") - { - REQUIRE(read_buffer == block_data); - } - } - - WHEN("reading over block boundaries") - { - kstd::vector read_buffer(1024); - auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 256, read_buffer.size()); - - THEN("the correct number of bytes is read") - { - REQUIRE(bytes_read == 1.5 * block_size); - } - - THEN("the data read matches the expected data across block boundaries") - { - for (size_t i = 0; i < bytes_read; ++i) - { - uint8_t expected_value = static_cast((256 + i) % 256); - REQUIRE(read_buffer[i] == expected_value); - } - } - } - - WHEN("reading beyond the device capacity") - { - kstd::vector read_buffer(block_size); - auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 1024, read_buffer.size()); - - THEN("no bytes are read") - { - REQUIRE(bytes_read == 0); - } - } - - WHEN("reading nothing") - { - kstd::vector read_buffer(block_size); - auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, 0); - - THEN("no bytes are read") - { - REQUIRE(bytes_read == 0); - } - } - - WHEN("reading with a null buffer") - { - THEN("the system panics") - { - REQUIRE_THROWS_AS(kernel::devices::block_device_utils::read(device, nullptr, 0, 512), kernel::tests::cpu::halt); - } - } - } -} - -SCENARIO("writing to a block device using block_device_utils", "[devices][block_device_utils]") -{ - GIVEN("a block device") - { - auto const block_size = 512; - auto device = - kstd::make_shared(0, 0, "test_block_device", block_size, 2 * block_size); - - WHEN("writing to the block device using block_device_utils") - { - kstd::vector write_buffer(block_size); - for (size_t i = 0; i < write_buffer.size(); ++i) - { - write_buffer[i] = static_cast(i % 256); - } - - auto bytes_written = - kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, write_buffer.size()); - - THEN("the correct number of bytes is written") - { - REQUIRE(bytes_written == write_buffer.size()); - } - - THEN("the data written matches the data read back from the block device") - { - kstd::vector read_buffer(block_size); - device->read_block(0, read_buffer.data()); - REQUIRE(read_buffer == write_buffer); - } - } - - WHEN("writing over block boundaries") - { - kstd::vector write_buffer(2 * block_size); - for (size_t i = 0; i < write_buffer.size(); ++i) - { - write_buffer[i] = static_cast(i % 256); - } - - auto bytes_written = - kernel::devices::block_device_utils::write(device, write_buffer.data(), 256, write_buffer.size()); - - THEN("the correct number of bytes is written") - { - REQUIRE(bytes_written == 1.5 * block_size); - } - - THEN("the data written matches the data read back from the block device across block boundaries") - { - kstd::vector read_buffer(2 * block_size); - auto bytes_read = kernel::devices::block_device_utils::read(device, read_buffer.data(), 256, 2 * block_size); - - for (size_t i = 0; i < bytes_read; ++i) - { - REQUIRE(read_buffer[i] == write_buffer[i]); - } - } - } - - WHEN("writing beyond the device capacity") - { - kstd::vector write_buffer(block_size); - auto bytes_written = - kernel::devices::block_device_utils::write(device, write_buffer.data(), 1024, write_buffer.size()); - - THEN("no bytes are written") - { - REQUIRE(bytes_written == 0); - } - } - - WHEN("writing nothing") - { - kstd::vector write_buffer(block_size); - auto bytes_written = kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, 0); - - THEN("no bytes are written") - { - REQUIRE(bytes_written == 0); - } - } - - WHEN("writing with a null buffer") - { - THEN("the system panics") - { - REQUIRE_THROWS_AS(kernel::devices::block_device_utils::write(device, nullptr, 0, block_size), - kernel::tests::cpu::halt); - } - } - } -} - -SCENARIO("block_device_utils with a non-block device", "[devices][block_device_utils]") -{ - GIVEN("a non-block device") - { - auto device = kstd::make_shared(0, 0, "test_character_device"); - - WHEN("attempting to read from the non-block device using block_device_utils") - { - kstd::vector read_buffer(512); - THEN("the system panics") - { - REQUIRE_THROWS_AS(kernel::devices::block_device_utils::read(device, read_buffer.data(), 0, read_buffer.size()), - kernel::tests::cpu::halt); - } - } - - WHEN("attempting to write to the non-block device using block_device_utils") - { - kstd::vector write_buffer(512); - THEN("the system panics") - { - REQUIRE_THROWS_AS( - kernel::devices::block_device_utils::write(device, write_buffer.data(), 0, write_buffer.size()), - kernel::tests::cpu::halt); - } - } - } -} diff --git a/kernel/src/devices/root_bus.cpp b/kernel/src/devices/root_bus.cpp deleted file mode 100644 index 1b754f23..00000000 --- a/kernel/src/devices/root_bus.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include - -#include - -namespace kernel::devices -{ - - root_bus::root_bus() - : kapi::devices::bus{0, 0, "system"} - {} - -} // namespace kernel::devices \ No newline at end of file diff --git a/kernel/src/devices/storage/controller.cpp b/kernel/src/devices/storage/controller.cpp deleted file mode 100644 index 171b918e..00000000 --- a/kernel/src/devices/storage/controller.cpp +++ /dev/null @@ -1,44 +0,0 @@ -#include - -#include - -#include -#include - -#include -#include - -namespace kernel::devices::storage -{ - auto controller::set_ids(size_t major, size_t minors_per_dev) -> void - { - m_major = major; - m_minors_per_device = minors_per_dev; - } - - auto controller::major() const -> size_t - { - return m_major; - } - - auto controller::device_by_minor(size_t minor) const -> kstd::shared_ptr - { - auto it = std::ranges::find_if(m_devices, [minor](auto const & device) { return device->minor() == minor; }); - - if (it != m_devices.end()) - { - return *it; - } - return nullptr; - } - - auto controller::devices_count() const -> size_t - { - return m_devices.size(); - } - - auto controller::all_devices() const -> kstd::vector> const & - { - return m_devices; - } -} // namespace kernel::devices::storage \ No newline at end of file diff --git a/kernel/src/devices/storage/management.cpp b/kernel/src/devices/storage/management.cpp deleted file mode 100644 index 06efc27d..00000000 --- a/kernel/src/devices/storage/management.cpp +++ /dev/null @@ -1,95 +0,0 @@ -#include - -#include -#include - -#include -#include -#include - -#include -#include - -#include -#include -#include -#include - -namespace -{ - constexpr size_t static minors_per_device = 16; - constexpr size_t static start_major = 1; - constinit size_t static next_free_major = start_major; - - constinit auto static storage_management = std::optional{}; -} // namespace - -namespace kernel::devices::storage -{ - auto management::init() -> void - { - if (storage_management) - { - kapi::system::panic("[DEVICES] Storage management has already been initialized."); - } - storage_management.emplace(management{}); - - auto ram_disk_controller = kstd::make_shared(&kapi::boot_modules::get_boot_module_registry()); - storage_management->add_controller(ram_disk_controller); - - std::ranges::for_each(storage_management->m_controllers, [](auto controller) { controller->probe(); }); - } - - auto management::get() -> management & - { - if (!storage_management) - { - kapi::system::panic("[DEVICES] Storage management has not been initialized."); - } - - return *storage_management; - } - - auto management::add_controller(kstd::shared_ptr const & controller) -> void - { - controller->set_ids(next_free_major++, minors_per_device); - m_controllers.push_back(controller); - } - - auto management::all_controllers() const -> kstd::vector> const & - { - return m_controllers; - } - - auto management::device_by_major_minor(size_t major, size_t minor) -> kstd::shared_ptr - { - auto found = std::ranges::find_if(m_controllers, [=](auto const & controller) { - if (controller != nullptr && controller->major() == major) - { - return controller->device_by_minor(minor) != nullptr; - } - return false; - }); - - if (found != std::ranges::cend(m_controllers)) - { - return found->get()->device_by_minor(minor); - } - - return nullptr; - } - - auto management::determine_boot_device() -> kstd::shared_ptr - { - return device_by_major_minor(start_major, 0); - } -} // namespace kernel::devices::storage - -namespace kernel::tests::devices::storage::management -{ - auto deinit() -> void - { - storage_management.reset(); - next_free_major = start_major; - } -} // namespace kernel::tests::devices::storage::management diff --git a/kernel/src/devices/storage/ram_disk/controller.cpp b/kernel/src/devices/storage/ram_disk/controller.cpp deleted file mode 100644 index 30441fa5..00000000 --- a/kernel/src/devices/storage/ram_disk/controller.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -#include - -#include - -#include - -#include -#include - -namespace kernel::devices::storage::ram_disk -{ - controller::controller(kapi::boot_modules::boot_module_registry const * registry) - : m_boot_module_registry(registry) - {} - - auto controller::probe() -> void - { - size_t current_device_index = 0; - - std::ranges::for_each(*m_boot_module_registry, [this, ¤t_device_index](auto const & module) { - auto const minor = current_device_index++ * m_minors_per_device; - m_devices.push_back(kstd::make_shared(module, m_major, minor)); - }); - } -} // namespace kernel::devices::storage::ram_disk \ No newline at end of file diff --git a/kernel/src/devices/storage/ram_disk/device.cpp b/kernel/src/devices/storage/ram_disk/device.cpp deleted file mode 100644 index 1557204b..00000000 --- a/kernel/src/devices/storage/ram_disk/device.cpp +++ /dev/null @@ -1,71 +0,0 @@ -#include - -#include - -#include -#include - -#include -#include - -#include - -namespace kernel::devices::storage::ram_disk -{ - namespace - { - constexpr size_t ram_disk_block_size = 512uz; - } // namespace - - device::device(kapi::boot_modules::boot_module const & module, size_t major, size_t minor) - : block_device(major, minor, "ram" + kstd::to_string(minor), ram_disk_block_size) - , m_boot_module(module) - {} - - auto device::init() -> bool - { - return m_boot_module.start_address.raw() != 0 && m_boot_module.size > 0; - } - - auto device::read_block(size_t block_index, void * buffer) const -> void - { - if (buffer == nullptr) - { - kapi::system::panic("[RAM DISK DEVICE] read_block called with null buffer."); - } - - auto const info = calculate_transfer(block_index); - - if (info.to_transfer > 0) - { - auto const src = static_cast(m_boot_module.start_address) + info.offset; - kstd::libc::memcpy(buffer, src, info.to_transfer); - } - - if (info.remainder > 0) - { - kstd::libc::memset(static_cast(buffer) + info.to_transfer, 0, info.remainder); - } - } - - auto device::write_block(size_t block_index, void const * buffer) -> void - { - if (buffer == nullptr) - { - kapi::system::panic("[RAM DISK DEVICE] write_block called with null buffer."); - } - - auto const info = calculate_transfer(block_index); - - if (info.to_transfer > 0) - { - auto const dest = static_cast(m_boot_module.start_address) + info.offset; - kstd::libc::memcpy(dest, buffer, info.to_transfer); - } - } - - auto device::size() const -> size_t - { - return m_boot_module.size; - } -} // namespace kernel::devices::storage::ram_disk \ No newline at end of file diff --git a/kernel/src/devices/storage/ram_disk/device.tests.cpp b/kernel/src/devices/storage/ram_disk/device.tests.cpp deleted file mode 100644 index d0fab76b..00000000 --- a/kernel/src/devices/storage/ram_disk/device.tests.cpp +++ /dev/null @@ -1,116 +0,0 @@ -#include - -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -SCENARIO("RAM Disk Device Construction and Initialization", "[ram_disk_device]") -{ - GIVEN("an empty boot module") - { - kapi::boot_modules::boot_module boot_module{}; - boot_module.start_address = kapi::memory::linear_address{nullptr}; - boot_module.size = 0; - - WHEN("constructing the device") - { - kernel::devices::storage::ram_disk::device device{boot_module, 0, 0}; - - THEN("init return false") - { - REQUIRE_FALSE(device.init()); - } - } - } - - GIVEN("a boot module with a valid start address and size") - { - kapi::boot_modules::boot_module boot_module{}; - boot_module.start_address = kapi::memory::linear_address{reinterpret_cast(0x1000)}; - boot_module.size = 512; - - WHEN("constructing the device") - { - kernel::devices::storage::ram_disk::device device{boot_module, 0, 0}; - - THEN("init return true") - { - REQUIRE(device.init()); - } - } - } -} - -SCENARIO("RAM Disk Device Read and Write", "[ram_disk_device]") -{ - GIVEN("a device initialized with a valid boot module") - { - auto storage = std::vector{4096, std::byte{0xff}}; - auto module = - kapi::boot_modules::boot_module{"test_module", kapi::memory::linear_address{storage.data()}, storage.size()}; - auto device = kernel::devices::storage::ram_disk::device{module, 0, 0}; - REQUIRE(device.init()); - - WHEN("reading a full block from the device") - { - auto buffer = std::vector(device.block_size()); - device.read_block(0, buffer.data()); - - THEN("the buffer is filled with the module data") - { - REQUIRE_THAT(buffer, Catch::Matchers::RangeEquals(std::views::take(storage, device.block_size()))); - } - } - - WHEN("reading from a block index beyond the module size") - { - auto buffer = std::vector(device.block_size()); - device.read_block(10, buffer.data()); - - THEN("the buffer is filled with zeros") - { - REQUIRE_THAT(buffer, Catch::Matchers::RangeEquals(std::vector(device.block_size(), std::byte{0}))); - } - } - - WHEN("reading into a null buffer") - { - REQUIRE_THROWS_AS(device.read_block(0, nullptr), std::runtime_error); - } - - WHEN("writing to a full block") - { - auto buffer = std::vector(device.block_size(), std::byte{0x01}); - device.write_block(0, buffer.data()); - - THEN("the module data is updated") - { - REQUIRE_THAT(std::views::take(storage, device.block_size()), Catch::Matchers::RangeEquals(buffer)); - } - } - - WHEN("writing to a block index beyond the module size") - { - auto buffer = std::vector(device.block_size(), std::byte{0x01}); - device.write_block(10, buffer.data()); - - THEN("the module data is not updated") - { - REQUIRE_THAT(storage, Catch::Matchers::RangeEquals(std::vector{4096, std::byte{0xff}})); - } - } - - WHEN("writing from a null buffer") - { - REQUIRE_THROWS_AS(device.write_block(0, nullptr), std::runtime_error); - } - } -} \ No newline at end of file diff --git a/kernel/src/filesystem/dentry.cpp b/kernel/src/filesystem/dentry.cpp deleted file mode 100644 index 01de276c..00000000 --- a/kernel/src/filesystem/dentry.cpp +++ /dev/null @@ -1,95 +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::get_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/src/filesystem/dentry.tests.cpp b/kernel/src/filesystem/dentry.tests.cpp deleted file mode 100644 index b7690f57..00000000 --- a/kernel/src/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.get_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.get_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/src/filesystem/devfs/filesystem.cpp b/kernel/src/filesystem/devfs/filesystem.cpp deleted file mode 100644 index ce887ff3..00000000 --- a/kernel/src/filesystem/devfs/filesystem.cpp +++ /dev/null @@ -1,81 +0,0 @@ -#include - -#include "kernel/filesystem/filesystem.hpp" -#include -#include -#include -#include -#include - -#include - -#include - -#include -#include - -namespace kernel::filesystem::devfs -{ - struct type final : kernel::filesystem::type - { - [[nodiscard]] auto name() const noexcept -> std::string_view override - { - return "devfs"; - } - - [[nodiscard]] auto requires_device() const noexcept -> bool override - { - return false; - } - - [[nodiscard]] auto make_instance() const -> kstd::shared_ptr override - { - return kstd::make_shared(); - } - }; - - [[gnu::used]] - constexpr auto registration = type_registration{}; - - auto filesystem::mount(kstd::shared_ptr const &) -> operation_result - { - m_root_inode = kstd::make_shared(); - build_device_inode_table(); - - return operation_result::success; - } - - auto filesystem::lookup(kstd::shared_ptr const & parent, std::string_view name) const - -> kstd::shared_ptr - { - if (!parent || !parent->is_directory()) - { - return nullptr; - } - - if (parent.get() != m_root_inode.get()) - { - return nullptr; - } - - auto it = std::ranges::find_if(m_inodes, [&](auto const & dev_node) { - if (auto device_inode_ptr = static_cast(dev_node.get())) - { - return device_inode_ptr->device()->name() == name; - } - return false; - }); - return (it != m_inodes.end()) ? *it : nullptr; - } - - auto filesystem::build_device_inode_table() -> void - { - m_inodes.clear(); - - auto storage_mgmt = devices::storage::management::get(); - std::ranges::for_each(storage_mgmt.all_controllers(), [&](auto const & controller) { - std::ranges::for_each(controller->all_devices(), - [&](auto const & device) { m_inodes.push_back(kstd::make_shared(device)); }); - }); - } -} // namespace kernel::filesystem::devfs \ No newline at end of file diff --git a/kernel/src/filesystem/devfs/filesystem.tests.cpp b/kernel/src/filesystem/devfs/filesystem.tests.cpp deleted file mode 100644 index 36cb4117..00000000 --- a/kernel/src/filesystem/devfs/filesystem.tests.cpp +++ /dev/null @@ -1,72 +0,0 @@ -#include - -#include -#include - -#include - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, - "Devfs filesystem lookup uses storage management devices", "[filesystem][devfs][filesystem]") -{ - GIVEN("a boot module registry with one module") - { - setup_modules(1); - - auto fs = kernel::filesystem::devfs::filesystem{}; - auto result = fs.mount(nullptr); - - THEN("mount succeeds") - { - REQUIRE(result == kernel::filesystem::filesystem::operation_result::success); - REQUIRE(fs.root_inode() != nullptr); - } - - THEN("lookup on root finds ram0 device inode") - { - auto inode = fs.lookup(fs.root_inode(), "ram0"); - REQUIRE(inode != nullptr); - REQUIRE(inode->is_device()); - } - - THEN("lookup of an unknown device returns null") - { - auto inode = fs.lookup(fs.root_inode(), "ram99"); - REQUIRE(inode == nullptr); - } - - THEN("lookup with wrong parent returns null") - { - auto other_fs = kernel::filesystem::devfs::filesystem{}; - other_fs.mount(nullptr); - - auto inode = fs.lookup(other_fs.root_inode(), "ram0"); - REQUIRE(inode == nullptr); - } - - THEN("lookup with a non-directory parent returns null") - { - auto non_directory_inode = fs.lookup(fs.root_inode(), "ram0"); - REQUIRE(non_directory_inode != nullptr); - REQUIRE_FALSE(non_directory_inode->is_directory()); - - auto result = fs.lookup(non_directory_inode, "anything"); - REQUIRE(result == nullptr); - } - } - - GIVEN("a boot module registry with three modules") - { - setup_modules(3, 2048); - - auto fs = kernel::filesystem::devfs::filesystem{}; - auto result = fs.mount(nullptr); - REQUIRE(result == kernel::filesystem::filesystem::operation_result::success); - - THEN("lookup finds all generated RAM devices") - { - REQUIRE(fs.lookup(fs.root_inode(), "ram0") != nullptr); - REQUIRE(fs.lookup(fs.root_inode(), "ram16") != nullptr); - REQUIRE(fs.lookup(fs.root_inode(), "ram32") != nullptr); - } - } -} diff --git a/kernel/src/filesystem/devfs/inode.cpp b/kernel/src/filesystem/devfs/inode.cpp deleted file mode 100644 index 7bbfbbed..00000000 --- a/kernel/src/filesystem/devfs/inode.cpp +++ /dev/null @@ -1,21 +0,0 @@ -#include - -#include - -namespace kernel::filesystem::devfs -{ - auto inode::read(void *, size_t, size_t) const -> size_t - { - return 0; - } - - auto inode::write(void const *, size_t, size_t) -> size_t - { - return 0; - } - - auto inode::is_directory() const -> bool - { - return true; - } -} // namespace kernel::filesystem::devfs \ No newline at end of file diff --git a/kernel/src/filesystem/devfs/inode.tests.cpp b/kernel/src/filesystem/devfs/inode.tests.cpp deleted file mode 100644 index ae26e742..00000000 --- a/kernel/src/filesystem/devfs/inode.tests.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include - -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.data(), 0, buffer.size()); - - THEN("no bytes are read") - { - REQUIRE(bytes_read == 0); - } - } - - WHEN("attempting to write to the devfs inode") - { - kstd::vector buffer(512); - auto bytes_written = inode.write(buffer.data(), 0, buffer.size()); - - THEN("no bytes are written") - { - REQUIRE(bytes_written == 0); - } - } - } -} diff --git a/kernel/src/filesystem/device_inode.cpp b/kernel/src/filesystem/device_inode.cpp deleted file mode 100644 index 81a784cd..00000000 --- a/kernel/src/filesystem/device_inode.cpp +++ /dev/null @@ -1,57 +0,0 @@ -#include - -#include - -#include -#include - -#include - -#include - -namespace kernel::filesystem -{ - device_inode::device_inode(kstd::shared_ptr const & device) - : m_device(device) - { - if (!device) - { - kapi::system::panic("[FILESYSTEM] device_inode constructed with null device."); - } - } - - auto device_inode::read(void * buffer, size_t offset, size_t size) const -> size_t - { - if (m_device->is_block_device()) - { - return devices::block_device_utils::read(m_device, buffer, offset, size); - } - else - { - kapi::system::panic("[FILESYSTEM] device_file::read called on non-block device."); - } - } - - auto device_inode::write(void const * buffer, size_t offset, size_t size) -> size_t - { - if (m_device->is_block_device()) - { - return devices::block_device_utils::write(m_device, buffer, offset, size); - } - else - { - kapi::system::panic("[FILESYSTEM] device_file::write called on non-block device."); - } - } - - auto device_inode::device() const -> kstd::shared_ptr const & - { - return m_device; - } - - auto device_inode::is_device() const -> bool - { - return true; - } - -} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/src/filesystem/device_inode.tests.cpp b/kernel/src/filesystem/device_inode.tests.cpp deleted file mode 100644 index 025a22a2..00000000 --- a/kernel/src/filesystem/device_inode.tests.cpp +++ /dev/null @@ -1,109 +0,0 @@ -#include - -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include - -SCENARIO("Device inode construction", "[filesystem][device_inode]") -{ - GIVEN("a block device") - { - auto device = kstd::make_shared(0, 0, "test_block_device", 512, 3 * 512); - - WHEN("constructing a device inode with the block device") - { - auto inode = kernel::filesystem::device_inode{device}; - - THEN("the device inode has the correct device") - { - REQUIRE(inode.device() == device); - } - - THEN("the device inode has the correct kind") - { - REQUIRE(inode.is_device()); - REQUIRE_FALSE(inode.is_directory()); - REQUIRE_FALSE(inode.is_regular()); - 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(0, 0, "test_block_device", 512, 3 * 512); - 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.data(), 256, write_buffer.size()); - - THEN("the correct number of bytes is written") - { - REQUIRE(bytes_written == 1024); - } - - THEN("the data written matches the data read back from the device inode") - { - kstd::vector read_buffer(1024); - auto bytes_read = inode.read(read_buffer.data(), 256, read_buffer.size()); - - REQUIRE(bytes_read == write_buffer.size()); - REQUIRE(read_buffer == write_buffer); - } - } - } -} - -SCENARIO("Device inode read/write with a non-block device", "[filesystem][device_inode]") -{ - GIVEN("a non-block device and a device inode for that device") - { - auto device = kstd::make_shared(0, 0, "test_character_device"); - auto inode = kernel::filesystem::device_inode{device}; - - WHEN("reading from the device inode") - { - kstd::vector read_buffer(512); - THEN("the system panics") - { - REQUIRE_THROWS_AS(inode.read(read_buffer.data(), 0, read_buffer.size()), kernel::tests::cpu::halt); - } - } - - WHEN("writing to the device inode") - { - kstd::vector write_buffer(512); - THEN("the system panics") - { - REQUIRE_THROWS_AS(inode.write(write_buffer.data(), 0, write_buffer.size()), kernel::tests::cpu::halt); - } - } - } -} diff --git a/kernel/src/filesystem/ext2/filesystem.cpp b/kernel/src/filesystem/ext2/filesystem.cpp deleted file mode 100644 index 3180a19a..00000000 --- a/kernel/src/filesystem/ext2/filesystem.cpp +++ /dev/null @@ -1,247 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -namespace kernel::filesystem::ext2 -{ - - struct type final : kernel::filesystem::type - { - [[nodiscard]] auto name() const noexcept -> std::string_view override - { - return "ext2"; - } - - [[nodiscard]] auto requires_device() const noexcept -> bool override - { - return true; - } - - [[nodiscard]] auto make_instance() const -> kstd::shared_ptr override - { - return kstd::make_shared(); - } - }; - - [[gnu::used]] - constexpr auto registration = type_registration{}; - - auto filesystem::mount(kstd::shared_ptr const & backing_inode) -> operation_result - { - kernel::filesystem::filesystem::mount(backing_inode); - - m_backing_inode->read(&m_superblock, constants::superblock_offset, sizeof(m_superblock)); - - if (m_superblock.magic != constants::magic_number) - { - return operation_result::invalid_magic_number; - } - - auto const blocks_per_group = m_superblock.blocks_per_group; - auto const num_block_groups = (m_superblock.blocks_count + blocks_per_group - 1) / blocks_per_group; - - m_block_group_descriptors = kstd::vector(num_block_groups); - - m_backing_inode->read(m_block_group_descriptors.data(), block_group_descriptor_table_offset(), - num_block_groups * sizeof(block_group_descriptor)); - - m_root_inode = read_inode(constants::root_inode_number); - - if (!m_root_inode || !m_root_inode->is_directory()) - { - return operation_result::invalid_root_inode; - } - return operation_result::success; - } - - auto filesystem::lookup(kstd::shared_ptr const & parent, std::string_view name) const - -> kstd::shared_ptr - { - if (!parent || !parent->is_directory()) - { - return nullptr; - } - - auto * ext2_parent = static_cast(parent.get()); - if (!ext2_parent) - { - return nullptr; - } - - auto const & inode_data = ext2_parent->data(); - kstd::vector buffer(block_size()); - - for (uint32_t i = 0; i < inode_block_count(inode_data); ++i) - { - auto const global_block_number = map_inode_block_index_to_global_block_number(i, inode_data); - auto const block_offset = global_block_number * block_size(); - m_backing_inode->read(buffer.data(), block_offset, block_size()); - - auto const * entry = reinterpret_cast(buffer.data()); - auto bytes_read = 0uz; - - while (bytes_read < block_size() && entry->inode != 0) - { - auto const entry_name = std::string_view{entry->name.data(), entry->name_len}; - if (entry_name == name) - { - return read_inode(entry->inode); - } - - bytes_read += entry->rec_len; - entry = reinterpret_cast(buffer.data() + bytes_read); - } - } - - return nullptr; - } - - auto filesystem::read_inode(uint32_t inode_number) const -> kstd::shared_ptr - { - auto const inodes_per_group = m_superblock.inodes_per_group; - auto const block_group_index = (inode_number - 1) / inodes_per_group; - auto const inode_index_within_group = (inode_number - 1) % inodes_per_group; - - if (block_group_index >= m_block_group_descriptors.size()) - { - return nullptr; - } - - auto const & block_group_descriptor = m_block_group_descriptors.at(block_group_index); - auto const inode_table_start_block = block_group_descriptor.inode_table; - auto const inode_table_offset = static_cast(inode_table_start_block) * block_size(); - auto const inode_offset = inode_table_offset + inode_index_within_group * inode_size(); - - auto new_inode_data = inode_data{}; - m_backing_inode->read(&new_inode_data, inode_offset, sizeof(inode_data)); - - return kstd::make_shared(this, new_inode_data); - } - - auto filesystem::indirect_levels() const -> std::array - { - return { - {{constants::singly_indirect_block_index, block_numbers_per_singly_indirect_block()}, - {constants::doubly_indirect_block_index, block_numbers_per_doubly_indirect_block()}, - {constants::triply_indirect_block_index, block_numbers_per_triply_indirect_block()}} - }; - } - - auto filesystem::map_inode_block_index_to_global_block_number(size_t inode_block_index, inode_data data) const - -> kstd::ssize_t - { - if (inode_block_index < constants::direct_block_count) - { - return data.block.at(inode_block_index); - } - - inode_block_index -= constants::direct_block_count; - - for (auto const & level : indirect_levels()) - { - if (inode_block_index >= level.capacity) - { - inode_block_index -= level.capacity; - continue; - } - - auto block_number = data.block[level.slot_index]; - if (block_number == 0) - { - return 0; - } - - for (auto stride = level.capacity / block_numbers_per_block();; stride /= block_numbers_per_block()) - { - auto const idx = inode_block_index / stride; - inode_block_index %= stride; - - block_number = read_block_number_at_index(block_number, idx); - if (block_number == 0) - { - return 0; - } - - if (stride == 1) - { - break; - } - } - - return block_number; - } - - return -1; - } - - auto filesystem::read_block_number_at_index(uint32_t block_number, size_t index) const -> uint32_t - { - uint32_t block_number_buffer = 0; - - auto const block_start_offset = block_number * block_size(); - auto const number_start_address = block_start_offset + index * sizeof(uint32_t); - m_backing_inode->read(&block_number_buffer, number_start_address, sizeof(uint32_t)); - - return block_number_buffer; - } - - auto filesystem::block_numbers_per_block() const -> size_t - { - return block_size() / sizeof(uint32_t); - } - - auto filesystem::block_numbers_per_singly_indirect_block() const -> size_t - { - return block_numbers_per_block(); - } - - auto filesystem::block_numbers_per_doubly_indirect_block() const -> size_t - { - return block_numbers_per_singly_indirect_block() * block_numbers_per_block(); - } - - auto filesystem::block_numbers_per_triply_indirect_block() const -> size_t - { - return block_numbers_per_doubly_indirect_block() * block_numbers_per_block(); - } - - auto filesystem::block_size() const -> size_t - { - return constants::base_block_size << m_superblock.log_block_size; - } - - auto filesystem::revision_level() const -> uint32_t - { - return m_superblock.rev_level; - } - - auto filesystem::inode_size() const -> uint16_t - { - return revision_level() == constants::good_old_revision ? 128 : m_superblock.inode_size; - } - - auto filesystem::inode_block_count(inode_data const & data) const -> uint32_t - { - return data.blocks / (2 << m_superblock.log_block_size); - } - - auto filesystem::block_group_descriptor_table_offset() const -> size_t - { - return block_size() == 1024 ? 2 * block_size() : block_size(); - } -} // namespace kernel::filesystem::ext2 diff --git a/kernel/src/filesystem/ext2/filesystem.tests.cpp b/kernel/src/filesystem/ext2/filesystem.tests.cpp deleted file mode 100644 index 83410702..00000000 --- a/kernel/src/filesystem/ext2/filesystem.tests.cpp +++ /dev/null @@ -1,139 +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 filesystem mount and lookup with real image", "[filesystem][ext2][filesystem][img]") -{ - auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; - - GIVEN("a mounted ext2 filesystem from a real image") - { - REQUIRE(std::filesystem::exists(image_path)); - REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); - - auto boot_device = kernel::devices::storage::management::get().determine_boot_device(); - REQUIRE(boot_device != nullptr); - - auto dev_inode = kstd::make_shared(boot_device); - - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); - - THEN("the root inode is available and is a directory") - { - REQUIRE(fs.root_inode() != nullptr); - REQUIRE(fs.root_inode()->is_directory()); - } - - THEN("lookup resolves known entries from the image") - { - auto information = fs.lookup(fs.root_inode(), "information"); - REQUIRE(information != nullptr); - REQUIRE(information->is_directory()); - - auto info_1 = fs.lookup(information, "info_1.txt"); - REQUIRE(info_1 != nullptr); - REQUIRE(info_1->is_regular()); - } - - THEN("lookup returns null for invalid inputs") - { - REQUIRE(fs.lookup(nullptr, "information") == nullptr); - - auto information = fs.lookup(fs.root_inode(), "information"); - REQUIRE(information != nullptr); - auto info_1 = fs.lookup(information, "info_1.txt"); - REQUIRE(info_1 != nullptr); - - REQUIRE(fs.lookup(info_1, "anything") == nullptr); - REQUIRE(fs.lookup(fs.root_inode(), "does_not_exist") == nullptr); - } - } -} - -SCENARIO("Ext2 filesystem rejects invalid magic", "[filesystem][ext2][filesystem]") -{ - auto const block_size = 1024; - GIVEN("a block device that does not contain an ext2 superblock") - { - auto device = kstd::make_shared(0, 0, "mock", block_size, 2 * block_size); - REQUIRE(device != nullptr); - - auto dev_inode = kstd::make_shared(device); - - auto fs = kernel::filesystem::ext2::filesystem{}; - - THEN("mount fails with invalid_magic_number") - { - REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::invalid_magic_number); - } - } -} - -SCENARIO("Ext2 block mapping includes direct and all indirect levels", "[filesystem][ext2][filesystem]") -{ - auto const block_size = 1024; - - GIVEN("a minimally valid ext2 layout with configured indirect block tables") - { - auto device = kstd::make_shared(0, 0, "mock", block_size, 128 * block_size); - REQUIRE(device != nullptr); - - kernel::tests::filesystem::ext2::setup_mock_ext2_layout(*device); - - auto dev_inode = kstd::make_shared(device); - - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); - - auto inode_data = kernel::filesystem::ext2::inode_data{}; - inode_data.block[0] = 7; - - inode_data.block[12] = 30; - kernel::tests::filesystem::ext2::write_u32(*device, 30 * block_size, 31); - - inode_data.block[13] = 40; - kernel::tests::filesystem::ext2::write_u32(*device, 40 * block_size, 41); - kernel::tests::filesystem::ext2::write_u32(*device, 41 * block_size, 42); - - inode_data.block[14] = 50; - kernel::tests::filesystem::ext2::write_u32(*device, 50 * block_size, 51); - kernel::tests::filesystem::ext2::write_u32(*device, 51 * block_size, 52); - kernel::tests::filesystem::ext2::write_u32(*device, 52 * block_size, 53); - - auto const numbers_per_block = static_cast(block_size / sizeof(uint32_t)); - 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("mapping resolves direct, singly, doubly and triply indirect indexes") - { - REQUIRE(fs.map_inode_block_index_to_global_block_number(0, inode_data) == 7); - REQUIRE(fs.map_inode_block_index_to_global_block_number(singly_start, inode_data) == 31); - REQUIRE(fs.map_inode_block_index_to_global_block_number(doubly_start, inode_data) == 42); - REQUIRE(fs.map_inode_block_index_to_global_block_number(triply_start, inode_data) == 53); - } - - THEN("mapping returns zero for out-of-range indexes") - { - auto const beyond_triply = triply_start + numbers_per_block * numbers_per_block * numbers_per_block; - REQUIRE(fs.map_inode_block_index_to_global_block_number(beyond_triply, inode_data) == -1); - } - } -} diff --git a/kernel/src/filesystem/ext2/inode.cpp b/kernel/src/filesystem/ext2/inode.cpp deleted file mode 100644 index 35a32ee4..00000000 --- a/kernel/src/filesystem/ext2/inode.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include - -#include -#include - -#include - -#include - -#include -#include -#include - -namespace kernel::filesystem::ext2 -{ - inode::inode(filesystem const * fs, inode_data const & data) - : m_filesystem(fs) - , m_data(data) - { - if (!m_filesystem) - { - kapi::system::panic("[EXT2] ext2::inode constructed with filesystem null pointer"); - } - } - - auto inode::read(void * buffer, size_t offset, size_t size) const -> size_t - { - auto const max_readable = this->size() - offset; - auto const requested_size = std::min(size, max_readable); - - if (is_symbolic_link() && this->size() <= sizeof(m_data.block)) - { - auto inline_target = reinterpret_cast(m_data.block.data()); - kstd::libc::memcpy(static_cast(buffer), inline_target + offset, requested_size); - return requested_size; - } - - auto block_index = offset / m_filesystem->block_size(); - auto in_block_offset = offset % m_filesystem->block_size(); - - auto bytes_read = 0uz; - - while (bytes_read < requested_size) - { - auto const block_number = m_filesystem->map_inode_block_index_to_global_block_number(block_index, m_data); - if (block_number == -1) - { - break; - } - - auto const bytes_to_read = std::min(requested_size - bytes_read, m_filesystem->block_size() - in_block_offset); - if (block_number == 0) - { - kstd::libc::memset(static_cast(buffer) + bytes_read, 0, bytes_to_read); - bytes_read += bytes_to_read; - } - else - { - auto const block_start_offset = block_number * m_filesystem->block_size(); - auto const read_offset = block_start_offset + in_block_offset; - - bytes_read += m_filesystem->backing_inode()->read(static_cast(buffer) + bytes_read, read_offset, - bytes_to_read); - } - - block_index++; - in_block_offset = 0; // After the first block, we always start at the beginning of the block - } - - return bytes_read; - } - - auto inode::write(void const *, size_t, size_t) -> size_t - { - kapi::system::panic("[EXT2] inode::write is not implemented yet"); - return 0; - } - - [[nodiscard]] auto inode::data() const -> inode_data const & - { - return m_data; - } - - auto inode::is_regular() const -> bool - { - return (m_data.mode & constants::mode_mask) == constants::mode_regular; - } - - auto inode::is_directory() const -> bool - { - return (m_data.mode & constants::mode_mask) == constants::mode_directory; - } - - auto inode::is_symbolic_link() const -> bool - { - return (m_data.mode & constants::mode_mask) == constants::mode_symbolic_link; - } - - auto inode::size() const -> uint64_t - { - uint64_t size = m_data.size; - - if (m_filesystem->revision_level() > constants::good_old_revision && is_regular()) - { - size |= static_cast(m_data.dir_acl) << 32; - } - - return size; - } - -} // namespace kernel::filesystem::ext2 diff --git a/kernel/src/filesystem/ext2/inode.tests.cpp b/kernel/src/filesystem/ext2/inode.tests.cpp deleted file mode 100644 index 4aecc04a..00000000 --- a/kernel/src/filesystem/ext2/inode.tests.cpp +++ /dev/null @@ -1,376 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include - -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(&fs, 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(&fs, 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(&fs, 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(&fs, data); - - REQUIRE_FALSE(inode.is_regular()); - REQUIRE_FALSE(inode.is_directory()); - REQUIRE_FALSE(inode.is_device()); - REQUIRE_FALSE(inode.is_symbolic_link()); - } - } - - GIVEN("no filesystem (null pointer)") - { - THEN("constructing an inode with a null filesystem pointer panics") - { - REQUIRE_THROWS_AS(kernel::filesystem::ext2::inode(nullptr, {}), kernel::tests::cpu::halt); - } - } -} - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_fixture, "Ext2 inode reads from real image", - "[filesystem][ext2][inode][img]") -{ - auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "ext2_1KB_fs.img"; - - GIVEN("a mounted ext2 filesystem and a regular file inode") - { - REQUIRE(std::filesystem::exists(image_path)); - REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path})); - - auto boot_device = kernel::devices::storage::management::get().determine_boot_device(); - REQUIRE(boot_device != nullptr); - - auto dev_inode = kstd::make_shared(boot_device); - - auto fs = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); - - auto information = fs.lookup(fs.root_inode(), "information"); - REQUIRE(information != nullptr); - auto file = fs.lookup(information, "info_1.txt"); - REQUIRE(file != nullptr); - REQUIRE(file->is_regular()); - - THEN("reading from offset zero returns expected file prefix") - { - auto buffer = kstd::vector(6); - auto const bytes_read = file->read(buffer.data(), 0, buffer.size()); - - REQUIRE(bytes_read == 6); - - auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; - REQUIRE(text == "info_1"); - } - - THEN("reading with an offset returns the expected byte") - { - auto buffer = kstd::vector(1); - auto const bytes_read = file->read(buffer.data(), 5, buffer.size()); - - REQUIRE(bytes_read == 1); - REQUIRE(static_cast(buffer[0]) == '1'); - } - } -} - -SCENARIO("Ext2 inode handles zeros in block mappings as file holes", "[filesystem][ext2][inode]") -{ - auto const block_size = 1024uz; - GIVEN("an ext2 inode with only direct mapped data blocks") - { - auto device = kstd::make_shared(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); - - auto data = kernel::filesystem::ext2::inode_data{}; - data.block[0] = 30; - data.block[1] = 0; - data.block[2] = 31; - data.size = block_size * 3; - - kernel::tests::filesystem::ext2::write_bytes(*device, 30 * block_size, "Hello", 5); - kernel::tests::filesystem::ext2::write_bytes(*device, 31 * block_size, "World!", 6); - - auto inode = kernel::filesystem::ext2::inode{&fs, data}; - - 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.data(), 0, buffer.size()); - REQUIRE(bytes_read == data.size); - - auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; - REQUIRE(text.substr(0, 5) == "Hello"); - REQUIRE(std::ranges::all_of(text.substr(5, block_size - 5), [](char c) { return c == '\0'; })); - REQUIRE(text.substr(2 * block_size, 6) == "World!"); - REQUIRE(std::ranges::all_of(text.substr(2 * block_size + 6, 3 * block_size), [](char c) { return c == '\0'; })); - } - } - - GIVEN("an ext2 indode with file holes in singly indirect blocks") - { - auto device = kstd::make_shared(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); - - auto data = kernel::filesystem::ext2::inode_data{}; - data.block[0] = 30; - data.block[12] = 31; - data.size = block_size * 15; - - kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size, 50); - kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size + 4, 0); - kernel::tests::filesystem::ext2::write_u32(*device, 31 * block_size + 8, 51); - - kernel::tests::filesystem::ext2::write_bytes(*device, 30 * block_size, "Hello", 5); - kernel::tests::filesystem::ext2::write_bytes(*device, 50 * block_size, "Blub", 4); - kernel::tests::filesystem::ext2::write_bytes(*device, 51 * block_size, "World!", 6); - - auto inode = kernel::filesystem::ext2::inode{&fs, data}; - - 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.data(), 0, buffer.size()); - REQUIRE(bytes_read == data.size); - - auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; - REQUIRE(text.substr(0, 5) == "Hello"); - REQUIRE(std::ranges::all_of(text.substr(5, 12 * block_size - 5), [](char c) { return c == '\0'; })); - REQUIRE(text.substr(12 * block_size, 4) == "Blub"); - REQUIRE( - std::ranges::all_of(text.substr(12 * block_size + 4, 2 * block_size - 4), [](char c) { return c == '\0'; })); - REQUIRE(text.substr(14 * block_size, 6) == "World!"); - REQUIRE( - std::ranges::all_of(text.substr(14 * block_size + 6, 1 * block_size - 6), [](char c) { return c == '\0'; })); - } - } - - GIVEN("an ext2 inode with zero singly indirect block pointer") - { - auto device = kstd::make_shared(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); - - auto data = kernel::filesystem::ext2::inode_data{}; - data.block[12] = 0; - data.size = block_size * 15; - - auto inode = kernel::filesystem::ext2::inode{&fs, data}; - - auto buffer = kstd::vector(block_size * 15, std::byte{0xAB}); - - THEN("all direct blocks are zero when singly indirect block pointer is zero") - { - auto const bytes_read = inode.read(buffer.data(), 0, buffer.size()); - REQUIRE(bytes_read == 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 = 1024uz; - GIVEN("an ext2 inode with two direct blocks and a block size of 1024 bytes") - { - auto device = kstd::make_shared(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); - - auto inode_data = kernel::filesystem::ext2::inode_data{}; - inode_data.size = block_size * 2; - inode_data.block[0] = 20; - kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size - 6, "Hello ", 6); - inode_data.block[1] = 21; - kernel::tests::filesystem::ext2::write_bytes(*device, 21 * block_size, "World!", 6); - auto inode = kernel::filesystem::ext2::inode{&fs, inode_data}; - - 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.data(), block_size - 6, buffer.size()); - REQUIRE(bytes_read == 12); - - auto const text = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; - REQUIRE(text == "Hello World!"); - } - } -} - -SCENARIO("Ext2 inode write is not implemented", "[filesystem][ext2][inode]") -{ - GIVEN("an ext2 inode") - { - auto fs = kernel::filesystem::ext2::filesystem{}; - auto inode = kernel::filesystem::ext2::inode{&fs, kernel::filesystem::ext2::inode_data{}}; - - THEN("writing to the inode panics") - { - auto buffer = kstd::vector(32, std::byte{0x00}); - REQUIRE_THROWS_AS(inode.write(buffer.data(), 0, buffer.size()), kernel::tests::cpu::halt); - } - } -} - -SCENARIO("Ext2 inode get_size() correctly returns size depending on revision level", "[filesystem][ext2][inode]") -{ - auto const block_size = 1024uz; - - 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(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); - - auto data = kernel::filesystem::ext2::inode_data{}; - data.size = 256; - data.dir_acl = 32; - - THEN("the inode size is 256 if mode = regular") - { - data.mode = kernel::filesystem::ext2::constants::mode_regular; - - auto inode = kernel::filesystem::ext2::inode{&fs, data}; - - REQUIRE(inode.size() == 256); - } - - THEN("the inode size is 256 if mode = directory") - { - data.mode = kernel::filesystem::ext2::constants::mode_directory; - - auto inode = kernel::filesystem::ext2::inode{&fs, data}; - - REQUIRE(inode.size() == 256); - } - } - - 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(0, 0, "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 = kernel::filesystem::ext2::filesystem{}; - REQUIRE(fs.mount(dev_inode) == kernel::filesystem::filesystem::operation_result::success); - - auto data = kernel::filesystem::ext2::inode_data{}; - data.size = 256; - data.dir_acl = 32; - - THEN("the inode size is 256 if mode = regular") - { - data.mode = kernel::filesystem::ext2::constants::mode_regular; - - auto inode = kernel::filesystem::ext2::inode{&fs, data}; - - REQUIRE(inode.size() == 0x0000'0020'0000'0100); - } - - THEN("the inode size is 256 if mode = directory") - { - data.mode = kernel::filesystem::ext2::constants::mode_directory; - - auto inode = kernel::filesystem::ext2::inode{&fs, data}; - - REQUIRE(inode.size() == 256); - } - } -} \ No newline at end of file diff --git a/kernel/src/filesystem/filesystem.cpp b/kernel/src/filesystem/filesystem.cpp deleted file mode 100644 index 24d0e229..00000000 --- a/kernel/src/filesystem/filesystem.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include - -#include -#include - -#include - -#include - -#include - -namespace kernel::filesystem -{ - namespace - { - constexpr auto static filesystem_factories = std::array{ - []() { return kstd::make_shared(); }, - }; - } // namespace - - auto filesystem::probe_and_mount(kstd::shared_ptr const & backing_inode) -> kstd::shared_ptr - { - if (!backing_inode) - { - kapi::system::panic("[FILESYSTEM] cannot mount filesystem: backing inode is null."); - } - - for (auto & factory : filesystem_factories) - { - auto fs = factory(); - if (fs->mount(backing_inode) == operation_result::success) - { - return fs; - } - } - - return nullptr; - } - - auto filesystem::mount(kstd::shared_ptr const & backing_inode) -> operation_result - { - if (!backing_inode) - { - kapi::system::panic("[FILESYSTEM] cannot mount filesystem: backing inode is null."); - } - - m_backing_inode = backing_inode; - return operation_result::success; - } - - auto filesystem::root_inode() const -> kstd::shared_ptr const & - { - return m_root_inode; - } - - auto filesystem::backing_inode() const -> kstd::shared_ptr const & - { - return m_backing_inode; - } -} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/src/filesystem/inode.cpp b/kernel/src/filesystem/inode.cpp deleted file mode 100644 index c1889179..00000000 --- a/kernel/src/filesystem/inode.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include - -namespace kernel::filesystem -{ - auto inode::is_directory() const -> bool - { - return false; - } - - auto inode::is_regular() const -> bool - { - return false; - } - - auto inode::is_device() const -> bool - { - return false; - } - - auto inode::is_symbolic_link() const -> bool - { - return false; - } -} // namespace kernel::filesystem \ No newline at end of file diff --git a/kernel/src/filesystem/mount.cpp b/kernel/src/filesystem/mount.cpp deleted file mode 100644 index ead7479d..00000000 --- a/kernel/src/filesystem/mount.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include - -#include -#include - -namespace kernel::filesystem -{ - mount::mount(kstd::shared_ptr const & mount_dentry, kstd::shared_ptr const & root_dentry, - kstd::shared_ptr const & fs, kstd::shared_ptr const & parent_mount, - kstd::shared_ptr const & source_mount) - : m_mount_dentry(mount_dentry) - , m_root_dentry(root_dentry) - , m_filesystem(fs) - , m_parent_mount(parent_mount) - , m_source_mount(source_mount) - , m_ref_count(0) - { - if (!m_filesystem) - { - kapi::system::panic("[FILESYSTEM] mount initialized with null filesystem."); - } - } - - auto mount::mount_dentry() const -> kstd::shared_ptr const & - { - return m_mount_dentry; - } - - auto mount::get_filesystem() const -> kstd::shared_ptr const & - { - return m_filesystem; - } - - auto mount::root_dentry() const -> kstd::shared_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 -> kstd::shared_ptr const & - { - return m_parent_mount; - } - - auto mount::source_mount() const -> kstd::shared_ptr - { - return m_source_mount.lock(); - } - - 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/src/filesystem/mount.tests.cpp b/kernel/src/filesystem/mount.tests.cpp deleted file mode 100644 index c9ff82e9..00000000 --- a/kernel/src/filesystem/mount.tests.cpp +++ /dev/null @@ -1,94 +0,0 @@ -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include - -SCENARIO("Mount construction", "[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, "/"); - - WHEN("constructing a mount with the filesystem and root dentry") - { - auto mount = kernel::filesystem::mount{root_dentry, root_dentry, fs, nullptr, nullptr}; - - THEN("the mount has the correct filesystem, root dentry, mount dentry, and mount path") - { - REQUIRE(mount.get_filesystem() == fs); - REQUIRE(mount.root_dentry() == root_dentry); - REQUIRE(mount.mount_dentry() == root_dentry); - 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{root_dentry, root_dentry, 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{root_dentry, root_dentry, fs, nullptr, nullptr}; - - 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{root_dentry, root_dentry, fs, 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/src/filesystem/mount_table.cpp b/kernel/src/filesystem/mount_table.cpp deleted file mode 100644 index e4baac7a..00000000 --- a/kernel/src/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/src/filesystem/mount_table.tests.cpp b/kernel/src/filesystem/mount_table.tests.cpp deleted file mode 100644 index 19b47b23..00000000 --- a/kernel/src/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 = kstd::make_shared(mount_dentry1, root_dentry1, fs1, 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 = kstd::make_shared(mount_dentry2, root_dentry2, fs2, 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 = kstd::make_shared(mount_dentry1, root_dentry1, fs1, 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 = kstd::make_shared(mount_dentry2, root_dentry2, fs2, 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 = kstd::make_shared(mount_dentry1, root_dentry1, fs1, 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 = kstd::make_shared(mount_dentry2, root_dentry2, fs2, mount1, 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 = kstd::make_shared(mount_dentry3, root_dentry3, fs3, mount2, 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 = kstd::make_shared(root_dentry, root_dentry, fs, nullptr, nullptr); - auto mount = kstd::make_shared(root_dentry, root_dentry, fs, nullptr, source_mount); - - 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/src/filesystem/open_file_descriptor.cpp b/kernel/src/filesystem/open_file_descriptor.cpp deleted file mode 100644 index a5567bf4..00000000 --- a/kernel/src/filesystem/open_file_descriptor.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include - -#include - -#include -#include - -#include - -namespace kernel::filesystem -{ - open_file_descriptor::open_file_descriptor(kstd::shared_ptr const & dentry) - : m_dentry(dentry) - , m_offset(0) - { - if (!dentry) - { - kstd::os::panic("[FILESYSTEM] open_file_descriptor constructed with null dentry."); - } - } - - auto open_file_descriptor::read(void * buffer, size_t size) -> size_t - { - auto read_bytes = m_dentry->get_inode()->read(buffer, m_offset, size); - m_offset += read_bytes; - return read_bytes; - } - - auto open_file_descriptor::write(void const * buffer, size_t size) -> size_t - { - auto written_bytes = m_dentry->get_inode()->write(buffer, m_offset, size); - m_offset += written_bytes; - return written_bytes; - } - - auto open_file_descriptor::offset() const -> size_t - { - return m_offset; - } - - 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/src/filesystem/open_file_descriptor.tests.cpp b/kernel/src/filesystem/open_file_descriptor.tests.cpp deleted file mode 100644 index 8c24cf0e..00000000 --- a/kernel/src/filesystem/open_file_descriptor.tests.cpp +++ /dev/null @@ -1,113 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - -#include -#include -#include - -SCENARIO("Open file descriptor construction", "[filesystem][open_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::open_file_descriptor{dentry}; - - THEN("the initial offset is zero") - { - REQUIRE(file_descriptor.offset() == 0); - } - } -} - -SCENARIO("Open file descriptor read/write offset management", "[filesystem][open_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::open_file_descriptor{dentry}; - - THEN("the offset is updated correctly after reads") - { - REQUIRE(file_descriptor.read(nullptr, 100) == 100); - REQUIRE(file_descriptor.offset() == 100); - REQUIRE(file_descriptor.read(nullptr, 50) == 50); - REQUIRE(file_descriptor.offset() == 150); - } - - THEN("the offset is updated correctly after writes") - { - REQUIRE(file_descriptor.write(nullptr, 200) == 200); - REQUIRE(file_descriptor.offset() == 200); - REQUIRE(file_descriptor.write(nullptr, 25) == 25); - REQUIRE(file_descriptor.offset() == 225); - } - - THEN("reads and writes both update the same offset") - { - REQUIRE(file_descriptor.read(nullptr, 10) == 10); - REQUIRE(file_descriptor.offset() == 10); - REQUIRE(file_descriptor.write(nullptr, 20) == 20); - REQUIRE(file_descriptor.offset() == 30); - REQUIRE(file_descriptor.read(nullptr, 5) == 5); - REQUIRE(file_descriptor.offset() == 35); - REQUIRE(file_descriptor.write(nullptr, 15) == 15); - REQUIRE(file_descriptor.offset() == 50); - } - } -} - -SCENARIO_METHOD(kernel::tests::filesystem::storage_boot_module_vfs_fixture, "Open file descriptor read with real image", - "[filesystem][open_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 != nullptr); - auto ofd = kstd::make_shared(dentry); - - THEN("the file can be read and the offset is updated") - { - kstd::vector buffer(32); - auto bytes_read = ofd->read(buffer.data(), buffer.size()); - REQUIRE(bytes_read == 7); - REQUIRE(ofd->offset() == 7); - - std::string_view buffer_as_str{reinterpret_cast(buffer.data()), static_cast(bytes_read)}; - REQUIRE(buffer_as_str == "info_1\n"); - } - - THEN("the file can be read multiple times") - { - kstd::vector buffer(4); - auto bytes_read_1 = ofd->read(buffer.data(), buffer.size() / 2); - REQUIRE(bytes_read_1 == buffer.size() / 2); - REQUIRE(ofd->offset() == buffer.size() / 2); - - auto bytes_read_2 = ofd->read(buffer.data() + buffer.size() / 2, buffer.size() / 2); - REQUIRE(bytes_read_2 == buffer.size() / 2); - REQUIRE(ofd->offset() == buffer.size()); - - std::string_view buffer_as_str{reinterpret_cast(buffer.data()), bytes_read_1 + bytes_read_2}; - REQUIRE(buffer_as_str == "info"); - } - } -} diff --git a/kernel/src/filesystem/open_file_table.cpp b/kernel/src/filesystem/open_file_table.cpp deleted file mode 100644 index 2afe3aa4..00000000 --- a/kernel/src/filesystem/open_file_table.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#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::ssize_t - { - if (!file_descriptor) - { - return -1; - } - - 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::shared_ptr - { - if (fd >= m_open_files.size()) - { - return nullptr; - } - - return m_open_files.at(fd); - } - - auto open_file_table::remove_file(size_t fd) -> kstd::ssize_t - { - if (fd >= m_open_files.size()) - { - return -1; - } - - m_open_files.at(fd) = nullptr; - return 0; - } -} // 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/src/filesystem/open_file_table.tests.cpp b/kernel/src/filesystem/open_file_table.tests.cpp deleted file mode 100644 index 3e91111d..00000000 --- a/kernel/src/filesystem/open_file_table.tests.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#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 code") - { - auto fd = table.add_file(nullptr); - REQUIRE(fd == -1); - } - - THEN("retrieving a file descriptor with an out-of-bounds file descriptor returns a null pointer") - { - auto retrieved_descriptor = table.file(1000); - REQUIRE(retrieved_descriptor == nullptr); - } - } -} - -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") - { - 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 == nullptr); - } - } - - WHEN("removing a file descriptor the other file descriptor keep the same index") - { - auto fd2 = table.add_file(file_descriptor); - 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/src/filesystem/path.tests.cpp b/kernel/src/filesystem/path.tests.cpp deleted file mode 100644 index 3c18b5c2..00000000 --- a/kernel/src/filesystem/path.tests.cpp +++ /dev/null @@ -1,69 +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)); - } - } -} \ No newline at end of file diff --git a/kernel/src/filesystem/rootfs/filesystem.cpp b/kernel/src/filesystem/rootfs/filesystem.cpp deleted file mode 100644 index 7fe5c1ea..00000000 --- a/kernel/src/filesystem/rootfs/filesystem.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include - -#include -#include -#include -#include - -#include - -#include - -namespace kernel::filesystem::rootfs -{ - - struct type final : kernel::filesystem::type - { - [[nodiscard]] auto name() const noexcept -> std::string_view override - { - return "rootfs"; - } - - [[nodiscard]] auto requires_device() const noexcept -> bool override - { - return true; - } - - [[nodiscard]] auto make_instance() const -> kstd::shared_ptr override - { - return kstd::make_shared(); - } - }; - - [[gnu::used]] - constexpr auto registration = type_registration{}; - - auto filesystem::mount(kstd::shared_ptr const &) -> operation_result - { - m_root_inode = kstd::make_shared(); - return operation_result::success; - } - - auto filesystem::lookup(kstd::shared_ptr const &, std::string_view) const - -> kstd::shared_ptr - { - return nullptr; - } -} // namespace kernel::filesystem::rootfs diff --git a/kernel/src/filesystem/rootfs/filesystem.tests.cpp b/kernel/src/filesystem/rootfs/filesystem.tests.cpp deleted file mode 100644 index ae320e95..00000000 --- a/kernel/src/filesystem/rootfs/filesystem.tests.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include - -#include - -#include -#include -#include - -#include - -SCENARIO("Rootfs filesystem mount and lookup", "[filesystem][rootfs][filesystem]") -{ - GIVEN("a mounted rootfs filesystem") - { - auto fs = kernel::filesystem::rootfs::filesystem{}; - auto result = fs.mount(nullptr); - - THEN("the filesystem can be mounted successfully") - { - REQUIRE(result == kernel::filesystem::filesystem::operation_result::success); - REQUIRE(fs.root_inode() != nullptr); - } - - THEN("looking up a non-existent directory returns null") - { - auto non_existent_inode_1 = fs.lookup(fs.root_inode(), ""); - REQUIRE(non_existent_inode_1 == nullptr); - auto non_existent_inode_2 = fs.lookup(fs.root_inode(), "nonexistent"); - REQUIRE(non_existent_inode_2 == nullptr); - } - - THEN("looking up with a null parent inode returns null") - { - auto result = fs.lookup(nullptr, "dev"); - REQUIRE(result == nullptr); - } - } -} diff --git a/kernel/src/filesystem/rootfs/inode.cpp b/kernel/src/filesystem/rootfs/inode.cpp deleted file mode 100644 index f64fb87c..00000000 --- a/kernel/src/filesystem/rootfs/inode.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - -#include - -#include - -namespace kernel::filesystem::rootfs -{ - auto inode::read(void *, size_t, size_t) const -> size_t - { - return 0; - } - - auto inode::write(void const *, size_t, size_t) -> size_t - { - return 0; - } - - auto inode::is_directory() const -> bool - { - return true; - } -} // namespace kernel::filesystem::rootfs diff --git a/kernel/src/filesystem/rootfs/inode.tests.cpp b/kernel/src/filesystem/rootfs/inode.tests.cpp deleted file mode 100644 index f4b634fa..00000000 --- a/kernel/src/filesystem/rootfs/inode.tests.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include - -#include -#include -#include - -#include - -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.data(), 0, buffer.size()); - - THEN("no bytes are read") - { - REQUIRE(bytes_read == 0); - } - } - - WHEN("writing to the inode") - { - kstd::vector buffer(10, 'x'); - auto bytes_written = inode.write(buffer.data(), 0, buffer.size()); - - THEN("no bytes are written") - { - REQUIRE(bytes_written == 0); - } - } - } -} diff --git a/kernel/src/filesystem/type_registry.cpp b/kernel/src/filesystem/type_registry.cpp deleted file mode 100644 index 74b12090..00000000 --- a/kernel/src/filesystem/type_registry.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#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 type_registry::pointer const __start_fs_types; - extern type_registry::pointer const __stop_fs_types; - // NOLINTEND(readability-identifier-naming) - } - - namespace - { - auto constinit instance = std::optional{}; - } - - auto type_registry::init() -> void - { - if (instance) - { - kapi::system::panic("[FILESYSTEM] tried to initialize type registry more than once!"); - } - - instance.emplace(); - - auto type_descriptors = std::span{&__start_fs_types, &__stop_fs_types} | // - std::views::filter([](auto p) { return p != nullptr; }); - - std::ranges::for_each(type_descriptors, [](auto descriptor) { - kstd::println("[FILESYSTEM] registering '{}'", descriptor->name()); - instance->add(descriptor); - }); - } - - auto type_registry::get() -> type_registry & - { - if (!instance) - { - kapi::system::panic("[FILESYSTEM] type registry has not been initialized!"); - } - - return *instance; - } - - auto type_registry::add(pointer descriptor) -> bool - { - auto result = m_descriptors.emplace(descriptor->name(), descriptor); - return result.second; - } - - auto type_registry::all() const noexcept -> std::span - { - return {m_descriptors.values().begin(), m_descriptors.values().end()}; - } - - auto type_registry::size() const noexcept -> std::size_t - { - return m_descriptors.size(); - } - -} // namespace kernel::filesystem diff --git a/kernel/src/filesystem/type_registry.tests.cpp b/kernel/src/filesystem/type_registry.tests.cpp deleted file mode 100644 index 8382579f..00000000 --- a/kernel/src/filesystem/type_registry.tests.cpp +++ /dev/null @@ -1,77 +0,0 @@ -#include - -#include -#include - -#include - -#include - -#include -#include - -struct test_type final : kernel::filesystem::type -{ - [[nodiscard]] auto name() const noexcept -> std::string_view override - { - return "bht_testfs"; - } - - [[nodiscard]] auto requires_device() const noexcept -> bool override - { - return false; - } - - [[nodiscard]] auto make_instance() const -> kstd::shared_ptr override - { - return nullptr; - } -}; - -SCENARIO("Filesystem type registry initialization and construction", "[filesystem]") -{ - GIVEN("A default constructed type_registry") - { - auto instance = kernel::filesystem::type_registry{}; - - WHEN("getting the span of filesystem descriptors") - { - auto descriptors = instance.all(); - - THEN("the span is empty") - { - REQUIRE(descriptors.empty()); - } - } - } -} - -SCENARIO("Filesystem type registry modifiers", "[filesystem]") -{ - GIVEN("A default constructed type_registry") - { - auto instance = kernel::filesystem::type_registry{}; - - WHEN("adding a type descriptor") - { - auto descriptor = test_type{}; - - instance.add(kstd::make_observer(&descriptor)); - - THEN("the size of the registry is one") - { - REQUIRE(instance.size() == 1); - } - - THEN("the span is not empty") - { - REQUIRE_FALSE(instance.all().empty()); - } - - THEN("the span's size is equal to the registry size") - { - REQUIRE(std::size(instance.all()) == std::size(instance)); - } - } - } -} diff --git a/kernel/src/filesystem/vfs.cpp b/kernel/src/filesystem/vfs.cpp deleted file mode 100644 index e5dff8c4..00000000 --- a/kernel/src/filesystem/vfs.cpp +++ /dev/null @@ -1,299 +0,0 @@ -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include -#include - -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 root_fs = kstd::make_shared(); - root_fs->mount(nullptr); - - auto root_fs_root_dentry = kstd::make_shared(nullptr, root_fs->root_inode(), "/"); - auto root_mount = kstd::make_shared(nullptr, root_fs_root_dentry, root_fs, nullptr, nullptr); - m_mount_table.add_mount(root_mount); - - // mount devfs at /dev (inside rootfs, temporary, will be shadowed) - auto device_fs = kstd::make_shared(); - device_fs->mount(nullptr); - graft_persistent_device_fs(device_fs); - - // mount boot fs at / (shadows rootfs), re-graft devfs - auto [boot_device_dentry, boot_device_mount_context] = resolve_path_internal("/dev/ram0"); - if (boot_device_dentry && boot_device_mount_context) - { - if (auto boot_root_fs = kernel::filesystem::filesystem::probe_and_mount(boot_device_dentry->get_inode())) - { - if (auto root_dentry = resolve_path("/")) - { - do_mount_internal(root_dentry, root_mount, boot_root_fs, boot_device_mount_context); - graft_persistent_device_fs(device_fs); - } - } - } - } - - 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::shared_ptr - { - auto [dentry, mount] = resolve_path_internal(path); - if (!dentry || !mount) - { - return nullptr; - } - mount->increment_ref_count(); - return dentry; - } - - auto vfs::close(std::string_view path) -> operation_result - { - if (auto mount = find_mount(path)) - { - mount->decrement_ref_count(); - return operation_result::success; - } - return operation_result::invalid_path; - } - - auto vfs::do_mount(std::string_view source, std::string_view target) -> operation_result - { - if (!path::is_valid_path(source) || !path::is_valid_path(target)) - { - return operation_result::invalid_path; - } - - auto [mount_point_dentry, mount_context] = resolve_path_internal(target); - if (mount_point_dentry && mount_context) - { - auto [source_dentry, source_mount_context] = resolve_path_internal(source); - if (source_dentry && source_mount_context) - { - if (auto fs = kernel::filesystem::filesystem::probe_and_mount(source_dentry->get_inode())) - { - do_mount_internal(mount_point_dentry, mount_context, fs, source_mount_context); - return operation_result::success; - } - return operation_result::invalid_filesystem; - } - return operation_result::non_existent_path; - } - return operation_result::mount_point_not_found; - } - - auto vfs::unmount(std::string_view path) -> operation_result - { - if (!path::is_valid_path(path)) - { - return operation_result::invalid_path; - } - - auto remove_result = m_mount_table.remove_mount(path); - if (remove_result == mount_table::operation_result::removed) - { - return operation_result::success; - } - else if (remove_result == mount_table::operation_result::mount_not_found) - { - return operation_result::mount_point_not_found; - } - - return operation_result::unmount_failed; - } - - auto vfs::do_mount_internal(kstd::shared_ptr const & mount_point_dentry, - kstd::shared_ptr const & parent_mount, kstd::shared_ptr const & fs, - kstd::shared_ptr const & source_mount) -> void - { - auto new_fs_root = - kstd::make_shared(mount_point_dentry->parent(), fs->root_inode(), mount_point_dentry->name()); - auto new_mount = kstd::make_shared(mount_point_dentry, new_fs_root, fs, parent_mount, source_mount); - m_mount_table.add_mount(new_mount); - } - - auto vfs::graft_persistent_device_fs(kstd::shared_ptr const & device_fs) -> void - { - auto [root_mount_point_dentry, root_mount] = resolve_path_internal("/"); - 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, device_fs->root_inode(), "dev"); - root_mount_point_dentry->add_child(dev_dentry); - } - - do_mount_internal(dev_dentry, root_mount, device_fs); - } - } - - auto vfs::resolve_path_internal(std::string_view path) const - -> std::pair, kstd::shared_ptr> - { - if (!path::is_valid_absolute_path(path)) - { - return {nullptr, nullptr}; - } - - 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 (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->get_filesystem(); - auto found_inode = current_fs->lookup(current_dentry->get_inode(), part); - if (!found_inode) - { - return {nullptr, nullptr}; - } - - 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->get_inode()->is_symbolic_link()) - { - if (symlink_counter++ > constants::symloop_max) - { - return {nullptr, nullptr}; - } - - kstd::vector buffer(constants::symlink_max_path_length); - auto const bytes_read = next_dentry->get_inode()->read(buffer.data(), 0, buffer.size()); - auto const symbolic_link_path = std::string_view{reinterpret_cast(buffer.data()), bytes_read}; - - 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 {current_dentry, current_mount}; - } - - auto vfs::resolve_path(std::string_view path) const -> kstd::shared_ptr - { - return resolve_path_internal(path).first; - } - - auto vfs::find_mount(std::string_view path) const -> kstd::shared_ptr - { - return resolve_path_internal(path).second; - } - -} // namespace kernel::filesystem - -namespace kernel::tests::filesystem::vfs -{ - auto deinit() -> void - { - active_vfs.reset(); - } -} // namespace kernel::tests::filesystem::vfs diff --git a/kernel/src/filesystem/vfs.tests.cpp b/kernel/src/filesystem/vfs.tests.cpp deleted file mode 100644 index f1d0df0d..00000000 --- a/kernel/src/filesystem/vfs.tests.cpp +++ /dev/null @@ -1,568 +0,0 @@ -#include - -#include -#include - -#include -#include - -#include - -#include -#include -#include -#include - -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 == nullptr); - - 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.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); - - auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); - REQUIRE(mounted_monkey_1 != nullptr); - REQUIRE(vfs.close(mounted_monkey_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - - REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); - auto unmounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); - REQUIRE(unmounted_monkey_1 == nullptr); - - auto info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1 != nullptr); - } - - THEN("third image can be mounted in a mounted file system, unmount only if no child mount exists") - { - REQUIRE(vfs.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.do_mount("/dev/ram32", "/information/monkey_house/infrastructure") == - kernel::filesystem::vfs::operation_result::success); - - auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); - auto mounted_fish1 = vfs.open("/information/monkey_house/infrastructure/enclosures/aquarium/tank_1/fish_1.txt"); - - REQUIRE(mounted_monkey_1 != nullptr); - REQUIRE(mounted_fish1 != nullptr); - - REQUIRE(vfs.close(mounted_monkey_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.close(mounted_fish1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - - REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::unmount_failed); - REQUIRE(vfs.unmount("/information/monkey_house/infrastructure") == - kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); - } - - THEN("image can be mounted, unmount only if no files are open") - { - REQUIRE(vfs.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); - - auto mounted_monkey_1 = vfs.open("/information/monkey_house/monkey_1.txt"); - REQUIRE(mounted_monkey_1 != nullptr); - - REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::unmount_failed); - - REQUIRE(vfs.close(mounted_monkey_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - - REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); - } - - THEN("file with invalid path or not opened file cannot be closed") - { - REQUIRE(vfs.close("invalid_path") == kernel::filesystem::vfs::operation_result::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 != nullptr); - - REQUIRE(vfs.close(info_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - REQUIRE_THROWS_AS(vfs.close(info_1->absolute_path()), std::runtime_error); - } - - THEN("images can be stacked mounted and correct file system is unmounted again") - { - REQUIRE(vfs.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.do_mount("/dev/ram32", "/information") == kernel::filesystem::vfs::operation_result::success); - - auto mounted_tickets = vfs.open("/information/entrance/tickets.txt"); - REQUIRE(mounted_tickets != nullptr); - - REQUIRE(vfs.close(mounted_tickets->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - - REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); - mounted_tickets = vfs.open("/information/entrance/tickets.txt"); - REQUIRE(mounted_tickets == nullptr); - - auto mounted_monkey = vfs.open("/information/monkey_house/monkey_1.txt"); - REQUIRE(mounted_monkey != nullptr); - } - - THEN("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.do_mount("/dev/ram16", "/") == kernel::filesystem::vfs::operation_result::success); - - info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1 == nullptr); - - auto water = vfs.open("/monkey_house/infrastructure/water.txt"); - REQUIRE(water != nullptr); - - REQUIRE(vfs.close(water->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - - REQUIRE(vfs.unmount("/") == kernel::filesystem::vfs::operation_result::success); - - 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.do_mount("/dev/ram16", "/") == kernel::filesystem::vfs::operation_result::success); - - info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1 == nullptr); - - auto water = vfs.open("/monkey_house/infrastructure/water.txt"); - REQUIRE(water != nullptr); - - REQUIRE(vfs.close(water->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - - auto dev_ram_16 = vfs.open("/dev/ram16"); - REQUIRE(dev_ram_16 == nullptr); - - REQUIRE(vfs.do_mount("/dev/ram32", "/") == kernel::filesystem::vfs::operation_result::non_existent_path); - - REQUIRE(vfs.unmount("/") == kernel::filesystem::vfs::operation_result::success); - - 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->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - - REQUIRE(vfs.unmount("/dev") == kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.unmount("/") == kernel::filesystem::vfs::operation_result::success); - - info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1 == nullptr); - - REQUIRE(vfs.do_mount("/dev/ram0", "/") == kernel::filesystem::vfs::operation_result::success); - - info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1 != nullptr); - - auto dev_ram_0 = vfs.open("/dev/ram0"); - REQUIRE(dev_ram_0 == nullptr); - } - - THEN("mount with null file system fails") - { - REQUIRE(vfs.do_mount("/closed.txt", "/information") == - kernel::filesystem::vfs::operation_result::invalid_filesystem); - } - - THEN("mount with invalid path fails") - { - REQUIRE(vfs.do_mount("/dev/ram16", "") == kernel::filesystem::vfs::operation_result::invalid_path); - REQUIRE(vfs.do_mount("/dev/ram16", "information") == - kernel::filesystem::vfs::operation_result::mount_point_not_found); - } - - THEN("mount with non-existent source path fails") - { - REQUIRE(vfs.do_mount("/dev/nonexistent", "/information") == - kernel::filesystem::vfs::operation_result::non_existent_path); - } - - THEN("mount with non-existent mount point fails") - { - REQUIRE(vfs.do_mount("/dev/ram16", "/information/nonexistent") == - kernel::filesystem::vfs::operation_result::mount_point_not_found); - } - - THEN("unmount with invalid path fails") - { - REQUIRE(vfs.unmount("") == kernel::filesystem::vfs::operation_result::invalid_path); - REQUIRE(vfs.unmount("information") == kernel::filesystem::vfs::operation_result::mount_point_not_found); - } - - THEN("unmounting non-existent mount point returns expected error code") - { - REQUIRE(vfs.unmount("/information/nonexistent") == - kernel::filesystem::vfs::operation_result::mount_point_not_found); - } - - 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.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); - - 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.do_mount("/dev/ram16", "/information") == kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.do_mount("/archiv/2024.img", "/information/monkey_house/infrastructure") == - kernel::filesystem::vfs::operation_result::success); - - 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 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.do_mount("/archiv/2024.img", "/information") == kernel::filesystem::vfs::operation_result::success); - - auto info_1 = vfs.open("/information/info_1.txt"); - REQUIRE(info_1 == nullptr); - - auto dentry = vfs.open("/information/sheep_1.txt"); - REQUIRE(dentry != nullptr); - auto sheep_1_ofd = kstd::make_shared(dentry); - - kstd::vector buffer(7); - auto bytes_read = sheep_1_ofd->read(buffer.data(), buffer.size()); - std::string_view buffer_as_str{reinterpret_cast(buffer.data()), bytes_read}; - REQUIRE(buffer_as_str == "sheep_1"); - - REQUIRE(vfs.close(dentry->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - - REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); - auto unmounted_sheep_1 = vfs.open("/information/sheep_1.txt"); - REQUIRE(unmounted_sheep_1 == nullptr); - } - - 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.do_mount("/archiv/2024.img", "/information") == kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.do_mount("/archiv/2025.img", "/information/stable") == - kernel::filesystem::vfs::operation_result::success); - - 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); - auto goat_1_ofd = kstd::make_shared(goat_1); - - kstd::vector sheep_buffer(7); - auto bytes_read = sheep_1_ofd->read(sheep_buffer.data(), sheep_buffer.size()); - std::string_view buffer_as_str{reinterpret_cast(sheep_buffer.data()), bytes_read}; - REQUIRE(buffer_as_str == "sheep_1"); - - kstd::vector goat_buffer(6); - bytes_read = goat_1_ofd->read(goat_buffer.data(), goat_buffer.size()); - buffer_as_str = std::string_view{reinterpret_cast(goat_buffer.data()), bytes_read}; - REQUIRE(buffer_as_str == "goat_1"); - - REQUIRE(vfs.close(sheep_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.close(goat_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - - REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::unmount_failed); - - REQUIRE(vfs.unmount("/information/stable") == kernel::filesystem::vfs::operation_result::success); - auto unmounted_goat_1 = vfs.open("/information/stable/petting_zoo/goat_1.txt"); - REQUIRE(unmounted_goat_1 == nullptr); - - auto still_mounted_sheep_1 = vfs.open("/information/sheep_1.txt"); - REQUIRE(still_mounted_sheep_1 != nullptr); - - REQUIRE(vfs.close(still_mounted_sheep_1->absolute_path()) == kernel::filesystem::vfs::operation_result::success); - - REQUIRE(vfs.unmount("/information") == kernel::filesystem::vfs::operation_result::success); - auto unmounted_sheep_1 = vfs.open("/information/sheep_1.txt"); - REQUIRE(unmounted_sheep_1 == nullptr); - } - } - - 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.do_mount("/dev/ram16", "/entrance") == kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.do_mount("/entrance/archiv/2024.img", "/enclosures") == - kernel::filesystem::vfs::operation_result::success); - - REQUIRE(vfs.unmount("/entrance") == kernel::filesystem::vfs::operation_result::unmount_failed); - REQUIRE(vfs.unmount("/enclosures") == kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.unmount("/entrance") == kernel::filesystem::vfs::operation_result::success); - } - - THEN("can mount filesystem onto the directory that contains it") - { - REQUIRE(vfs.do_mount("/dev/ram16", "/entrance") == kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.do_mount("/entrance/archiv/2024.img", "/entrance") == - kernel::filesystem::vfs::operation_result::success); - - REQUIRE(vfs.unmount("/entrance") == kernel::filesystem::vfs::operation_result::success); - REQUIRE(vfs.unmount("/entrance") == kernel::filesystem::vfs::operation_result::success); - } - } - - 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 == nullptr); - } - - 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 == nullptr); - } - - 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 == nullptr); - } - } - - 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(); - vfs.do_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(); - vfs.do_mount("/dev/ram16", "/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); - } - } -} diff --git a/kernel/src/main.cpp b/kernel/src/main.cpp deleted file mode 100644 index 8dc13490..00000000 --- a/kernel/src/main.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include "kernel/filesystem/type_registry.hpp" -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include - -using namespace kstd::units_literals; - -auto run_demo() -> void -{ - // 1) open a file - kstd::println("attempting to open /entrance/tickets.txt"); - auto fd_1 = kapi::filesystem::open("/entrance/tickets.txt"); - if (fd_1 == -1) - { - kapi::system::panic("demo failed"); - } - else - { - kstd::println("--> successfully opened /entrance/tickets.txt with file descriptor {}", fd_1); - } - - // 2) read from the file - kstd::vector buffer_1{10}; - auto bytes_read = kapi::filesystem::read(fd_1, buffer_1.data(), buffer_1.size()); - auto buffer_as_str = std::string_view{reinterpret_cast(buffer_1.data()), static_cast(bytes_read)}; - kstd::println("--> read {} bytes from /entrance/tickets.txt: {}", bytes_read, buffer_as_str); - kstd::println(""); - - // 3) show that /entrance/information/info_1.txt is not accessible before mounting - kstd::println("attempting to open /entrance/information/info_1.txt before mounting"); - auto fd_before_mount = kapi::filesystem::open("/entrance/information/info_1.txt"); - if (fd_before_mount == -1) - { - kstd::println("--> as expected the file could not be opened before mounting"); - } - - // 4) mount a new filesystem on top of /entrance - kstd::println("mount /dev/ram16 to /entrance"); - if (kapi::filesystem::mount("/dev/ram16", "/entrance") == 0) - { - kstd::println("--> successfully mounted /dev/ram16 to /entrance"); - } - else - { - kapi::system::panic("demo failed"); - } - kstd::println(""); - - // 5) open a file from the new filesystem - kstd::println("attempting to open /entrance/information/info_1.txt"); - auto fd_2 = kapi::filesystem::open("/entrance/information/info_1.txt"); - if (fd_2 != -1) - { - kstd::println("--> successfully opened /entrance/information/info_1.txt with file descriptor {}", fd_2); - } - else - { - kapi::system::panic("demo failed"); - } - - // 6) read from the new file - kstd::vector buffer_2{10}; - bytes_read = kapi::filesystem::read(fd_2, buffer_2.data(), buffer_2.size()); - buffer_as_str = std::string_view{reinterpret_cast(buffer_2.data()), static_cast(bytes_read)}; - kstd::println("--> read {} bytes from /entrance/information/info_1.txt: {} ", bytes_read, buffer_as_str); - - // 7) open device as file - kstd::println("attempting to open /dev/ram32 as a file"); - auto fd_3 = kapi::filesystem::open("/dev/ram32"); - if (fd_3 != -1) - { - kstd::println("--> successfully opened /dev/ram32 as a file with file descriptor {}", fd_3); - } - else - { - kapi::system::panic("demo failed"); - } - - // 8) read from the device file - kstd::vector buffer_3{2}; - bytes_read = kapi::filesystem::read(fd_3, buffer_3.data(), buffer_3.size()); - kstd::println("--> read {} bytes from /dev/ram32: {::#04x} ", bytes_read, buffer_3); - - // 9) write to the device file - auto const default_buffer_value = std::byte{0xAA}; - kstd::vector write_buffer{default_buffer_value, default_buffer_value}; - auto bytes_written = kapi::filesystem::write(fd_3, write_buffer.data(), write_buffer.size()); - kstd::println("--> written {} bytes to /dev/ram32: {::#04x}", bytes_written, write_buffer); - - // 10) do memory dump to show that the write to the device file had an effect -} - -auto main() -> int -{ - kapi::cio::init(); - kstd::println("[OS] IO subsystem initialized."); - - kapi::cpu::init(); - - kapi::memory::init(); - kernel::memory::init_heap(kapi::memory::heap_base); - kapi::system::memory_initialized(); - kapi::memory::init_mmio(kapi::memory::mmio_base, 1_GiB / kapi::memory::page::size); - kstd::println("[OS] Memory subsystem initialized."); - - kapi::devices::init(); - kstd::println("[OS] System root bus initialized."); - - kapi::devices::init_platform_devices(); - kstd::println("[OS] Platform devices initialized."); - - kapi::interrupts::enable(); - kstd::println("[OS] Interrupts enabled."); - - kapi::boot_modules::init(); - kstd::println("[OS] Boot module registry initialized."); - - kernel::devices::storage::management::init(); - kstd::println("[OS] Storage management initialized."); - - kernel::filesystem::open_file_table::init(); - kstd::println("[OS] Global open file table initialized."); - - kernel::filesystem::type_registry::init(); - kstd::println("[OS] Builtin filesystems registered."); - - kernel::filesystem::vfs::init(); - kstd::println("[OS] Virtual filesystem initialized."); - - // TODO BA-FS26 remove demo code? - // run_demo(); - - kapi::system::panic("Returning from kernel main!"); -} diff --git a/kernel/src/memory.cpp b/kernel/src/memory.cpp deleted file mode 100644 index 6a85c0e7..00000000 --- a/kernel/src/memory.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include - -#include -#include - -#include -#include - -#include -#include - -#include -#include - -namespace kernel::memory -{ - - namespace - { - struct null_allocator : heap_allocator - { - null_allocator static instance; - - [[nodiscard]] auto allocate(kstd::units::bytes, kstd::units::bytes) noexcept -> void * override - { - kstd::print(kstd::print_sink::stderr, "Tried to allocate memory without an active heap!"); - return nullptr; - } - - auto deallocate(void *) noexcept -> void override - { - kstd::print(kstd::print_sink::stderr, "Tried to deallocate memory without an active heap!"); - } - }; - - constinit null_allocator null_allocator::instance{}; - - auto constinit active_heap_allocator = std::optional{&null_allocator::instance}; - auto constinit basic_allocator = std::optional{}; - } // namespace - - auto get_heap_allocator() -> heap_allocator & - { - return *active_heap_allocator.value(); - } - - auto init_heap(kapi::memory::linear_address base) -> void - { - auto static constinit is_initialized = std::atomic_flag{}; - - if (is_initialized.test_and_set()) - { - kapi::system::panic("[OS:MEM] The heap has already been initialized."); - } - - auto & instance = basic_allocator.emplace(base); - active_heap_allocator = &instance; - kstd::println("[OS:MEM] Heap initialized. Dynamic memory available."); - } - -} // namespace kernel::memory \ No newline at end of file diff --git a/kernel/src/memory/bitmap_allocator.cpp b/kernel/src/memory/bitmap_allocator.cpp deleted file mode 100644 index 240e2afe..00000000 --- a/kernel/src/memory/bitmap_allocator.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include - -#include - -#include -#include -#include -#include -#include -#include -#include - -namespace kernel::memory -{ - - bitmap_frame_allocator::bitmap_frame_allocator(std::span storage, std::size_t frame_count) noexcept - : m_bitmap{storage} - , m_frame_count{frame_count} - , m_last_index{} - { - constexpr auto bits_per_word = 64uz; - auto to_fill = (frame_count + bits_per_word - 1) / bits_per_word; - std::ranges::fill(std::views::take(m_bitmap, to_fill), ~0uz); - } - - auto bitmap_frame_allocator::allocate_many(std::size_t count) noexcept - -> std::optional> - { - if (count == 0) - { - return std::nullopt; - } - - auto free_count = 0uz; - auto first_free = 0uz; - - for (auto i = 0uz; i < m_frame_count; ++i) - { - auto const current = (m_last_index + i) % m_frame_count; - if (!test(current)) - { - if (free_count == 0) - { - first_free = current; - } - - ++free_count; - - if (free_count == count) - { - for (auto j = 0uz; j < count; ++j) - { - set(first_free + j); - } - m_last_index = first_free + count; - return std::make_pair(kapi::memory::frame{first_free}, count); - } - } - else - { - free_count = 0; - } - } - - return std::nullopt; - } - - auto bitmap_frame_allocator::release_many(std::pair frame_set) -> void - { - auto const [start, count] = frame_set; - for (auto i = 0uz; i < count; ++i) - { - clear(start.number() + i); - } - } - - auto bitmap_frame_allocator::mark_used(kapi::memory::frame frame) -> void - { - set(frame.number()); - } - - auto bitmap_frame_allocator::test(std::size_t index) const noexcept -> bool - { - constexpr auto bits_per_word = 64uz; - auto entry_entry = index / bits_per_word; - auto entry_offset = index % bits_per_word; - return (m_bitmap[entry_entry] & (1uz << entry_offset)); - } - - auto bitmap_frame_allocator::set(std::size_t index) noexcept -> void - { - constexpr auto bits_per_word = 64uz; - auto entry_entry = index / bits_per_word; - auto entry_offset = index % bits_per_word; - m_bitmap[entry_entry] |= (1uz << entry_offset); - } - - auto bitmap_frame_allocator::clear(std::size_t index) noexcept -> void - { - constexpr auto bits_per_word = 64uz; - auto entry_entry = index / bits_per_word; - auto entry_offset = index % bits_per_word; - m_bitmap[entry_entry] &= ~(1uz << entry_offset); - } - -} // namespace kernel::memory \ No newline at end of file diff --git a/kernel/src/memory/bitmap_allocator.tests.cpp b/kernel/src/memory/bitmap_allocator.tests.cpp deleted file mode 100644 index 05d11a33..00000000 --- a/kernel/src/memory/bitmap_allocator.tests.cpp +++ /dev/null @@ -1,288 +0,0 @@ -#include - -#include - -#include -#include -#include - -#include -#include -#include -#include -#include - -constexpr auto all_bits_set = std::numeric_limits::max(); -constexpr auto available_frames = 1024uz; - -SCENARIO("Bitmap allocator construction and initialization", "[memory][bitmap_allocator]") -{ - GIVEN("A storage region") - { - auto storage = std::vector(available_frames / 64, 0uz); - - WHEN("constructing the allocator with 0 frames") - { - auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), 0}; - - THEN("the storage region is not modified") - { - REQUIRE_THAT(storage, Catch::Matchers::RangeEquals(std::vector(16, 0uz))); - } - } - - WHEN("constructing with 1 frame") - { - auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), 1}; - - THEN("the first word of the storage region is set to all ones") - { - REQUIRE_THAT(std::views::take(storage, 1), Catch::Matchers::RangeEquals(std::vector(1, all_bits_set))); - } - - THEN("the rest of the storage region is not modified") - { - REQUIRE_THAT(std::views::drop(storage, 1), Catch::Matchers::RangeEquals(std::vector(15, 0uz))); - } - } - - WHEN("constructing with 64 frames") - { - auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), 64}; - - THEN("the first word of the storage region is set to all ones") - { - REQUIRE_THAT(std::views::take(storage, 1), Catch::Matchers::RangeEquals(std::vector(1, all_bits_set))); - } - - THEN("the rest of the storage region is not modified") - { - REQUIRE_THAT(std::views::drop(storage, 1), Catch::Matchers::RangeEquals(std::vector(15, 0uz))); - } - } - - WHEN("constructing with all available frames") - { - auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; - - THEN("the storage region is filled with all ones") - { - REQUIRE_THAT(storage, Catch::Matchers::RangeEquals(std::vector(16, all_bits_set))); - } - } - - WHEN("constructing with half the available frames") - { - auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames / 2}; - - THEN("the first half of the storage region is filled with all ones") - { - REQUIRE_THAT(std::views::take(storage, (available_frames / 2) / 64), - Catch::Matchers::RangeEquals(std::vector((available_frames / 2) / 64, all_bits_set))); - } - - THEN("the second half of the storage region is filled with all zeros") - { - REQUIRE_THAT(std::views::drop(storage, (available_frames / 2) / 64), - Catch::Matchers::RangeEquals(std::vector((available_frames / 2) / 64, 0uz))); - } - } - } -} - -SCENARIO("Bitmap allocator frame allocation", "[memory][bitmap_allocator]") -{ - GIVEN("A storage region") - { - auto storage = std::vector(available_frames / 64, 0uz); - - AND_GIVEN("an allocator constructed with all available frames but no free ones") - { - auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; - - WHEN("allocating 1 frame") - { - auto result = allocator.allocate_many(1); - - THEN("the result is empty") - { - REQUIRE_FALSE(result.has_value()); - } - } - } - - AND_GIVEN("an allocator constructed with all available frames but only one free one") - { - auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; - allocator.release_many({kapi::memory::frame{0}, 1}); - - WHEN("allocating 1 frame") - { - auto result = allocator.allocate_many(1); - - THEN("the result is not empty") - { - REQUIRE(result.has_value()); - } - - THEN("the result contains 1 frame") - { - REQUIRE(result->second == 1); - } - } - - WHEN("allocating more frames than are free") - { - auto result = allocator.allocate_many(2); - - THEN("the result is empty") - { - REQUIRE_FALSE(result.has_value()); - } - - AND_WHEN("allocating a single frame") - { - auto result = allocator.allocate_many(1); - - THEN("the result is not empty") - { - REQUIRE(result.has_value()); - } - } - - WHEN("allocating 0 frames") - { - auto result = allocator.allocate_many(0); - - THEN("the result is empty") - { - REQUIRE_FALSE(result.has_value()); - } - } - } - } - - AND_GIVEN("an allocator with many single frame holes") - { - auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; - for (auto i = 0uz; i < available_frames; i += 2) - { - allocator.release_many({kapi::memory::frame{i}, 1}); - } - - WHEN("allocating 1 frame") - { - auto result = allocator.allocate_many(1); - - THEN("the result is not empty") - { - REQUIRE(result.has_value()); - } - - THEN("the result contains 1 frame") - { - REQUIRE(result->second == 1); - } - } - - WHEN("allocating 2 frames") - { - auto result = allocator.allocate_many(2); - - THEN("the result is empty") - { - REQUIRE_FALSE(result.has_value()); - } - } - } - - AND_GIVEN("and allocator with all frames marked as free") - { - auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; - allocator.release_many({kapi::memory::frame{0}, available_frames}); - - WHEN("allocating 1 frame") - { - auto result = allocator.allocate_many(1); - - THEN("the result is not empty") - { - REQUIRE(result.has_value()); - } - - THEN("the result contains 1 frame") - { - REQUIRE(result->second == 1); - } - } - - WHEN("allocating multiple frames") - { - auto result = allocator.allocate_many(20); - - THEN("the result is not empty") - { - REQUIRE(result.has_value()); - } - - THEN("the result contains 20 frames") - { - REQUIRE(result->second == 20); - } - } - - WHEN("marking all frames as used") - { - for (auto i = 0uz; i < available_frames; i++) - { - allocator.mark_used(kapi::memory::frame{i}); - } - - THEN("the allocator has no free frames") - { - REQUIRE_FALSE(allocator.allocate_many(1).has_value()); - } - } - } - - AND_GIVEN("an allocator with a contiguous block of free frames") - { - auto allocator = kernel::memory::bitmap_frame_allocator{std::span(storage), available_frames}; - allocator.release_many({kapi::memory::frame{0}, available_frames}); - for (auto i = 0uz; i < available_frames / 2; i++) - { - allocator.mark_used(kapi::memory::frame{i}); - } - - WHEN("allocating a single frame") - { - auto result = allocator.allocate_many(1); - - THEN("the result is not empty") - { - REQUIRE(result.has_value()); - } - - THEN("the result contains 1 frame") - { - REQUIRE(result->second == 1); - } - } - - WHEN("allocating multiple frames") - { - auto result = allocator.allocate_many(20); - - THEN("the result is not empty") - { - REQUIRE(result.has_value()); - } - - THEN("the result contains 20 frames") - { - REQUIRE(result->second == 20); - } - } - } - } -} \ No newline at end of file diff --git a/kernel/src/memory/block_list_allocator.cpp b/kernel/src/memory/block_list_allocator.cpp deleted file mode 100644 index 6e68ada7..00000000 --- a/kernel/src/memory/block_list_allocator.cpp +++ /dev/null @@ -1,215 +0,0 @@ -#include - -#include - -#include -#include - -#include -#include - -#include -#include -#include -#include - -using namespace kstd::units_literals; - -namespace kernel::memory -{ - - namespace - { - [[nodiscard]] constexpr auto align_up(std::byte * pointer, kstd::units::bytes alignment) noexcept -> std::byte * - { - auto const remainder = std::bit_cast(pointer) % static_cast(alignment); - return remainder == 0 ? pointer : pointer + static_cast(alignment) - remainder; - } - } // namespace - - block_list_allocator::block_list_allocator(kapi::memory::linear_address base) noexcept - : heap_allocator{} - , m_base{base} - , m_frontier{base} - , m_block_list{} - , m_lock{} - {} - - auto block_list_allocator::allocate(kstd::units::bytes size, kstd::units::bytes alignment) noexcept -> void * - { - kstd::lock_guard guard{m_lock}; - - for (auto attempt = 0uz; attempt < 2uz; ++attempt) - { - auto current = m_block_list; - while (current != nullptr) - { - if (current->free) - { - auto const raw_block = reinterpret_cast(current); - auto const unaligned_payload = raw_block + allocated_metadata_size; - auto const aligned_payload = align_up(unaligned_payload, alignment); - auto const required_padding = static_cast(aligned_payload - unaligned_payload); - auto const total_required_size = required_padding + allocated_metadata_size + size; - - if (current->usable_size >= total_required_size) - { - auto const payload_header = aligned_payload - sizeof(block_header *) - sizeof(block_header); - auto const front_padding = static_cast(payload_header - raw_block); - - auto payload_block = current; - - if (front_padding >= allocated_metadata_size + minimum_allocation_size) - { - payload_block = reinterpret_cast(payload_header); - std::construct_at(payload_block, current->usable_size - front_padding, true, current->next); - - if (payload_block->next) - { - payload_block->next->prev = payload_block; - } - - current->usable_size = front_padding - allocated_metadata_size; - current->next = payload_block; - payload_block->prev = current; - } - - auto const header_size = - static_cast(aligned_payload - reinterpret_cast(payload_block)); - auto const payload_size = header_size - allocated_metadata_size + size; - split(payload_block, payload_size, 0_B); - - payload_block->free = false; - - auto back_pointer = reinterpret_cast(aligned_payload - sizeof(block_header *)); - *back_pointer = payload_block; - - return reinterpret_cast(aligned_payload); - } - } - current = current->next; - } - - auto const search_size = size + alignment; - if (attempt == 0uz && !expand(search_size)) - { - return nullptr; - } - } - - return nullptr; - } - - auto block_list_allocator::deallocate(void * ptr) noexcept -> void - { - if (!ptr) - { - return; - } - - kstd::lock_guard guard{m_lock}; - - auto const aligned_payload = reinterpret_cast(ptr); - auto back_pointer = reinterpret_cast(aligned_payload - sizeof(block_header *)); - auto block = *back_pointer; - - block->free = true; - coalesce(block); - } - - auto block_list_allocator::expand(kstd::units::bytes size) noexcept -> bool - { - auto const total_required_size = size + allocated_metadata_size; - auto const frames_needed = (total_required_size + kapi::memory::frame::size - 1_B) / kapi::memory::frame::size; - - auto const flags = kapi::memory::page_mapper::flags::writable | kapi::memory::page_mapper::flags::supervisor_only | - kapi::memory::page_mapper::flags::global; - - for (auto i = 0uz; i < frames_needed; ++i) - { - auto frame = kapi::memory::allocate_frame(); - if (!frame) - { - kapi::system::panic("[OS:Heap] OOM when expanding heap."); - return false; - } - - auto page = kapi::memory::page::containing(m_frontier + i * kapi::memory::page::size); - kapi::memory::map(page, *frame, flags); - } - - auto block = static_cast(m_frontier); - std::construct_at(block, frames_needed * kapi::memory::frame::size - allocated_metadata_size, true, nullptr, - nullptr); - - m_frontier += frames_needed * kapi::memory::frame::size; - - if (!m_block_list) - { - m_block_list = block; - } - else - { - auto current = m_block_list; - while (current->next) - { - current = current->next; - } - current->next = block; - block->prev = current; - } - - coalesce(block); - return true; - } - - auto block_list_allocator::coalesce(block_header * block) noexcept -> void - { - if (block->next && block->next->free) - { - auto follower = block->next; - block->usable_size += follower->usable_size + allocated_metadata_size; - block->next = follower->next; - if (block->next) - { - block->next->prev = block; - } - std::destroy_at(follower); - } - - if (block->prev && block->prev->free) - { - auto leader = block->prev; - leader->usable_size += block->usable_size + allocated_metadata_size; - leader->next = block->next; - if (block->next) - { - block->next->prev = leader; - } - std::destroy_at(block); - } - } - - auto block_list_allocator::split(block_header * block, kstd::units::bytes size, kstd::units::bytes padding) noexcept - -> void - { - auto const new_block_size = size + padding; - - if (block->usable_size > new_block_size + allocated_metadata_size + minimum_allocation_size) - { - auto raw_block = reinterpret_cast(block); - auto new_block = reinterpret_cast(raw_block + allocated_metadata_size + new_block_size); - std::construct_at(new_block, block->usable_size - new_block_size - allocated_metadata_size, true, block->next, - block); - - if (new_block->next) - { - new_block->next->prev = new_block; - } - - block->usable_size = new_block_size; - block->next = new_block; - } - } - -} // namespace kernel::memory \ No newline at end of file diff --git a/kernel/src/memory/block_list_allocator.tests.cpp b/kernel/src/memory/block_list_allocator.tests.cpp deleted file mode 100644 index c5f84c52..00000000 --- a/kernel/src/memory/block_list_allocator.tests.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include - -#include - -#include - -#include - -#include - -#include - -using namespace kstd::units_literals; - -SCENARIO("Block List Allocator Operations", "[memory][allocator]") -{ - GIVEN("A newly initialized block list allocator mapped via the test sandbox") - { - kernel::tests::memory::deinit(); - kapi::memory::init(); - - auto sandbox_base = kernel::tests::memory::virtual_base(); - kernel::memory::block_list_allocator allocator{sandbox_base}; - - WHEN("a basic allocation request is made") - { - void * ptr = allocator.allocate(128_B, 8_B); - - THEN("a valid, non-null pointer is returned") - { - REQUIRE(ptr != nullptr); - } - - AND_THEN("the returned memory is writeable without causing segmentation faults") - { - auto byte_ptr = static_cast(ptr); - byte_ptr[0] = std::byte{0xDE}; - byte_ptr[127] = std::byte{0xAD}; - REQUIRE(byte_ptr[0] == std::byte{0xDE}); - REQUIRE(byte_ptr[127] == std::byte{0xAD}); - } - - allocator.deallocate(ptr); - } - - WHEN("multiple allocations are made sequentially") - { - void * ptr1 = allocator.allocate(64_B, 8_B); - void * ptr2 = allocator.allocate(64_B, 8_B); - void * ptr3 = allocator.allocate(1_KiB, 16_B); - - THEN("they return distinct, non-overlapping memory blocks") - { - REQUIRE(ptr1 != nullptr); - REQUIRE(ptr2 != nullptr); - REQUIRE(ptr3 != nullptr); - REQUIRE(ptr1 != ptr2); - REQUIRE(ptr2 != ptr3); - REQUIRE(ptr1 != ptr3); - } - - allocator.deallocate(ptr1); - allocator.deallocate(ptr2); - allocator.deallocate(ptr3); - } - - WHEN("a block is allocated and then completely freed") - { - void * original_ptr = allocator.allocate(512_B, 16_B); - allocator.deallocate(original_ptr); - - AND_WHEN("a new allocation of equal or smaller size is requested") - { - void * new_ptr = allocator.allocate(128_B, 16_B); - - THEN("the allocator actively reuses the coalesced space") - { - REQUIRE(new_ptr == original_ptr); - } - - allocator.deallocate(new_ptr); - } - } - } -} diff --git a/kernel/src/memory/mmio_allocator.cpp b/kernel/src/memory/mmio_allocator.cpp deleted file mode 100644 index ba23dbde..00000000 --- a/kernel/src/memory/mmio_allocator.cpp +++ /dev/null @@ -1,111 +0,0 @@ -#include - -#include -#include - -#include - -#include -#include -#include - -namespace kernel::memory -{ - - mmio_allocator::mmio_allocator(kapi::memory::linear_address base, std::size_t pages) - : m_head{make_node(base, pages, nullptr, nullptr, true)} - {} - - auto mmio_allocator::allocate(std::size_t count) -> kapi::memory::linear_address - { - if (count == 0 || !m_head) - { - return {}; - } - - auto current = m_head; - while (current) - { - if (current->is_free && current->page_count >= count) - { - if (current->page_count > count) - { - auto new_base = current->base + (count * kapi::memory::page::size); - auto split_node = make_node(new_base, current->page_count - count, std::move(current->next), current, true); - - if (current->next) - { - current->next->previous = split_node; - } - current->next = split_node; - current->page_count = count; - } - - current->is_free = false; - return current->base; - } - current = current->next; - } - - kapi::system::panic("[OS:MEM] MMIO alloctor out of memory!"); - return {}; - } - - auto mmio_allocator::release(kapi::memory::linear_address base) -> void - { - auto current = m_head; - - while (current) - { - if (current->base == base && !current->is_free) - { - current->is_free = true; - - if (current->next && current->next->is_free) - { - auto removed = current->next; - current->page_count += removed->page_count; - current->next = removed->next; - if (current->next) - { - current->next->previous = current; - } - destroy_node(removed); - } - - if (current->previous && current->previous->is_free) - { - auto removed = current; - removed->previous->page_count += removed->page_count; - removed->previous->next = removed->next; - if (removed->next) - { - removed->next->previous = removed->previous; - } - destroy_node(removed); - } - return; - } - current = current->next; - } - } - - auto mmio_allocator::make_node(kapi::memory::linear_address base, std::size_t page_count, node * next, - node * previous, bool is_free) -> node * - { - using traits = std::allocator_traits>; - - auto new_node = traits::allocate(m_allocator, 1); - traits::construct(m_allocator, new_node, base, page_count, next, previous, is_free); - return new_node; - } - - auto mmio_allocator::destroy_node(node * instance) -> void - { - using traits = std::allocator_traits>; - - traits::destroy(m_allocator, instance); - traits::deallocate(m_allocator, instance, 1); - } - -} // namespace kernel::memory diff --git a/kernel/src/memory/operators.cpp b/kernel/src/memory/operators.cpp deleted file mode 100644 index 5673d680..00000000 --- a/kernel/src/memory/operators.cpp +++ /dev/null @@ -1,102 +0,0 @@ -#include - -#include - -#include - -#include -#include - -[[nodiscard]] auto operator new(std::size_t size, std::align_val_t alignment, std::nothrow_t const &) noexcept -> void * -{ - auto & allocator = kernel::memory::get_heap_allocator(); - return allocator.allocate(static_cast(size), - static_cast(static_cast(alignment))); -} - -[[nodiscard]] auto operator new(std::size_t size, std::align_val_t alignment) -> void * -{ - auto pointer = operator new(size, alignment, std::nothrow); - - if (pointer == nullptr) - { - kapi::system::panic("[OS:Heap] Out of memory!"); - } - - return pointer; -} - -[[nodiscard]] auto operator new(std::size_t size, std::nothrow_t const &) noexcept -> void * -{ - return operator new(size, std::align_val_t{__STDCPP_DEFAULT_NEW_ALIGNMENT__}, std::nothrow); -} - -[[nodiscard]] auto operator new(std::size_t size) -> void * -{ - return operator new(size, std::align_val_t{__STDCPP_DEFAULT_NEW_ALIGNMENT__}); -} - -[[nodiscard]] auto operator new[](std::size_t size, std::align_val_t alignment, std::nothrow_t const &) noexcept - -> void * -{ - return operator new(size, alignment, std::nothrow); -} - -[[nodiscard]] auto operator new[](std::size_t size, std::align_val_t alignment) -> void * -{ - return operator new(size, alignment); -} - -[[nodiscard]] auto operator new[](std::size_t size, std::nothrow_t const &) noexcept -> void * -{ - return ::operator new(size); -} - -[[nodiscard]] auto operator new[](std::size_t size) -> void * -{ - return ::operator new(size); -} - -auto operator delete(void * pointer) noexcept -> void -{ - if (pointer != nullptr) - { - auto & allocator = kernel::memory::get_heap_allocator(); - allocator.deallocate(pointer); - } -} - -auto operator delete(void * pointer, std::size_t) noexcept -> void -{ - ::operator delete(pointer); -} - -auto operator delete(void * pointer, std::align_val_t) noexcept -> void -{ - ::operator delete(pointer); -} - -auto operator delete(void * pointer, std::size_t size, std::align_val_t) noexcept -> void -{ - ::operator delete(pointer, size); -} - -auto operator delete[](void * pointer) noexcept -> void -{ - ::operator delete(pointer); -} - -auto operator delete[](void * pointer, std::size_t size) noexcept -> void -{ - ::operator delete(pointer, size); -} - -auto operator delete[](void * pointer, std::align_val_t) noexcept -> void -{ - ::operator delete(pointer); -} - -auto operator delete[](void * pointer, std::size_t size, std::align_val_t) noexcept -> void -{ - ::operator delete(pointer, size); -} diff --git a/kernel/src/test_support/devices/block_device.cpp b/kernel/src/test_support/devices/block_device.cpp deleted file mode 100644 index 9a9e544d..00000000 --- a/kernel/src/test_support/devices/block_device.cpp +++ /dev/null @@ -1,61 +0,0 @@ -#include - -#include - -#include -#include - -#include -#include -#include -#include -#include - -namespace kernel::tests::devices -{ - block_device::block_device(size_t major, size_t minor, kstd::string const & name, size_t block_size, - size_t initial_size) - : kernel::devices::block_device(major, minor, name, block_size) - { - data.resize(initial_size, 0); - } - - auto block_device::init() -> bool - { - return true; - } - - auto block_device::read_block(size_t block_index, void * buffer) const -> void - { - auto const offset = block_index * block_size(); - if (offset >= data.size()) - { - kstd::libc::memset(buffer, 0, block_size()); - return; - } - - auto const bytes_to_read = std::min(block_size(), data.size() - offset); - kstd::libc::memcpy(buffer, data.data() + offset, bytes_to_read); - if (bytes_to_read < block_size()) - { - kstd::libc::memset(static_cast(buffer) + bytes_to_read, 0, block_size() - bytes_to_read); - } - } - - auto block_device::write_block(size_t block_index, void const * buffer) -> void - { - auto const offset = block_index * block_size(); - auto const write_end = offset + block_size(); - if (write_end > data.size()) - { - data.resize(write_end, 0); - } - - kstd::libc::memcpy(data.data() + offset, static_cast(buffer), block_size()); - } - - auto block_device::size() const -> size_t - { - return data.size(); - } -} // namespace kernel::tests::devices \ No newline at end of file diff --git a/kernel/src/test_support/devices/character_device.cpp b/kernel/src/test_support/devices/character_device.cpp deleted file mode 100644 index 3806654f..00000000 --- a/kernel/src/test_support/devices/character_device.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include - -#include - -#include - -#include - -namespace kernel::tests::devices -{ - character_device::character_device(size_t major, size_t minor, kstd::string const & name) - : kapi::devices::device(major, minor, name) - {} - - auto character_device::init() -> bool - { - return true; - } -} // namespace kernel::tests::devices \ No newline at end of file diff --git a/kernel/src/test_support/filesystem/ext2.cpp b/kernel/src/test_support/filesystem/ext2.cpp deleted file mode 100644 index 52b6efe7..00000000 --- a/kernel/src/test_support/filesystem/ext2.cpp +++ /dev/null @@ -1,68 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -#include -#include - -namespace kernel::tests::filesystem::ext2 -{ - namespace - { - constexpr uint32_t root_directory_data_block = 20; - } // namespace - - auto write_bytes(kernel::tests::devices::block_device & device, size_t offset, void const * source, size_t size) - -> void - { - auto const required_size = offset + size; - if (device.data.size() < required_size) - { - device.data.resize(required_size, 0); - } - - std::memcpy(device.data.data() + offset, source, size); - } - - auto write_u32(kernel::tests::devices::block_device & device, size_t offset, uint32_t value) -> void - { - write_bytes(device, offset, &value, sizeof(value)); - } - - auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device) -> void - { - auto superblock = kernel::filesystem::ext2::superblock{}; - superblock.magic = kernel::filesystem::ext2::constants::magic_number; - superblock.log_block_size = 0; - superblock.blocks_count = 64; - superblock.blocks_per_group = 64; - superblock.inodes_per_group = 32; - superblock.rev_level = 1; - superblock.inode_size = 128; - setup_mock_ext2_layout(device, superblock); - } - - auto setup_mock_ext2_layout(kernel::tests::devices::block_device & device, - kernel::filesystem::ext2::superblock const & superblock) -> void - { - write_bytes(device, kernel::filesystem::ext2::constants::superblock_offset, &superblock, sizeof(superblock)); - - auto group_descriptor = kernel::filesystem::ext2::block_group_descriptor{}; - group_descriptor.inode_table = 5; - write_bytes(device, 2048, &group_descriptor, sizeof(group_descriptor)); - - auto root_inode_data = kernel::filesystem::ext2::inode_data{}; - root_inode_data.mode = kernel::filesystem::ext2::constants::mode_directory; - root_inode_data.blocks = 2; - root_inode_data.block[0] = root_directory_data_block; - - auto const root_inode_offset = - static_cast(group_descriptor.inode_table) * kernel::filesystem::ext2::constants::base_block_size + - (kernel::filesystem::ext2::constants::root_inode_number - 1) * superblock.inode_size; - write_bytes(device, root_inode_offset, &root_inode_data, sizeof(root_inode_data)); - } -} // namespace kernel::tests::filesystem::ext2 \ No newline at end of file diff --git a/kernel/src/test_support/filesystem/filesystem.cpp b/kernel/src/test_support/filesystem/filesystem.cpp deleted file mode 100644 index ec706077..00000000 --- a/kernel/src/test_support/filesystem/filesystem.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include - -#include -#include - -#include - -#include - -namespace kernel::tests::filesystem -{ - auto filesystem::lookup(kstd::shared_ptr const &, std::string_view) const - -> kstd::shared_ptr - { - return kstd::make_shared(); - } -} // namespace kernel::tests::filesystem \ No newline at end of file diff --git a/kernel/src/test_support/filesystem/inode.cpp b/kernel/src/test_support/filesystem/inode.cpp deleted file mode 100644 index 0c8d9563..00000000 --- a/kernel/src/test_support/filesystem/inode.cpp +++ /dev/null @@ -1,23 +0,0 @@ -#include - -#include - -#include - -namespace kernel::tests::filesystem -{ - auto inode::read(void *, size_t, size_t size) const -> size_t - { - return size; - } - - auto inode::write(void const *, size_t, size_t size) -> size_t - { - return size; - } - - auto inode::is_regular() const -> bool - { - return true; - } -} // namespace kernel::tests::filesystem \ No newline at end of file diff --git a/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp b/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp deleted file mode 100644 index aabaaceb..00000000 --- a/kernel/src/test_support/filesystem/storage_boot_module_fixture.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#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() - { - kernel::tests::devices::storage::management::deinit(); - kernel::tests::boot_modules::deinit(); - } - - auto storage_boot_module_fixture::setup_modules(std::size_t module_count, std::size_t module_size) -> void - { - m_module_names.clear(); - m_module_data.clear(); - m_registry = {}; - - m_module_names.reserve(module_count); - m_module_data.reserve(module_count); - - for (std::size_t i = 0; i < module_count; ++i) - { - m_module_names.push_back(std::format("test_mod{}", i)); - m_module_data.emplace_back(module_size, std::byte{static_cast(0x40 + (i % 16))}); - } - - for (std::size_t i = 0; i < module_count; ++i) - { - m_registry.add_boot_module(kapi::boot_modules::boot_module{ - m_module_names[i].c_str(), kapi::memory::linear_address{m_module_data[i].data()}, m_module_data[i].size()}); - } - - kapi::boot_modules::set_boot_module_registry(m_registry); - kernel::devices::storage::management::init(); - } - - auto storage_boot_module_fixture::setup_modules_from_img(std::vector const & module_names, - std::vector const & img_paths) -> void - { - m_module_names.clear(); - m_module_data.clear(); - m_registry = {}; - - if (module_names.size() != img_paths.size()) - { - throw std::invalid_argument{"Module names and image paths vectors must have the same size."}; - } - - for (size_t i = 0; i < module_names.size(); ++i) - { - setup_module_from_img(module_names[i], img_paths[i]); - } - - kapi::boot_modules::set_boot_module_registry(m_registry); - kernel::devices::storage::management::init(); - } - - auto storage_boot_module_fixture::setup_module_from_img(std::string const & module_name, - std::filesystem::path const & img_path) -> void - { - m_module_names.push_back(module_name); - auto & mapped_image = m_mapped_images.emplace_back(img_path); - - m_registry.add_boot_module(kapi::boot_modules::boot_module{ - m_module_names.back().c_str(), kapi::memory::linear_address{mapped_image.mapping}, mapped_image.size}); - } -} // namespace kernel::tests::filesystem \ No newline at end of file diff --git a/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp b/kernel/src/test_support/filesystem/storage_boot_module_vfs_fixture.cpp deleted file mode 100644 index 02ccfec3..00000000 --- a/kernel/src/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/src/test_support/filesystem/test_assets/README.md b/kernel/src/test_support/filesystem/test_assets/README.md deleted file mode 120000 index 718a2275..00000000 --- a/kernel/src/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/src/test_support/filesystem/test_assets/ext2_1KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_1KB_fs.img deleted file mode 100644 index a5202ca3..00000000 --- a/kernel/src/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/src/test_support/filesystem/test_assets/ext2_2KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_2KB_fs.img deleted file mode 100644 index 7f297f07..00000000 --- a/kernel/src/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/src/test_support/filesystem/test_assets/ext2_4KB_fs.img b/kernel/src/test_support/filesystem/test_assets/ext2_4KB_fs.img deleted file mode 100644 index c3f6dafb..00000000 --- a/kernel/src/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/src/test_support/kapi/cio.cpp b/kernel/src/test_support/kapi/cio.cpp deleted file mode 100644 index 98bc99d7..00000000 --- a/kernel/src/test_support/kapi/cio.cpp +++ /dev/null @@ -1,54 +0,0 @@ -#include - -#include - -#include - -#include -#include -#include - -namespace -{ - - auto constinit is_initialized = std::atomic_flag{}; - auto constinit device = std::optional{}; - -} // namespace - -namespace kapi::cio -{ - - auto init() -> void - { - if (is_initialized.test_and_set()) - { - throw std::logic_error("kapi::cio::init() called more than once"); - } - - device.emplace(); - set_output_device(*device); - } - -} // namespace kapi::cio - -namespace kernel::tests::cio -{ - - auto deinit() -> void - { - if (!is_initialized.test()) - { - throw std::logic_error("kapi::cio::deinit() called before kapi::cio::init()"); - } - - device.reset(); - is_initialized.clear(); - } - - auto log_buffer() -> kernel::tests::log_buffer & - { - return device->log_buffer(); - } - -} // namespace kernel::tests::cio \ No newline at end of file diff --git a/kernel/src/test_support/kapi/cpu.cpp b/kernel/src/test_support/kapi/cpu.cpp deleted file mode 100644 index 5d956331..00000000 --- a/kernel/src/test_support/kapi/cpu.cpp +++ /dev/null @@ -1,52 +0,0 @@ -#include - -#include - -#include -#include - -namespace -{ - auto static initialized = std::atomic_flag{}; -} - -namespace kapi::cpu -{ - - auto init() -> void - { - if (initialized.test_and_set()) - { - throw std::logic_error("kapi::cpu::init() called more than once"); - } - - // TODO: make sure that simulated interrupt can run. - } - - auto halt() -> void - { - throw kernel::tests::cpu::halt{}; - } - - auto discover_topology() -> bool - { - // TODO: implement more meaningful simulated CPU topology discovery - return true; - } - -} // namespace kapi::cpu - -namespace kernel::tests::cpu -{ - - auto deinit() -> void - { - if (!initialized.test()) - { - throw std::logic_error{"kapi::cpu::reset() called before kapi::cpu::init()"}; - } - - initialized.clear(); - } - -} // namespace kernel::tests::cpu \ No newline at end of file diff --git a/kernel/src/test_support/kapi/interrupts.cpp b/kernel/src/test_support/kapi/interrupts.cpp deleted file mode 100644 index 00772669..00000000 --- a/kernel/src/test_support/kapi/interrupts.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include - -namespace kapi::interrupts -{ - - auto enable() -> void - { - // TODO: enable simulated interrupts. - } - -} // namespace kapi::interrupts \ No newline at end of file diff --git a/kernel/src/test_support/kapi/memory.cpp b/kernel/src/test_support/kapi/memory.cpp deleted file mode 100644 index 7fc95cb0..00000000 --- a/kernel/src/test_support/kapi/memory.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include - -#include -#include - -#include - -#include - -#include - -namespace -{ - //! The size of the simulated RAM. - constexpr auto physical_size = kstd::units::MiB(32); - constexpr auto virtual_size = kstd::units::GiB(1); - - constexpr auto number_of_frames = physical_size / kapi::memory::frame::size; - - auto constinit bump_allocator = std::optional{}; - auto constinit test_mapper = std::optional{}; - - auto constinit old_allocator = std::optional{}; - auto constinit old_mapper = std::optional{}; - - auto handoff_to_kernel_pmm(kapi::memory::frame_allocator & new_allocator) -> void - { - auto first_free_frame = bump_allocator->next_free_frame; - auto number_of_free_frames = number_of_frames - first_free_frame; - new_allocator.release_many({kapi::memory::frame{first_free_frame}, number_of_free_frames}); - } - -} // namespace - -namespace kapi::memory -{ - - auto init() -> void - { - bump_allocator.emplace(); - test_mapper.emplace(physical_size, virtual_size); - - old_allocator = set_frame_allocator(*bump_allocator); - old_mapper = set_page_mapper(*test_mapper); - - init_pmm(physical_size / frame::size, handoff_to_kernel_pmm); - } -} // namespace kapi::memory - -namespace kernel::tests::memory -{ - - auto deinit() -> void - { - if (old_allocator && *old_allocator) - { - set_frame_allocator(**old_allocator); - } - - if (old_mapper && *old_mapper) - { - set_page_mapper(**old_mapper); - } - - bump_allocator.reset(); - test_mapper.reset(); - } - - auto virtual_base() -> kapi::memory::linear_address - { - return test_mapper->memory.virtual_base(); - } - -} // namespace kernel::tests::memory \ No newline at end of file diff --git a/kernel/src/test_support/log_buffer.cpp b/kernel/src/test_support/log_buffer.cpp deleted file mode 100644 index 04d875bd..00000000 --- a/kernel/src/test_support/log_buffer.cpp +++ /dev/null @@ -1,33 +0,0 @@ -#include - -#include -#include -#include - -namespace kernel::tests -{ - - auto log_buffer::append(std::string const & message) -> void - { - m_messages.push_back(message); - } - - auto log_buffer::clear() -> void - { - m_messages.clear(); - } - - auto log_buffer::flat_messages() -> std::string - { - return std::ranges::fold_left(m_messages, std::string{}, [](std::string accumulator, std::string const & message) { - accumulator += message; - return accumulator; - }); - } - - auto log_buffer::messages() -> std::vector const & - { - return m_messages; - } - -} // namespace kernel::tests diff --git a/kernel/src/test_support/output_device.cpp b/kernel/src/test_support/output_device.cpp deleted file mode 100644 index 45fb4bc8..00000000 --- a/kernel/src/test_support/output_device.cpp +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include - -#include - -#include -#include -#include - -namespace kernel::tests::cio -{ - - auto output_device::write(kapi::cio::output_stream stream, std::string_view text) -> void - { - auto & standard_stream = stream == kapi::cio::output_stream::stdout ? std::cout : std::cerr; - standard_stream << text; - if (text != "\n") - { - m_log_buffer.append(std::string{text}); - } - } - - auto output_device::log_buffer() noexcept -> kernel::tests::log_buffer & - { - return m_log_buffer; - } - -} // namespace kernel::tests::cio \ No newline at end of file diff --git a/kernel/src/test_support/page_mapper.cpp b/kernel/src/test_support/page_mapper.cpp deleted file mode 100644 index 3d50ff1a..00000000 --- a/kernel/src/test_support/page_mapper.cpp +++ /dev/null @@ -1,70 +0,0 @@ -#include - -#include - -#include - -#include -#include -#include - -namespace kernel::tests -{ - - page_mapper::page_mapper(kstd::units::bytes physical_size, kstd::units::bytes virtual_size) - : memory{physical_size, virtual_size} - {} - - auto page_mapper::map(kapi::memory::page page, kapi::memory::frame frame, flags) -> std::byte * - { - auto result = page_mappings.insert({page.number(), frame}); - if (!result.second) - { - auto error = std::format("Page {} was already mapped!", page.number()); - throw std::invalid_argument{error}; - } - - auto page_address = page.start_address(); - auto virtual_base = memory.virtual_base(); - auto virtual_end = virtual_base + memory.virtual_size(); - - if (page_address >= virtual_base && page_address < virtual_end) - { - auto virtual_target = static_cast(page_address); - auto physical_offset = frame.number() * kapi::memory::frame::size; - return memory.map(kapi::memory::page::size, virtual_target, physical_offset.value); - } - else if (page_address >= kapi::memory::mmio_base) - { - throw std::runtime_error("MMIO mapping not yet supported in testing!"); - } - else if (page_address >= kapi::memory::higher_half_direct_map_base) - { - auto offset = frame.number() * kapi::memory::frame::size; - return memory.physical_base() + offset; - } - - return nullptr; - } - - auto page_mapper::unmap(kapi::memory::page page) -> void - { - if (!try_unmap(page)) - { - auto error = std::format("Page {} was never mapped!", page.number()); - throw std::invalid_argument{error}; - } - } - - auto page_mapper::try_unmap(kapi::memory::page page) noexcept -> bool - { - if (page_mappings.contains(page.number())) - { - page_mappings.erase(page.number()); - return true; - } - - return false; - } - -} // namespace kernel::tests \ No newline at end of file diff --git a/kernel/src/test_support/simulated_memory.cpp b/kernel/src/test_support/simulated_memory.cpp deleted file mode 100644 index 074e6b10..00000000 --- a/kernel/src/test_support/simulated_memory.cpp +++ /dev/null @@ -1,106 +0,0 @@ -#include - -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -namespace kernel::tests -{ - - simulated_memory::simulated_memory(kstd::units::bytes physical_size, kstd::units::bytes virtual_size) - : m_descriptor{memfd_create("teachos_simulated_memory", 0)} - , m_physical_size{physical_size} - , m_virtual_size{virtual_size} - { - if (m_descriptor < 0) - { - auto error = std::format("Failed to allocate backing memory: {}", strerror(errno)); - throw std::runtime_error(error); - } - - if (ftruncate(m_descriptor, static_cast(m_physical_size.value)) < 0) - { - auto error = std::format("Failed to reserve backing memory: {}", strerror(errno)); - throw std::runtime_error(error); - } - - auto physical_storage = mmap(nullptr, m_physical_size.value, PROT_READ | PROT_WRITE, MAP_SHARED, m_descriptor, 0); - if (physical_storage == MAP_FAILED) - { - auto error = std::format("Failed to map backing memory: {}", strerror(errno)); - throw std::runtime_error(error); - } - - auto virtual_pointer = mmap(nullptr, virtual_size.value, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); - if (virtual_pointer == MAP_FAILED) - { - auto error = std::format("Failed to reserve virtual memory: {}", strerror(errno)); - throw std::runtime_error(error); - } - - m_physical_base = static_cast(physical_storage); - m_virtual_base = static_cast(virtual_pointer); - - clear(); - } - - simulated_memory::~simulated_memory() - { - munmap(m_virtual_base, m_virtual_size.value); - munmap(m_physical_base, m_physical_size.value); - close(m_descriptor); - } - - auto simulated_memory::clear() -> void - { - std::memset(m_physical_base, 0, m_physical_size.value); - } - - auto simulated_memory::physical_base() noexcept -> std::byte * - { - return m_physical_base; - } - - auto simulated_memory::physical_base() const noexcept -> std::byte const * - { - return m_physical_base; - } - - auto simulated_memory::physical_size() const noexcept -> kstd::units::bytes - { - return m_physical_size; - } - - auto simulated_memory::virtual_base() const noexcept -> kapi::memory::linear_address - { - return kapi::memory::linear_address{m_virtual_base}; - } - - auto simulated_memory::virtual_size() const noexcept -> kstd::units::bytes - { - return m_virtual_size; - } - - auto simulated_memory::map(kstd::units::bytes size, std::byte * to, off_t offset) -> std::byte * - { - auto mapped_ptr = mmap(to, size.value, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, m_descriptor, offset); - if (mapped_ptr == MAP_FAILED) - { - auto error = std::format("Failed to map page: {}", strerror(errno)); - throw std::runtime_error(error); - } - - return static_cast(mapped_ptr); - } - -} // namespace kernel::tests \ No newline at end of file diff --git a/kernel/src/test_support/state_reset_listener.cpp b/kernel/src/test_support/state_reset_listener.cpp deleted file mode 100644 index 6bb7537c..00000000 --- a/kernel/src/test_support/state_reset_listener.cpp +++ /dev/null @@ -1,48 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include -#include -#include -#include - -struct state_reset_listener : Catch::EventListenerBase -{ - using EventListenerBase::EventListenerBase; - - void testCaseStarting(Catch::TestCaseInfo const &) override - { - kernel::filesystem::open_file_table::init(); - - kapi::cio::init(); - kapi::cpu::init(); - kapi::memory::init(); - } - - void testCaseEnded(Catch::TestCaseStats const &) override - { - kernel::tests::filesystem::open_file_table::deinit(); - kernel::tests::filesystem::vfs::deinit(); - kernel::tests::boot_modules::deinit(); - kernel::tests::devices::storage::management::deinit(); - - kernel::tests::memory::deinit(); - kernel::tests::cpu::deinit(); - kernel::tests::cio::deinit(); - } -}; - -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wpedantic" -CATCH_REGISTER_LISTENER(state_reset_listener); -#pragma GCC diagnostic pop \ No newline at end of file -- cgit v1.2.3