aboutsummaryrefslogtreecommitdiff
path: root/kernel/kernel/filesystems/fat32/file_allocation_table.tests.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kernel/kernel/filesystems/fat32/file_allocation_table.tests.cpp')
-rw-r--r--kernel/kernel/filesystems/fat32/file_allocation_table.tests.cpp160
1 files changed, 160 insertions, 0 deletions
diff --git a/kernel/kernel/filesystems/fat32/file_allocation_table.tests.cpp b/kernel/kernel/filesystems/fat32/file_allocation_table.tests.cpp
index bcc85a51..1b79d2bf 100644
--- a/kernel/kernel/filesystems/fat32/file_allocation_table.tests.cpp
+++ b/kernel/kernel/filesystems/fat32/file_allocation_table.tests.cpp
@@ -1,6 +1,7 @@
#include <kernel/filesystems/fat32/file_allocation_table.hpp>
#include <kernel/devices/storage.hpp>
+#include <kernel/filesystems/fat32/directory_entry.hpp>
#include <kernel/filesystems/fat32/driver_state.hpp>
#include <kernel/filesystems/fat32/error.hpp>
#include <kernel/filesystems/fat32/filesystem.hpp>
@@ -10,14 +11,20 @@
#include <kernel/vfs/device_inode.hpp>
#include <kstd/memory.hpp>
+#include <kstd/span.hpp>
#include <kstd/units.hpp>
#include <kstd/vector.hpp>
#include <catch2/catch_test_macros.hpp>
+#include <algorithm>
+#include <array>
+#include <cstddef>
#include <cstdint>
#include <filesystem>
#include <optional>
+#include <span>
+#include <string_view>
using namespace kstd::units_literals;
@@ -70,6 +77,40 @@ SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_fixture, "Fat32 cluster
REQUIRE(kernel::filesystems::fat32::next_cluster(100, *driver_state).error() ==
kernel::filesystems::fat32::errc::corrupted_cluster_chain);
}
+
+ THEN("the root directory entries can be read from its cluster chain")
+ {
+ using namespace kernel::filesystems::fat32;
+
+ auto entries = std::array<directory_entry, 3>{};
+ auto const buffer = std::as_writable_bytes(std::span{entries});
+ auto const read_result = read_cluster_chain(driver_state->boot_sector.root_cluster, buffer, 0_B, *driver_state);
+ REQUIRE(read_result);
+ REQUIRE(*read_result == kstd::bytes{buffer.size()});
+
+ auto const & volume_label = entries[0];
+ REQUIRE(std::string_view{volume_label.name.data(), volume_label.name.size()} == "TESTFAT ");
+ REQUIRE(volume_label.attributes == constants::attribute_volume_id);
+
+ auto const & hello = entries[1];
+ REQUIRE(std::string_view{hello.name.data(), hello.name.size()} == "HELLO TXT");
+ REQUIRE(hello.attributes == constants::attribute_archive);
+ REQUIRE(hello.first_cluster_high == 0);
+ REQUIRE(hello.first_cluster_low == 3);
+ REQUIRE(hello.file_size == 12);
+
+ REQUIRE(static_cast<std::uint8_t>(entries[2].name[0]) == constants::end_of_directory_marker);
+ }
+
+ THEN("the contents of HELLO.TXT can be read from its cluster chain")
+ {
+ auto contents = std::array<char, 12>{};
+ auto const read_result = kernel::filesystems::fat32::read_cluster_chain(
+ 3, std::as_writable_bytes(std::span{contents}), 0_B, *driver_state);
+ REQUIRE(read_result);
+ REQUIRE(*read_result == 12_B);
+ REQUIRE(std::string_view{contents.data(), contents.size()} == "Hello World!");
+ }
}
}
@@ -199,4 +240,123 @@ SCENARIO("Fat32 cluster chain traversal", "[filesystem][fat32][fat]")
}
}
+SCENARIO("Fat32 cluster chain reading", "[filesystem][fat32][fat]")
+{
+ auto const block_size = 512_B;
+
+ GIVEN("a mounted minimal fat32 layout with a three cluster chain")
+ {
+ auto device = kstd::make_shared<kernel::tests::devices::block_device>("mock", block_size, 18 * block_size);
+ kernel::tests::filesystems::fat32::setup_mock_fat32_layout(*device);
+
+ using kernel::filesystems::fat32::errc;
+ using kernel::filesystems::fat32::read_cluster_chain;
+ using kernel::tests::filesystems::fat32::write_cluster_data;
+ using kernel::tests::filesystems::fat32::write_fat_entry;
+
+ auto cluster_data = std::array<std::byte, 512>{};
+ for (auto const & [cluster, fill] : {std::pair{3u, 'A'}, std::pair{4u, 'B'}, std::pair{5u, 'C'}})
+ {
+ std::ranges::fill(cluster_data, static_cast<std::byte>(fill));
+ write_cluster_data(*device, cluster, cluster_data);
+ }
+
+ write_fat_entry(*device, 3, 4);
+ write_fat_entry(*device, 4, 5);
+ write_fat_entry(*device, 5, 0x0FFF'FFF8);
+
+ auto dev_inode = kstd::make_shared<kernel::vfs::device_inode>(device);
+ auto fs = kstd::make_shared<kernel::filesystems::fat32::filesystem>();
+ auto mount = fs->mount(dev_inode);
+ REQUIRE(mount);
+ auto driver_state = static_pointer_cast<kernel::filesystems::fat32::driver_state>(mount->second);
+ REQUIRE(driver_state);
+
+ THEN("the whole chain is read in order")
+ {
+ auto buffer = std::array<std::byte, 3 * 512>{};
+ auto const read_result = read_cluster_chain(3, buffer, 0_B, *driver_state);
+ REQUIRE(read_result);
+ REQUIRE(*read_result == 1536_B);
+ REQUIRE(buffer[0] == std::byte{'A'});
+ REQUIRE(buffer[511] == std::byte{'A'});
+ REQUIRE(buffer[512] == std::byte{'B'});
+ REQUIRE(buffer[1024] == std::byte{'C'});
+ REQUIRE(buffer[1535] == std::byte{'C'});
+ }
+
+ THEN("a read spanning a cluster boundary continues in the next cluster")
+ {
+ auto buffer = std::array<std::byte, 24>{};
+ auto const read_result = read_cluster_chain(3, buffer, 500_B, *driver_state);
+ REQUIRE(read_result);
+ REQUIRE(*read_result == 24_B);
+ REQUIRE(std::ranges::count(buffer, std::byte{'A'}) == 12);
+ REQUIRE(std::ranges::count(buffer, std::byte{'B'}) == 12);
+ REQUIRE(buffer[11] == std::byte{'A'});
+ REQUIRE(buffer[12] == std::byte{'B'});
+ }
+
+ THEN("an offset beyond the first cluster skips the preceding clusters")
+ {
+ auto buffer = std::array<std::byte, 10>{};
+ auto const read_result = read_cluster_chain(3, buffer, 1100_B, *driver_state);
+ REQUIRE(read_result);
+ REQUIRE(*read_result == 10_B);
+ REQUIRE(std::ranges::count(buffer, std::byte{'C'}) == 10);
+ }
+
+ THEN("a read is truncated at the end of the chain")
+ {
+ auto buffer = std::array<std::byte, 4 * 512>{};
+ auto const read_result = read_cluster_chain(3, buffer, 0_B, *driver_state);
+ REQUIRE(read_result);
+ REQUIRE(*read_result == 1536_B);
+
+ auto const tail_result = read_cluster_chain(3, buffer, 1500_B, *driver_state);
+ REQUIRE(tail_result);
+ REQUIRE(*tail_result == 36_B);
+ }
+
+ THEN("reading at or past the end of the chain reads nothing")
+ {
+ auto buffer = std::array<std::byte, 16>{};
+ REQUIRE(read_cluster_chain(3, buffer, 1536_B, *driver_state) == 0_B);
+ REQUIRE(read_cluster_chain(3, buffer, 5000_B, *driver_state) == 0_B);
+ }
+
+ THEN("an empty buffer reads nothing")
+ {
+ REQUIRE(read_cluster_chain(3, std::span<std::byte>{}, 0_B, *driver_state) == 0_B);
+ }
+
+ THEN("a chain cannot start outside the data region")
+ {
+ auto buffer = std::array<std::byte, 16>{};
+ REQUIRE(read_cluster_chain(0, buffer, 0_B, *driver_state).error() == errc::invalid_cluster);
+ REQUIRE(read_cluster_chain(18, buffer, 0_B, *driver_state).error() == errc::invalid_cluster);
+ }
+
+ THEN("a chain running into a free cluster is corrupted")
+ {
+ write_fat_entry(*device, 8, 9);
+ write_fat_entry(*device, 9, 0);
+
+ auto buffer = std::array<std::byte, 3 * 512>{};
+ REQUIRE(read_cluster_chain(8, buffer, 0_B, *driver_state).error() == errc::corrupted_cluster_chain);
+ }
+
+ THEN("a cyclic chain is detected while reading and while skipping")
+ {
+ write_fat_entry(*device, 13, 14);
+ write_fat_entry(*device, 14, 13);
+
+ auto buffer = std::array<std::byte, 17 * 512>{};
+ REQUIRE(read_cluster_chain(13, buffer, 0_B, *driver_state).error() == errc::corrupted_cluster_chain);
+ REQUIRE(read_cluster_chain(13, std::span{buffer}.first(16), 20 * 512_B, *driver_state).error() ==
+ errc::corrupted_cluster_chain);
+ }
+ }
+}
+
// NOLINTEND(readability-magic-numbers)