aboutsummaryrefslogtreecommitdiff
path: root/kernel
diff options
context:
space:
mode:
Diffstat (limited to 'kernel')
-rw-r--r--kernel/kernel/filesystems/fat32/directory_iterator.tests.cpp354
-rw-r--r--kernel/kernel/filesystems/fat32/file_allocation_table.tests.cpp160
-rw-r--r--kernel/kernel/test_support/filesystems/fat32.cpp10
-rw-r--r--kernel/kernel/test_support/filesystems/fat32.hpp4
4 files changed, 528 insertions, 0 deletions
diff --git a/kernel/kernel/filesystems/fat32/directory_iterator.tests.cpp b/kernel/kernel/filesystems/fat32/directory_iterator.tests.cpp
new file mode 100644
index 00000000..204c5a40
--- /dev/null
+++ b/kernel/kernel/filesystems/fat32/directory_iterator.tests.cpp
@@ -0,0 +1,354 @@
+#include <kernel/filesystems/fat32/directory_iterator.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/file_allocation_table.hpp>
+#include <kernel/filesystems/fat32/filesystem.hpp>
+#include <kernel/test_support/devices/block_device.hpp>
+#include <kernel/test_support/filesystems/fat32.hpp>
+#include <kernel/test_support/vfs/storage_boot_module_fixture.hpp>
+#include <kernel/vfs/device_inode.hpp>
+
+#include <kstd/memory.hpp>
+#include <kstd/units.hpp>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include <algorithm>
+#include <array>
+#include <cstddef>
+#include <cstdint>
+#include <filesystem>
+#include <span>
+#include <string>
+#include <string_view>
+#include <vector>
+
+using namespace kstd::units_literals;
+
+// NOLINTBEGIN(readability-magic-numbers)
+
+namespace
+{
+ using kernel::filesystems::fat32::directory_entry;
+ using kernel::filesystems::fat32::directory_iterator;
+
+ constexpr auto entries_per_cluster = 16uz;
+
+ auto make_entry(std::string_view name, std::uint8_t attributes, std::uint32_t first_cluster = 0,
+ std::uint32_t file_size = 0) -> directory_entry
+ {
+ auto entry = directory_entry{};
+ std::ranges::fill(entry.name, ' ');
+ std::ranges::copy(name.substr(0, entry.name.size()), entry.name.begin());
+ entry.attributes = attributes;
+ entry.first_cluster_high = static_cast<std::uint16_t>(first_cluster >> 16);
+ entry.first_cluster_low = static_cast<std::uint16_t>(first_cluster & 0xFFFF);
+ entry.file_size = file_size;
+ return entry;
+ }
+
+ auto file_entry(std::string_view name) -> directory_entry
+ {
+ return make_entry(name, kernel::filesystems::fat32::constants::attribute_archive);
+ }
+
+ auto write_entries(kernel::tests::devices::block_device & device, std::uint32_t cluster,
+ std::span<directory_entry const> entries) -> void
+ {
+ auto cluster_entries = std::array<directory_entry, entries_per_cluster>{};
+ std::ranges::copy(entries, cluster_entries.begin());
+ kernel::tests::filesystems::fat32::write_cluster_data(device, cluster, std::as_bytes(std::span{cluster_entries}));
+ }
+
+ auto collect_names(directory_iterator it) -> std::vector<std::string>
+ {
+ auto names = std::vector<std::string>{};
+ for (; it != directory_iterator{}; ++it)
+ {
+ REQUIRE(names.size() < 64); // fails loudly on a non-terminating loop rather than hanging the suite
+ auto const name = it->entry.short_name();
+ names.emplace_back(name.data(), name.size());
+ }
+ return names;
+ }
+} // namespace
+
+SCENARIO("Fat32 short names are formatted for display", "[filesystem][fat32][readdir]")
+{
+ using namespace kernel::filesystems::fat32;
+
+ THEN("padding is removed and the extension is separated by a dot")
+ {
+ REQUIRE(make_entry("HELLO TXT", 0).short_name() == "HELLO.TXT");
+ REQUIRE(make_entry("ABCDEFGHIJK", 0).short_name() == "ABCDEFGH.IJK");
+ }
+
+ THEN("a name without extension has no dot")
+ {
+ REQUIRE(make_entry("README", 0).short_name() == "README");
+ }
+
+ THEN("the dot entries keep their names")
+ {
+ REQUIRE(make_entry(".", constants::attribute_directory).short_name() == ".");
+ REQUIRE(make_entry("..", constants::attribute_directory).short_name() == "..");
+ }
+
+ THEN("the lowercase flags are honored per component")
+ {
+ auto entry = make_entry("HELLO TXT", 0);
+
+ entry.nt_reserved = constants::lowercase_base_name;
+ REQUIRE(entry.short_name() == "hello.TXT");
+
+ entry.nt_reserved = constants::lowercase_extension;
+ REQUIRE(entry.short_name() == "HELLO.txt");
+
+ entry.nt_reserved = constants::lowercase_base_name | constants::lowercase_extension;
+ REQUIRE(entry.short_name() == "hello.txt");
+ }
+
+ THEN("an escaped leading 0xE5 is restored")
+ {
+ auto entry = make_entry("XAB", 0);
+ entry.name[0] = static_cast<char>(constants::escaped_deleted_entry_marker);
+ auto const name = entry.short_name();
+ REQUIRE(static_cast<std::uint8_t>(name[0]) == constants::deleted_entry_marker);
+ REQUIRE(std::string_view{name.data() + 1, name.size() - 1} == "AB");
+ }
+
+ THEN("the first cluster combines both halves")
+ {
+ REQUIRE(make_entry("A", 0, 0x0012'3456).first_cluster() == 0x0012'3456);
+ }
+}
+
+SCENARIO_METHOD(kernel::tests::vfs::storage_boot_module_fixture, "Fat32 directory_iterator walks a real directory",
+ "[filesystem][fat32][readdir][img]")
+{
+ auto const image_path = std::filesystem::path{KERNEL_TEST_ASSETS_DIR} / "fat32.img";
+
+ GIVEN("a mounted fat32 filesystem from a real image")
+ {
+ REQUIRE(std::filesystem::exists(image_path));
+ REQUIRE_NOTHROW(setup_modules_from_img({"test_img_module"}, {image_path}));
+
+ auto boot_device = kernel::devices::storage::determine_boot_device();
+ REQUIRE(boot_device);
+
+ auto dev_inode = kstd::make_shared<kernel::vfs::device_inode>(boot_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);
+
+ auto const root_cluster = driver_state->boot_sector.root_cluster;
+
+ THEN("the volume label is skipped and only HELLO.TXT is found")
+ {
+ REQUIRE(collect_names(directory_iterator{root_cluster, *driver_state}) == std::vector<std::string>{"HELLO.TXT"});
+ }
+
+ THEN("the record describes HELLO.TXT and where it is stored")
+ {
+ auto it = directory_iterator{root_cluster, *driver_state};
+ REQUIRE(it != directory_iterator{});
+ REQUIRE(it->entry.first_cluster() == 3);
+ REQUIRE(it->entry.file_size == 12);
+ REQUIRE(it->offset == 32_B);
+ REQUIRE(it->location == kernel::filesystems::fat32::cluster_offset(root_cluster, *driver_state) + 32_B);
+ REQUIRE(it->inode_number() == it->location.value);
+ REQUIRE(it.offset() == 64_B);
+
+ ++it;
+ REQUIRE(it == directory_iterator{});
+ REQUIRE_FALSE(it.error());
+ }
+ }
+}
+
+SCENARIO("Fat32 directory_iterator on a mock layout", "[filesystem][fat32][readdir]")
+{
+ auto const block_size = 512_B;
+
+ GIVEN("a mounted minimal fat32 layout")
+ {
+ 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);
+
+ 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);
+
+ using namespace kernel::filesystems::fat32;
+ using kernel::tests::filesystems::fat32::write_fat_entry;
+
+ WHEN("the directory contains deleted, long name and volume label entries")
+ {
+ auto deleted = file_entry("GONE TXT");
+ deleted.name[0] = static_cast<char>(constants::deleted_entry_marker);
+
+ auto const entries = std::array{
+ make_entry("LABEL", constants::attribute_volume_id),
+ make_entry("Ah\0e\0l\0l\0o", constants::attribute_long_name),
+ file_entry("HELLO TXT"),
+ deleted,
+ make_entry("SUBDIR", constants::attribute_directory, 5),
+ directory_entry{},
+ file_entry("HIDDEN TXT"),
+ };
+ write_entries(*device, 2, entries);
+
+ THEN("only the regular entries before the end marker are visited")
+ {
+ REQUIRE(collect_names(directory_iterator{2, *driver_state}) ==
+ std::vector<std::string>{"HELLO.TXT", "SUBDIR"});
+ }
+
+ THEN("the records carry the offsets of the visited entries")
+ {
+ auto it = directory_iterator{2, *driver_state};
+ REQUIRE(it->offset == 64_B);
+ REQUIRE(it->location == 1024_B + 64_B);
+ ++it;
+ REQUIRE(it->offset == 128_B);
+ REQUIRE(it->entry.first_cluster() == 5);
+ }
+
+ THEN("iteration can be resumed behind a visited entry")
+ {
+ auto it = directory_iterator{2, *driver_state};
+ REQUIRE(collect_names(directory_iterator{2, it.offset(), *driver_state}) ==
+ std::vector<std::string>{"SUBDIR"});
+ }
+
+ THEN("an offset inside an entry is rounded down to its start")
+ {
+ REQUIRE(directory_iterator{2, 70_B, *driver_state}->offset == 64_B);
+ }
+
+ THEN("copies of an iterator advance independently")
+ {
+ auto first = directory_iterator{2, *driver_state};
+ auto second = first;
+ ++first;
+ REQUIRE(second->entry.short_name() == "HELLO.TXT");
+ REQUIRE(first->entry.short_name() == "SUBDIR");
+ REQUIRE(first != second);
+ ++second;
+ REQUIRE(first == second);
+ }
+ }
+
+ WHEN("the directory spans two clusters")
+ {
+ auto first_cluster_entries = std::array<directory_entry, entries_per_cluster>{};
+ for (auto i = 0uz; i < first_cluster_entries.size(); ++i)
+ {
+ first_cluster_entries[i] = file_entry(std::string{"FILE"} + static_cast<char>('A' + i));
+ }
+ write_entries(*device, 3, first_cluster_entries);
+ write_entries(*device, 4, std::array{file_entry("LAST TXT")});
+ write_fat_entry(*device, 3, 4);
+ write_fat_entry(*device, 4, 0x0FFF'FFFF);
+
+ THEN("the entries of both clusters are visited in order")
+ {
+ auto const names = collect_names(directory_iterator{3, *driver_state});
+ REQUIRE(names.size() == 17);
+ REQUIRE(names.front() == "FILEA");
+ REQUIRE(names.back() == "LAST.TXT");
+ }
+
+ THEN("the entries of the second cluster are located in it")
+ {
+ auto it = directory_iterator{3, 512_B, *driver_state};
+ REQUIRE(it->offset == 512_B);
+ REQUIRE(it->location == cluster_offset(4, *driver_state));
+ REQUIRE(it->entry.short_name() == "LAST.TXT");
+ }
+
+ THEN("an offset behind the chain yields no entries and no error")
+ {
+ auto it = directory_iterator{3, 1024_B, *driver_state};
+ REQUIRE(it == directory_iterator{});
+ REQUIRE_FALSE(it.error());
+ }
+ }
+
+ WHEN("a full directory has no end marker")
+ {
+ auto entries = std::array<directory_entry, entries_per_cluster>{};
+ std::ranges::fill(entries, file_entry("FILE"));
+ write_entries(*device, 6, entries);
+ write_fat_entry(*device, 6, 0x0FFF'FFFF);
+
+ THEN("iteration ends with the chain")
+ {
+ auto it = directory_iterator{6, *driver_state};
+ REQUIRE(collect_names(it).size() == entries_per_cluster);
+ std::ranges::advance(it, entries_per_cluster);
+ REQUIRE(it == directory_iterator{});
+ REQUIRE_FALSE(it.error());
+ }
+ }
+
+ THEN("a directory cannot start outside the data region")
+ {
+ auto it = directory_iterator{0, *driver_state};
+ REQUIRE(it == directory_iterator{});
+ REQUIRE(it.error() == errc::invalid_cluster);
+ }
+
+ WHEN("the directory's chain is cyclic")
+ {
+ auto entries = std::array<directory_entry, entries_per_cluster>{};
+ std::ranges::fill(entries, file_entry("LOOP"));
+ write_entries(*device, 13, entries);
+ write_entries(*device, 14, entries);
+ write_fat_entry(*device, 13, 14);
+ write_fat_entry(*device, 14, 13);
+
+ THEN("iteration ends with a corrupted chain error")
+ {
+ auto it = directory_iterator{13, *driver_state};
+ auto count = 0uz;
+ for (; it != directory_iterator{} && count < 1000; ++it)
+ {
+ ++count;
+ }
+ REQUIRE(count < 1000);
+ REQUIRE(it.error() == errc::corrupted_cluster_chain);
+ }
+ }
+
+ WHEN("the directory's chain runs into a free cluster")
+ {
+ auto entries = std::array<directory_entry, entries_per_cluster>{};
+ std::ranges::fill(entries, file_entry("FILE"));
+ write_entries(*device, 8, entries);
+ write_entries(*device, 9, entries);
+ write_fat_entry(*device, 8, 9);
+ write_fat_entry(*device, 9, 0);
+
+ THEN("iteration ends with a corrupted chain error once the free cluster is followed")
+ {
+ auto it = directory_iterator{8, *driver_state};
+ while (it != directory_iterator{})
+ {
+ ++it;
+ }
+ REQUIRE(it.error() == errc::corrupted_cluster_chain);
+ }
+ }
+ }
+}
+
+// NOLINTEND(readability-magic-numbers)
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)
diff --git a/kernel/kernel/test_support/filesystems/fat32.cpp b/kernel/kernel/test_support/filesystems/fat32.cpp
index 4513a53a..233cc09f 100644
--- a/kernel/kernel/test_support/filesystems/fat32.cpp
+++ b/kernel/kernel/test_support/filesystems/fat32.cpp
@@ -7,8 +7,10 @@
#include <kstd/units.hpp>
+#include <cstddef>
#include <cstdint>
#include <cstring>
+#include <span>
using namespace kstd::units_literals;
@@ -70,4 +72,12 @@ namespace kernel::tests::filesystems::fat32
auto const offset = reserved_sectors * sector_size + cluster * kernel::filesystems::fat32::constants::fat_entry_size;
write_bytes(device, offset, &value, kstd::size_of(value));
}
+
+ auto write_cluster_data(kernel::tests::devices::block_device & device, std::uint32_t cluster,
+ std::span<std::byte const> data) -> void
+ {
+ auto const data_start = (reserved_sectors + fat_sectors) * sector_size;
+ auto const offset = data_start + (cluster - kernel::filesystems::fat32::constants::first_data_cluster) * sector_size;
+ write_bytes(device, offset, data.data(), kstd::bytes{data.size()});
+ }
} // namespace kernel::tests::filesystems::fat32
diff --git a/kernel/kernel/test_support/filesystems/fat32.hpp b/kernel/kernel/test_support/filesystems/fat32.hpp
index e53974fb..1eb28c6f 100644
--- a/kernel/kernel/test_support/filesystems/fat32.hpp
+++ b/kernel/kernel/test_support/filesystems/fat32.hpp
@@ -4,7 +4,9 @@
#include <kernel/filesystems/fat32/boot_sector.hpp>
#include <kernel/test_support/devices/block_device.hpp>
+#include <cstddef>
#include <cstdint>
+#include <span>
namespace kernel::tests::filesystems::fat32
{
@@ -16,6 +18,8 @@ namespace kernel::tests::filesystems::fat32
kernel::filesystems::fat32::boot_sector const & boot_sector) -> void;
auto write_fat_entry(kernel::tests::devices::block_device & device, std::uint32_t cluster, std::uint32_t value)
-> void;
+ auto write_cluster_data(kernel::tests::devices::block_device & device, std::uint32_t cluster,
+ std::span<std::byte const> data) -> void;
} // namespace kernel::tests::filesystems::fat32