#include #include "kernel/filesystem/ext2/superblock.hpp" #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 read stops when block mapping resolves to zero", "[filesystem][ext2][inode]") { auto const block_size = 1024uz; GIVEN("an ext2 inode without 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.blocks = 2; data.block[0] = 0; auto inode = kernel::filesystem::ext2::inode{&fs, data}; auto buffer = kstd::vector(32, std::byte{0xAB}); THEN("no bytes are read") { auto const bytes_read = inode.read(buffer.data(), 0, buffer.size()); REQUIRE(bytes_read == 0); } } } SCENARIO("Ext2 inode read across block boundaries", "[filesystem][ext2][inode]") { auto const block_size = 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.blocks = 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.get_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.get_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.get_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.get_size() == 256); } } }